aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.debianinstall/debian-setup.sh24
-rw-r--r--.gitlab-ci.yml2
-rw-r--r--CHANGELOG107
-rw-r--r--Zotlabs/Daemon/Cache_embeds.php15
-rw-r--r--Zotlabs/Daemon/Cron.php5
-rw-r--r--Zotlabs/Lib/Activity.php51
-rw-r--r--Zotlabs/Lib/Apps.php2
-rw-r--r--Zotlabs/Lib/DReport.php10
-rw-r--r--Zotlabs/Lib/Enotify.php47
-rw-r--r--Zotlabs/Lib/IConfig.php6
-rw-r--r--Zotlabs/Lib/JcsEddsa2022.php25
-rw-r--r--Zotlabs/Lib/JcsEddsa2022SignException.php15
-rw-r--r--Zotlabs/Lib/Keyutils.php82
-rw-r--r--Zotlabs/Lib/Libzot.php3
-rw-r--r--Zotlabs/Lib/MessageFilter.php154
-rw-r--r--Zotlabs/Lib/Text.php9
-rw-r--r--Zotlabs/Lib/ThreadItem.php133
-rw-r--r--Zotlabs/Lib/ThreadStream.php9
-rw-r--r--Zotlabs/Lib/Traits/HelpHelperTrait.php2
-rw-r--r--Zotlabs/Module/Acl.php16
-rw-r--r--Zotlabs/Module/Attach_edit.php5
-rw-r--r--Zotlabs/Module/Channel.php64
-rw-r--r--Zotlabs/Module/Cloud.php29
-rw-r--r--Zotlabs/Module/Display.php29
-rw-r--r--Zotlabs/Module/File_upload.php67
-rw-r--r--Zotlabs/Module/Help.php4
-rw-r--r--Zotlabs/Module/Home.php14
-rw-r--r--Zotlabs/Module/Hq.php16
-rw-r--r--Zotlabs/Module/Id.php6
-rw-r--r--Zotlabs/Module/Invite.php54
-rw-r--r--Zotlabs/Module/Item.php192
-rw-r--r--Zotlabs/Module/Lang.php16
-rw-r--r--Zotlabs/Module/Like.php40
-rw-r--r--Zotlabs/Module/Login.php11
-rw-r--r--Zotlabs/Module/Network.php49
-rw-r--r--Zotlabs/Module/Oep.php1
-rw-r--r--Zotlabs/Module/Photos.php17
-rw-r--r--Zotlabs/Module/Pin.php5
-rw-r--r--Zotlabs/Module/Pubstream.php15
-rw-r--r--Zotlabs/Module/Regate.php4
-rw-r--r--Zotlabs/Module/Request.php89
-rw-r--r--Zotlabs/Module/Rpost.php2
-rw-r--r--Zotlabs/Module/Settings/Display.php9
-rw-r--r--Zotlabs/Module/Sse_bs.php146
-rw-r--r--Zotlabs/Module/Viewsrc.php32
-rw-r--r--Zotlabs/Module/Wall_attach.php9
-rw-r--r--Zotlabs/Module/Xref.php26
-rw-r--r--Zotlabs/Photo/PhotoGd.php15
-rw-r--r--Zotlabs/Render/SmartyInterface.php2
-rw-r--r--Zotlabs/Render/Theme.php15
-rw-r--r--Zotlabs/Storage/Browser.php8
-rw-r--r--Zotlabs/Web/HTTPSig.php58
-rw-r--r--Zotlabs/Web/Router.php91
-rw-r--r--Zotlabs/Web/Session.php2
-rw-r--r--Zotlabs/Web/WebServer.php86
-rw-r--r--Zotlabs/Widget/Activity_order.php42
-rw-r--r--Zotlabs/Widget/Album.php3
-rw-r--r--Zotlabs/Widget/Channel_activities.php2
-rw-r--r--Zotlabs/Widget/Messages.php20
-rw-r--r--Zotlabs/Widget/Notifications.php52
-rw-r--r--Zotlabs/Widget/Pinned.php95
-rw-r--r--Zotlabs/Widget/Portfolio.php4
-rw-r--r--Zotlabs/Zot6/Zot6Handler.php2
-rw-r--r--boot.php386
-rw-r--r--composer.json5
-rw-r--r--composer.lock411
-rw-r--r--doc/LICENSE428
-rw-r--r--doc/de/SUMMARY.md142
-rw-r--r--doc/de/about.md88
-rw-r--r--doc/de/about/.gitkeep0
-rw-r--r--doc/de/about/about.bb23
-rw-r--r--doc/de/about_hub.bb (renamed from doc/en/about/about_hub.bb)0
-rw-r--r--doc/de/adminmanual/CLI_tools.md103
-rw-r--r--doc/de/adminmanual/Creating-Templates.md79
-rw-r--r--doc/de/adminmanual/Installation_using_docker.md56
-rw-r--r--doc/de/adminmanual/Nomad---A-High-Level-Overview.md111
-rw-r--r--doc/de/adminmanual/Primary-Directory.md35
-rw-r--r--doc/de/adminmanual/Schema-development.md45
-rw-r--r--doc/de/adminmanual/Widgets.md99
-rw-r--r--doc/de/adminmanual/Zot---A-High-Level-Overview.md111
-rw-r--r--doc/de/adminmanual/administration.md13
-rw-r--r--doc/de/adminmanual/advanced_configurations.md326
-rw-r--r--doc/de/adminmanual/automated_installation.md26
-rw-r--r--doc/de/adminmanual/before_you_start.md17
-rw-r--r--doc/de/adminmanual/channel_directory.md15
-rw-r--r--doc/de/adminmanual/database.md74
-rw-r--r--doc/de/adminmanual/directories.md87
-rw-r--r--doc/de/adminmanual/faq_admins.md89
-rw-r--r--doc/de/adminmanual/federation_addons.md8
-rw-r--r--doc/de/adminmanual/filesync.md42
-rw-r--r--doc/de/adminmanual/further_help.md3
-rw-r--r--doc/de/adminmanual/hub_snapshot_tools.md127
-rw-r--r--doc/de/adminmanual/installation.md8
-rw-r--r--doc/de/adminmanual/installation_requirements.md14
-rw-r--r--doc/de/adminmanual/manual_for_administrators.md30
-rw-r--r--doc/de/adminmanual/manual_installation.md60
-rw-r--r--doc/de/adminmanual/pic/adm01.pngbin0 -> 371226 bytes
-rw-r--r--doc/de/adminmanual/problems-following-an-update.md31
-rw-r--r--doc/de/adminmanual/recommended_addons.md7
-rw-r--r--doc/de/adminmanual/service_classes.md57
-rw-r--r--doc/de/adminmanual/theme_management.md7
-rw-r--r--doc/de/adminmanual/troubleshooting.md51
-rw-r--r--doc/de/admins.bb10
-rw-r--r--doc/de/basicconcepts.md67
-rw-r--r--doc/de/bugs.bb34
-rw-r--r--doc/de/channels.bb26
-rw-r--r--doc/de/credits.md91
-rw-r--r--doc/de/database/db_abook.bb (renamed from doc/en/database/db_abook.bb)0
-rw-r--r--doc/de/database/db_account.bb (renamed from doc/en/database/db_account.bb)0
-rw-r--r--doc/de/database/db_addon.bb (renamed from doc/en/database/db_addon.bb)0
-rw-r--r--doc/de/database/db_app.bb (renamed from doc/en/database/db_app.bb)0
-rw-r--r--doc/de/database/db_attach.bb (renamed from doc/en/database/db_attach.bb)0
-rw-r--r--doc/de/database/db_auth_codes.bb (renamed from doc/en/database/db_auth_codes.bb)0
-rw-r--r--doc/de/database/db_cache.bb (renamed from doc/en/database/db_cache.bb)0
-rw-r--r--doc/de/database/db_channel.bb (renamed from doc/en/database/db_channel.bb)0
-rw-r--r--doc/de/database/db_chat.bb (renamed from doc/en/database/db_chat.bb)0
-rw-r--r--doc/de/database/db_chatpresence.bb (renamed from doc/en/database/db_chatpresence.bb)0
-rw-r--r--doc/de/database/db_chatroom.bb (renamed from doc/en/database/db_chatroom.bb)0
-rw-r--r--doc/de/database/db_clients.bb (renamed from doc/en/database/db_clients.bb)0
-rw-r--r--doc/de/database/db_config.bb (renamed from doc/en/database/db_config.bb)0
-rw-r--r--doc/de/database/db_conv.bb (renamed from doc/en/database/db_conv.bb)0
-rw-r--r--doc/de/database/db_event.bb (renamed from doc/en/database/db_event.bb)0
-rw-r--r--doc/de/database/db_fcontact.bb (renamed from doc/en/database/db_fcontact.bb)0
-rw-r--r--doc/de/database/db_ffinder.bb (renamed from doc/en/database/db_ffinder.bb)0
-rw-r--r--doc/de/database/db_fserver.bb (renamed from doc/en/database/db_fserver.bb)0
-rw-r--r--doc/de/database/db_fsuggest.bb (renamed from doc/en/database/db_fsuggest.bb)0
-rw-r--r--doc/de/database/db_hook.bb (renamed from doc/en/database/db_hook.bb)0
-rw-r--r--doc/de/database/db_hubloc.bb (renamed from doc/en/database/db_hubloc.bb)0
-rw-r--r--doc/de/database/db_issue.bb (renamed from doc/en/database/db_issue.bb)0
-rw-r--r--doc/de/database/db_item.bb (renamed from doc/en/database/db_item.bb)0
-rw-r--r--doc/de/database/db_item_id.bb (renamed from doc/en/database/db_item_id.bb)0
-rw-r--r--doc/de/database/db_likes.bb (renamed from doc/en/database/db_likes.bb)0
-rw-r--r--doc/de/database/db_mail.bb (renamed from doc/en/database/db_mail.bb)0
-rw-r--r--doc/de/database/db_menu.bb (renamed from doc/en/database/db_menu.bb)0
-rw-r--r--doc/de/database/db_menu_item.bb (renamed from doc/en/database/db_menu_item.bb)0
-rw-r--r--doc/de/database/db_notify.bb (renamed from doc/en/database/db_notify.bb)0
-rw-r--r--doc/de/database/db_obj.bb (renamed from doc/en/database/db_obj.bb)0
-rw-r--r--doc/de/database/db_outq.bb (renamed from doc/en/database/db_outq.bb)0
-rw-r--r--doc/de/database/db_pconfig.bb (renamed from doc/en/database/db_pconfig.bb)0
-rw-r--r--doc/de/database/db_pgrp.bb (renamed from doc/en/database/db_pgrp.bb)0
-rw-r--r--doc/de/database/db_pgrp_member.bb (renamed from doc/en/database/db_pgrp_member.bb)0
-rw-r--r--doc/de/database/db_photo.bb (renamed from doc/en/database/db_photo.bb)0
-rw-r--r--doc/de/database/db_poll.bb (renamed from doc/en/database/db_poll.bb)0
-rw-r--r--doc/de/database/db_poll_elm.bb (renamed from doc/en/database/db_poll_elm.bb)0
-rw-r--r--doc/de/database/db_profdef.bb (renamed from doc/en/database/db_profdef.bb)0
-rw-r--r--doc/de/database/db_profext.bb (renamed from doc/en/database/db_profext.bb)0
-rw-r--r--doc/de/database/db_profile.bb (renamed from doc/en/database/db_profile.bb)0
-rw-r--r--doc/de/database/db_profile_check.bb (renamed from doc/en/database/db_profile_check.bb)0
-rw-r--r--doc/de/database/db_register.bb (renamed from doc/en/database/db_register.bb)0
-rw-r--r--doc/de/database/db_session.bb (renamed from doc/en/database/db_session.bb)0
-rw-r--r--doc/de/database/db_shares.bb (renamed from doc/en/database/db_shares.bb)0
-rw-r--r--doc/de/database/db_sign.bb (renamed from doc/en/database/db_sign.bb)0
-rw-r--r--doc/de/database/db_site.bb (renamed from doc/en/database/db_site.bb)0
-rw-r--r--doc/de/database/db_source.bb (renamed from doc/en/database/db_source.bb)0
-rw-r--r--doc/de/database/db_spam.bb (renamed from doc/en/database/db_spam.bb)0
-rw-r--r--doc/de/database/db_sys_perms.bb (renamed from doc/en/database/db_sys_perms.bb)0
-rw-r--r--doc/de/database/db_term.bb (renamed from doc/en/database/db_term.bb)0
-rw-r--r--doc/de/database/db_tokens.bb (renamed from doc/en/database/db_tokens.bb)0
-rw-r--r--doc/de/database/db_updates.bb (renamed from doc/en/database/db_updates.bb)0
-rw-r--r--doc/de/database/db_verify.bb (renamed from doc/en/database/db_verify.bb)0
-rw-r--r--doc/de/database/db_vote.bb (renamed from doc/en/database/db_vote.bb)0
-rw-r--r--doc/de/database/db_xchan.bb (renamed from doc/en/database/db_xchan.bb)0
-rw-r--r--doc/de/database/db_xchat.bb (renamed from doc/en/database/db_xchat.bb)0
-rw-r--r--doc/de/database/db_xconfig.bb (renamed from doc/en/database/db_xconfig.bb)0
-rw-r--r--doc/de/database/db_xign.bb (renamed from doc/en/database/db_xign.bb)0
-rw-r--r--doc/de/database/db_xlink.bb (renamed from doc/en/database/db_xlink.bb)0
-rw-r--r--doc/de/database/db_xprof.bb (renamed from doc/en/database/db_xprof.bb)0
-rw-r--r--doc/de/database/db_xtag.bb (renamed from doc/en/database/db_xtag.bb)0
-rw-r--r--doc/de/develop.bb30
-rw-r--r--doc/de/developer/API.md787
-rw-r--r--doc/de/developer/Plugins.md251
-rw-r--r--doc/de/developer/attribution.md3
-rw-r--r--doc/de/developer/code_of_conduct.md8
-rw-r--r--doc/de/developer/coding_style.md12
-rw-r--r--doc/de/developer/dev-function-overview.md43
-rw-r--r--doc/de/developer/dev_beginner.md460
-rw-r--r--doc/de/developer/developers_guide.md27
-rw-r--r--doc/de/developer/doco.md29
-rw-r--r--doc/de/developer/enforcement.md5
-rw-r--r--doc/de/developer/federate.md56
-rw-r--r--doc/de/developer/file_system_layout.md20
-rw-r--r--doc/de/developer/git_for_non_developers.md72
-rw-r--r--doc/de/developer/git_repository.md6
-rw-r--r--doc/de/developer/hooks.md698
-rw-r--r--doc/de/developer/licensing.md3
-rw-r--r--doc/de/developer/magic_auth.md49
-rw-r--r--doc/de/developer/nomad_protocol.md47
-rw-r--r--doc/de/developer/nomad_structures.md141
-rw-r--r--doc/de/developer/our_pledge.md3
-rw-r--r--doc/de/developer/our_responsibilities.md3
-rw-r--r--doc/de/developer/our_standards.md24
-rw-r--r--doc/de/developer/scope.md3
-rw-r--r--doc/de/developer/technical_introduction.md309
-rw-r--r--doc/de/developer/testing.md131
-rw-r--r--doc/de/developer/tools_workflows.md5
-rw-r--r--doc/de/developer/translations.md57
-rw-r--r--doc/de/developer/unorganized.md56
-rw-r--r--doc/de/developer/versions.md23
-rw-r--r--doc/de/developer/who_is_a_hubzilla_developer.md14
-rw-r--r--doc/de/developer/zot_protocol.md47
-rw-r--r--doc/de/developer/zot_structures.md141
-rw-r--r--doc/de/features.bb203
-rw-r--r--doc/de/functions.md229
-rw-r--r--doc/de/general.bb17
-rw-r--r--doc/de/glossary.md31
-rw-r--r--doc/de/hook/about_hook.bb (renamed from doc/en/hook/about_hook.bb)0
-rw-r--r--doc/de/hook/accept_follow.bb (renamed from doc/en/hook/accept_follow.bb)0
-rw-r--r--doc/de/hook/account_downgrade.bb (renamed from doc/en/hook/account_downgrade.bb)0
-rw-r--r--doc/de/hook/account_settings.bb (renamed from doc/en/hook/account_settings.bb)0
-rw-r--r--doc/de/hook/account_settings_post.bb (renamed from doc/en/hook/account_settings_post.bb)0
-rw-r--r--doc/de/hook/activity_decode_mapper.bb (renamed from doc/en/hook/activity_decode_mapper.bb)0
-rw-r--r--doc/de/hook/activity_filter.bb (renamed from doc/en/hook/activity_filter.bb)0
-rw-r--r--doc/de/hook/activity_mapper.bb (renamed from doc/en/hook/activity_mapper.bb)0
-rw-r--r--doc/de/hook/activity_obj_decode_mapper.bb (renamed from doc/en/hook/activity_obj_decode_mapper.bb)0
-rw-r--r--doc/de/hook/activity_obj_mapper.bb (renamed from doc/en/hook/activity_obj_mapper.bb)0
-rw-r--r--doc/de/hook/activity_order.bb (renamed from doc/en/hook/activity_order.bb)0
-rw-r--r--doc/de/hook/activity_received.bb (renamed from doc/en/hook/activity_received.bb)0
-rw-r--r--doc/de/hook/addon_app_installed_filter.bb (renamed from doc/en/hook/addon_app_installed_filter.bb)0
-rw-r--r--doc/de/hook/affinity_labels.bb (renamed from doc/en/hook/affinity_labels.bb)0
-rw-r--r--doc/de/hook/api_perm_is_allowed.bb (renamed from doc/en/hook/api_perm_is_allowed.bb)0
-rw-r--r--doc/de/hook/app_destroy.bb (renamed from doc/en/hook/app_destroy.bb)0
-rw-r--r--doc/de/hook/app_installed_filter.bb (renamed from doc/en/hook/app_installed_filter.bb)0
-rw-r--r--doc/de/hook/atom_author.bb (renamed from doc/en/hook/atom_author.bb)0
-rw-r--r--doc/de/hook/atom_entry.bb (renamed from doc/en/hook/atom_entry.bb)0
-rw-r--r--doc/de/hook/atom_feed.bb (renamed from doc/en/hook/atom_feed.bb)0
-rw-r--r--doc/de/hook/atom_feed_end.bb (renamed from doc/en/hook/atom_feed_end.bb)0
-rw-r--r--doc/de/hook/attach_delete.bb (renamed from doc/en/hook/attach_delete.bb)0
-rw-r--r--doc/de/hook/attach_upload_file.bb (renamed from doc/en/hook/attach_upload_file.bb)0
-rw-r--r--doc/de/hook/authenticate.bb (renamed from doc/en/hook/authenticate.bb)0
-rw-r--r--doc/de/hook/author_is_pmable.bb (renamed from doc/en/hook/author_is_pmable.bb)0
-rw-r--r--doc/de/hook/bb2diaspora.bb (renamed from doc/en/hook/bb2diaspora.bb)0
-rw-r--r--doc/de/hook/bbcode.bb (renamed from doc/en/hook/bbcode.bb)0
-rw-r--r--doc/de/hook/bbcode_filter.bb (renamed from doc/en/hook/bbcode_filter.bb)0
-rw-r--r--doc/de/hook/build_pagehead.bb (renamed from doc/en/hook/build_pagehead.bb)0
-rw-r--r--doc/de/hook/can_comment_on_post.bb (renamed from doc/en/hook/can_comment_on_post.bb)0
-rw-r--r--doc/de/hook/change_channel.bb (renamed from doc/en/hook/change_channel.bb)0
-rw-r--r--doc/de/hook/channel_links.bb (renamed from doc/en/hook/channel_links.bb)0
-rw-r--r--doc/de/hook/channel_remove.bb (renamed from doc/en/hook/channel_remove.bb)0
-rw-r--r--doc/de/hook/chat_message.bb (renamed from doc/en/hook/chat_message.bb)0
-rw-r--r--doc/de/hook/chat_post.bb (renamed from doc/en/hook/chat_post.bb)0
-rw-r--r--doc/de/hook/check_account_email.bb (renamed from doc/en/hook/check_account_email.bb)0
-rw-r--r--doc/de/hook/check_account_invite.bb (renamed from doc/en/hook/check_account_invite.bb)0
-rw-r--r--doc/de/hook/check_account_password.bb (renamed from doc/en/hook/check_account_password.bb)0
-rw-r--r--doc/de/hook/check_channelallowed.bb (renamed from doc/en/hook/check_channelallowed.bb)0
-rw-r--r--doc/de/hook/check_siteallowed.bb (renamed from doc/en/hook/check_siteallowed.bb)0
-rw-r--r--doc/de/hook/collect_public_recipients.bb (renamed from doc/en/hook/collect_public_recipients.bb)0
-rw-r--r--doc/de/hook/comments_are_now_closed.bb (renamed from doc/en/hook/comments_are_now_closed.bb)0
-rw-r--r--doc/de/hook/connect_premium.bb (renamed from doc/en/hook/connect_premium.bb)0
-rw-r--r--doc/de/hook/connection_remove.bb (renamed from doc/en/hook/connection_remove.bb)0
-rw-r--r--doc/de/hook/connector_settings.bb (renamed from doc/en/hook/connector_settings.bb)0
-rw-r--r--doc/de/hook/construct_page.bb (renamed from doc/en/hook/construct_page.bb)0
-rw-r--r--doc/de/hook/contact_block_end.bb (renamed from doc/en/hook/contact_block_end.bb)0
-rw-r--r--doc/de/hook/contact_edit.bb (renamed from doc/en/hook/contact_edit.bb)0
-rw-r--r--doc/de/hook/contact_edit_post.bb (renamed from doc/en/hook/contact_edit_post.bb)0
-rw-r--r--doc/de/hook/contact_select_options.bb (renamed from doc/en/hook/contact_select_options.bb)0
-rw-r--r--doc/de/hook/content_security_policy.bb (renamed from doc/en/hook/content_security_policy.bb)0
-rw-r--r--doc/de/hook/conversation_start.bb (renamed from doc/en/hook/conversation_start.bb)0
-rw-r--r--doc/de/hook/create_identity.bb (renamed from doc/en/hook/create_identity.bb)0
-rw-r--r--doc/de/hook/cron.bb (renamed from doc/en/hook/cron.bb)0
-rw-r--r--doc/de/hook/cron_daily.bb (renamed from doc/en/hook/cron_daily.bb)0
-rw-r--r--doc/de/hook/cron_weekly.bb (renamed from doc/en/hook/cron_weekly.bb)0
-rw-r--r--doc/de/hook/crypto_methods.bb (renamed from doc/en/hook/crypto_methods.bb)0
-rw-r--r--doc/de/hook/daemon_addon.bb (renamed from doc/en/hook/daemon_addon.bb)0
-rw-r--r--doc/de/hook/daemon_master_release.bb (renamed from doc/en/hook/daemon_master_release.bb)0
-rw-r--r--doc/de/hook/directory_item.bb (renamed from doc/en/hook/directory_item.bb)0
-rw-r--r--doc/de/hook/discover_channel_webfinger.bb (renamed from doc/en/hook/discover_channel_webfinger.bb)0
-rw-r--r--doc/de/hook/display_item.bb (renamed from doc/en/hook/display_item.bb)0
-rw-r--r--doc/de/hook/display_settings.bb (renamed from doc/en/hook/display_settings.bb)0
-rw-r--r--doc/de/hook/display_settings_post.bb (renamed from doc/en/hook/display_settings_post.bb)0
-rw-r--r--doc/de/hook/donate_contributors.bb (renamed from doc/en/hook/donate_contributors.bb)0
-rw-r--r--doc/de/hook/donate_plugin.bb (renamed from doc/en/hook/donate_plugin.bb)0
-rw-r--r--doc/de/hook/donate_sponsors.bb (renamed from doc/en/hook/donate_sponsors.bb)0
-rw-r--r--doc/de/hook/dreport_is_storable.bb (renamed from doc/en/hook/dreport_is_storable.bb)0
-rw-r--r--doc/de/hook/dreport_process.bb (renamed from doc/en/hook/dreport_process.bb)0
-rw-r--r--doc/de/hook/drop_item.bb (renamed from doc/en/hook/drop_item.bb)0
-rw-r--r--doc/de/hook/dropdown_extras.bb (renamed from doc/en/hook/dropdown_extras.bb)0
-rw-r--r--doc/de/hook/encode_object.bb (renamed from doc/en/hook/encode_object.bb)0
-rw-r--r--doc/de/hook/enotify.bb (renamed from doc/en/hook/enotify.bb)0
-rw-r--r--doc/de/hook/enotify_mail.bb (renamed from doc/en/hook/enotify_mail.bb)0
-rw-r--r--doc/de/hook/enotify_store.bb (renamed from doc/en/hook/enotify_store.bb)0
-rw-r--r--doc/de/hook/event_created.bb (renamed from doc/en/hook/event_created.bb)0
-rw-r--r--doc/de/hook/event_store_event.bb (renamed from doc/en/hook/event_store_event.bb)0
-rw-r--r--doc/de/hook/event_updated.bb (renamed from doc/en/hook/event_updated.bb)0
-rw-r--r--doc/de/hook/externals_url_select.bb (renamed from doc/en/hook/externals_url_select.bb)0
-rw-r--r--doc/de/hook/feature_enabled.bb (renamed from doc/en/hook/feature_enabled.bb)0
-rw-r--r--doc/de/hook/feature_settings.bb (renamed from doc/en/hook/feature_settings.bb)0
-rw-r--r--doc/de/hook/feature_settings_post.bb (renamed from doc/en/hook/feature_settings_post.bb)0
-rw-r--r--doc/de/hook/fetch_and_store.bb (renamed from doc/en/hook/fetch_and_store.bb)0
-rw-r--r--doc/de/hook/follow.bb (renamed from doc/en/hook/follow.bb)0
-rw-r--r--doc/de/hook/follow_allow.bb (renamed from doc/en/hook/follow_allow.bb)0
-rw-r--r--doc/de/hook/gender_selector.bb (renamed from doc/en/hook/gender_selector.bb)0
-rw-r--r--doc/de/hook/gender_selector_min.bb (renamed from doc/en/hook/gender_selector_min.bb)0
-rw-r--r--doc/de/hook/generate_map.bb (renamed from doc/en/hook/generate_map.bb)0
-rw-r--r--doc/de/hook/generate_named_map.bb (renamed from doc/en/hook/generate_named_map.bb)0
-rw-r--r--doc/de/hook/get_all_api_perms.bb (renamed from doc/en/hook/get_all_api_perms.bb)0
-rw-r--r--doc/de/hook/get_all_perms.bb (renamed from doc/en/hook/get_all_perms.bb)0
-rw-r--r--doc/de/hook/get_default_export_sections (renamed from doc/en/hook/get_default_export_sections)0
-rw-r--r--doc/de/hook/get_features.bb (renamed from doc/en/hook/get_features.bb)0
-rw-r--r--doc/de/hook/get_photo.bb (renamed from doc/en/hook/get_photo.bb)0
-rw-r--r--doc/de/hook/get_profile_photo.bb (renamed from doc/en/hook/get_profile_photo.bb)0
-rw-r--r--doc/de/hook/get_role_perms.bb (renamed from doc/en/hook/get_role_perms.bb)0
-rw-r--r--doc/de/hook/global_permissions.bb (renamed from doc/en/hook/global_permissions.bb)0
-rw-r--r--doc/de/hook/home_content.bb (renamed from doc/en/hook/home_content.bb)0
-rw-r--r--doc/de/hook/home_init.bb (renamed from doc/en/hook/home_init.bb)0
-rw-r--r--doc/de/hook/hostxrd.bb (renamed from doc/en/hook/hostxrd.bb)0
-rw-r--r--doc/de/hook/html2bbcode.bb (renamed from doc/en/hook/html2bbcode.bb)0
-rw-r--r--doc/de/hook/identity_basic_export.bb (renamed from doc/en/hook/identity_basic_export.bb)0
-rw-r--r--doc/de/hook/import_author_xchan.bb (renamed from doc/en/hook/import_author_xchan.bb)0
-rw-r--r--doc/de/hook/import_channel.bb (renamed from doc/en/hook/import_channel.bb)0
-rw-r--r--doc/de/hook/import_directory_profile.bb (renamed from doc/en/hook/import_directory_profile.bb)0
-rw-r--r--doc/de/hook/import_xchan.bb (renamed from doc/en/hook/import_xchan.bb)0
-rw-r--r--doc/de/hook/item_custom.bb (renamed from doc/en/hook/item_custom.bb)0
-rw-r--r--doc/de/hook/item_photo_menu.bb (renamed from doc/en/hook/item_photo_menu.bb)0
-rw-r--r--doc/de/hook/item_store.bb (renamed from doc/en/hook/item_store.bb)0
-rw-r--r--doc/de/hook/item_store_update.bb (renamed from doc/en/hook/item_store_update.bb)0
-rw-r--r--doc/de/hook/item_stored.bb (renamed from doc/en/hook/item_stored.bb)0
-rw-r--r--doc/de/hook/item_stored_update.bb (renamed from doc/en/hook/item_stored_update.bb)0
-rw-r--r--doc/de/hook/item_translate.bb (renamed from doc/en/hook/item_translate.bb)0
-rw-r--r--doc/de/hook/jot_header_tpl_filter.bb (renamed from doc/en/hook/jot_header_tpl_filter.bb)0
-rw-r--r--doc/de/hook/jot_networks.bb (renamed from doc/en/hook/jot_networks.bb)0
-rw-r--r--doc/de/hook/jot_tool.bb (renamed from doc/en/hook/jot_tool.bb)0
-rw-r--r--doc/de/hook/jot_tpl_filter.bb (renamed from doc/en/hook/jot_tpl_filter.bb)0
-rw-r--r--doc/de/hook/legal_webbie.bb (renamed from doc/en/hook/legal_webbie.bb)0
-rw-r--r--doc/de/hook/legal_webbie_text.bb (renamed from doc/en/hook/legal_webbie_text.bb)0
-rw-r--r--doc/de/hook/load_pdl.bb (renamed from doc/en/hook/load_pdl.bb)0
-rw-r--r--doc/de/hook/local_dir_update.bb (renamed from doc/en/hook/local_dir_update.bb)0
-rw-r--r--doc/de/hook/logged_in.bb (renamed from doc/en/hook/logged_in.bb)0
-rw-r--r--doc/de/hook/logger.bb (renamed from doc/en/hook/logger.bb)0
-rw-r--r--doc/de/hook/logging_out.bb (renamed from doc/en/hook/logging_out.bb)0
-rw-r--r--doc/de/hook/login_hook.bb (renamed from doc/en/hook/login_hook.bb)0
-rw-r--r--doc/de/hook/magic_auth.bb (renamed from doc/en/hook/magic_auth.bb)0
-rw-r--r--doc/de/hook/magic_auth_openid_success.bb (renamed from doc/en/hook/magic_auth_openid_success.bb)0
-rw-r--r--doc/de/hook/magic_auth_success.bb (renamed from doc/en/hook/magic_auth_success.bb)0
-rw-r--r--doc/de/hook/main_slider.bb (renamed from doc/en/hook/main_slider.bb)0
-rw-r--r--doc/de/hook/marital_selector.bb (renamed from doc/en/hook/marital_selector.bb)0
-rw-r--r--doc/de/hook/marital_selector_min.bb (renamed from doc/en/hook/marital_selector_min.bb)0
-rw-r--r--doc/de/hook/markdown_to_bb.bb (renamed from doc/en/hook/markdown_to_bb.bb)0
-rw-r--r--doc/de/hook/module_loaded.bb (renamed from doc/en/hook/module_loaded.bb)0
-rw-r--r--doc/de/hook/module_mod_aftercontent.bb (renamed from doc/en/hook/module_mod_aftercontent.bb)0
-rw-r--r--doc/de/hook/module_mod_content.bb (renamed from doc/en/hook/module_mod_content.bb)0
-rw-r--r--doc/de/hook/module_mod_init.bb (renamed from doc/en/hook/module_mod_init.bb)0
-rw-r--r--doc/de/hook/module_mod_post.bb (renamed from doc/en/hook/module_mod_post.bb)0
-rw-r--r--doc/de/hook/mood_verbs.bb (renamed from doc/en/hook/mood_verbs.bb)0
-rw-r--r--doc/de/hook/nav.bb (renamed from doc/en/hook/nav.bb)0
-rw-r--r--doc/de/hook/network_content_init.bb (renamed from doc/en/hook/network_content_init.bb)0
-rw-r--r--doc/de/hook/network_ping.bb (renamed from doc/en/hook/network_ping.bb)0
-rw-r--r--doc/de/hook/network_to_name.bb (renamed from doc/en/hook/network_to_name.bb)0
-rw-r--r--doc/de/hook/notifier_end.bb (renamed from doc/en/hook/notifier_end.bb)0
-rw-r--r--doc/de/hook/notifier_hub.bb (renamed from doc/en/hook/notifier_hub.bb)0
-rw-r--r--doc/de/hook/notifier_normal.bb (renamed from doc/en/hook/notifier_normal.bb)0
-rw-r--r--doc/de/hook/obj_verbs.bb (renamed from doc/en/hook/obj_verbs.bb)0
-rw-r--r--doc/de/hook/oembed_probe.bb (renamed from doc/en/hook/oembed_probe.bb)0
-rw-r--r--doc/de/hook/other_encapsulate.bb (renamed from doc/en/hook/other_encapsulate.bb)0
-rw-r--r--doc/de/hook/other_unencapsulate.bb (renamed from doc/en/hook/other_unencapsulate.bb)0
-rw-r--r--doc/de/hook/page_content_top.bb (renamed from doc/en/hook/page_content_top.bb)0
-rw-r--r--doc/de/hook/page_end.bb (renamed from doc/en/hook/page_end.bb)0
-rw-r--r--doc/de/hook/page_header.bb (renamed from doc/en/hook/page_header.bb)0
-rw-r--r--doc/de/hook/page_meta.bb (renamed from doc/en/hook/page_meta.bb)0
-rw-r--r--doc/de/hook/parse_atom.bb (renamed from doc/en/hook/parse_atom.bb)0
-rw-r--r--doc/de/hook/parse_link.bb (renamed from doc/en/hook/parse_link.bb)0
-rw-r--r--doc/de/hook/pdl_selector.bb (renamed from doc/en/hook/pdl_selector.bb)0
-rw-r--r--doc/de/hook/perm_is_allowed.bb (renamed from doc/en/hook/perm_is_allowed.bb)0
-rw-r--r--doc/de/hook/permissions_create.bb (renamed from doc/en/hook/permissions_create.bb)0
-rw-r--r--doc/de/hook/permissions_update.bb (renamed from doc/en/hook/permissions_update.bb)0
-rw-r--r--doc/de/hook/permit_hook.bb (renamed from doc/en/hook/permit_hook.bb)0
-rw-r--r--doc/de/hook/personal_xrd.bb (renamed from doc/en/hook/personal_xrd.bb)0
-rw-r--r--doc/de/hook/photo_post_end.bb (renamed from doc/en/hook/photo_post_end.bb)0
-rw-r--r--doc/de/hook/photo_upload_begin.bb (renamed from doc/en/hook/photo_upload_begin.bb)0
-rw-r--r--doc/de/hook/photo_upload_end.bb (renamed from doc/en/hook/photo_upload_end.bb)0
-rw-r--r--doc/de/hook/photo_upload_file.bb (renamed from doc/en/hook/photo_upload_file.bb)0
-rw-r--r--doc/de/hook/photo_upload_form.bb (renamed from doc/en/hook/photo_upload_form.bb)0
-rw-r--r--doc/de/hook/photo_view_filter.bb (renamed from doc/en/hook/photo_view_filter.bb)0
-rw-r--r--doc/de/hook/poke_verbs.bb (renamed from doc/en/hook/poke_verbs.bb)0
-rw-r--r--doc/de/hook/post_local.bb (renamed from doc/en/hook/post_local.bb)0
-rw-r--r--doc/de/hook/post_local_end.bb (renamed from doc/en/hook/post_local_end.bb)0
-rw-r--r--doc/de/hook/post_local_start.bb (renamed from doc/en/hook/post_local_start.bb)0
-rw-r--r--doc/de/hook/post_mail.bb (renamed from doc/en/hook/post_mail.bb)0
-rw-r--r--doc/de/hook/post_mail_end.bb (renamed from doc/en/hook/post_mail_end.bb)0
-rw-r--r--doc/de/hook/post_remote.bb (renamed from doc/en/hook/post_remote.bb)0
-rw-r--r--doc/de/hook/post_remote_end.bb (renamed from doc/en/hook/post_remote_end.bb)0
-rw-r--r--doc/de/hook/post_remote_update.bb (renamed from doc/en/hook/post_remote_update.bb)0
-rw-r--r--doc/de/hook/post_remote_update_end.bb (renamed from doc/en/hook/post_remote_update_end.bb)0
-rw-r--r--doc/de/hook/prepare_body.bb (renamed from doc/en/hook/prepare_body.bb)0
-rw-r--r--doc/de/hook/prepare_body_final.bb (renamed from doc/en/hook/prepare_body_final.bb)0
-rw-r--r--doc/de/hook/prepare_body_init.bb (renamed from doc/en/hook/prepare_body_init.bb)0
-rw-r--r--doc/de/hook/privacygroup_extras.bb (renamed from doc/en/hook/privacygroup_extras.bb)0
-rw-r--r--doc/de/hook/privacygroup_extras_drop.bb (renamed from doc/en/hook/privacygroup_extras_drop.bb)0
-rw-r--r--doc/de/hook/privacygroup_extras_post.bb (renamed from doc/en/hook/privacygroup_extras_post.bb)0
-rw-r--r--doc/de/hook/proc_run.bb (renamed from doc/en/hook/proc_run.bb)0
-rw-r--r--doc/de/hook/process_channel_sync_delivery.bb (renamed from doc/en/hook/process_channel_sync_delivery.bb)0
-rw-r--r--doc/de/hook/profile_advanced.bb (renamed from doc/en/hook/profile_advanced.bb)0
-rw-r--r--doc/de/hook/profile_edit.bb (renamed from doc/en/hook/profile_edit.bb)0
-rw-r--r--doc/de/hook/profile_photo_content_end.bb (renamed from doc/en/hook/profile_photo_content_end.bb)0
-rw-r--r--doc/de/hook/profile_post.bb (renamed from doc/en/hook/profile_post.bb)0
-rw-r--r--doc/de/hook/profile_sidebar.bb (renamed from doc/en/hook/profile_sidebar.bb)0
-rw-r--r--doc/de/hook/profile_sidebar_enter.bb (renamed from doc/en/hook/profile_sidebar_enter.bb)0
-rw-r--r--doc/de/hook/register_account.bb (renamed from doc/en/hook/register_account.bb)0
-rw-r--r--doc/de/hook/render_location.bb (renamed from doc/en/hook/render_location.bb)0
-rw-r--r--doc/de/hook/replace_macros.bb (renamed from doc/en/hook/replace_macros.bb)0
-rw-r--r--doc/de/hook/reverse_magic_auth.bb (renamed from doc/en/hook/reverse_magic_auth.bb)0
-rw-r--r--doc/de/hook/settings_form.bb (renamed from doc/en/hook/settings_form.bb)0
-rw-r--r--doc/de/hook/settings_post.bb (renamed from doc/en/hook/settings_post.bb)0
-rw-r--r--doc/de/hook/sexpref_selector.bb (renamed from doc/en/hook/sexpref_selector.bb)0
-rw-r--r--doc/de/hook/sexpref_selector_min.bb (renamed from doc/en/hook/sexpref_selector_min.bb)0
-rw-r--r--doc/de/hook/smilie.bb (renamed from doc/en/hook/smilie.bb)0
-rw-r--r--doc/de/hook/status_editor.bb (renamed from doc/en/hook/status_editor.bb)0
-rw-r--r--doc/de/hook/stream_item.bb (renamed from doc/en/hook/stream_item.bb)0
-rw-r--r--doc/de/hook/system_app_installed_filter.bb (renamed from doc/en/hook/system_app_installed_filter.bb)0
-rw-r--r--doc/de/hook/tagged.bb (renamed from doc/en/hook/tagged.bb)0
-rw-r--r--doc/de/hook/update_unseen.bb (renamed from doc/en/hook/update_unseen.bb)0
-rw-r--r--doc/de/hook/validate_channelname.bb (renamed from doc/en/hook/validate_channelname.bb)0
-rw-r--r--doc/de/hook/webfinger.bb (renamed from doc/en/hook/webfinger.bb)0
-rw-r--r--doc/de/hook/well_known.bb (renamed from doc/en/hook/well_known.bb)0
-rw-r--r--doc/de/hook/wiki_preprocess.bb (renamed from doc/en/hook/wiki_preprocess.bb)0
-rw-r--r--doc/de/hook/zid.bb (renamed from doc/en/hook/zid.bb)0
-rw-r--r--doc/de/hook/zid_init.bb (renamed from doc/en/hook/zid_init.bb)0
-rw-r--r--doc/de/hook/zot_best_algorithm.bb (renamed from doc/en/hook/zot_best_algorithm.bb)0
-rw-r--r--doc/de/hook/zot_finger.bb (renamed from doc/en/hook/zot_finger.bb)0
-rw-r--r--doc/de/hooks.html (renamed from doc/en/hooks.html)0
-rw-r--r--doc/de/main.bb11
-rw-r--r--doc/de/member/AdvancedSearch.md30
-rw-r--r--doc/de/member/NSFW.md21
-rw-r--r--doc/de/member/account_settings.md5
-rw-r--r--doc/de/member/accounts_profiles_channels_basics.md30
-rw-r--r--doc/de/member/additional_features.md31
-rw-r--r--doc/de/member/addressbook.md23
-rw-r--r--doc/de/member/apps.md59
-rw-r--r--doc/de/member/article.md27
-rw-r--r--doc/de/member/bbcode.md79
-rw-r--r--doc/de/member/blocking_channels.md19
-rw-r--r--doc/de/member/bookmarks.md27
-rw-r--r--doc/de/member/calendar.md27
-rw-r--r--doc/de/member/channel_locations.md7
-rw-r--r--doc/de/member/channel_roles.md41
-rw-r--r--doc/de/member/channel_settings.md15
-rw-r--r--doc/de/member/channels.md22
-rw-r--r--doc/de/member/chat_rooms.md17
-rw-r--r--doc/de/member/clone.md53
-rw-r--r--doc/de/member/cloud_storage.md10
-rw-r--r--doc/de/member/comanche.md278
-rw-r--r--doc/de/member/commenting.md9
-rw-r--r--doc/de/member/connecting_with_channels.md33
-rw-r--r--doc/de/member/connection_editor.md32
-rw-r--r--doc/de/member/connections.md24
-rw-r--r--doc/de/member/content_filters.md141
-rw-r--r--doc/de/member/conversation_features.md9
-rw-r--r--doc/de/member/create_channels.md9
-rw-r--r--doc/de/member/delete.md3
-rw-r--r--doc/de/member/delete_account.md8
-rw-r--r--doc/de/member/deleting_channel.md9
-rw-r--r--doc/de/member/diaspora_compat.md33
-rw-r--r--doc/de/member/direct_messages.md9
-rw-r--r--doc/de/member/directory.md15
-rw-r--r--doc/de/member/display_settings.md19
-rw-r--r--doc/de/member/encryption.md15
-rw-r--r--doc/de/member/files.md31
-rw-r--r--doc/de/member/follow_conversation.md3
-rw-r--r--doc/de/member/gallery.md7
-rw-r--r--doc/de/member/guest_access.md15
-rw-r--r--doc/de/member/important_apps.md17
-rw-r--r--doc/de/member/insert_images.md53
-rw-r--r--doc/de/member/interact.md25
-rw-r--r--doc/de/member/link_to_source.md3
-rw-r--r--doc/de/member/mentions.md9
-rw-r--r--doc/de/member/overview.md3
-rw-r--r--doc/de/member/permissions.md7
-rw-r--r--doc/de/member/permissions_channel_roles.md45
-rw-r--r--doc/de/member/permissions_contact_roles.md17
-rw-r--r--doc/de/member/permissions_content.md38
-rw-r--r--doc/de/member/photos.md17
-rw-r--r--doc/de/member/pic/anzeinst01.pngbin0 -> 158390 bytes
-rw-r--r--doc/de/member/pic/anzeinst02.pngbin0 -> 70507 bytes
-rw-r--r--doc/de/member/pic/anzeinst03.pngbin0 -> 87707 bytes
-rw-r--r--doc/de/member/pic/anzeinst04.pngbin0 -> 100155 bytes
-rw-r--r--doc/de/member/pic/apps01.pngbin0 -> 112759 bytes
-rw-r--r--doc/de/member/pic/apps02.pngbin0 -> 116905 bytes
-rw-r--r--doc/de/member/pic/apps03.pngbin0 -> 4966 bytes
-rw-r--r--doc/de/member/pic/apps04.pngbin0 -> 2084 bytes
-rw-r--r--doc/de/member/pic/apps05.pngbin0 -> 5269 bytes
-rw-r--r--doc/de/member/pic/apps06.pngbin0 -> 2558 bytes
-rw-r--r--doc/de/member/pic/apps07.pngbin0 -> 23964 bytes
-rw-r--r--doc/de/member/pic/apps08.pngbin0 -> 1135 bytes
-rw-r--r--doc/de/member/pic/artikel01.pngbin0 -> 26149 bytes
-rw-r--r--doc/de/member/pic/artikel02.pngbin0 -> 52995 bytes
-rw-r--r--doc/de/member/pic/artikel03.pngbin0 -> 29633 bytes
-rw-r--r--doc/de/member/pic/artikel04.pngbin0 -> 66553 bytes
-rw-r--r--doc/de/member/pic/artikel05.pngbin0 -> 7776 bytes
-rw-r--r--doc/de/member/pic/author.pngbin0 -> 5688 bytes
-rw-r--r--doc/de/member/pic/bild01.pngbin0 -> 25262 bytes
-rw-r--r--doc/de/member/pic/bild02.pngbin0 -> 24360 bytes
-rw-r--r--doc/de/member/pic/bild03.pngbin0 -> 48384 bytes
-rw-r--r--doc/de/member/pic/bild04.pngbin0 -> 41497 bytes
-rw-r--r--doc/de/member/pic/bild05.pngbin0 -> 9779 bytes
-rw-r--r--doc/de/member/pic/bild06.pngbin0 -> 42816 bytes
-rw-r--r--doc/de/member/pic/bookm01.pngbin0 -> 8889 bytes
-rw-r--r--doc/de/member/pic/bookm02.pngbin0 -> 5323 bytes
-rw-r--r--doc/de/member/pic/bookm03.pngbin0 -> 19880 bytes
-rw-r--r--doc/de/member/pic/carddav01.pngbin0 -> 54753 bytes
-rw-r--r--doc/de/member/pic/carddav02.pngbin0 -> 31096 bytes
-rw-r--r--doc/de/member/pic/carddav03.pngbin0 -> 39179 bytes
-rw-r--r--doc/de/member/pic/carddav04.pngbin0 -> 36998 bytes
-rw-r--r--doc/de/member/pic/carddav05.pngbin0 -> 75320 bytes
-rw-r--r--doc/de/member/pic/carddav06.pngbin0 -> 13325 bytes
-rw-r--r--doc/de/member/pic/carddav07.pngbin0 -> 19086 bytes
-rw-r--r--doc/de/member/pic/center.pngbin0 -> 3096 bytes
-rw-r--r--doc/de/member/pic/chat01.pngbin0 -> 26833 bytes
-rw-r--r--doc/de/member/pic/chat02.pngbin0 -> 40254 bytes
-rw-r--r--doc/de/member/pic/chat03.pngbin0 -> 38345 bytes
-rw-r--r--doc/de/member/pic/code.pngbin0 -> 9206 bytes
-rw-r--r--doc/de/member/pic/contfilter01.pngbin0 -> 39194 bytes
-rw-r--r--doc/de/member/pic/contfilter02.pngbin0 -> 48243 bytes
-rw-r--r--doc/de/member/pic/croles1.pngbin0 -> 134124 bytes
-rw-r--r--doc/de/member/pic/croles2.pngbin0 -> 50769 bytes
-rw-r--r--doc/de/member/pic/dateien01.pngbin0 -> 33138 bytes
-rw-r--r--doc/de/member/pic/dateien02.pngbin0 -> 19003 bytes
-rw-r--r--doc/de/member/pic/dateien03.pngbin0 -> 12837 bytes
-rw-r--r--doc/de/member/pic/einst01.pngbin0 -> 20963 bytes
-rw-r--r--doc/de/member/pic/einst02.pngbin0 -> 14415 bytes
-rw-r--r--doc/de/member/pic/einst03.pngbin0 -> 17916 bytes
-rw-r--r--doc/de/member/pic/feateinst01.pngbin0 -> 22554 bytes
-rw-r--r--doc/de/member/pic/feateinst02.pngbin0 -> 43748 bytes
-rw-r--r--doc/de/member/pic/feateinst03.pngbin0 -> 51816 bytes
-rw-r--r--doc/de/member/pic/feateinst04.pngbin0 -> 37917 bytes
-rw-r--r--doc/de/member/pic/feateinst05.pngbin0 -> 60531 bytes
-rw-r--r--doc/de/member/pic/feateinst06.pngbin0 -> 34318 bytes
-rw-r--r--doc/de/member/pic/feateinst07.pngbin0 -> 133220 bytes
-rw-r--r--doc/de/member/pic/feateinst08.pngbin0 -> 37036 bytes
-rw-r--r--doc/de/member/pic/feateinst09.pngbin0 -> 105349 bytes
-rw-r--r--doc/de/member/pic/feateinst10.pngbin0 -> 47171 bytes
-rw-r--r--doc/de/member/pic/feateinst11.pngbin0 -> 49836 bytes
-rw-r--r--doc/de/member/pic/font.pngbin0 -> 1700 bytes
-rw-r--r--doc/de/member/pic/fotos01.pngbin0 -> 164156 bytes
-rw-r--r--doc/de/member/pic/fotos02.pngbin0 -> 804 bytes
-rw-r--r--doc/de/member/pic/fotos03.pngbin0 -> 113084 bytes
-rw-r--r--doc/de/member/pic/fotos04.pngbin0 -> 69038 bytes
-rw-r--r--doc/de/member/pic/galerie01.pngbin0 -> 77088 bytes
-rw-r--r--doc/de/member/pic/hbar.pngbin0 -> 3998 bytes
-rw-r--r--doc/de/member/pic/highlited.pngbin0 -> 1751 bytes
-rw-r--r--doc/de/member/pic/image.pngbin0 -> 54664 bytes
-rw-r--r--doc/de/member/pic/inhber01.pngbin0 -> 1617 bytes
-rw-r--r--doc/de/member/pic/inhber02.pngbin0 -> 1392 bytes
-rw-r--r--doc/de/member/pic/inhber03.pngbin0 -> 1667 bytes
-rw-r--r--doc/de/member/pic/inhber04.pngbin0 -> 2228 bytes
-rw-r--r--doc/de/member/pic/inhber05.pngbin0 -> 21622 bytes
-rw-r--r--doc/de/member/pic/inhber06.pngbin0 -> 88677 bytes
-rw-r--r--doc/de/member/pic/interag01.pngbin0 -> 2373 bytes
-rw-r--r--doc/de/member/pic/interag02.pngbin0 -> 103021 bytes
-rw-r--r--doc/de/member/pic/interag03.pngbin0 -> 34038 bytes
-rw-r--r--doc/de/member/pic/kalender01.pngbin0 -> 44229 bytes
-rw-r--r--doc/de/member/pic/kalender02.pngbin0 -> 16968 bytes
-rw-r--r--doc/de/member/pic/kalender03.pngbin0 -> 17290 bytes
-rw-r--r--doc/de/member/pic/kalender04.pngbin0 -> 41619 bytes
-rw-r--r--doc/de/member/pic/kalender05.pngbin0 -> 16190 bytes
-rw-r--r--doc/de/member/pic/kaneinst01.pngbin0 -> 163821 bytes
-rw-r--r--doc/de/member/pic/kaneinst02.pngbin0 -> 136212 bytes
-rw-r--r--doc/de/member/pic/kanloe.pngbin0 -> 6797 bytes
-rw-r--r--doc/de/member/pic/klone01.pngbin0 -> 232388 bytes
-rw-r--r--doc/de/member/pic/klone02.pngbin0 -> 14732 bytes
-rw-r--r--doc/de/member/pic/klone03.pngbin0 -> 18088 bytes
-rw-r--r--doc/de/member/pic/klone04.pngbin0 -> 65885 bytes
-rw-r--r--doc/de/member/pic/klone05.pngbin0 -> 127551 bytes
-rw-r--r--doc/de/member/pic/klone06.pngbin0 -> 62525 bytes
-rw-r--r--doc/de/member/pic/kloneinst01.pngbin0 -> 286898 bytes
-rw-r--r--doc/de/member/pic/kmerkmale.pngbin0 -> 46119 bytes
-rw-r--r--doc/de/member/pic/kommentieren.pngbin0 -> 4656 bytes
-rw-r--r--doc/de/member/pic/ktoeinst01.pngbin0 -> 59391 bytes
-rw-r--r--doc/de/member/pic/ktoloe.pngbin0 -> 7317 bytes
-rw-r--r--doc/de/member/pic/mauth.pngbin0 -> 4785 bytes
-rw-r--r--doc/de/member/pic/nsfw01.pngbin0 -> 55823 bytes
-rw-r--r--doc/de/member/pic/nsfw02.pngbin0 -> 53680 bytes
-rw-r--r--doc/de/member/pic/nsfw03.pngbin0 -> 18230 bytes
-rw-r--r--doc/de/member/pic/nsfw04.pngbin0 -> 60267 bytes
-rw-r--r--doc/de/member/pic/ordner01.pngbin0 -> 19452 bytes
-rw-r--r--doc/de/member/pic/ordner02.pngbin0 -> 21901 bytes
-rw-r--r--doc/de/member/pic/ordner03.pngbin0 -> 9825 bytes
-rw-r--r--doc/de/member/pic/ordner04.pngbin0 -> 9629 bytes
-rw-r--r--doc/de/member/pic/pgroups01.pngbin0 -> 26547 bytes
-rw-r--r--doc/de/member/pic/pgroups02.pngbin0 -> 61552 bytes
-rw-r--r--doc/de/member/pic/pgroups03.pngbin0 -> 34222 bytes
-rw-r--r--doc/de/member/pic/priveinst01.pngbin0 -> 71156 bytes
-rw-r--r--doc/de/member/pic/qrcode.pngbin0 -> 273 bytes
-rw-r--r--doc/de/member/pic/quellcode.pngbin0 -> 53357 bytes
-rw-r--r--doc/de/member/pic/quote.pngbin0 -> 1701 bytes
-rw-r--r--doc/de/member/pic/red.pngbin0 -> 983 bytes
-rw-r--r--doc/de/member/pic/size.pngbin0 -> 7619 bytes
-rw-r--r--doc/de/member/pic/statusfilter.pngbin0 -> 14341 bytes
-rw-r--r--doc/de/member/pic/stern.pngbin0 -> 5664 bytes
-rw-r--r--doc/de/member/pic/streinst01.pngbin0 -> 153673 bytes
-rw-r--r--doc/de/member/pic/suche01.pngbin0 -> 767 bytes
-rw-r--r--doc/de/member/pic/suche02.pngbin0 -> 3815 bytes
-rw-r--r--doc/de/member/pic/suche03.pngbin0 -> 40892 bytes
-rw-r--r--doc/de/member/pic/table1.pngbin0 -> 7050 bytes
-rw-r--r--doc/de/member/pic/table2.pngbin0 -> 6532 bytes
-rw-r--r--doc/de/member/pic/table3.pngbin0 -> 3775 bytes
-rw-r--r--doc/de/member/pic/teilen.pngbin0 -> 6455 bytes
-rw-r--r--doc/de/member/pic/verbindungen01.pngbin0 -> 192790 bytes
-rw-r--r--doc/de/member/pic/verbindungen02.pngbin0 -> 36647 bytes
-rw-r--r--doc/de/member/pic/verbindungen03.pngbin0 -> 35629 bytes
-rw-r--r--doc/de/member/pic/verbindungen04.pngbin0 -> 67759 bytes
-rw-r--r--doc/de/member/pic/verbindungen05.pngbin0 -> 38471 bytes
-rw-r--r--doc/de/member/pic/verbindungen06.pngbin0 -> 33526 bytes
-rw-r--r--doc/de/member/pic/verzeichnis.pngbin0 -> 368155 bytes
-rw-r--r--doc/de/member/pic/video_poster.pngbin0 -> 17729 bytes
-rw-r--r--doc/de/member/pic/webseiten01.pngbin0 -> 23408 bytes
-rw-r--r--doc/de/member/pic/webseiten02.pngbin0 -> 35491 bytes
-rw-r--r--doc/de/member/pic/webseiten03.pngbin0 -> 28965 bytes
-rw-r--r--doc/de/member/pic/webseiten04.pngbin0 -> 34342 bytes
-rw-r--r--doc/de/member/pic/webseiten05.pngbin0 -> 52292 bytes
-rw-r--r--doc/de/member/pic/webseiten06.pngbin0 -> 23341 bytes
-rw-r--r--doc/de/member/pic/wiki01.pngbin0 -> 11513 bytes
-rw-r--r--doc/de/member/pic/wiki02.pngbin0 -> 34939 bytes
-rw-r--r--doc/de/member/pic/wiki03.pngbin0 -> 54716 bytes
-rw-r--r--doc/de/member/pic/wiki04.pngbin0 -> 171749 bytes
-rw-r--r--doc/de/member/posting.md17
-rw-r--r--doc/de/member/privacy_groups.md23
-rw-r--r--doc/de/member/privacy_settings.md6
-rw-r--r--doc/de/member/profiles.md23
-rw-r--r--doc/de/member/protection_of_privacy.md67
-rw-r--r--doc/de/member/public_stream.md12
-rw-r--r--doc/de/member/registration.md19
-rw-r--r--doc/de/member/repeat.md5
-rw-r--r--doc/de/member/save_to_folder.md25
-rw-r--r--doc/de/member/search.md15
-rw-r--r--doc/de/member/settings.md37
-rw-r--r--doc/de/member/share.md5
-rw-r--r--doc/de/member/show_source_code.md15
-rw-r--r--doc/de/member/stream_settings.md9
-rw-r--r--doc/de/member/superblock.md15
-rw-r--r--doc/de/member/tags.md5
-rw-r--r--doc/de/member/the_grid.md3
-rw-r--r--doc/de/member/the_stream.md7
-rw-r--r--doc/de/member/toggle_star_status.md9
-rw-r--r--doc/de/member/websites.md143
-rw-r--r--doc/de/member/wikis.md39
-rw-r--r--doc/de/member_faq.md17
-rw-r--r--doc/de/members.bb25
-rw-r--r--doc/de/pic/ui01.pngbin0 -> 218419 bytes
-rw-r--r--doc/de/pic/ui02.pngbin0 -> 29754 bytes
-rw-r--r--doc/de/platzhalter.md1
-rw-r--r--doc/de/profiles.bb40
-rw-r--r--doc/de/registration.bb36
-rw-r--r--doc/de/roles.bb60
-rw-r--r--doc/de/the_hubzilla_project.md116
-rw-r--r--doc/de/toc.html104
-rw-r--r--doc/de/tutorials/DerivedTheme.md96
-rw-r--r--doc/de/tutorials/customise_look.md221
-rw-r--r--doc/de/tutorials/pic/02.pngbin0 -> 69550 bytes
-rw-r--r--doc/de/tutorials/pic/03.pngbin0 -> 18731 bytes
-rw-r--r--doc/de/tutorials/pic/04.pngbin0 -> 53482 bytes
-rw-r--r--doc/de/tutorials/pic/05.pngbin0 -> 26665 bytes
-rw-r--r--doc/de/tutorials/pic/06.pngbin0 -> 39055 bytes
-rw-r--r--doc/de/tutorials/pic/07.pngbin0 -> 65530 bytes
-rw-r--r--doc/de/tutorials/pic/08.pngbin0 -> 24603 bytes
-rw-r--r--doc/de/tutorials/pic/09.pngbin0 -> 23763 bytes
-rw-r--r--doc/de/tutorials/pic/10.pngbin0 -> 67730 bytes
-rw-r--r--doc/de/tutorials/pic/11.pngbin0 -> 14390 bytes
-rw-r--r--doc/de/tutorials/pic/12.pngbin0 -> 72842 bytes
-rw-r--r--doc/de/tutorials/pic/13.pngbin0 -> 81388 bytes
-rw-r--r--doc/de/tutorials/pic/14.pngbin0 -> 60861 bytes
-rw-r--r--doc/de/tutorials/pic/15.pngbin0 -> 21657 bytes
-rw-r--r--doc/de/tutorials/pic/16.pngbin0 -> 20138 bytes
-rw-r--r--doc/de/tutorials/pic/17.pngbin0 -> 27437 bytes
-rw-r--r--doc/de/tutorials/pic/18.pngbin0 -> 65125 bytes
-rw-r--r--doc/de/tutorials/pic/19.pngbin0 -> 62228 bytes
-rw-r--r--doc/de/tutorials/pic/20.pngbin0 -> 53937 bytes
-rw-r--r--doc/de/tutorials/pic/fedidb.pngbin0 -> 244957 bytes
-rw-r--r--doc/de/tutorials/pic/fedieverse-observer.pngbin0 -> 106457 bytes
-rw-r--r--doc/de/tutorials/pic/hzreg01.pngbin0 -> 47088 bytes
-rw-r--r--doc/de/tutorials/pic/hzreg01a.pngbin0 -> 126943 bytes
-rw-r--r--doc/de/tutorials/pic/hzreg02.pngbin0 -> 80507 bytes
-rw-r--r--doc/de/tutorials/pic/hzreg03.pngbin0 -> 106952 bytes
-rw-r--r--doc/de/tutorials/pic/hzreg04.pngbin0 -> 64621 bytes
-rw-r--r--doc/de/tutorials/pic/nomadapp.pngbin0 -> 172012 bytes
-rw-r--r--doc/de/tutorials/pic/pdle01.pngbin0 -> 1339614 bytes
-rw-r--r--doc/de/tutorials/pic/pdle02.pngbin0 -> 207233 bytes
-rw-r--r--doc/de/tutorials/pic/pdle03.pngbin0 -> 428803 bytes
-rw-r--r--doc/de/tutorials/pic/pdle04.pngbin0 -> 209056 bytes
-rw-r--r--doc/de/tutorials/pic/pdle05.pngbin0 -> 199353 bytes
-rw-r--r--doc/de/tutorials/pic/pdle06.pngbin0 -> 241793 bytes
-rw-r--r--doc/de/tutorials/pic/pdle07.pngbin0 -> 216392 bytes
-rw-r--r--doc/de/tutorials/pic/pdle08.pngbin0 -> 200478 bytes
-rw-r--r--doc/de/tutorials/pic/pdle09.pngbin0 -> 79722 bytes
-rw-r--r--doc/de/tutorials/pic/pdle10.pngbin0 -> 39266 bytes
-rw-r--r--doc/de/tutorials/pic/pdle11.pngbin0 -> 28468 bytes
-rw-r--r--doc/de/tutorials/pic/pdle12.pngbin0 -> 65077 bytes
-rw-r--r--doc/de/tutorials/pic/pdle13.pngbin0 -> 236620 bytes
-rw-r--r--doc/de/tutorials/pic/pdle14.pngbin0 -> 217419 bytes
-rw-r--r--doc/de/tutorials/pic/pdle15.pngbin0 -> 235097 bytes
-rw-r--r--doc/de/tutorials/pic/pubsites.pngbin0 -> 167653 bytes
-rw-r--r--doc/de/tutorials/pic/rega.pngbin0 -> 197041 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt01.pngbin0 -> 147642 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt02.pngbin0 -> 40127 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt03.pngbin0 -> 19324 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt04.pngbin0 -> 32439 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt05.pngbin0 -> 37690 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt05a.pngbin0 -> 26775 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt06.pngbin0 -> 70132 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt07.pngbin0 -> 85328 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt08.pngbin0 -> 85325 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt09.pngbin0 -> 68681 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt10.pngbin0 -> 71936 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt11.pngbin0 -> 72832 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt12.pngbin0 -> 75061 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt13.pngbin0 -> 79633 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt14.pngbin0 -> 80973 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt15.pngbin0 -> 10289 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt16.pngbin0 -> 19490 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt17.pngbin0 -> 93005 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt18.pngbin0 -> 90178 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt19.pngbin0 -> 89177 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt20.pngbin0 -> 93194 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt21.pngbin0 -> 89314 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt22.pngbin0 -> 84386 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt23.pngbin0 -> 120335 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt24.pngbin0 -> 120201 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt25.pngbin0 -> 132346 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt26.pngbin0 -> 98324 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt27.pngbin0 -> 89246 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt28.pngbin0 -> 83517 bytes
-rw-r--r--doc/de/tutorials/pic/tut_opt29.pngbin0 -> 297471 bytes
-rw-r--r--doc/de/tutorials/step_with_hubzilla.md160
-rw-r--r--doc/de/tutorials/tutorials.md7
-rw-r--r--doc/de/ui.md5
-rw-r--r--doc/en/AdvancedSearch.md53
-rw-r--r--doc/en/Comparison-of-activity-stream-networks.md23
-rw-r--r--doc/en/Creating-Templates.md93
-rw-r--r--doc/en/DerivedTheme1.md101
-rw-r--r--doc/en/Developers.md54
-rw-r--r--doc/en/Features.md108
-rw-r--r--doc/en/Hubzilla_on_OpenShift.bb105
-rw-r--r--doc/en/Plugins.md263
-rw-r--r--doc/en/Primary-Directory.md47
-rw-r--r--doc/en/Remove-Account.md26
-rw-r--r--doc/en/Schema-development.md81
-rw-r--r--doc/en/TermsOfService.md11
-rw-r--r--doc/en/Translations.md93
-rw-r--r--doc/en/Widgets.md174
-rw-r--r--doc/en/Zot---A-High-Level-Overview.md109
-rw-r--r--doc/en/about.md45
-rw-r--r--doc/en/about/about.bb204
-rw-r--r--doc/en/about/project.bb186
-rw-r--r--doc/en/accounts_profiles_channels_basics.bb19
-rw-r--r--doc/en/acl_dialog_post.html16
-rw-r--r--doc/en/addons.bb117
-rw-r--r--doc/en/addons_gnusocial.bb65
-rw-r--r--doc/en/admin/administrator_guide.md464
-rw-r--r--doc/en/admin/hub_snapshots.md127
-rw-r--r--doc/en/admin/zarlog_msgs.md112
-rw-r--r--doc/en/adminmanual/CLI_tools.md112
-rw-r--r--doc/en/adminmanual/Creating-Templates.md86
-rw-r--r--doc/en/adminmanual/Installation_using_docker.md56
-rw-r--r--doc/en/adminmanual/Nomad---A-High-Level-Overview.md107
-rw-r--r--doc/en/adminmanual/Primary-Directory.md46
-rw-r--r--doc/en/adminmanual/Schema-development.md76
-rw-r--r--doc/en/adminmanual/Widgets.md157
-rw-r--r--doc/en/adminmanual/Zot---A-High-Level-Overview.md107
-rw-r--r--doc/en/adminmanual/administration.md11
-rw-r--r--doc/en/adminmanual/advanced_configurations.md182
-rw-r--r--doc/en/adminmanual/automated_installation.md26
-rw-r--r--doc/en/adminmanual/before_you_start.md15
-rw-r--r--doc/en/adminmanual/channel_dirctory.md15
-rw-r--r--doc/en/adminmanual/database.md72
-rw-r--r--doc/en/adminmanual/directories.md89
-rw-r--r--doc/en/adminmanual/faq_admins.md87
-rw-r--r--doc/en/adminmanual/federation_addons.md8
-rw-r--r--doc/en/adminmanual/filesync.md53
-rw-r--r--doc/en/adminmanual/further_help.md3
-rw-r--r--doc/en/adminmanual/hub_snapshot_tools.md127
-rw-r--r--doc/en/adminmanual/installation.md8
-rw-r--r--doc/en/adminmanual/installation_requirements.md14
-rw-r--r--doc/en/adminmanual/keywords.md1
-rw-r--r--doc/en/adminmanual/manual_for_administrators.md29
-rw-r--r--doc/en/adminmanual/manual_installation.md60
-rw-r--r--doc/en/adminmanual/pic/adm01.pngbin0 -> 362351 bytes
-rw-r--r--doc/en/adminmanual/problems-following-an-update.md39
-rw-r--r--doc/en/adminmanual/recommended_addons.md7
-rw-r--r--doc/en/adminmanual/service_classes.md57
-rw-r--r--doc/en/adminmanual/theme_management.md7
-rw-r--r--doc/en/adminmanual/troubleshooting.md55
-rw-r--r--doc/en/admins.bb15
-rw-r--r--doc/en/api/api_albums.md66
-rw-r--r--doc/en/api/api_filedata.md66
-rw-r--r--doc/en/api/api_files.md103
-rw-r--r--doc/en/api/api_functions.bb133
-rw-r--r--doc/en/api/api_group_members.md133
-rw-r--r--doc/en/api/api_item_update.md225
-rw-r--r--doc/en/api/api_posting.bb24
-rw-r--r--doc/en/api/api_xchan.md44
-rw-r--r--doc/en/api/group.md41
-rw-r--r--doc/en/api/statuses_update.bb23
-rw-r--r--doc/en/campaign.bb237
-rw-r--r--doc/en/checking_account_quota_usage.bb20
-rw-r--r--doc/en/comanche.bb261
-rw-r--r--doc/en/context/de/admin/logs/help.html8
-rw-r--r--doc/en/context/de/admin/queue/help.html4
-rw-r--r--doc/en/context/de/admin/security/help.html4
-rw-r--r--doc/en/context/de/appman/help.html4
-rw-r--r--doc/en/context/de/channel/help.html8
-rw-r--r--doc/en/context/de/chat/help.html8
-rw-r--r--doc/en/context/de/cloud/help.html6
-rw-r--r--doc/en/context/de/connections/help.html8
-rw-r--r--doc/en/context/de/connections/ifpending/help.html8
-rw-r--r--doc/en/context/de/events/help.html8
-rw-r--r--doc/en/context/de/mail/help.html12
-rw-r--r--doc/en/context/de/network/help.html10
-rw-r--r--doc/en/context/de/photos/help.html6
-rw-r--r--doc/en/context/de/profile/help.html6
-rw-r--r--doc/en/context/de/register/help.html12
-rw-r--r--doc/en/context/de/settings/account/help.html19
-rw-r--r--doc/en/context/de/settings/channel/help.html19
-rw-r--r--doc/en/context/de/settings/features/help.html12
-rw-r--r--doc/en/context/de/settings/tokens/help.html20
-rw-r--r--doc/en/context/de/wiki/help.html10
-rw-r--r--doc/en/context/en/admin/addons/assets/addon_repo_gui_1.pngbin16983 -> 0 bytes
-rw-r--r--doc/en/context/en/admin/addons/help.html14
-rw-r--r--doc/en/context/en/admin/logs/help.html8
-rw-r--r--doc/en/context/en/admin/queue/help.html4
-rw-r--r--doc/en/context/en/admin/security/help.html4
-rw-r--r--doc/en/context/en/appman/help.html4
-rw-r--r--doc/en/context/en/cards/help.html20
-rw-r--r--doc/en/context/en/channel/help.html6
-rw-r--r--doc/en/context/en/chat/help.html8
-rw-r--r--doc/en/context/en/cloud/help.html6
-rw-r--r--doc/en/context/en/connections/help.html8
-rw-r--r--doc/en/context/en/connections/ifpending/help.html8
-rw-r--r--doc/en/context/en/connedit/help.html12
-rw-r--r--doc/en/context/en/events/help.html8
-rw-r--r--doc/en/context/en/mail/help.html10
-rw-r--r--doc/en/context/en/network/help.html10
-rw-r--r--doc/en/context/en/photos/help.html6
-rw-r--r--doc/en/context/en/profile/help.html6
-rw-r--r--doc/en/context/en/profiles/help.html35
-rw-r--r--doc/en/context/en/register/help.html12
-rw-r--r--doc/en/context/en/settings/account/help.html35
-rw-r--r--doc/en/context/en/settings/channel/help.html35
-rw-r--r--doc/en/context/en/settings/features/help.html12
-rw-r--r--doc/en/context/en/settings/tokens/help.html23
-rw-r--r--doc/en/context/en/webpages/help.html8
-rw-r--r--doc/en/context/en/wiki/help.html10
l---------doc/en/context/es1
-rw-r--r--doc/en/context/es-es/admin/addons/assets/addon_repo_gui_1.pngbin16983 -> 0 bytes
-rw-r--r--doc/en/context/es-es/admin/addons/help.html14
-rw-r--r--doc/en/context/es-es/admin/logs/help.html9
-rw-r--r--doc/en/context/es-es/admin/queue/help.html4
-rw-r--r--doc/en/context/es-es/admin/security/help.html4
-rw-r--r--doc/en/context/es-es/appman/help.html4
-rw-r--r--doc/en/context/es-es/articles/help.html20
-rw-r--r--doc/en/context/es-es/cards/help.html19
-rw-r--r--doc/en/context/es-es/channel/help.html8
-rw-r--r--doc/en/context/es-es/chat/help.html8
-rw-r--r--doc/en/context/es-es/cloud/help.html6
-rw-r--r--doc/en/context/es-es/connections/help.html15
-rw-r--r--doc/en/context/es-es/connections/ifpending/help.html15
-rw-r--r--doc/en/context/es-es/connedit/help.html12
-rw-r--r--doc/en/context/es-es/events/help.html8
-rw-r--r--doc/en/context/es-es/mail/help.html10
-rw-r--r--doc/en/context/es-es/network/help.html12
-rw-r--r--doc/en/context/es-es/photos/help.html6
-rw-r--r--doc/en/context/es-es/profile/help.html6
-rw-r--r--doc/en/context/es-es/profiles/help.html35
-rw-r--r--doc/en/context/es-es/settings/account/help.html35
-rw-r--r--doc/en/context/es-es/settings/channel/help.html35
-rw-r--r--doc/en/context/es-es/settings/features/help.html12
-rw-r--r--doc/en/context/es-es/settings/tokens/help.html23
-rw-r--r--doc/en/context/es-es/webpages/help.html8
-rw-r--r--doc/en/context/es-es/wiki/help.html10
-rw-r--r--doc/en/context/fr/admin/logs/help.html8
-rw-r--r--doc/en/context/fr/admin/queue/help.html4
-rw-r--r--doc/en/context/fr/appman/help.html5
-rw-r--r--doc/en/context/fr/cards/help.html20
-rw-r--r--doc/en/context/fr/channel/help.html6
-rw-r--r--doc/en/context/fr/chat/help.html8
-rw-r--r--doc/en/context/fr/cloud/help.html7
-rw-r--r--doc/en/context/fr/connections/help.html8
-rw-r--r--doc/en/context/fr/connections/ifpending/help.html8
-rw-r--r--doc/en/context/fr/connedit/help.html13
-rw-r--r--doc/en/context/fr/events/help.html8
-rw-r--r--doc/en/context/fr/mail/help.html10
-rw-r--r--doc/en/context/fr/photos/help.html6
-rw-r--r--doc/en/context/fr/profile/help.html6
-rw-r--r--doc/en/context/fr/settings/account/help.html18
-rw-r--r--doc/en/context/fr/settings/channel/help.html18
-rw-r--r--doc/en/context/fr/settings/features/help.html12
-rw-r--r--doc/en/context/fr/settings/tokens20
-rw-r--r--doc/en/context/fr/webpages/help.html8
-rw-r--r--doc/en/context/fr/wiki/help.html13
-rw-r--r--doc/en/context/it/wiki/help.html10
-rw-r--r--doc/en/context/pl/admin/addons/assets/addon_repo_gui_1.pngbin16983 -> 0 bytes
-rw-r--r--doc/en/context/pl/admin/addons/help.html14
-rw-r--r--doc/en/context/pl/admin/logs/help.html20
-rw-r--r--doc/en/context/pl/admin/queue/help.html7
-rw-r--r--doc/en/context/pl/admin/security/help.html8
-rw-r--r--doc/en/context/pl/appman/help.html10
-rw-r--r--doc/en/context/pl/cards/help.html31
-rw-r--r--doc/en/context/pl/channel/help.html13
-rw-r--r--doc/en/context/pl/chat/help.html19
-rw-r--r--doc/en/context/pl/cloud/help.html17
-rw-r--r--doc/en/context/pl/connections/help.html24
-rw-r--r--doc/en/context/pl/connections/ifpending/help.html24
-rw-r--r--doc/en/context/pl/connedit/help.html44
-rw-r--r--doc/en/context/pl/events/help.html18
-rw-r--r--doc/en/context/pl/mail/help.html25
-rw-r--r--doc/en/context/pl/network/help.html36
-rw-r--r--doc/en/context/pl/photos/help.html15
-rw-r--r--doc/en/context/pl/profile/help.html17
-rw-r--r--doc/en/context/pl/profiles/help.html49
-rw-r--r--doc/en/context/pl/register/help.html54
-rw-r--r--doc/en/context/pl/settings/account/help.html49
-rw-r--r--doc/en/context/pl/settings/channel/help.html49
-rw-r--r--doc/en/context/pl/settings/features/help.html50
-rw-r--r--doc/en/context/pl/settings/tokens/help.html43
-rw-r--r--doc/en/context/pl/webpages/help.html24
-rw-r--r--doc/en/context/pl/wiki/help.html29
-rw-r--r--doc/en/context/ru/cards/help.html16
-rw-r--r--doc/en/context/ru/connections/help.html7
-rw-r--r--doc/en/context/ru/network/help.html9
-rw-r--r--doc/en/credits.md86
-rw-r--r--doc/en/database.bb71
-rw-r--r--doc/en/dev-function-overview.md51
-rw-r--r--doc/en/dev_beginner.bb419
-rw-r--r--doc/en/develop.bb31
-rw-r--r--doc/en/developer/API.md787
-rw-r--r--doc/en/developer/Plugins.md259
-rw-r--r--doc/en/developer/api_zot.bb766
-rw-r--r--doc/en/developer/attribution.md3
-rw-r--r--doc/en/developer/code_of_conduct.md8
-rw-r--r--doc/en/developer/coding_style.md12
-rw-r--r--doc/en/developer/covenant.bb47
-rw-r--r--doc/en/developer/dev-function-overview.md49
-rw-r--r--doc/en/developer/dev_beginner.md548
-rw-r--r--doc/en/developer/developer_guide.bb176
-rw-r--r--doc/en/developer/developers_guide.md27
-rw-r--r--doc/en/developer/doco.md34
-rw-r--r--doc/en/developer/enforcement.md4
-rw-r--r--doc/en/developer/federate.md69
-rw-r--r--doc/en/developer/file_system_layout.md20
-rw-r--r--doc/en/developer/git_for_non_developers.md100
-rw-r--r--doc/en/developer/git_repository.md6
-rw-r--r--doc/en/developer/hooks.md458
-rw-r--r--doc/en/developer/licensing.md3
-rw-r--r--doc/en/developer/magic_auth.md49
-rw-r--r--doc/en/developer/nomad_protocol.md44
-rw-r--r--doc/en/developer/nomad_structures.md110
-rw-r--r--doc/en/developer/our_pledge.md3
-rw-r--r--doc/en/developer/our_responsibilities.md3
-rw-r--r--doc/en/developer/our_standards.md17
-rw-r--r--doc/en/developer/scope.md3
-rw-r--r--doc/en/developer/technical_introduction.md292
-rw-r--r--doc/en/developer/testing.md32
-rw-r--r--doc/en/developer/tools_workflows.md5
-rw-r--r--doc/en/developer/translations.md51
-rw-r--r--doc/en/developer/unorganized.md12
-rw-r--r--doc/en/developer/versions.md22
-rw-r--r--doc/en/developer/who_is_a_hubzilla_developer.md14
-rw-r--r--doc/en/developer/zot_protocol.bb478
-rw-r--r--doc/en/developer/zot_protocol.md44
-rw-r--r--doc/en/developer/zot_structures.md110
-rw-r--r--doc/en/developer_function_primer.bb47
-rw-r--r--doc/en/developers.bb71
-rw-r--r--doc/en/diaspora_compat.bb68
-rw-r--r--doc/en/directories.bb95
-rw-r--r--doc/en/dnt-policy.txt218
-rw-r--r--doc/en/doco.bb33
-rw-r--r--doc/en/encryption.bb18
-rw-r--r--doc/en/external-resource-links.bb21
-rw-r--r--doc/en/extra_features.bb98
-rw-r--r--doc/en/faq_admins.bb78
-rw-r--r--doc/en/feature/access_tokens.bb47
-rw-r--r--doc/en/feature/additional/access.md41
-rw-r--r--doc/en/feature/additional/composition.md67
-rw-r--r--doc/en/feature/additional/filtering.md57
-rw-r--r--doc/en/feature/additional/general.md130
-rw-r--r--doc/en/feature/additional/overview.md33
-rw-r--r--doc/en/feature/additional/posts.md57
-rw-r--r--doc/en/federate.bb71
-rw-r--r--doc/en/filesync.md61
-rw-r--r--doc/en/first-post.bb3
-rw-r--r--doc/en/functions.md163
-rw-r--r--doc/en/gdpr1.md114
-rw-r--r--doc/en/general.bb18
-rw-r--r--doc/en/git_for_non_developers.bb71
-rw-r--r--doc/en/glossary.md20
-rw-r--r--doc/en/hidden_configs.bb127
-rw-r--r--doc/en/hooklist.bb698
-rw-r--r--doc/en/intro_for_developers.bb113
-rw-r--r--doc/en/macros/addons_footer.bb2
-rw-r--r--doc/en/macros/cloud_footer.bb2
-rw-r--r--doc/en/macros/de/addons_footer.bb2
-rw-r--r--doc/en/macros/de/cloud_footer.bb2
-rw-r--r--doc/en/macros/de/main_footer.bb1
-rw-r--r--doc/en/macros/de/troubleshooting_footer.bb2
-rw-r--r--doc/en/macros/main_footer.bb1
-rw-r--r--doc/en/macros/pl/addons_footer.bb2
-rw-r--r--doc/en/macros/pl/cloud_footer.bb2
-rw-r--r--doc/en/macros/pl/main_footer.bb1
-rw-r--r--doc/en/macros/pl/troubleshooting_footer.bb2
-rw-r--r--doc/en/macros/troubleshooting_footer.bb2
-rw-r--r--doc/en/member/AdvancedSearch.md50
-rw-r--r--doc/en/member/NSFW.md14
-rw-r--r--doc/en/member/account_settings.md6
-rw-r--r--doc/en/member/accounts_profiles_channels_basics.md31
-rw-r--r--doc/en/member/additional_features.md29
-rw-r--r--doc/en/member/addressbook.md23
-rw-r--r--doc/en/member/apps.md58
-rw-r--r--doc/en/member/article.md25
-rw-r--r--doc/en/member/assets/bookmarker-save-icon.pngbin33987 -> 0 bytes
-rw-r--r--doc/en/member/assets/bookmarks-menu-dropdown.pngbin53526 -> 0 bytes
-rw-r--r--doc/en/member/assets/privacy-group-tool-public.pngbin39262 -> 0 bytes
-rw-r--r--doc/en/member/assets/privacy-tool-3.pngbin140093 -> 0 bytes
-rw-r--r--doc/en/member/assets/qr_text_to_post.pngbin273 -> 0 bytes
-rw-r--r--doc/en/member/assets/zat_dialog.pngbin81837 -> 0 bytes
-rw-r--r--doc/en/member/bbcode.html337
-rw-r--r--doc/en/member/bbcode.md81
-rw-r--r--doc/en/member/blocking_channels.md19
-rw-r--r--doc/en/member/bookmarks.md27
-rw-r--r--doc/en/member/calendar.md22
-rw-r--r--doc/en/member/channel_locations.md6
-rw-r--r--doc/en/member/channel_roles.md41
-rw-r--r--doc/en/member/channel_settings.md15
-rw-r--r--doc/en/member/channels.md15
-rw-r--r--doc/en/member/chat_rooms.md13
-rw-r--r--doc/en/member/clone.md47
-rw-r--r--doc/en/member/cloud_storage.md8
-rw-r--r--doc/en/member/comanche.md255
-rw-r--r--doc/en/member/commenting.md8
-rw-r--r--doc/en/member/connecting_with_channels.md31
-rw-r--r--doc/en/member/connection_editor.md31
-rw-r--r--doc/en/member/connections.md23
-rw-r--r--doc/en/member/conversation_features.md8
-rw-r--r--doc/en/member/create_channels.md9
-rw-r--r--doc/en/member/delete.md3
-rw-r--r--doc/en/member/delete_account.md7
-rw-r--r--doc/en/member/deleting_channel.md9
-rw-r--r--doc/en/member/diaspora_compat.md37
-rw-r--r--doc/en/member/direct_messages.md9
-rw-r--r--doc/en/member/directory.md15
-rw-r--r--doc/en/member/display_settings.md18
-rw-r--r--doc/en/member/encryption.md18
-rw-r--r--doc/en/member/files.md28
-rw-r--r--doc/en/member/follow_conversation.md3
-rw-r--r--doc/en/member/gallery.md6
-rw-r--r--doc/en/member/guest_access.md15
-rw-r--r--doc/en/member/important_apps.md16
-rw-r--r--doc/en/member/insert_images.md50
-rw-r--r--doc/en/member/interact.md24
-rw-r--r--doc/en/member/link_to_source.md3
-rw-r--r--doc/en/member/member_faq.bb10
-rw-r--r--doc/en/member/member_guide.bb1087
-rw-r--r--doc/en/member/mentions.md8
-rw-r--r--doc/en/member/overview.md3
-rw-r--r--doc/en/member/permissions.md7
-rw-r--r--doc/en/member/permissions_channel_roles.md45
-rw-r--r--doc/en/member/permissions_contact_roles.md17
-rw-r--r--doc/en/member/permissions_content.md32
-rw-r--r--doc/en/member/photos.md17
-rw-r--r--doc/en/member/pic/apps01.pngbin0 -> 131264 bytes
-rw-r--r--doc/en/member/pic/apps02.pngbin0 -> 108799 bytes
-rw-r--r--doc/en/member/pic/apps03.pngbin0 -> 6812 bytes
-rw-r--r--doc/en/member/pic/apps04.pngbin0 -> 1102 bytes
-rw-r--r--doc/en/member/pic/apps05.pngbin0 -> 6397 bytes
-rw-r--r--doc/en/member/pic/apps06.pngbin0 -> 2451 bytes
-rw-r--r--doc/en/member/pic/apps07.pngbin0 -> 1067 bytes
-rw-r--r--doc/en/member/pic/apps08.pngbin0 -> 24742 bytes
-rw-r--r--doc/en/member/pic/article01.pngbin0 -> 25055 bytes
-rw-r--r--doc/en/member/pic/article02.pngbin0 -> 62194 bytes
-rw-r--r--doc/en/member/pic/article03.pngbin0 -> 28635 bytes
-rw-r--r--doc/en/member/pic/article04.pngbin0 -> 79891 bytes
-rw-r--r--doc/en/member/pic/article05.pngbin0 -> 8379 bytes
-rw-r--r--doc/en/member/pic/author.pngbin0 -> 5688 bytes
-rw-r--r--doc/en/member/pic/block-etc01.pngbin0 -> 14068 bytes
-rw-r--r--doc/en/member/pic/bookm01.pngbin0 -> 8889 bytes
-rw-r--r--doc/en/member/pic/bookm02.pngbin0 -> 5323 bytes
-rw-r--r--doc/en/member/pic/bookm03.pngbin0 -> 18613 bytes
-rw-r--r--doc/en/member/pic/cal01.pngbin0 -> 61023 bytes
-rw-r--r--doc/en/member/pic/cal02.pngbin0 -> 8850 bytes
-rw-r--r--doc/en/member/pic/cal03.pngbin0 -> 47732 bytes
-rw-r--r--doc/en/member/pic/cal04.pngbin0 -> 31991 bytes
-rw-r--r--doc/en/member/pic/cal05.pngbin0 -> 42499 bytes
-rw-r--r--doc/en/member/pic/carddav01.pngbin0 -> 44156 bytes
-rw-r--r--doc/en/member/pic/carddav02.pngbin0 -> 28522 bytes
-rw-r--r--doc/en/member/pic/carddav03.pngbin0 -> 35419 bytes
-rw-r--r--doc/en/member/pic/carddav04.pngbin0 -> 31491 bytes
-rw-r--r--doc/en/member/pic/carddav05.pngbin0 -> 20985 bytes
-rw-r--r--doc/en/member/pic/carddav06.pngbin0 -> 13346 bytes
-rw-r--r--doc/en/member/pic/carddav07.pngbin0 -> 18724 bytes
-rw-r--r--doc/en/member/pic/center.pngbin0 -> 3096 bytes
-rw-r--r--doc/en/member/pic/chat01.pngbin0 -> 25605 bytes
-rw-r--r--doc/en/member/pic/chat02.pngbin0 -> 36372 bytes
-rw-r--r--doc/en/member/pic/clone01.pngbin0 -> 197683 bytes
-rw-r--r--doc/en/member/pic/clone02.pngbin0 -> 63003 bytes
-rw-r--r--doc/en/member/pic/clone03.pngbin0 -> 54846 bytes
-rw-r--r--doc/en/member/pic/clone04.pngbin0 -> 109242 bytes
-rw-r--r--doc/en/member/pic/clone05.pngbin0 -> 53903 bytes
-rw-r--r--doc/en/member/pic/code.pngbin0 -> 9206 bytes
-rw-r--r--doc/en/member/pic/comment01.pngbin0 -> 4484 bytes
-rw-r--r--doc/en/member/pic/conn01.pngbin0 -> 110791 bytes
-rw-r--r--doc/en/member/pic/conn02.pngbin0 -> 37268 bytes
-rw-r--r--doc/en/member/pic/conn03.pngbin0 -> 32595 bytes
-rw-r--r--doc/en/member/pic/conn04.pngbin0 -> 35911 bytes
-rw-r--r--doc/en/member/pic/conn05.pngbin0 -> 50155 bytes
-rw-r--r--doc/en/member/pic/conn06.pngbin0 -> 35873 bytes
-rw-r--r--doc/en/member/pic/croles1.pngbin0 -> 122810 bytes
-rw-r--r--doc/en/member/pic/croles2.pngbin0 -> 47504 bytes
-rw-r--r--doc/en/member/pic/delacc01.pngbin0 -> 7020 bytes
-rw-r--r--doc/en/member/pic/delchan01.pngbin0 -> 6861 bytes
-rw-r--r--doc/en/member/pic/directory.pngbin0 -> 337611 bytes
-rw-r--r--doc/en/member/pic/files01.pngbin0 -> 34420 bytes
-rw-r--r--doc/en/member/pic/files02.pngbin0 -> 22336 bytes
-rw-r--r--doc/en/member/pic/files03.pngbin0 -> 10752 bytes
-rw-r--r--doc/en/member/pic/font.pngbin0 -> 1700 bytes
-rw-r--r--doc/en/member/pic/gal01.pngbin0 -> 113448 bytes
-rw-r--r--doc/en/member/pic/hbar.pngbin0 -> 3998 bytes
-rw-r--r--doc/en/member/pic/highlited.pngbin0 -> 1751 bytes
-rw-r--r--doc/en/member/pic/image.pngbin0 -> 54664 bytes
-rw-r--r--doc/en/member/pic/interact01.pngbin0 -> 3651 bytes
-rw-r--r--doc/en/member/pic/interact02.pngbin0 -> 42024 bytes
-rw-r--r--doc/en/member/pic/interact03.pngbin0 -> 20715 bytes
-rw-r--r--doc/en/member/pic/mauth.pngbin0 -> 4785 bytes
-rw-r--r--doc/en/member/pic/nsfw01.pngbin0 -> 61324 bytes
-rw-r--r--doc/en/member/pic/nsfw02.pngbin0 -> 83857 bytes
-rw-r--r--doc/en/member/pic/nsfw03.pngbin0 -> 74822 bytes
-rw-r--r--doc/en/member/pic/perm01.pngbin0 -> 1679 bytes
-rw-r--r--doc/en/member/pic/perm02.pngbin0 -> 1309 bytes
-rw-r--r--doc/en/member/pic/perm03.pngbin0 -> 1447 bytes
-rw-r--r--doc/en/member/pic/perm04.pngbin0 -> 2248 bytes
-rw-r--r--doc/en/member/pic/perm05.pngbin0 -> 20925 bytes
-rw-r--r--doc/en/member/pic/perm06.pngbin0 -> 89619 bytes
-rw-r--r--doc/en/member/pic/pgroups01.pngbin0 -> 188073 bytes
-rw-r--r--doc/en/member/pic/pgroups02.pngbin0 -> 191088 bytes
-rw-r--r--doc/en/member/pic/pgroups03.pngbin0 -> 107628 bytes
-rw-r--r--doc/en/member/pic/pgroups04.pngbin0 -> 35036 bytes
-rw-r--r--doc/en/member/pic/photos01.pngbin0 -> 164111 bytes
-rw-r--r--doc/en/member/pic/photos02.pngbin0 -> 833 bytes
-rw-r--r--doc/en/member/pic/photos03.pngbin0 -> 112591 bytes
-rw-r--r--doc/en/member/pic/photos04.pngbin0 -> 71983 bytes
-rw-r--r--doc/en/member/pic/picture01.pngbin0 -> 25711 bytes
-rw-r--r--doc/en/member/pic/picture02.pngbin0 -> 24496 bytes
-rw-r--r--doc/en/member/pic/picture03.pngbin0 -> 42064 bytes
-rw-r--r--doc/en/member/pic/picture04.pngbin0 -> 41670 bytes
-rw-r--r--doc/en/member/pic/picture05.pngbin0 -> 9190 bytes
-rw-r--r--doc/en/member/pic/picture06.pngbin0 -> 47280 bytes
-rw-r--r--doc/en/member/pic/posting01.pngbin0 -> 6175 bytes
-rw-r--r--doc/en/member/pic/qrcode.pngbin0 -> 273 bytes
-rw-r--r--doc/en/member/pic/quote.pngbin0 -> 1701 bytes
-rw-r--r--doc/en/member/pic/red.pngbin0 -> 983 bytes
-rw-r--r--doc/en/member/pic/savefolder01.pngbin0 -> 15803 bytes
-rw-r--r--doc/en/member/pic/savefolder02.pngbin0 -> 15536 bytes
-rw-r--r--doc/en/member/pic/savefolder03.pngbin0 -> 5836 bytes
-rw-r--r--doc/en/member/pic/savefolder04.pngbin0 -> 7977 bytes
-rw-r--r--doc/en/member/pic/search01.pngbin0 -> 736 bytes
-rw-r--r--doc/en/member/pic/search02.pngbin0 -> 4181 bytes
-rw-r--r--doc/en/member/pic/search03.pngbin0 -> 5290 bytes
-rw-r--r--doc/en/member/pic/settings01.pngbin0 -> 14841 bytes
-rw-r--r--doc/en/member/pic/settings02.pngbin0 -> 15396 bytes
-rw-r--r--doc/en/member/pic/settings03.pngbin0 -> 36443 bytes
-rw-r--r--doc/en/member/pic/settings04.pngbin0 -> 116642 bytes
-rw-r--r--doc/en/member/pic/settings05.pngbin0 -> 220775 bytes
-rw-r--r--doc/en/member/pic/settings06.pngbin0 -> 48473 bytes
-rw-r--r--doc/en/member/pic/settings07.pngbin0 -> 133792 bytes
-rw-r--r--doc/en/member/pic/settings08.pngbin0 -> 52300 bytes
-rw-r--r--doc/en/member/pic/settings09.pngbin0 -> 151238 bytes
-rw-r--r--doc/en/member/pic/settings10.pngbin0 -> 77294 bytes
-rw-r--r--doc/en/member/pic/settings11.pngbin0 -> 70263 bytes
-rw-r--r--doc/en/member/pic/settings12.pngbin0 -> 11978 bytes
-rw-r--r--doc/en/member/pic/settings13.pngbin0 -> 86494 bytes
-rw-r--r--doc/en/member/pic/settings14.pngbin0 -> 29791 bytes
-rw-r--r--doc/en/member/pic/settings15.pngbin0 -> 47847 bytes
-rw-r--r--doc/en/member/pic/settings16.pngbin0 -> 55056 bytes
-rw-r--r--doc/en/member/pic/settings17.pngbin0 -> 40825 bytes
-rw-r--r--doc/en/member/pic/settings18.pngbin0 -> 60672 bytes
-rw-r--r--doc/en/member/pic/settings19.pngbin0 -> 41842 bytes
-rw-r--r--doc/en/member/pic/settings20.pngbin0 -> 113454 bytes
-rw-r--r--doc/en/member/pic/settings21.pngbin0 -> 43152 bytes
-rw-r--r--doc/en/member/pic/settings22.pngbin0 -> 91173 bytes
-rw-r--r--doc/en/member/pic/settings23.pngbin0 -> 50263 bytes
-rw-r--r--doc/en/member/pic/settings24.pngbin0 -> 54629 bytes
-rw-r--r--doc/en/member/pic/size.pngbin0 -> 7619 bytes
-rw-r--r--doc/en/member/pic/source.pngbin0 -> 24716 bytes
-rw-r--r--doc/en/member/pic/star.pngbin0 -> 3882 bytes
-rw-r--r--doc/en/member/pic/table1.pngbin0 -> 7050 bytes
-rw-r--r--doc/en/member/pic/table2.pngbin0 -> 6532 bytes
-rw-r--r--doc/en/member/pic/table3.pngbin0 -> 3775 bytes
-rw-r--r--doc/en/member/pic/video_poster.pngbin0 -> 17729 bytes
-rw-r--r--doc/en/member/pic/websites01.pngbin0 -> 26517 bytes
-rw-r--r--doc/en/member/pic/websites02.pngbin0 -> 36583 bytes
-rw-r--r--doc/en/member/pic/websites03.pngbin0 -> 28913 bytes
-rw-r--r--doc/en/member/pic/websites04.pngbin0 -> 36368 bytes
-rw-r--r--doc/en/member/pic/websites05.pngbin0 -> 45807 bytes
-rw-r--r--doc/en/member/pic/websites06.pngbin0 -> 23428 bytes
-rw-r--r--doc/en/member/pic/wiki01.pngbin0 -> 11900 bytes
-rw-r--r--doc/en/member/pic/wiki02.pngbin0 -> 30064 bytes
-rw-r--r--doc/en/member/pic/wiki03.pngbin0 -> 203519 bytes
-rw-r--r--doc/en/member/pic/wiki04.pngbin0 -> 35923 bytes
-rw-r--r--doc/en/member/posting.md16
-rw-r--r--doc/en/member/privacy_groups.md24
-rw-r--r--doc/en/member/privacy_settings.md5
-rw-r--r--doc/en/member/profiles.md21
-rw-r--r--doc/en/member/protection_of_privacy.md65
-rw-r--r--doc/en/member/public_stream.md12
-rw-r--r--doc/en/member/registration.md19
-rw-r--r--doc/en/member/repeat.md5
-rw-r--r--doc/en/member/save_to_folder.md22
-rw-r--r--doc/en/member/search.md14
-rw-r--r--doc/en/member/settings.md34
-rw-r--r--doc/en/member/share.md5
-rw-r--r--doc/en/member/show_source_code.md13
-rw-r--r--doc/en/member/stream_settings.md9
-rw-r--r--doc/en/member/superblock.md15
-rw-r--r--doc/en/member/tags.md5
-rw-r--r--doc/en/member/the_grid.md3
-rw-r--r--doc/en/member/the_stream.md7
-rw-r--r--doc/en/member/toggle_star_status.md8
-rw-r--r--doc/en/member/websites.md139
-rw-r--r--doc/en/member/wikis.md31
-rw-r--r--doc/en/pic/ui01.pngbin0 -> 212583 bytes
-rw-r--r--doc/en/pic/ui02.pngbin0 -> 29513 bytes
-rw-r--r--doc/en/placeholder.md1
-rw-r--r--doc/en/plugins.bb312
-rw-r--r--doc/en/problems-following-an-update.bb38
-rw-r--r--doc/en/red2pi.bb342
-rw-r--r--doc/en/roadmap.bb28
-rw-r--r--doc/en/schema_development.bb78
-rw-r--r--doc/en/schemaspy_hubzilla/zot.meta.xml283
-rw-r--r--doc/en/sql_conventions.bb91
-rw-r--r--doc/en/the_hubzilla_project.md78
-rw-r--r--doc/en/toc.html78
-rw-r--r--doc/en/tutorials/DerivedTheme1.md96
-rw-r--r--doc/en/tutorials/assets/0965ace945f0c95ae38aa5bfedd230d2a7233d3915ac15d629f9dd845854.pngbin249151 -> 0 bytes
-rw-r--r--doc/en/tutorials/assets/1ebe02c205962dd25035c441631745d16acdb7a44e50d148256c8ad26a67.pngbin293314 -> 0 bytes
-rw-r--r--doc/en/tutorials/assets/2243e48ccea25bd907cce3dbd6fc9f7cd832a4c91a4c5dd294b7b219e7d8.pngbin178913 -> 0 bytes
-rw-r--r--doc/en/tutorials/assets/25eaad2435200f72a1dd3a00ba17a76ca6db4c246b3c4fa286b390cae7c8.pngbin43797 -> 0 bytes
-rw-r--r--doc/en/tutorials/assets/2b539d5a8474d6ec6dc91155b628d9be5f99ab04a78108ec404f53ec7bb5.pngbin46752 -> 0 bytes
-rw-r--r--doc/en/tutorials/assets/31f42a02bdbae095e0329db6c3814e2975979aff12f873f43d81724c5e61.pngbin257211 -> 0 bytes
-rw-r--r--doc/en/tutorials/assets/324247680b605fd214fd61aecd8f216fa8f5dfa0f16a04c8e968fdbc43d0.pngbin91478 -> 0 bytes
-rw-r--r--doc/en/tutorials/assets/3656a67dce40a1fc2515e9089217f2e136d4fcf8babe77bac00ecaad43ce.pngbin293611 -> 0 bytes
-rw-r--r--doc/en/tutorials/assets/458a842c2ea0fbe3b7869bb14dfffe1e5be098d1cd6e590bbead25b4cc05.pngbin318766 -> 0 bytes
-rw-r--r--doc/en/tutorials/assets/4aaaf1e124514c8d6999a5fe1d07be5af460cda4ba6cde9106ebc1564bb0.pngbin298857 -> 0 bytes
-rw-r--r--doc/en/tutorials/assets/4cf326152797a8ecdf5630e921756f825ee00f8ee464d3ef9fed971d2852.pngbin532838 -> 0 bytes
-rw-r--r--doc/en/tutorials/assets/75d2927b7ad0d2043d4d3b6ba1364fac8ead173edd39340adaf78be11c9d.pngbin137827 -> 0 bytes
-rw-r--r--doc/en/tutorials/assets/7c976a06662a1357b3da8ed0680d1a721c85f2ae2bdd5739a8def466010e.pngbin466641 -> 0 bytes
-rw-r--r--doc/en/tutorials/assets/99a6efda4df631dfb2d2a849412044cc6a0f8aebeac289d28786f2649d24.pngbin240495 -> 0 bytes
-rw-r--r--doc/en/tutorials/assets/9eae9fad774a4cd29e665961d35affbd053368056f562c58200fb41027b0.pngbin665755 -> 0 bytes
-rw-r--r--doc/en/tutorials/assets/b0bfdf02aef3710a37bb6092c3240b291eca8afa73133b3ac03b86f3302d.pngbin324092 -> 0 bytes
-rw-r--r--doc/en/tutorials/assets/b334915c03a665493915598c69c17a87c910a39db2cd3b5292e4623ea4c4.pngbin466584 -> 0 bytes
-rw-r--r--doc/en/tutorials/assets/b3eece28e8db67f1024af42055f0f24ed5e81ba622aca8cac576ccf5930e.pngbin155763 -> 0 bytes
-rw-r--r--doc/en/tutorials/assets/bdbcf0ffd9004657237f6b7b7863da5a8e39a5bc17d2c67fa160efef2056.pngbin134643 -> 0 bytes
-rw-r--r--doc/en/tutorials/assets/c4cad3e4c356dd2a227df79bd4dc6d47edf1b66ea243f005b6b452ec366b.pngbin306069 -> 0 bytes
-rw-r--r--doc/en/tutorials/assets/c9a880cc82ffa1f7c2f460397bb083bf7dc2a2b8f065e64da598b45b4a2b.pngbin57185 -> 0 bytes
-rw-r--r--doc/en/tutorials/assets/d080e92d797af5e863fa39b2084c16a8410de1f7a6559633435817444aef.pngbin255688 -> 0 bytes
-rw-r--r--doc/en/tutorials/assets/e05248fdc5688d6d24bde52432fdc7b39692a094559aa504de99352940b1.pngbin83265 -> 0 bytes
-rw-r--r--doc/en/tutorials/assets/e5d5674a34e848e2cce90a60fc416415271d9c51b81ad2a950fb0157222a.pngbin264916 -> 0 bytes
-rw-r--r--doc/en/tutorials/assets/ef78bc6aa3fafebd46f353514c907b3fdfe019918fc5553bb3f31388a36f.pngbin68194 -> 0 bytes
-rw-r--r--doc/en/tutorials/assets/facb0bdfdecb4c779de9048cd14b417c0d76de17af476be5f296b78d70e9.pngbin99210 -> 0 bytes
-rw-r--r--doc/en/tutorials/customise_look.md214
-rw-r--r--doc/en/tutorials/personal_channel.html162
-rw-r--r--doc/en/tutorials/pic/fedidb.pngbin0 -> 244957 bytes
-rw-r--r--doc/en/tutorials/pic/fedieverse-observer.pngbin0 -> 106457 bytes
-rw-r--r--doc/en/tutorials/pic/hopt01.pngbin0 -> 150759 bytes
-rw-r--r--doc/en/tutorials/pic/hopt02.pngbin0 -> 151567 bytes
-rw-r--r--doc/en/tutorials/pic/hopt03.pngbin0 -> 19815 bytes
-rw-r--r--doc/en/tutorials/pic/hopt04.pngbin0 -> 14919 bytes
-rw-r--r--doc/en/tutorials/pic/hopt05.pngbin0 -> 65825 bytes
-rw-r--r--doc/en/tutorials/pic/hopt06.pngbin0 -> 52620 bytes
-rw-r--r--doc/en/tutorials/pic/hopt07.pngbin0 -> 58430 bytes
-rw-r--r--doc/en/tutorials/pic/hopt08.pngbin0 -> 55982 bytes
-rw-r--r--doc/en/tutorials/pic/hopt09.pngbin0 -> 57157 bytes
-rw-r--r--doc/en/tutorials/pic/hopt10.pngbin0 -> 58212 bytes
-rw-r--r--doc/en/tutorials/pic/hopt11.pngbin0 -> 61304 bytes
-rw-r--r--doc/en/tutorials/pic/hopt12.pngbin0 -> 62719 bytes
-rw-r--r--doc/en/tutorials/pic/hopt13.pngbin0 -> 27056 bytes
-rw-r--r--doc/en/tutorials/pic/hopt14.pngbin0 -> 81987 bytes
-rw-r--r--doc/en/tutorials/pic/hopt15.pngbin0 -> 81777 bytes
-rw-r--r--doc/en/tutorials/pic/hopt16.pngbin0 -> 82299 bytes
-rw-r--r--doc/en/tutorials/pic/hopt17.pngbin0 -> 86757 bytes
-rw-r--r--doc/en/tutorials/pic/hopt18.pngbin0 -> 81963 bytes
-rw-r--r--doc/en/tutorials/pic/hopt19.pngbin0 -> 63556 bytes
-rw-r--r--doc/en/tutorials/pic/hopt20.pngbin0 -> 83873 bytes
-rw-r--r--doc/en/tutorials/pic/hopt21.pngbin0 -> 71989 bytes
-rw-r--r--doc/en/tutorials/pic/hopt22.pngbin0 -> 106180 bytes
-rw-r--r--doc/en/tutorials/pic/hopt23.pngbin0 -> 92702 bytes
-rw-r--r--doc/en/tutorials/pic/hopt24.pngbin0 -> 77255 bytes
-rw-r--r--doc/en/tutorials/pic/hopt25.pngbin0 -> 593830 bytes
-rw-r--r--doc/en/tutorials/pic/hopt26.pngbin0 -> 768390 bytes
-rw-r--r--doc/en/tutorials/pic/hopt27.pngbin0 -> 417262 bytes
-rw-r--r--doc/en/tutorials/pic/hopt28.pngbin0 -> 680827 bytes
-rw-r--r--doc/en/tutorials/pic/hopt29.pngbin0 -> 545331 bytes
-rw-r--r--doc/en/tutorials/pic/hopt30.pngbin0 -> 504309 bytes
-rw-r--r--doc/en/tutorials/pic/hopt31.pngbin0 -> 622842 bytes
-rw-r--r--doc/en/tutorials/pic/hopt32.pngbin0 -> 561752 bytes
-rw-r--r--doc/en/tutorials/pic/hopt33.pngbin0 -> 508595 bytes
-rw-r--r--doc/en/tutorials/pic/hopt34.pngbin0 -> 866202 bytes
-rw-r--r--doc/en/tutorials/pic/hopt35.pngbin0 -> 835740 bytes
-rw-r--r--doc/en/tutorials/pic/hopt36.pngbin0 -> 672671 bytes
-rw-r--r--doc/en/tutorials/pic/hopt37.pngbin0 -> 589760 bytes
-rw-r--r--doc/en/tutorials/pic/hopt38.pngbin0 -> 546046 bytes
-rw-r--r--doc/en/tutorials/pic/hopt39.pngbin0 -> 854145 bytes
-rw-r--r--doc/en/tutorials/pic/hopt40.pngbin0 -> 520843 bytes
-rw-r--r--doc/en/tutorials/pic/hopt41.pngbin0 -> 558247 bytes
-rw-r--r--doc/en/tutorials/pic/hopt42.pngbin0 -> 503358 bytes
-rw-r--r--doc/en/tutorials/pic/hopt43.pngbin0 -> 496619 bytes
-rw-r--r--doc/en/tutorials/pic/hopt44.pngbin0 -> 497219 bytes
-rw-r--r--doc/en/tutorials/pic/hopt45.pngbin0 -> 497785 bytes
-rw-r--r--doc/en/tutorials/pic/nomadapp.pngbin0 -> 172012 bytes
-rw-r--r--doc/en/tutorials/pic/pubsites.pngbin0 -> 167653 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep01.pngbin0 -> 5442 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep02.pngbin0 -> 21136 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep03.pngbin0 -> 133116 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep04.pngbin0 -> 152224 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep05.pngbin0 -> 33470 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep06.pngbin0 -> 35411 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep07.pngbin0 -> 153697 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep08.pngbin0 -> 70091 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep09.pngbin0 -> 63172 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep10.pngbin0 -> 10344 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep11.pngbin0 -> 68563 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep12.pngbin0 -> 45833 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep13.pngbin0 -> 15734 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep14.pngbin0 -> 60783 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep15.pngbin0 -> 91689 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep16.pngbin0 -> 14547 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep17.pngbin0 -> 13144 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep18.pngbin0 -> 32375 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep19.pngbin0 -> 13828 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep20.pngbin0 -> 11379 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep21.pngbin0 -> 24265 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep22.pngbin0 -> 16603 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep23.pngbin0 -> 7502 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep24.pngbin0 -> 12928 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep25.pngbin0 -> 32799 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep26.pngbin0 -> 37503 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep27.pngbin0 -> 12229 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep28.pngbin0 -> 25357 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep29.pngbin0 -> 31320 bytes
-rw-r--r--doc/en/tutorials/pic/stepbystep30.pngbin0 -> 22395 bytes
-rw-r--r--doc/en/tutorials/step_with_hubzilla.md157
-rw-r--r--doc/en/ui.md5
-rw-r--r--include/account.php296
-rw-r--r--include/acl_selectors.php2
-rw-r--r--include/api_zot.php11
-rw-r--r--include/attach.php24
-rw-r--r--include/auth.php20
-rw-r--r--include/bbcode.php80
-rw-r--r--include/channel.php24
-rw-r--r--include/conversation.php258
-rw-r--r--include/crypto.php155
-rw-r--r--include/dba/dba_pdo.php2
-rw-r--r--include/event.php2
-rw-r--r--include/html2plain.php4
-rw-r--r--include/items.php637
-rw-r--r--include/js_strings.php3
-rw-r--r--include/language.php49
-rw-r--r--include/markdown.php12
-rw-r--r--include/nav.php2
-rw-r--r--include/network.php33
-rw-r--r--include/observer.php68
-rw-r--r--include/photo/photo_driver.php151
-rw-r--r--include/security.php38
-rw-r--r--include/socgraph.php11
-rw-r--r--include/text.php26
-rw-r--r--install/INSTALL.txt6
-rw-r--r--install/schema_mysql.sql2
-rw-r--r--library/ASNValue.class.php169
-rw-r--r--library/asn1.php291
-rw-r--r--phpstan.neon.dist2
-rw-r--r--tests/unit/Lib/ActivityTest.php48
-rw-r--r--tests/unit/Lib/JcsEddsa2022Test.php8
-rw-r--r--tests/unit/Lib/KeyutilsTest.php49
-rw-r--r--tests/unit/Lib/MessageFilterTest.php207
-rw-r--r--tests/unit/Module/HelpTest.php4
-rw-r--r--tests/unit/Module/MagicTest.php8
-rw-r--r--tests/unit/Module/TestCase.php2
-rw-r--r--tests/unit/Widget/MessagesWidgetTest.php83
-rw-r--r--tests/unit/includes/AccountTest.php21
-rw-r--r--tests/unit/includes/BBCodeTest.php31
-rw-r--r--tests/unit/includes/MarkdownTest.php4
-rw-r--r--tests/unit/includes/PhotodriverTest.php58
-rwxr-xr-xutil/Doxyfile4
-rwxr-xr-xutil/Doxygen.footer4
-rw-r--r--util/hmessages.po1890
-rwxr-xr-xutil/init_sys_channel13
-rw-r--r--vendor/bakame/http-structured-fields/LICENSE21
-rw-r--r--vendor/bakame/http-structured-fields/composer.json106
-rw-r--r--vendor/bakame/http-structured-fields/src/Bytes.php83
-rw-r--r--vendor/bakame/http-structured-fields/src/DataType.php45
-rw-r--r--vendor/bakame/http-structured-fields/src/Dictionary.php788
-rw-r--r--vendor/bakame/http-structured-fields/src/DisplayString.php103
-rw-r--r--vendor/bakame/http-structured-fields/src/ForbiddenOperation.php11
-rw-r--r--vendor/bakame/http-structured-fields/src/Ietf.php73
-rw-r--r--vendor/bakame/http-structured-fields/src/InnerList.php478
-rw-r--r--vendor/bakame/http-structured-fields/src/InvalidArgument.php11
-rw-r--r--vendor/bakame/http-structured-fields/src/InvalidOffset.php38
-rw-r--r--vendor/bakame/http-structured-fields/src/Item.php502
-rw-r--r--vendor/bakame/http-structured-fields/src/Key.php53
-rw-r--r--vendor/bakame/http-structured-fields/src/Member.php116
-rw-r--r--vendor/bakame/http-structured-fields/src/MissingFeature.php13
-rw-r--r--vendor/bakame/http-structured-fields/src/OuterList.php430
-rw-r--r--vendor/bakame/http-structured-fields/src/ParameterAccess.php260
-rw-r--r--vendor/bakame/http-structured-fields/src/Parameters.php763
-rw-r--r--vendor/bakame/http-structured-fields/src/Parser.php523
-rw-r--r--vendor/bakame/http-structured-fields/src/StructuredFieldError.php11
-rw-r--r--vendor/bakame/http-structured-fields/src/StructuredFieldProvider.php28
-rw-r--r--vendor/bakame/http-structured-fields/src/SyntaxError.php11
-rw-r--r--vendor/bakame/http-structured-fields/src/Token.php52
-rw-r--r--vendor/bakame/http-structured-fields/src/Type.php88
-rw-r--r--vendor/bakame/http-structured-fields/src/Validation/ErrorCode.php26
-rw-r--r--vendor/bakame/http-structured-fields/src/Validation/ItemValidator.php103
-rw-r--r--vendor/bakame/http-structured-fields/src/Validation/ParametersValidator.php244
-rw-r--r--vendor/bakame/http-structured-fields/src/Validation/Result.php34
-rw-r--r--vendor/bakame/http-structured-fields/src/Validation/ValidatedItem.php19
-rw-r--r--vendor/bakame/http-structured-fields/src/Validation/ValidatedParameters.php68
-rw-r--r--vendor/bakame/http-structured-fields/src/Validation/Violation.php12
-rw-r--r--vendor/bakame/http-structured-fields/src/Validation/ViolationList.php169
-rw-r--r--vendor/composer/autoload_classmap.php450
-rw-r--r--vendor/composer/autoload_files.php3
-rw-r--r--vendor/composer/autoload_psr4.php6
-rw-r--r--vendor/composer/autoload_static.php476
-rw-r--r--vendor/composer/installed.json429
-rw-r--r--vendor/composer/installed.php87
-rw-r--r--vendor/guzzlehttp/psr7/CHANGELOG.md475
-rw-r--r--vendor/guzzlehttp/psr7/LICENSE26
-rw-r--r--vendor/guzzlehttp/psr7/README.md887
-rw-r--r--vendor/guzzlehttp/psr7/composer.json93
-rw-r--r--vendor/guzzlehttp/psr7/src/AppendStream.php248
-rw-r--r--vendor/guzzlehttp/psr7/src/BufferStream.php147
-rw-r--r--vendor/guzzlehttp/psr7/src/CachingStream.php153
-rw-r--r--vendor/guzzlehttp/psr7/src/DroppingStream.php49
-rw-r--r--vendor/guzzlehttp/psr7/src/Exception/MalformedUriException.php14
-rw-r--r--vendor/guzzlehttp/psr7/src/FnStream.php180
-rw-r--r--vendor/guzzlehttp/psr7/src/Header.php134
-rw-r--r--vendor/guzzlehttp/psr7/src/HttpFactory.php94
-rw-r--r--vendor/guzzlehttp/psr7/src/InflateStream.php37
-rw-r--r--vendor/guzzlehttp/psr7/src/LazyOpenStream.php49
-rw-r--r--vendor/guzzlehttp/psr7/src/LimitStream.php157
-rw-r--r--vendor/guzzlehttp/psr7/src/Message.php246
-rw-r--r--vendor/guzzlehttp/psr7/src/MessageTrait.php265
-rw-r--r--vendor/guzzlehttp/psr7/src/MimeType.php1259
-rw-r--r--vendor/guzzlehttp/psr7/src/MultipartStream.php165
-rw-r--r--vendor/guzzlehttp/psr7/src/NoSeekStream.php28
-rw-r--r--vendor/guzzlehttp/psr7/src/PumpStream.php179
-rw-r--r--vendor/guzzlehttp/psr7/src/Query.php118
-rw-r--r--vendor/guzzlehttp/psr7/src/Request.php159
-rw-r--r--vendor/guzzlehttp/psr7/src/Response.php161
-rw-r--r--vendor/guzzlehttp/psr7/src/Rfc7230.php23
-rw-r--r--vendor/guzzlehttp/psr7/src/ServerRequest.php340
-rw-r--r--vendor/guzzlehttp/psr7/src/Stream.php283
-rw-r--r--vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php156
-rw-r--r--vendor/guzzlehttp/psr7/src/StreamWrapper.php207
-rw-r--r--vendor/guzzlehttp/psr7/src/UploadedFile.php211
-rw-r--r--vendor/guzzlehttp/psr7/src/Uri.php743
-rw-r--r--vendor/guzzlehttp/psr7/src/UriComparator.php52
-rw-r--r--vendor/guzzlehttp/psr7/src/UriNormalizer.php220
-rw-r--r--vendor/guzzlehttp/psr7/src/UriResolver.php211
-rw-r--r--vendor/guzzlehttp/psr7/src/Utils.php477
-rw-r--r--vendor/macgirvin/http-message-signer/LICENSE30
-rw-r--r--vendor/macgirvin/http-message-signer/README.md130
-rw-r--r--vendor/macgirvin/http-message-signer/TESTING5
-rw-r--r--vendor/macgirvin/http-message-signer/composer.json29
-rw-r--r--vendor/macgirvin/http-message-signer/phpunit.xml20
-rw-r--r--vendor/macgirvin/http-message-signer/src/HttpMessageSigner.php876
-rw-r--r--vendor/macgirvin/http-message-signer/src/StructuredFieldTypes.php91
-rw-r--r--vendor/macgirvin/http-message-signer/src/UnProcessableSignatureException.php8
-rw-r--r--vendor/paragonie/random_compat/LICENSE22
-rwxr-xr-xvendor/paragonie/random_compat/build-phar.sh5
-rw-r--r--vendor/paragonie/random_compat/composer.json34
-rw-r--r--vendor/paragonie/random_compat/dist/random_compat.phar.pubkey5
-rw-r--r--vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc11
-rw-r--r--vendor/paragonie/random_compat/lib/random.php32
-rw-r--r--vendor/paragonie/random_compat/other/build_phar.php57
-rw-r--r--vendor/paragonie/random_compat/psalm-autoload.php9
-rw-r--r--vendor/paragonie/random_compat/psalm.xml19
-rw-r--r--vendor/phpseclib/phpseclib/README.md29
-rw-r--r--vendor/phpseclib/phpseclib/appveyor.yml27
-rw-r--r--vendor/phpseclib/phpseclib/composer.json20
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Common/Functions/Strings.php507
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/AES.php64
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/Base.php2908
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/Blowfish.php682
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/ChaCha20.php799
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/AsymmetricKey.php581
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/BlockCipher.php24
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/JWK.php77
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/OpenSSH.php224
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS.php72
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS1.php209
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS8.php766
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PuTTY.php380
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Signature/Raw.php60
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/PrivateKey.php31
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/PublicKey.php25
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/StreamCipher.php54
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/SymmetricKey.php3398
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Traits/Fingerprint.php57
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Traits/PasswordProtected.php46
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/DES.php535
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/DH.php405
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/Formats/Keys/PKCS1.php77
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/Formats/Keys/PKCS8.php132
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/Parameters.php36
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/PrivateKey.php75
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/PublicKey.php49
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA.php337
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/OpenSSH.php118
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PKCS1.php143
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PKCS8.php146
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PuTTY.php112
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/Raw.php85
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/XML.php132
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/ASN1.php62
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/Raw.php25
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/SSH2.php74
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Parameters.php36
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/PrivateKey.php154
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/PublicKey.php87
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC.php480
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Base.php218
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Binary.php373
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/KoblitzPrime.php335
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Montgomery.php279
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Prime.php785
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/TwistedEdwards.php215
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Curve25519.php81
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Curve448.php92
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Ed25519.php333
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Ed448.php273
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP160r1.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP160t1.php47
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP192r1.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP192t1.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP224r1.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP224t1.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP256r1.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP256t1.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP320r1.php40
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP320t1.php40
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP384r1.php58
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP384t1.php58
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP512r1.php58
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP512t1.php58
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistb233.php18
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistb409.php18
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk163.php18
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk233.php18
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk283.php18
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk409.php18
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp192.php18
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp224.php18
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp256.php18
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp384.php18
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp521.php18
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistt571.php18
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v1.php18
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v2.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v3.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v1.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v2.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v3.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime256v1.php18
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp112r1.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp112r2.php35
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp128r1.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp128r2.php35
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160k1.php46
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160r1.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160r2.php35
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp192k1.php45
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp192r1.php78
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp224k1.php45
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp224r1.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp256k1.php49
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp256r1.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp384r1.php52
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp521r1.php46
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect113r1.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect113r2.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect131r1.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect131r2.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163k1.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163r1.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163r2.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect193r1.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect193r2.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect233k1.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect233r1.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect239k1.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect283k1.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect283r1.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect409k1.php38
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect409r1.php38
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect571k1.php42
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect571r1.php42
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/Common.php549
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/JWK.php189
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/MontgomeryPrivate.php101
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/MontgomeryPublic.php71
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/OpenSSH.php209
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PKCS1.php194
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PKCS8.php237
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PuTTY.php138
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/XML.php486
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/libsodium.php116
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/ASN1.php62
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/IEEE.php68
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/Raw.php25
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/SSH2.php94
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Parameters.php36
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/PrivateKey.php283
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/PublicKey.php173
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/Hash.php2002
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/PublicKeyLoader.php112
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/RC2.php430
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/RC4.php186
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php3395
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/JWK.php142
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/MSBLOB.php224
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/OpenSSH.php132
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PKCS1.php187
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PKCS8.php122
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PSS.php238
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PuTTY.php124
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/Raw.php184
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/XML.php171
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/PrivateKey.php530
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/PublicKey.php513
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/Random.php162
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/Rijndael.php715
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/Salsa20.php528
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/TripleDES.php248
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Crypt/Twofish.php500
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Exception/BadConfigurationException.php23
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Exception/BadDecryptionException.php23
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Exception/BadModeException.php23
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Exception/ConnectionClosedException.php23
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Exception/FileNotFoundException.php23
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Exception/InconsistentSetupException.php23
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Exception/InsufficientSetupException.php23
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Exception/InvalidPacketLengthException.php10
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Exception/NoKeyLoadedException.php23
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Exception/NoSupportedAlgorithmsException.php23
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Exception/TimeoutException.php10
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Exception/UnableToConnectException.php23
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedAlgorithmException.php23
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedCurveException.php23
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedFormatException.php23
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedOperationException.php23
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ANSI.php152
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1.php562
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Element.php22
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AccessDescription.php32
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AdministrationDomainName.php36
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AlgorithmIdentifier.php35
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AnotherName.php37
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Attribute.php37
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeType.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeTypeAndValue.php32
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeValue.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Attributes.php31
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AuthorityInfoAccessSyntax.php31
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AuthorityKeyIdentifier.php45
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BaseDistance.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BasicConstraints.php39
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttribute.php32
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttributes.php31
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInStandardAttributes.php67
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CPSuri.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLDistributionPoints.php31
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLNumber.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLReason.php41
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertPolicyId.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Certificate.php33
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateIssuer.php24
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateList.php33
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificatePolicies.php31
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateSerialNumber.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificationRequest.php33
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificationRequestInfo.php41
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Characteristic_two.php36
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CountryName.php36
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Curve.php36
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DHParameter.php38
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAParams.php33
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAPrivateKey.php36
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAPublicKey.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DigestInfo.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DirectoryString.php35
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DisplayText.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DistributionPoint.php45
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DistributionPointName.php40
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DssSigValue.php32
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECParameters.php45
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECPoint.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECPrivateKey.php48
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EDIPartyName.php42
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EcdsaSigValue.php32
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EncryptedData.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EncryptedPrivateKeyInfo.php32
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtKeyUsageSyntax.php31
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Extension.php43
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtensionAttribute.php42
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtensionAttributes.php31
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Extensions.php33
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/FieldElement.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/FieldID.php35
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralName.php80
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralNames.php31
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralSubtree.php42
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralSubtrees.php31
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/HashAlgorithm.php24
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/HoldInstructionCode.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/InvalidityDate.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/IssuerAltName.php24
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/IssuingDistributionPoint.php68
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyIdentifier.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyPurposeId.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyUsage.php39
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/MaskGenAlgorithm.php24
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Name.php31
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NameConstraints.php40
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NetworkAddress.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NoticeReference.php37
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NumericUserIdentifier.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ORAddress.php33
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OneAsymmetricKey.php48
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OrganizationName.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OrganizationalUnitNames.php31
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OtherPrimeInfo.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OtherPrimeInfos.php32
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBEParameter.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBES2params.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBKDF2params.php41
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBMAC1params.php34
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PKCS9String.php32
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Pentanomial.php33
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PersonalName.php54
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyInformation.php38
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyMappings.php37
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyQualifierId.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyQualifierInfo.php32
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PostalAddress.php32
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Prime_p.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateDomainName.php32
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKey.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKeyInfo.php41
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKeyUsagePeriod.php40
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKey.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKeyAndChallenge.php32
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKeyInfo.php35
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RC2CBCParameter.php37
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RDNSequence.php38
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSAPrivateKey.php44
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSAPublicKey.php32
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSASSA_PSS_params.php58
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ReasonFlags.php39
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RelativeDistinguishedName.php37
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RevokedCertificate.php35
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SignedPublicKeyAndChallenge.php33
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SpecifiedECDomain.php45
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectAltName.php24
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectDirectoryAttributes.php31
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectInfoAccessSyntax.php31
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectPublicKeyInfo.php32
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TBSCertList.php54
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TBSCertificate.php65
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TerminalIdentifier.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Time.php32
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Trinomial.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/UniqueIdentifier.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/UserNotice.php38
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Validity.php32
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_ca_policy_url.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_cert_type.php40
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_comment.php26
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/File/X509.php3053
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger.php3801
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath.php702
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Base.php110
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/BuiltIn.php40
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/DefaultEngine.php25
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/OpenSSL.php25
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Reductions/Barrett.php196
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Reductions/EvalBarrett.php108
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/Engine.php1293
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/GMP.php697
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/GMP/DefaultEngine.php40
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/OpenSSL.php68
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP.php1360
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Base.php143
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/DefaultEngine.php25
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Montgomery.php89
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/OpenSSL.php25
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Barrett.php296
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Classic.php42
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/EvalBarrett.php500
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Montgomery.php126
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/MontgomeryMult.php76
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/PowerOfTwo.php59
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP32.php371
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP64.php372
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BinaryField.php203
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/BinaryField/Integer.php516
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/Common/FiniteField.php22
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/Common/FiniteField/Integer.php44
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/PrimeField.php118
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Math/PrimeField/Integer.php442
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Net/SCP.php349
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Net/SFTP.php2239
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Net/SFTP/Stream.php192
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Net/SSH1.php1662
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/Net/SSH2.php4777
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/System/SSH/Agent.php245
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/System/SSH/Agent/Identity.php317
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/System/SSH/Common/Traits/ReadBytes.php37
-rw-r--r--vendor/phpseclib/phpseclib/phpseclib/bootstrap.php7
-rw-r--r--vendor/phpseclib/phpseclib2_compat/.github/workflows/ci.yml61
-rw-r--r--vendor/phpseclib/phpseclib2_compat/.github/workflows/update.php12
-rw-r--r--vendor/phpseclib/phpseclib2_compat/LICENSE21
-rw-r--r--vendor/phpseclib/phpseclib2_compat/README.md92
-rw-r--r--vendor/phpseclib/phpseclib2_compat/composer.json30
-rw-r--r--vendor/phpseclib/phpseclib2_compat/src/Crypt/AES.php93
-rw-r--r--vendor/phpseclib/phpseclib2_compat/src/Crypt/Base.php585
-rw-r--r--vendor/phpseclib/phpseclib2_compat/src/Crypt/Blowfish.php66
-rw-r--r--vendor/phpseclib/phpseclib2_compat/src/Crypt/DES.php65
-rw-r--r--vendor/phpseclib/phpseclib2_compat/src/Crypt/Hash.php145
-rw-r--r--vendor/phpseclib/phpseclib2_compat/src/Crypt/RC2.php132
-rw-r--r--vendor/phpseclib/phpseclib2_compat/src/Crypt/RC4.php74
-rw-r--r--vendor/phpseclib/phpseclib2_compat/src/Crypt/RSA.php948
-rw-r--r--vendor/phpseclib/phpseclib2_compat/src/Crypt/Random.php36
-rw-r--r--vendor/phpseclib/phpseclib2_compat/src/Crypt/Rijndael.php137
-rw-r--r--vendor/phpseclib/phpseclib2_compat/src/Crypt/TripleDES.php116
-rw-r--r--vendor/phpseclib/phpseclib2_compat/src/Crypt/Twofish.php67
-rw-r--r--vendor/phpseclib/phpseclib2_compat/src/File/ANSI.php32
-rw-r--r--vendor/phpseclib/phpseclib2_compat/src/File/ASN1.php95
-rw-r--r--vendor/phpseclib/phpseclib2_compat/src/File/ASN1/Element.php28
-rw-r--r--vendor/phpseclib/phpseclib2_compat/src/File/X509.php407
-rw-r--r--vendor/phpseclib/phpseclib2_compat/src/Math/BigInteger.php308
-rw-r--r--vendor/phpseclib/phpseclib2_compat/src/Net/SFTP.php276
-rw-r--r--vendor/phpseclib/phpseclib2_compat/src/Net/SFTP/Stream.php75
-rw-r--r--vendor/phpseclib/phpseclib2_compat/src/Net/SSH2.php233
-rw-r--r--vendor/phpseclib/phpseclib2_compat/src/System/SSH/Agent.php47
-rw-r--r--vendor/phpseclib/phpseclib2_compat/src/System/SSH/Agent/Identity.php34
-rw-r--r--vendor/ralouphie/getallheaders/LICENSE21
-rw-r--r--vendor/ralouphie/getallheaders/README.md27
-rw-r--r--vendor/ralouphie/getallheaders/composer.json26
-rw-r--r--vendor/ralouphie/getallheaders/src/getallheaders.php46
-rw-r--r--vendor/stephenhill/base58/src/Base58.php4
-rw-r--r--view/css/conversation.css114
-rw-r--r--view/css/mod_help.css2
-rw-r--r--view/js/autocomplete.js2
-rw-r--r--view/js/main.js644
-rw-r--r--view/nb/hmessages.po21075
-rw-r--r--view/nb/hstrings.php5340
-rw-r--r--view/pdl/mod_dreport.pdl7
-rw-r--r--view/php/default.php20
-rw-r--r--view/theme/redbasic/css/style.css1
-rw-r--r--view/theme/redbasic/js/redbasic.js13
-rw-r--r--view/tpl/breadcrumb.tpl2
-rw-r--r--view/tpl/comment_item.tpl12
-rw-r--r--view/tpl/conv_frame.tpl18
-rw-r--r--view/tpl/conv_item.tpl97
-rw-r--r--view/tpl/dreport.tpl2
-rw-r--r--view/tpl/head.tpl1
-rw-r--r--view/tpl/jot-header.tpl65
-rw-r--r--view/tpl/js_strings.tpl3
-rw-r--r--view/tpl/notifications_widget.tpl52
-rw-r--r--view/tpl/pinned_item.tpl226
-rw-r--r--view/tpl/search_item.tpl4
-rw-r--r--view/tpl/settings_display.tpl2
1866 files changed, 99093 insertions, 51055 deletions
diff --git a/.debianinstall/debian-setup.sh b/.debianinstall/debian-setup.sh
index 758d13e68..2982b82eb 100644
--- a/.debianinstall/debian-setup.sh
+++ b/.debianinstall/debian-setup.sh
@@ -160,8 +160,8 @@ function install_composer {
print_info "We check if Composer is already downloaded"
if [ ! -f /usr/local/bin/composer ]
then
- EXPECTED_CHECKSUM="$(php -r 'copy("https://composer.github.io/installer.sig", "php://stdout");')"
- php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
+ EXPECTED_CHECKSUM="`wget -qO- https://composer.github.io/installer.sig`"
+ wget https://getcomposer.org/installer -O composer-setup.php
ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")"
if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]
then
@@ -171,6 +171,7 @@ function install_composer {
fi
php composer-setup.php --quiet
RESULT=$?
+ composer --version
rm composer-setup.php
# exit $RESULT
# We install Composer globally
@@ -181,7 +182,7 @@ function install_composer {
fi
cd $install_path
export COMPOSER_ALLOW_SUPERUSER=1;
- /usr/local/bin/composer install --no-dev
+ /usr/local/bin/composer install --no-dev --quiet
/usr/local/bin/composer show
export COMPOSER_ALLOW_SUPERUSER=0;
}
@@ -307,10 +308,19 @@ function install_run_selfhost {
# https://carol.selfhost.de/update?username=123456&password=supersafe
#
# the prefered way
- wget --output-document=$selfhostdir/$selfhostscript https://jonaspasche.de/selfhost-updater
- echo "router" > $selfhostdir/device
- echo "$selfhost_user" > $selfhostdir/user
- echo "$selfhost_pass" > $selfhostdir/pass
+ if [ ! -f $selfhostdir/$selfhostscript ]
+ then
+ wget --output-document=$selfhostdir/$selfhostscript https://jonaspasche.de/selfhost-updater
+ if [ ! -s $selfhostdir/$selfhostscript ]
+ then
+ die "Failed to download selfHOST file for dynDNS"
+ fi
+ echo "router" > $selfhostdir/device
+ echo "$selfhost_user" > $selfhostdir/user
+ echo "$selfhost_pass" > $selfhostdir/pass
+ print_info "Wrote file to update dynamic IP. File: $selfhostdir/$selfhostscript"
+ fi
+ print_info "executing $selfhostdir/$selfhostscript update..."
bash $selfhostdir/$selfhostscript update
fi
}
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ffa63f9d8..892333a59 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -33,7 +33,7 @@ before_script:
- pecl install xdebug yaml
- docker-php-ext-enable xdebug yaml
- docker-php-ext-configure gd --with-jpeg=/usr/include/
- - docker-php-ext-install gd bcmath intl pdo_mysql pdo_pgsql zip
+ - docker-php-ext-install gd bcmath intl pdo_mysql pdo_pgsql zip exif
# Install composer
- curl -sS https://getcomposer.org/installer | php
diff --git a/CHANGELOG b/CHANGELOG
index 951c97b6f..cb3c3b495 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,110 @@
+Hubzilla 10.4 (2025-??-??)
+ - Introduce util/init_sys_channel to create the sys channel if required
+ - Upgrade http-message-signer to version 2.2
+ - Upgrade phpseclib to version 3
+ - Minor cleanup to the account functions and added tests
+ - Do not sign (request-target) on response
+ - Start verifying incoming RFC9421 HTTP signatures
+ - Convert geo URIs into clickable links
+ - Allow geo URIs in url bbcode tags
+ - Cleanup obsolete and unused functions
+ - Remove unused Xref module
+ - Make sure we have the keys before attempting to sign with JcsEddsa2022
+ - Implement lazy loading of toplevel comments
+ - Update german help files
+ - Add App::$page_layouts attribute for comanche
+ - Refactor and fix numerous issues in guess_image_type()
+ - Add tests for Widget\Messages
+ - Refactor cache_embeds daemon to be called with uuid (instead of item id) so that it will only be processed once
+ - Add avif support for php-gd
+ - Exclude Add/Remove items from network nouveau query
+ - Always preload images and remove pre image preload setting
+ - Introduce per channel conversation mode setting
+ - Add API docs for the observer file
+ - Move observer helper functions to separate source
+ - Introduce helper functions to access the various fields of the xchan stored in App::$observer
+ - Refactor item_normal() to accept an owner uid
+ - Implement reply modal if comment replies are enabled
+ - Streamline wording conversation > comment > reply
+ - Streamline default ordering to created date
+ - Default to threaded conversation mode
+ - Implement lazy loading of reactions
+ - Do not store dismissed create activities in dreport
+ - Refactor mod item to deprecate x() and use $_POST instead of $_REQUEST superglobal
+ - Improved styling for dreport module
+ - Start deprecation of the function x()
+ - Minor cleanup and refactor for Web/Router
+ - Minor cleanup and refactor for Lib/Webserver
+ - Set App::$query_string from from server.request_uri instead of server.query_string because the latter will mostly be urldecoded by the server already
+ - Refactor language selector
+ - Extend message filter to support until=2025-04-18 20:49:00 for date/time based filtering and add tests
+ - Extend message filter to deal with && and || conditions and add tests
+ - Prevent storing files/folder with filenames exceeding their max name length
+ - Deal with attachment of type link
+ - Revert translation of network to stream
+ - Updated debian install script
+ - Add suport for strong bbcode tag
+ - Change photo.filename to type text for new installs
+ - Provide methods to get mid and uuid from activity object
+ - Minor update for boxy schema
+ - Update composer libs
+ - Reorganize emojis and allow custom site emojis
+ - Change item.obj and item.target to mediumtext for mysql new installs
+ - Move jot related functions to jot-header and some cleanup
+ - Port photo selector to vanilla javascript
+ - Enable photo selector for comments if OCAP access is enabled
+ - Add :hubzilla: emoji
+ - Add :zot: emoji
+ - Include unapproved connections in deliverable_abook_xchans()
+ - Port showHideComments() to vanilla javascript
+ - Disable browser rotating image based on EXIF metadata
+
+ Bugfixes
+ - Fix markdown issue with mentions
+ - Fix issue where item_wall was not set for article and card item types
+ - Fix first created account was not necessarily the admin account
+ - Fix notice not emited on failed login
+ - Fix intro notifications not handled via /notify/view and hence not marked seen
+ - Fix undefined static function in Zot6Handler
+ - Fix missing return in Render\Theme::current
+ - Fix announce source title (addr) not correct
+ - Fix offset calculation if element position is relative
+ - Fix autosave for comments
+ - Fix notfication issue with update activities
+ - Fix sess_data not updated to mediumtext in mysql schema file
+ - Fix title and summary converted to bbcode
+ - Fix preloading images if dom element is not yet in page
+ - Fix verb and hash for notifications
+ - Fix notification button for medium screen size (right aside collapsed)
+ - Fix new result set created for updated results (dreport)
+ - Fix regex to catch codeblocks with params like class in smilies()
+ - Fix term.imgurl not stored in item_store_update()
+ - Fix folder names are not URL escaped in Files app (issue #1903)
+ - Fix stephenhill/base58 PHP warnings
+ - Fix color bbcode markup
+ - Fix video poster display issue
+ - Fix relayed emoji reactions
+ - Fix some javascript errors on mobile devices
+ - Fix our own activities visible in unseen forum notifications
+ - Fix duplicated head_get_icon()
+
+ Addons
+ - Gallery: look for templates in theme directory first
+ - Articles: look for templates in theme directory first
+ - Wiki: look for templates in theme directory first
+ - Pubcrawl: remove unused force note setting
+ - Flashcards: major refactor and added functionality
+ - Superblock: refactor and new siteblock option for admins
+ - Cart: fix issue related to HTTP3
+ - Pubcrawl: avoid DB lookup if not valid AS request in mod followers and mod following
+ - Photocache: implement prefetch via cache_embeds daemon and minor refactor
+ - Articles: fix Add/Remove activities not dismissed in channel activities query
+ - Cards: fix Add/Remove activities not dismissed in channel activities query
+ - Diaspora: make sure item_thread_top is set for reshares (info for filters)
+ - Gallery: fix missing folder field from query
+ - Pubcrawl: update mod ap_probe to show a visual representation if applicable
+
+
Hubzilla 10.2.3 (2025-04-11)
- Fix bogus merge from 10.2.2 release
diff --git a/Zotlabs/Daemon/Cache_embeds.php b/Zotlabs/Daemon/Cache_embeds.php
index d5adfcc59..99b1c7ac0 100644
--- a/Zotlabs/Daemon/Cache_embeds.php
+++ b/Zotlabs/Daemon/Cache_embeds.php
@@ -6,23 +6,28 @@ class Cache_embeds {
static public function run($argc,$argv) {
- if(! $argc == 2)
+ if(!$argc == 2) {
return;
+ }
- $c = q("select body from item where id = %d ",
- dbesc(intval($argv[1]))
+ $c = q("select uid, aid, body, item_private from item where uuid = '%s'",
+ dbesc($argv[1])
);
- if(! $c)
+ if(!$c) {
return;
+ }
$item = $c[0];
// bbcode conversion by default processes embeds that aren't already cached.
// Ignore the returned html output.
-
bbcode($item['body']);
+ // photocache addon hook to prefetch one copy of public item images for the sys channel
+ call_hooks('cache_prefetch_hook', $item);
+
return;
}
+
}
diff --git a/Zotlabs/Daemon/Cron.php b/Zotlabs/Daemon/Cron.php
index a38809f45..131abe2e1 100644
--- a/Zotlabs/Daemon/Cron.php
+++ b/Zotlabs/Daemon/Cron.php
@@ -125,14 +125,15 @@ class Cron {
$r = q("SELECT DISTINCT xchan, content FROM photo WHERE photo_usage = %d AND expires < %s - INTERVAL %s",
intval(PHOTO_CACHE),
db_utcnow(),
- db_quoteinterval(Config::Get('system', 'cache_expire_days', 7) . ' DAY')
+ db_quoteinterval(Config::Get('system', 'default_expire_days', 30) . ' DAY')
);
if ($r) {
q("DELETE FROM photo WHERE photo_usage = %d AND expires < %s - INTERVAL %s",
intval(PHOTO_CACHE),
db_utcnow(),
- db_quoteinterval(Config::Get('system', 'cache_expire_days', 7) . ' DAY')
+ db_quoteinterval(Config::Get('system', 'default_expire_days', 30) . ' DAY')
);
+
foreach ($r as $rr) {
$file = dbunescbin($rr['content']);
if (is_file($file)) {
diff --git a/Zotlabs/Lib/Activity.php b/Zotlabs/Lib/Activity.php
index 4e04283ba..64588f9e3 100644
--- a/Zotlabs/Lib/Activity.php
+++ b/Zotlabs/Lib/Activity.php
@@ -13,6 +13,7 @@ use Zotlabs\Entity\Item;
require_once('include/event.php');
require_once('include/html2plain.php');
require_once('include/items.php');
+require_once('include/markdown.php');
class Activity {
@@ -68,10 +69,10 @@ class Activity {
if ($j) {
xchan_query($j, true);
$items = fetch_post_tags($j);
- }
- if ($items) {
- return self::encode_item(array_shift($items));
+ if ($items) {
+ return self::encode_item(array_shift($items));
+ }
}
return null;
@@ -580,14 +581,12 @@ class Activity {
}
}
- if (intval($i['item_wall'])) {
- $ret['commentPolicy'] = map_scope(PermissionLimits::Get($i['uid'], 'post_comments'));
- }
-
if (intval($i['item_private']) === 2) {
$ret['directMessage'] = true;
}
+ $ret['commentPolicy'] = (($i['item_wall']) ? map_scope(PermissionLimits::Get($i['uid'], 'post_comments')) : '');
+
if (array_key_exists('comments_closed', $i) && $i['comments_closed'] !== EMPTY_STR && $i['comments_closed'] > NULL_DATE) {
if ($ret['commentPolicy']) {
$ret['commentPolicy'] .= ' ';
@@ -853,6 +852,8 @@ class Activity {
$entry['type'] = $att['mediaType'];
} elseif (array_key_exists('type', $att) && $att['type'] === 'Image') {
$entry['type'] = 'image/jpeg';
+ } elseif (array_key_exists('type', $att) && $att['type'] === 'Link') {
+ $entry['type'] = 'text/uri-list';
}
if (array_key_exists('name', $att) && $att['name']) {
$entry['name'] = html2plain(purify_html($att['name']), 256);
@@ -2275,8 +2276,8 @@ class Activity {
if (!array_key_exists('edited', $s))
$s['edited'] = $s['created'];
- $s['title'] = (($response_activity) ? EMPTY_STR : self::bb_content($content, 'name'));
- $s['summary'] = self::bb_content($content, 'summary');
+ $s['title'] = (($response_activity) ? EMPTY_STR : html2plain($content['name']));
+ $s['summary'] = (($content['summary'] !== $content['content']) ? html2plain($content['summary']) : '');
$s['body'] = ((self::bb_content($content, 'bbcode') && (!$response_activity)) ? self::bb_content($content, 'bbcode') : self::bb_content($content, 'content'));
// peertube quirks
@@ -2726,7 +2727,7 @@ class Activity {
$relay = $channel['channel_hash'] === $parent[0]['owner_xchan'];
- if (str_contains($parent[0]['tgt_type'], 'Collection') && !$relay && !$isCollectionOperation) {
+ if (str_contains($parent[0]['tgt_type'], 'Collection') && !$relay && !$is_collection_operation) {
logger('not a collection activity');
return;
}
@@ -2958,7 +2959,7 @@ class Activity {
if (intval($parent[0]['item_private']) === 0) {
if (intval($item['item_private'])) {
- $item['item_restrict'] = $item['item_restrict'] | 1;
+ $item['item_restrict'] = ((isset($item['item_restrict'])) ? $item['item_restrict'] | 1 : 1);
$item['allow_cid'] = '<' . $channel['channel_hash'] . '>';
$item['allow_gid'] = $item['deny_cid'] = $item['deny_gid'] = '';
}
@@ -3013,8 +3014,7 @@ class Activity {
}
if ($x['success']) {
-
- if ($relay && $channel['channel_hash'] === $x['item']['owner_xchan'] && $x['item']['verb'] !== 'Add' && !$isCollectionOperation) {
+ if ($relay && $channel['channel_hash'] === $x['item']['owner_xchan'] && $x['item']['verb'] !== 'Add' && !$is_collection_operation) {
$approval = Activity::addToCollection($channel, $act->data, $x['item']['parent_mid'], $x['item'], deliver: false);
}
@@ -3032,13 +3032,8 @@ class Activity {
}
}
- $r = q("select * from item where id = %d limit 1",
- intval($x['item_id'])
- );
+ send_status_notifications($x['item_id'], $x['item']);
- if ($r) {
- send_status_notifications($x['item_id'], $r[0]);
- }
sync_an_item($channel['channel_id'], $x['item_id']);
}
@@ -3355,10 +3350,10 @@ class Activity {
if (array_key_exists('startTime', $act) && strpos($act['startTime'], -1, 1) === 'Z') {
$adjust = true;
$event['adjust'] = 1;
- $event['dtstart'] = datetime_convert('UTC', 'UTC', $event['startTime'] . (($adjust) ? '' : 'Z'));
+ $event['dtstart'] = datetime_convert('UTC', 'UTC', $act['startTime'] . (($adjust) ? '' : 'Z'));
}
if (array_key_exists('endTime', $act)) {
- $event['dtend'] = datetime_convert('UTC', 'UTC', $event['endTime'] . (($adjust) ? '' : 'Z'));
+ $event['dtend'] = datetime_convert('UTC', 'UTC', $act['endTime'] . (($adjust) ? '' : 'Z'));
}
else {
$event['nofinish'] = true;
@@ -3557,12 +3552,22 @@ class Activity {
}
foreach ($actor['tag'] as $t) {
+ // TODO: implement FEP-fb2a at the sending side and deprecate PropertyValue
if ((isset($t['type']) && $t['type'] === 'PropertyValue') &&
(isset($t['name']) && $t['name'] === 'Protocol') &&
- (isset($t['value']) && in_array($t['value'], ['zot6', 'activitypub', 'diaspora']))
+ isset($t['value'])
) {
- $ret[] = $t['value'];
+ $ret[] = trim($t['value']);
}
+
+ // FEP-fb2a - actor metadata
+ if ((isset($t['type']) && $t['type'] === 'Note') &&
+ (isset($t['name']) && $t['name'] === 'Protocols') &&
+ isset($t['content'])
+ ) {
+ $ret = array_map('trim', explode(',', $t['content']));
+ }
+
}
return $ret;
diff --git a/Zotlabs/Lib/Apps.php b/Zotlabs/Lib/Apps.php
index 0dc405ea9..337344645 100644
--- a/Zotlabs/Lib/Apps.php
+++ b/Zotlabs/Lib/Apps.php
@@ -341,7 +341,7 @@ class Apps {
'Suggest Channels' => t('Suggest Channels'),
'Login' => t('Login'),
'Channel Manager' => t('Channel Manager'),
- 'Network' => t('Stream'),
+ 'Network' => t('Network'),
'Settings' => t('Settings'),
'Files' => t('Files'),
'Webpages' => t('Webpages'),
diff --git a/Zotlabs/Lib/DReport.php b/Zotlabs/Lib/DReport.php
index ac8e0d377..99bb05293 100644
--- a/Zotlabs/Lib/DReport.php
+++ b/Zotlabs/Lib/DReport.php
@@ -35,7 +35,7 @@ class DReport {
}
function addto_update($status) {
- $this->status = $this->status . ' ' . $status;
+ $this->status = $this->status . ', ' . $status;
}
@@ -89,8 +89,14 @@ class DReport {
if(array_key_exists('reject',$dr) && intval($dr['reject']))
return false;
- if(! ($dr['sender']))
+ if (!$dr['sender']) {
return false;
+ }
+
+ // do not store dismissed create activities
+ if ($dr['status'] === 'not a collection activity') {
+ return false;
+ }
// Is the sender one of our channels?
diff --git a/Zotlabs/Lib/Enotify.php b/Zotlabs/Lib/Enotify.php
index 6820091d5..6d5e249ef 100644
--- a/Zotlabs/Lib/Enotify.php
+++ b/Zotlabs/Lib/Enotify.php
@@ -95,8 +95,8 @@ class Enotify {
if (array_key_exists('verb', $params['item'])) {
// localize_item() alters the original item so make a copy first
$i = $params['item'];
- logger('calling localize');
- localize_item($i);
+ // logger('calling localize');
+ // localize_item($i);
$title = $i['title'];
$body = $i['body'];
$private = (($i['item_private']) || intval($i['item_obscured']));
@@ -131,9 +131,9 @@ class Enotify {
logger('notification: mail');
$subject = sprintf( t('[$Projectname:Notify] New direct message received at %s'), $sitename);
- $preamble = sprintf( t('%1$s sent you a new direct message at %2$s'), $sender['xchan_name'], $sitename);
+ $preamble = sprintf( t('%1$s sent you a new private message at %2$s'), $sender['xchan_name'], $sitename);
$epreamble = sprintf( t('%1$s sent you %2$s.'), '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', '[zrl=$itemlink]' . t('a direct message') . '[/zrl]');
- $sitelink = t('Please visit %s to view and/or reply to your direct messages.');
+ $sitelink = t('Please visit %s to view and/or reply to your private messages.');
$tsitelink = sprintf( $sitelink, $siteurl . '/hq/' . gen_link_id($params['item']['mid']));
$hsitelink = sprintf( $sitelink, '<a href="' . $siteurl . '/hq/' . gen_link_id($params['item']['mid']) . '">' . $sitename . '</a>');
$itemlink = $siteurl . '/hq/' . gen_link_id($params['item']['mid']);
@@ -146,7 +146,7 @@ class Enotify {
$itemlink = $params['link'];
- $action = (($moderated) ? t('requested to comment on') : t('commented on'));
+ $action = (($moderated) ? t('requested to post in') : t('posted in'));
if(array_key_exists('item',$params)) {
@@ -164,8 +164,8 @@ class Enotify {
if(activity_match($params['verb'], ['Dislike', ACTIVITY_DISLIKE]))
$action = (($moderated) ? t('requested to dislike') : t('disliked'));
- if(activity_match($params['verb'], ACTIVITY_SHARE))
- $action = t('repeated');
+ if(activity_match($params['verb'], [ACTIVITY_SHARE]))
+ $action = (($moderated) ? t('requested to repeat') : t('repeated'));
}
@@ -271,7 +271,7 @@ class Enotify {
$itemlink = $params['link'];
- if (array_key_exists('item',$params) && (activity_match($params['item']['verb'], ['Like', 'Dislike', ACTIVITY_LIKE, ACTIVITY_DISLIKE]))) {
+ if (array_key_exists('item',$params) && (activity_match($params['item']['verb'], ['Like', 'Dislike', ACTIVITY_LIKE, ACTIVITY_DISLIKE, 'Announce']))) {
if(! $always_show_in_notices || !($vnotify & VNOTIFY_LIKE) || !feature_enabled($recip['channel_id'], 'dislike')) {
logger('notification: not a visible activity. Ignoring.');
pop_lang();
@@ -327,6 +327,9 @@ class Enotify {
if(activity_match($params['item']['verb'], ['Dislike', ACTIVITY_DISLIKE]))
$verb = (($moderated) ? t('requested to dislike') : t('disliked'));
+ if(activity_match($params['item']['verb'], [ACTIVITY_SHARE]))
+ $verb = (($moderated) ? t('requested to repeat') : t('repeated'));
+
// "your post"
if ($parent_item['author']['xchan_hash'] === $recip['channel_hash']) {
$dest_str = sprintf(t('%1$s %2$s [zrl=%3$s]your %4$s[/zrl]'),
@@ -508,9 +511,14 @@ class Enotify {
*/
+ $hash = ((in_array($params['verb'], ['Create', 'Update'])) ? $params['item']['uuid'] : $params['item']['thr_parent_uuid']);
+
+ if (!$hash) {
+ $hash = new_uuid();
+ }
$datarray = [];
- $datarray['hash'] = $params['item']['uuid'] ?? new_uuid();
+ $datarray['hash'] = $hash;
$datarray['sender_hash'] = $sender['xchan_hash'];
$datarray['xname'] = $sender['xchan_name'];
$datarray['url'] = $sender['xchan_url'];
@@ -569,8 +577,9 @@ class Enotify {
dbesc($datarray['otype'])
);
- $r = q("select id from notify where hash = '%s' and ntype = %d and uid = %d limit 1",
+ $r = q("select id from notify where hash = '%s' and link = '%s' and ntype = %d and uid = %d limit 1",
dbesc($datarray['hash']),
+ dbesc($itemlink),
intval($datarray['ntype']),
intval($recip['channel_id'])
);
@@ -848,8 +857,8 @@ class Enotify {
}
else {
$itemem_text = (($item['item_thread_top'])
- ? (($item['obj_type'] === 'Question') ? t('created a new poll') : t('created a new post'))
- : (($item['obj_type'] === 'Answer') ? sprintf( t('voted on %s\'s poll'), '[bdi]' . $item['owner']['xchan_name'] . '[/bdi]') : sprintf( t('commented on %s\'s post'), '[bdi]' . $item['owner']['xchan_name'] . '[/bdi]'))
+ ? (($item['obj_type'] === 'Question') ? t('started a poll') : t('started a conversation'))
+ : (($item['obj_type'] === 'Answer') ? sprintf( t('voted on %s\'s poll'), '[bdi]' . $item['owner']['xchan_name'] . '[/bdi]') : sprintf( t('posted in %s\'s conversation'), '[bdi]' . $item['owner']['xchan_name'] . '[/bdi]'))
);
if(in_array($item['obj_type'], ['Document', 'Video', 'Audio', 'Image'])) {
@@ -861,12 +870,7 @@ class Enotify {
if($item['edited'] > $item['created']) {
$edit = true;
- if($item['item_thread_top']) {
- $itemem_text = sprintf( t('edited a post dated %s'), relative_date($item['created']));
- }
- else {
- $itemem_text = sprintf( t('edited a comment dated %s'), relative_date($item['created']));
- }
+ $itemem_text = sprintf( t('edited a message dated %s'), relative_date($item['created']));
}
@@ -886,7 +890,7 @@ class Enotify {
'when' => (($edit) ? datetime_convert('UTC', date_default_timezone_get(), $item['edited']) : datetime_convert('UTC', date_default_timezone_get(), $item['created'])),
'class' => (intval($item['item_unseen']) ? 'notify-unseen' : 'notify-seen'),
// 'b64mid' => (($item['mid']) ? gen_link_id($item['mid']) : ''),
- 'b64mid' => (($item['uuid']) ? $item['uuid'] : ''),
+ 'b64mid' => ((in_array($item['verb'] , ['Like', 'Dislike', 'Announce']) && !empty($item['thr_parent_uuid'])) ? $item['thr_parent_uuid'] : $item['uuid'] ?? ''),
//'b64mid' => ((in_array($item['verb'], [ACTIVITY_LIKE, ACTIVITY_DISLIKE])) ? gen_link_id($item['thr_parent']) : gen_link_id($item['mid'])),
'thread_top' => (($item['item_thread_top']) ? true : false),
'message' => bbcode(escape_tags($itemem_text)),
@@ -906,14 +910,13 @@ class Enotify {
}
static public function format_notify($tt) {
-
$message = trim(strip_tags(bbcode($tt['msg'])));
if(strpos($message, $tt['xname']) === 0)
$message = substr($message, strlen($tt['xname']) + 1);
$x = [
- 'notify_link' => (($tt['ntype'] === NOTIFY_MAIL) ? $tt['link'] : z_root() . '/notify/view/' . $tt['id']),
+ 'notify_link' => (($tt['ntype'] === NOTIFY_INTRO) ? z_root() . '/notify/view/' . $tt['id'] : $tt['link']),
'name' => $tt['xname'],
'url' => $tt['url'],
'photo' => $tt['photo'],
@@ -925,11 +928,9 @@ class Enotify {
];
return $x;
-
}
static public function format_intros($rr) {
-
return [
'notify_link' => z_root() . '/connections#' . $rr['abook_id'],
'name' => $rr['xchan_name'],
diff --git a/Zotlabs/Lib/IConfig.php b/Zotlabs/Lib/IConfig.php
index 74c1107f0..3540c2b24 100644
--- a/Zotlabs/Lib/IConfig.php
+++ b/Zotlabs/Lib/IConfig.php
@@ -13,6 +13,7 @@ class IConfig {
static public function Get(&$item, $family, $key, $default = false) {
$is_item = false;
+ $iid = null;
if(is_array($item)) {
$is_item = true;
@@ -27,12 +28,13 @@ class IConfig {
elseif(intval($item))
$iid = $item;
- if(! $iid)
+ if (!$iid)
return $default;
+
if(is_array($item) && array_key_exists('iconfig',$item) && is_array($item['iconfig'])) {
foreach($item['iconfig'] as $c) {
- if($c['iid'] == $iid && $c['cat'] == $family && $c['k'] == $key)
+ if (isset($c['iid']) && $c['iid'] == $iid && isset($c['cat']) && $c['cat'] == $family && isset($c['k']) && $c['k'] == $key)
return $c['v'];
}
}
diff --git a/Zotlabs/Lib/JcsEddsa2022.php b/Zotlabs/Lib/JcsEddsa2022.php
index 14f16c94b..c56f093af 100644
--- a/Zotlabs/Lib/JcsEddsa2022.php
+++ b/Zotlabs/Lib/JcsEddsa2022.php
@@ -7,11 +7,28 @@ use StephenHill\Base58;
class JcsEddsa2022 {
- public function __construct() {
- return $this;
- }
-
+ /**
+ * Sign arbitrary data with the keys of the provided channel.
+ *
+ * @param $data The data to be signed.
+ * @param array $channel A channel as an array of key/value pairs.
+ *
+ * @return An array with the following fields:
+ * - `type`: The type of signature, always `DataIntegrityProof`.
+ * - `cryptosuite`: The cryptographic algorithm used, always `eddsa-jcs-2022`.
+ * - `created`: The UTC date and timestamp when the signature was created.
+ * - `verificationMethod`: The channel URL and the public key separated by a `#`.
+ * - `proofPurpose`: The purpose of the signature, always `assertionMethod`.
+ * - `proofValue`: The signature itself.
+ *
+ * @throws JcsEddsa2022SignatureException if the channel is missing, or
+ * don't have valid keys.
+ */
public function sign($data, $channel): array {
+ if (!is_array($channel) || !isset($channel['channel_epubkey'], $channel['channel_eprvkey'])) {
+ throw new JcsEddsa2022SignException('Invalid or missing channel provided.');
+ }
+
$base58 = new Base58();
$pubkey = (new Multibase())->publicKey($channel['channel_epubkey']);
$options = [
diff --git a/Zotlabs/Lib/JcsEddsa2022SignException.php b/Zotlabs/Lib/JcsEddsa2022SignException.php
new file mode 100644
index 000000000..81d02d631
--- /dev/null
+++ b/Zotlabs/Lib/JcsEddsa2022SignException.php
@@ -0,0 +1,15 @@
+<?php
+/*
+ * SPDX-FileCopyrightText: 2025 The Hubzilla Community
+ * SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+namespace Zotlabs\Lib;
+
+use Exception;
+
+class JcsEddsa2022SignException extends Exception
+{
+}
diff --git a/Zotlabs/Lib/Keyutils.php b/Zotlabs/Lib/Keyutils.php
index 616ecfcf6..33f910236 100644
--- a/Zotlabs/Lib/Keyutils.php
+++ b/Zotlabs/Lib/Keyutils.php
@@ -2,8 +2,8 @@
namespace Zotlabs\Lib;
-use phpseclib\Crypt\RSA;
-use phpseclib\Math\BigInteger;
+use phpseclib3\Crypt\PublicKeyLoader;
+use phpseclib3\Math\BigInteger;
/**
* Keyutils
@@ -16,41 +16,42 @@ class Keyutils {
* @param string $e exponent
* @return string
*/
- public static function meToPem($m, $e) {
-
- $rsa = new RSA();
- $rsa->loadKey([
+ public static function meToPem(string $m, string $e): string
+ {
+ $parsedKey = PublicKeyLoader::load([
'e' => new BigInteger($e, 256),
'n' => new BigInteger($m, 256)
]);
- return $rsa->getPublicKey();
-
+ if (method_exists($parsedKey, 'getPublicKey')) {
+ $parsedKey = $parsedKey->getPublicKey();
+ }
+ return $parsedKey->toString('PKCS8');
}
/**
* @param string key
* @return string
*/
- public static function rsaToPem($key) {
-
- $rsa = new RSA();
- $rsa->setPublicKey($key);
-
- return $rsa->getPublicKey(RSA::PUBLIC_FORMAT_PKCS8);
-
+ public static function rsaToPem(string $key): string
+ {
+ $parsedKey = PublicKeyLoader::load($key);
+ if (method_exists($parsedKey, 'getPublicKey')) {
+ $parsedKey = $parsedKey->getPublicKey();
+ }
+ return $parsedKey->toString('PKCS8');
}
/**
* @param string key
* @return string
*/
- public static function pemToRsa($key) {
-
- $rsa = new RSA();
- $rsa->setPublicKey($key);
-
- return $rsa->getPublicKey(RSA::PUBLIC_FORMAT_PKCS1);
-
+ public static function pemToRsa(string $key): string
+ {
+ $parsedKey = PublicKeyLoader::load($key);
+ if (method_exists($parsedKey, 'getPublicKey')) {
+ $parsedKey = $parsedKey->getPublicKey();
+ }
+ return $parsedKey->toString('PKCS1');
}
/**
@@ -58,23 +59,28 @@ class Keyutils {
* @param string $m reference modulo
* @param string $e reference exponent
*/
- public static function pemToMe($key, &$m, &$e) {
-
- $rsa = new RSA();
- $rsa->loadKey($key);
- $rsa->setPublicKey();
-
- $m = $rsa->modulus->toBytes();
- $e = $rsa->exponent->toBytes();
-
+ public static function pemToMe(string $key): array
+ {
+ $parsedKey = PublicKeyLoader::load($key);
+ if (method_exists($parsedKey, 'getPublicKey')) {
+ $parsedKey = $parsedKey->getPublicKey();
+ }
+ $raw = $parsedKey->toString('Raw');
+
+ $m = $raw['n'];
+ $e = $raw['e'];
+
+ return [$m->toBytes(), $e->toBytes()];
}
/**
* @param string $pubkey
* @return string
*/
- public static function salmonKey($pubkey) {
- self::pemToMe($pubkey, $m, $e);
+ public static function salmonKey(string $pubkey): string
+ {
+ [$m, $e] = self::pemToMe($pubkey);
+ /** @noinspection PhpRedundantOptionalArgumentInspection */
return 'RSA' . '.' . base64url_encode($m, true) . '.' . base64url_encode($e, true);
}
@@ -82,11 +88,13 @@ class Keyutils {
* @param string $key
* @return string
*/
- public static function convertSalmonKey($key) {
- if (strstr($key, ','))
+ public static function convertSalmonKey(string $key): string
+ {
+ if (str_contains($key, ',')) {
$rawkey = substr($key, strpos($key, ',') + 1);
- else
+ } else {
$rawkey = substr($key, 5);
+ }
$key_info = explode('.', $rawkey);
@@ -96,4 +104,4 @@ class Keyutils {
return self::meToPem($m, $e);
}
-} \ No newline at end of file
+}
diff --git a/Zotlabs/Lib/Libzot.php b/Zotlabs/Lib/Libzot.php
index 57c110d8b..d2d696356 100644
--- a/Zotlabs/Lib/Libzot.php
+++ b/Zotlabs/Lib/Libzot.php
@@ -1542,6 +1542,7 @@ class Libzot {
$local_public = $public;
$item_result = null;
+ $parent = null;
$DR = new DReport(z_root(), $sender, $d, $arr['mid'], $arr['uuid']);
@@ -1996,7 +1997,7 @@ class Libzot {
}
$DR->addto_update('relayed');
- $result[] = $DR->get();
+ $result = [$DR->get()];
}
}
diff --git a/Zotlabs/Lib/MessageFilter.php b/Zotlabs/Lib/MessageFilter.php
index e7382c0d5..3f2db88c3 100644
--- a/Zotlabs/Lib/MessageFilter.php
+++ b/Zotlabs/Lib/MessageFilter.php
@@ -8,17 +8,18 @@ class MessageFilter {
public static function evaluate($item, $incl, $excl) {
- $text = prepare_text($item['body'],((isset($item['mimetype'])) ? $item['mimetype'] : 'text/bbcode'));
- $text = html2plain(($item['title']) ? $item['title'] . ' ' . $text : $text);
+ $text = prepare_text($item['body'], ((isset($item['mimetype'])) ? $item['mimetype'] : 'text/bbcode'));
+ $text = html2plain((!empty($item['title'])) ? $item['title'] . ' ' . $text : $text);
$lang = null;
-
if ((strpos($incl, 'lang=') !== false) || (strpos($excl, 'lang=') !== false) || (strpos($incl, 'lang!=') !== false) || (strpos($excl, 'lang!=') !== false)) {
$lang = detect_language($text);
}
$tags = ((isset($item['term']) && is_array($item['term']) && count($item['term'])) ? $item['term'] : false);
+ $until = null;
+
// exclude always has priority
$exclude = (($excl) ? explode("\n", $excl) : null);
@@ -41,7 +42,13 @@ class MessageFilter {
return false;
}
}
- elseif (substr($word, 0, 1) === '#' && $tags) {
+ elseif (str_starts_with($word, 'until=')) {
+ $until = strtotime(trim(substr($word, 6)));
+ if ($until > strtotime($item['created'] . ' UTC')) {
+ return false;
+ }
+ }
+ elseif (substr($word, 0, 1) === '#' && $tags) {
foreach ($tags as $t) {
if ((($t['ttype'] == TERM_HASHTAG) || ($t['ttype'] == TERM_COMMUNITYTAG)) && (($t['term'] === substr($word, 1)) || (substr($word, 1) === '*'))) {
return false;
@@ -89,7 +96,13 @@ class MessageFilter {
return true;
}
}
- elseif (substr($word, 0, 1) === '#' && $tags) {
+ elseif (str_starts_with($word, 'until=')) {
+ $until = strtotime(trim(substr($word, 6)));
+ if ($until > strtotime($item['created'] . ' UTC')) {
+ return true;
+ }
+ }
+ elseif (substr($word, 0, 1) === '#' && $tags) {
foreach ($tags as $t) {
if ((($t['ttype'] == TERM_HASHTAG) || ($t['ttype'] == TERM_COMMUNITYTAG)) && (($t['term'] === substr($word, 1)) || (substr($word, 1) === '*'))) {
return true;
@@ -124,9 +137,7 @@ class MessageFilter {
/**
- * @brief Test for Conditional Execution conditions. Shamelessly ripped off from Code/Render/Comanche
- *
- * This is extensible. The first version of variable testing supports tests of the forms:
+ * Evaluate a conditional expression with support for AND (&&) and OR (||) operators.
*
* - ?foo ~= baz which will check if item.foo contains the string 'baz';
* - ?foo == baz which will check if item.foo is the string 'baz';
@@ -143,103 +154,110 @@ class MessageFilter {
*
* The values 0, '', an empty array, and an unset value will all evaluate to false.
*
- * @param string $s
- * @param array $item
- * @return bool
+ * @param string $s The condition string to evaluate.
+ * @param array $item The associative array providing variable values.
+ * @return bool True if the condition is met, false otherwise.
*/
- public static function test_condition($s,$item) {
+ public static function test_condition($s, $item) {
+ $s = trim($s);
- if (preg_match('/(.*?)\s\~\=\s(.*?)$/', $s, $matches)) {
- $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
- if (stripos($x, trim($matches[2])) !== false) {
- return true;
+ // Handle OR (||)
+ // Split on '||' not inside quotes
+ $or_parts = preg_split('/\s*\|\|\s*/', $s);
+ if (count($or_parts) > 1) {
+ foreach ($or_parts as $part) {
+ if (self::test_condition(ltrim($part, '?+'), $item)) {
+ return true;
+ }
}
return false;
}
- if (preg_match('/(.*?)\s\=\=\s(.*?)$/', $s, $matches)) {
- $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
- if ($x == trim($matches[2])) {
- return true;
+ // Handle AND (&&)
+ // Split on '&&' not inside quotes
+ $and_parts = preg_split('/\s*\&\&\s*/', $s);
+ if (count($and_parts) > 1) {
+ foreach ($and_parts as $part) {
+ if (!self::test_condition(ltrim($part, '?+'), $item)) {
+ return false;
+ }
}
- return false;
+ return true;
+ }
+
+ // Basic checks
+
+ // Contains substring (case-insensitive)
+ if (preg_match('/(.*?)\s\~\=\s(.*?)$/', $s, $matches)) {
+ $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR);
+ return (stripos($x, trim($matches[2])) !== false);
+ }
+
+ // Equality
+ if (preg_match('/(.*?)\s\=\=\s(.*?)$/', $s, $matches)) {
+ $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR);
+ return ($x == trim($matches[2]));
}
+ // Inequality
if (preg_match('/(.*?)\s\!\=\s(.*?)$/', $s, $matches)) {
- $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
- if ($x != trim($matches[2])) {
- return true;
- }
- return false;
+ $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR);
+ return ($x != trim($matches[2]));
}
+ // Greater than or equal
if (preg_match('/(.*?)\s\>\=\s(.*?)$/', $s, $matches)) {
- $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
- if ($x >= trim($matches[2])) {
- return true;
- }
- return false;
+ $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR);
+ return ($x >= trim($matches[2]));
}
+ // Less than or equal
if (preg_match('/(.*?)\s\<\=\s(.*?)$/', $s, $matches)) {
- $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
- if ($x <= trim($matches[2])) {
- return true;
- }
- return false;
+ $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR);
+ return ($x <= trim($matches[2]));
}
+ // Greater than
if (preg_match('/(.*?)\s\>\s(.*?)$/', $s, $matches)) {
- $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
- if ($x > trim($matches[2])) {
- return true;
- }
- return false;
+ $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR);
+ return ($x > trim($matches[2]));
}
- if (preg_match('/(.*?)\s\>\s(.*?)$/', $s, $matches)) {
- $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
- if ($x < trim($matches[2])) {
- return true;
- }
- return false;
+ // Less than
+ if (preg_match('/(.*?)\s\<\s(.*?)$/', $s, $matches)) {
+ $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR);
+ return ($x < trim($matches[2]));
}
- if (preg_match('/[\$](.*?)\s\{\}\s(.*?)$/', $s, $matches)) {
- $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
- if (is_array($x) && in_array(trim($matches[2]), $x)) {
- return true;
- }
- return false;
+ // Array contains value
+ if (preg_match('/(.*?)\s\{\}\s(.*?)$/', $s, $matches)) {
+ $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR);
+ return (is_array($x) && in_array(trim($matches[2]), $x));
}
+ // Array contains key
if (preg_match('/(.*?)\s\{\*\}\s(.*?)$/', $s, $matches)) {
- $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
- if (is_array($x) && array_key_exists(trim($matches[2]), $x)) {
- return true;
- }
- return false;
+ $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR);
+ return (is_array($x) && array_key_exists(trim($matches[2]), $x));
}
// Ordering of this check (for falsiness) with relation to the following one (check for truthiness) is important.
+ // Falsy check
if (preg_match('/\!(.*?)$/', $s, $matches)) {
- $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
- if (!$x) {
- return true;
- }
- return false;
+ $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR);
+ return !$x;
}
+ // Truthy check (default)
if (preg_match('/(.*?)$/', $s, $matches)) {
- $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
- if ($x) {
- return true;
- }
- return false;
+ $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR);
+ return (bool)$x;
}
+ // If no conditions matched, return false
return false;
}
+
}
diff --git a/Zotlabs/Lib/Text.php b/Zotlabs/Lib/Text.php
index f593f9dd6..4a962670a 100644
--- a/Zotlabs/Lib/Text.php
+++ b/Zotlabs/Lib/Text.php
@@ -21,4 +21,13 @@ class Text {
return htmlspecialchars($string, ENT_COMPAT, 'UTF-8', false);
}
+ public static function rawurlencode_parts(string $string): string {
+ if (!$string) {
+ return EMPTY_STR;
+ }
+
+ return implode('/', array_map('rawurlencode', explode('/', $string)));
+ }
+
+
}
diff --git a/Zotlabs/Lib/ThreadItem.php b/Zotlabs/Lib/ThreadItem.php
index d0fa1e587..46fe6d815 100644
--- a/Zotlabs/Lib/ThreadItem.php
+++ b/Zotlabs/Lib/ThreadItem.php
@@ -4,8 +4,6 @@ namespace Zotlabs\Lib;
use App;
use Zotlabs\Access\AccessList;
-use Zotlabs\Lib\Apps;
-use Zotlabs\Lib\Config;
require_once('include/text.php');
@@ -26,6 +24,7 @@ class ThreadItem {
private $parent = null;
private $conversation = null;
private $redirect_url = null;
+ private $owner_addr = '';
private $owner_url = '';
private $owner_photo = '';
private $owner_name = '';
@@ -35,14 +34,12 @@ class ThreadItem {
private $channel = null;
private $display_mode = 'normal';
private $reload = '';
- private $mid_uuid_map = [];
-
public function __construct($data) {
$this->data = $data;
$this->toplevel = ($this->get_id() == $this->get_data_value('parent'));
- $this->threaded = Config::Get('system','thread_allow');
+ $this->threaded = ((local_channel()) ? PConfig::Get(local_channel(), 'system', 'thread_allow', true) : Config::Get('system', 'thread_allow', true));
// Prepare the children
if(isset($data['children'])) {
@@ -65,8 +62,6 @@ class ThreadItem {
unset($this->data['children']);
}
-
-
// allow a site to configure the order and content of the reaction emoji list
if($this->toplevel) {
$x = Config::Get('system','reactions');
@@ -84,7 +79,7 @@ class ThreadItem {
* _ false on failure
*/
- public function get_template_data($conv_responses, $mid_uuid_map, $thread_level=1, $conv_flags = []) {
+ public function get_template_data($thread_level=1, $conv_flags = []) {
$result = [];
$item = $this->get_data();
@@ -103,6 +98,8 @@ class ThreadItem {
$conv = $this->get_conversation();
$observer = $conv->get_observer();
+ $conv->mid_uuid_map[$item['mid']] = $item['uuid'];
+
$acl = new AccessList([]);
$acl->set($item);
@@ -114,7 +111,7 @@ class ThreadItem {
$locktype = intval($item['item_private']);
if ($locktype === 2) {
- $lock = t('Direct message');
+ $lock = t('Private message');
}
// 0 = limited based on public policy
@@ -209,9 +206,9 @@ class ThreadItem {
}
if (in_array($item['obj_type'], ['Event', ACTIVITY_OBJ_EVENT])) {
- $response_verbs[] = 'attendyes';
- $response_verbs[] = 'attendno';
- $response_verbs[] = 'attendmaybe';
+ $response_verbs[] = 'accept';
+ $response_verbs[] = 'reject';
+ $response_verbs[] = 'tentativeaccept';
if($this->is_commentable() && $observer) {
$isevent = true;
$attend = array( t('I will attend'), t('I will not attend'), t('I might attend'));
@@ -222,17 +219,8 @@ class ThreadItem {
$response_verbs[] = 'answer';
}
- if (!feature_enabled($conv->get_profile_owner(),'dislike')) {
- unset($conv_responses['dislike']);
- }
-
- $responses = get_responses($conv_responses,$response_verbs,$this,$item);
-
- $my_responses = [];
- foreach($response_verbs as $v) {
- $my_responses[$v] = ((isset($conv_responses[$v][$item['mid'] . '-m'])) ? 1 : 0);
- }
-
+ $response_verbs[] = 'comment';
+ $responses = get_responses($response_verbs, $item);
/*
* We should avoid doing this all the time, but it depends on the conversation mode
@@ -242,7 +230,13 @@ class ThreadItem {
$this->check_wall_to_wall();
+ $children = $this->get_children();
+ $children_count = count($children);
+
if($this->is_toplevel()) {
+ $conv->comments_total = $responses['comment']['count'] ?? 0;
+ $conv->comments_loaded = $children_count;
+
if((local_channel() && $conv->get_profile_owner() === local_channel()) || (local_channel() && App::$module === 'pubstream')) {
$star = [
'toggle' => t("Toggle Star Status"),
@@ -254,7 +248,6 @@ class ThreadItem {
$is_comment = true;
}
-
$verified = (intval($item['item_verified']) ? t('Message signature validated') : '');
$forged = ((($item['sig']) && (! intval($item['item_verified']))) ? t('Message signature incorrect') : '');
$unverified = '' ; // (($this->is_wall_to_wall() && (! intval($item['item_verified']))) ? t('Message cannot be verified') : '');
@@ -287,15 +280,11 @@ class ThreadItem {
if((in_array($item['obj_type'], ['Event', ACTIVITY_OBJ_EVENT])) && $conv->get_profile_owner() == local_channel())
$has_event = true;
- $like = [];
- $dislike = [];
$reply_to = [];
$reactions_allowed = false;
if($this->is_commentable() && $observer) {
- $like = array( t("I like this \x28toggle\x29"), t("like"));
- $dislike = array( t("I don't like this \x28toggle\x29"), t("dislike"));
- $reply_to = array( t("Reply to this comment"), t("reply"), t("Reply to"));
+ $reply_to = array( t("Reply to this message"), t("reply"), t("Reply to"));
$reactions_allowed = true;
}
@@ -339,9 +328,8 @@ class ThreadItem {
$viewthread = z_root() . '/channel/' . $owner_address . '?f=&mid=' . urlencode(gen_link_id($item['mid']));
$comment_count_txt = ['label' => sprintf(tt('%d comment', '%d comments', $total_children), $total_children), 'count' => $total_children];
- $list_unseen_txt = $unseen_comments ? ['label' => sprintf(t('%d unseen'), $unseen_comments), 'count' => $unseen_comments] : [];
- $children = $this->get_children();
+ $list_unseen_txt = $unseen_comments ? ['label' => sprintf(t('%d unseen'), $unseen_comments), 'count' => $unseen_comments] : [];
$has_tags = (($body['tags'] || $body['categories'] || $body['mentions'] || $body['attachments'] || $body['folders']) ? true : false);
@@ -351,14 +339,7 @@ class ThreadItem {
$midb64 = $item['uuid'];
$mids = [ $item['uuid'] ];
- $response_mids = [];
- foreach($response_verbs as $v) {
- if(isset($conv_responses[$v]['mids'][$item['mid']])) {
- $response_mids = array_merge($response_mids, $conv_responses[$v]['mids'][$item['mid']]);
- }
- }
- $mids = array_merge($mids, $response_mids);
$json_mids = json_encode($mids);
// Pinned item processing
@@ -372,6 +353,22 @@ class ThreadItem {
$contact = App::$contacts[$item['author_xchan']];
}
+ $blog_mode = $this->get_display_mode() === 'list';
+ $load_more = false;
+ $load_more_title = '';
+ $comments_total_percent = 0;
+ if (($conv->comments_total > $conv->comments_loaded) || ($blog_mode && $conv->comments_total > 3)) {
+ // provide a load more comments button
+ $load_more = true;
+ $load_more_title = sprintf(t('Load the next few of total %d comments'), $conv->comments_total);
+ $comments_total_percent = round(100 * 3 / $conv->comments_total);
+ }
+
+ $expand = '';
+ if ($this->threaded && !empty($item['comment_count'] && !$this->is_toplevel())) {
+ $expand = t('Expand Replies');
+ }
+
$tmp_item = array(
'template' => $this->get_template(),
'mode' => $mode,
@@ -384,9 +381,9 @@ class ThreadItem {
'folders' => $body['folders'],
'text' => strip_tags($body['html']),
'id' => $this->get_id(),
+ 'parent' => $item['parent'],
'mid' => $midb64,
'mids' => $json_mids,
- 'parent' => $item['parent'],
'author_id' => (($item['author']['xchan_addr']) ? $item['author']['xchan_addr'] : $item['author']['xchan_url']),
'author_is_group_actor' => (($item['author']['xchan_pubforum']) ? t('Forum') : ''),
'isevent' => $isevent,
@@ -431,6 +428,7 @@ class ThreadItem {
'vote_title' => t('Voting Options'),
'is_comment' => $is_comment,
'is_new' => $is_new,
+ 'owner_addr' => $this->get_owner_addr(),
'owner_url' => $this->get_owner_url(),
'owner_photo' => $this->get_owner_photo(),
'owner_name' => $this->get_owner_name(),
@@ -440,13 +438,12 @@ class ThreadItem {
'reactions' => $this->reactions,
// Item toolbar buttons
'emojis' => (($this->is_toplevel() && $this->is_commentable() && $observer && feature_enabled($conv->get_profile_owner(),'emojis')) ? '1' : ''),
- 'like' => $like,
- 'dislike' => ((feature_enabled($conv->get_profile_owner(),'dislike')) ? $dislike : ''),
- 'reply_to' => (((! $this->is_toplevel()) && feature_enabled($conv->get_profile_owner(),'reply_to')) ? $reply_to : ''),
+ 'reply_to' => ((feature_enabled($conv->get_profile_owner(),'reply_to')) ? $reply_to : ''),
'top_hint' => t("Go to previous comment"),
'share' => $share,
'embed' => $embed,
'rawmid' => $item['mid'],
+ 'parent_mid' => $item['parent_mid'],
'plink' => get_plink($item),
'edpost' => $edpost,
'star' => ((feature_enabled($conv->get_profile_owner(),'star_posts') && ($item['item_type'] == ITEM_TYPE_POST)) ? $star : ''),
@@ -466,16 +463,17 @@ class ThreadItem {
'list_unseen_txt' => $list_unseen_txt,
'markseen' => t('Mark all comments seen'),
'responses' => $responses,
- 'my_responses' => $my_responses,
+ // 'my_responses' => $my_responses,
'modal_dismiss' => t('Close'),
'comment' => ($item['item_delayed'] ? '' : $this->get_comment_box()),
+ 'comment_hidden' => feature_enabled($conv->get_profile_owner(),'reply_to'),
'no_comment' => (($item['item_thread_top'] && $item['item_nocomment'])? t('Comments disabled') : ''),
'previewing' => ($conv->is_preview() ? true : false ),
'preview_lbl' => t('This is an unsaved preview'),
'wait' => t('Please wait'),
'thread_level' => $thread_level,
'settings' => $settings,
- 'thr_parent_uuid' => (($item['parent_mid'] != $item['thr_parent']) ? $mid_uuid_map[$item['thr_parent']] : ''),
+ 'thr_parent_uuid' => (($item['parent_mid'] !== $item['thr_parent'] && isset($conv->mid_uuid_map[$item['thr_parent']])) ? $conv->mid_uuid_map[$item['thr_parent']] : ''),
'contact_id' => (($contact) ? $contact['abook_id'] : ''),
'moderate' => ($item['item_blocked'] == ITEM_MODERATED),
'moderate_approve' => t('Approve'),
@@ -483,7 +481,25 @@ class ThreadItem {
'rtl' => in_array($item['lang'], rtl_languages()),
'reactions_allowed' => $reactions_allowed,
'reaction_str' => [t('Add yours'), t('Remove yours')],
- 'is_contained' => $this->is_toplevel() && str_contains($item['tgt_type'], 'Collection')
+ 'is_contained' => $this->is_toplevel() && str_contains($item['tgt_type'], 'Collection'),
+ 'observer_activity' => [
+ 'like' => intval($item['observer_like_count'] ?? 0),
+ 'dislike' => intval($item['observer_dislike_count'] ?? 0),
+ 'announce' => intval($item['observer_announce_count'] ?? 0),
+ 'comment' => intval($item['observer_comment_count'] ?? 0),
+ 'accept' => intval($item['observer_accept_count'] ?? 0),
+ 'reject' => intval($item['observer_reject_count'] ?? 0),
+ 'tentativeaccept' => intval($item['observer_tentativeaccept_count'] ?? 0)
+ ],
+ 'threaded' => $this->threaded,
+ 'blog_mode' => $blog_mode,
+ 'collapse_comments' => t('show less'),
+ 'expand_comments' => $this->threaded ? t('show more') : t('show all'),
+ 'load_more' => $load_more,
+ 'load_more_title' => $load_more_title,
+ 'comments_total' => $conv->comments_total,
+ 'comments_total_percent' => $comments_total_percent,
+ 'expand' => $expand
);
$arr = array('item' => $item, 'output' => $tmp_item);
@@ -492,25 +508,19 @@ class ThreadItem {
$result = $arr['output'];
$result['children'] = array();
- $nb_children = count($children);
- $visible_comments = Config::Get('system', 'expanded_comments', 3);
+ $visible_comments = 3; // Config::Get('system', 'expanded_comments', 3);
- if(($this->get_display_mode() === 'normal') && ($nb_children > 0)) {
+ if(($this->get_display_mode() === 'normal') && ($children_count > 0)) {
foreach($children as $child) {
- $result['children'][] = $child->get_template_data($conv_responses, $mid_uuid_map, $thread_level + 1,$conv_flags);
+ $result['children'][] = $child->get_template_data($thread_level + 1, $conv_flags);
}
+
// Collapse
- if(($nb_children > $visible_comments) || ($thread_level > 1)) {
+ if($thread_level === 1 && $children_count > $visible_comments) {
$result['children'][0]['comment_firstcollapsed'] = true;
$result['children'][0]['num_comments'] = $comment_count_txt['label'];
- $result['children'][0]['hide_text'] = t('show all');
- if($thread_level > 1) {
- $result['children'][$nb_children - 1]['comment_lastcollapsed'] = true;
- }
- else {
- $result['children'][$nb_children - ($visible_comments + 1)]['comment_lastcollapsed'] = true;
- }
+ $result['children'][$children_count - ($visible_comments + 1)]['comment_lastcollapsed'] = true;
}
}
@@ -763,7 +773,7 @@ class ThreadItem {
*/
private function get_comment_box() {
- if(!$this->is_toplevel() && !Config::Get('system','thread_allow')) {
+ if(!$this->is_toplevel()) {
return '';
}
@@ -834,6 +844,7 @@ class ThreadItem {
$conv = $this->get_conversation();
$this->wall_to_wall = false;
$this->owner_url = '';
+ $this->owner_addr = '';
$this->owner_photo = '';
$this->owner_name = '';
@@ -842,12 +853,14 @@ class ThreadItem {
if($this->is_toplevel() && ($this->get_data_value('author_xchan') != $this->get_data_value('owner_xchan'))) {
$this->owner_url = chanlink_hash($this->data['owner']['xchan_hash']);
+ $this->owner_addr = $this->data['owner']['xchan_addr'];
$this->owner_photo = $this->data['owner']['xchan_photo_s'];
$this->owner_name = $this->data['owner']['xchan_name'];
$this->wall_to_wall = true;
}
elseif($this->is_toplevel() && $this->get_data_value('verb') === 'Announce' && isset($this->data['source'])) {
$this->owner_url = chanlink_hash($this->data['source']['xchan_hash']);
+ $this->owner_addr = $this->data['source']['xchan_addr'];
$this->owner_photo = $this->data['source']['xchan_photo_s'];
$this->owner_name = $this->data['source']['xchan_name'];
$this->wall_to_wall = true;
@@ -862,6 +875,10 @@ class ThreadItem {
return $this->owner_url;
}
+ private function get_owner_addr() {
+ return $this->owner_addr;
+ }
+
private function get_owner_photo() {
return $this->owner_photo;
}
diff --git a/Zotlabs/Lib/ThreadStream.php b/Zotlabs/Lib/ThreadStream.php
index fb3b6dd9b..1d968fa1a 100644
--- a/Zotlabs/Lib/ThreadStream.php
+++ b/Zotlabs/Lib/ThreadStream.php
@@ -24,6 +24,10 @@ class ThreadStream {
private $prepared_item = '';
public $reload = '';
private $cipher = 'AES-128-CCM';
+ public $mid_uuid_map = [];
+ public $comments_total = 0;
+ public $comments_loaded = 0;
+
// $prepared_item is for use by alternate conversation structures such as photos
// wherein we've already prepared a top level item which doesn't look anything like
@@ -211,16 +215,15 @@ class ThreadStream {
* _ The data requested on success
* _ false on failure
*/
- public function get_template_data($conv_responses, $mid_uuid_map) {
+ public function get_template_data() {
$result = array();
foreach($this->threads as $item) {
-
if(($item->get_data_value('id') == $item->get_data_value('parent')) && $this->prepared_item) {
$item_data = $this->prepared_item;
}
else {
- $item_data = $item->get_template_data($conv_responses, $mid_uuid_map);
+ $item_data = $item->get_template_data();
}
if(!$item_data) {
logger('Failed to get item template data ('. $item->get_id() .').', LOGGER_DEBUG, LOG_ERR);
diff --git a/Zotlabs/Lib/Traits/HelpHelperTrait.php b/Zotlabs/Lib/Traits/HelpHelperTrait.php
index 63b0eb22e..69b3cc21b 100644
--- a/Zotlabs/Lib/Traits/HelpHelperTrait.php
+++ b/Zotlabs/Lib/Traits/HelpHelperTrait.php
@@ -89,7 +89,7 @@ trait HelpHelperTrait {
);
return bbcode(
- t("This page is not yet available in {$prefered_language_name}. See [observer.baseurl]/help/developer/developer_guide#Translations for information about how to help.")
+ t("This page is not yet available in {$prefered_language_name}. See [observer.baseurl]/help/developer/developers_guide#Translations for information about how to help.")
);
}
}
diff --git a/Zotlabs/Module/Acl.php b/Zotlabs/Module/Acl.php
index 1de7a3d02..cf9278f5e 100644
--- a/Zotlabs/Module/Acl.php
+++ b/Zotlabs/Module/Acl.php
@@ -29,11 +29,11 @@ class Acl extends \Zotlabs\Web\Controller {
// logger('mod_acl: ' . print_r($_GET,true),LOGGER_DATA);
- $start = (x($_REQUEST,'start') ? $_REQUEST['start'] : 0);
- $count = (x($_REQUEST,'count') ? $_REQUEST['count'] : 500);
- $search = (x($_REQUEST,'search') ? $_REQUEST['search'] : '');
- $type = (x($_REQUEST,'type') ? $_REQUEST['type'] : '');
- $noforums = (x($_REQUEST,'n') ? $_REQUEST['n'] : false);
+ $start = (!empty($_REQUEST['start']) ? $_REQUEST['start'] : 0);
+ $count = (!empty($_REQUEST['count']) ? $_REQUEST['count'] : 500);
+ $search = (!empty($_REQUEST['search']) ? $_REQUEST['search'] : '');
+ $type = (!empty($_REQUEST['type']) ? $_REQUEST['type'] : '');
+ $noforums = (!empty($_REQUEST['n']) ? $_REQUEST['n'] : false);
// $type =
@@ -53,7 +53,7 @@ class Acl extends \Zotlabs\Web\Controller {
// List of channels whose connections to also suggest,
// e.g. currently viewed channel or channels mentioned in a post
- $extra_channels = (x($_REQUEST,'extra_channels') ? $_REQUEST['extra_channels'] : array());
+ $extra_channels = (!empty($_REQUEST['extra_channels']) ? $_REQUEST['extra_channels'] : []);
// The different autocomplete libraries use different names for the search text
// parameter. Internally we'll use $search to represent the search text no matter
@@ -416,7 +416,7 @@ class Acl extends \Zotlabs\Web\Controller {
}
$dirmode = intval(Config::Get('system','directory_mode'));
- $search = ((x($_REQUEST,'search')) ? htmlentities($_REQUEST['search'],ENT_COMPAT,'UTF-8',false) : '');
+ $search = ((!empty($_REQUEST['search'])) ? htmlentities($_REQUEST['search'], ENT_COMPAT, 'UTF-8', false) : '');
if(! $search || mb_strlen($search) < 2)
return array();
@@ -446,7 +446,7 @@ class Acl extends \Zotlabs\Web\Controller {
$token = Config::Get('system','realm_token');
- $count = (x($_REQUEST,'count') ? $_REQUEST['count'] : 100);
+ $count = (!empty($_REQUEST['count']) ? $_REQUEST['count'] : 100);
if($url) {
$query = $url . '?f=' . (($token) ? '&t=' . urlencode($token) : '');
$query .= '&name=' . urlencode($search) . "&limit=$count" . (($address) ? '&address=' . urlencode(punify($search)) : '');
diff --git a/Zotlabs/Module/Attach_edit.php b/Zotlabs/Module/Attach_edit.php
index 5880d8f13..4cde1c168 100644
--- a/Zotlabs/Module/Attach_edit.php
+++ b/Zotlabs/Module/Attach_edit.php
@@ -133,6 +133,11 @@ class Attach_edit extends Controller {
}
$x = attach_move($channel_id, $resource, $newfolder, (($single) ? $newfilename : ''));
+ if (!$x['success']) {
+ notice($x['message'] . EOL);
+ goaway($return_path);
+ }
+
$actions_done .= 'move,';
}
diff --git a/Zotlabs/Module/Channel.php b/Zotlabs/Module/Channel.php
index f3855b7e8..a72e70b79 100644
--- a/Zotlabs/Module/Channel.php
+++ b/Zotlabs/Module/Channel.php
@@ -85,7 +85,7 @@ class Channel extends Controller {
$headers = [
'Content-Type' => 'application/x-zot+json',
'Digest' => HTTPSig::generate_digest_header($data),
- '(request-target)' => strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI']
+ 'Date' => datetime_convert('UTC','UTC', 'now', 'D, d M Y H:i:s \\G\\M\\T')
];
$h = HTTPSig::create_sig($headers, $channel['channel_prvkey'], channel_url($channel));
@@ -266,7 +266,7 @@ class Channel extends Controller {
'default_location' => (($is_owner) ? App::$profile['channel_location'] : ''),
'nickname' => App::$profile['channel_address'],
'lockstate' => (((strlen(App::$profile['channel_allow_cid'])) || (strlen(App::$profile['channel_allow_gid'])) || (strlen(App::$profile['channel_deny_cid'])) || (strlen(App::$profile['channel_deny_gid']))) ? 'lock' : 'unlock'),
- 'acl' => (($is_owner) ? populate_acl($channel_acl, true, PermissionDescription::fromGlobalPermission('view_stream'), get_post_aclDialogDescription(), 'acl_dialog_post') : ''),
+ 'acl' => (($is_owner) ? populate_acl($channel_acl, true, PermissionDescription::fromGlobalPermission('view_stream'), get_post_aclDialogDescription(), 'member/permissions') : ''),
'permissions' => $channel_acl,
'showacl' => (($is_owner) ? 'yes' : ''),
'bang' => '',
@@ -298,12 +298,15 @@ class Channel extends Controller {
$item_normal = item_normal();
$item_normal_update = item_normal_update();
- $sql_extra = item_permissions_sql(App::$profile['profile_uid']);
+ $sql_extra = '';
+ $permission_sql = item_permissions_sql(App::$profile['profile_uid']);
- if (feature_enabled(App::$profile['profile_uid'], 'channel_list_mode') && (!$mid))
+ $page_mode = 'client';
+
+ $blog_mode = feature_enabled(App::$profile['profile_uid'], 'channel_list_mode') && !$mid;
+ if ($blog_mode) {
$page_mode = 'list';
- else
- $page_mode = 'client';
+ }
$abook_uids = " and abook.abook_channel = " . intval(App::$profile['profile_uid']) . " ";
@@ -334,8 +337,8 @@ class Channel extends Controller {
if (($update) && (!$load)) {
if ($mid) {
- $r = q("SELECT parent AS item_id, uuid from item where $identifier = '%s' and uid = %d $item_normal_update
- AND item_wall = 1 $simple_update $sql_extra limit 1",
+ $r = q("SELECT *, parent AS item_id from item where $identifier = '%s' and uid = %d $item_normal_update
+ AND item_wall = 1 $simple_update $permission_sql $sql_extra limit 1",
dbesc($mid),
intval(App::$profile['profile_uid'])
);
@@ -346,6 +349,7 @@ class Channel extends Controller {
WHERE uid = %d $item_normal_update
AND item_wall = 1 $simple_update
AND (abook.abook_blocked = 0 or abook.abook_flags is null)
+ $permission_sql
$sql_extra
ORDER BY created DESC",
intval(App::$profile['profile_uid'])
@@ -382,8 +386,8 @@ class Channel extends Controller {
if ($noscript_content || $load) {
if ($mid) {
- $r = q("SELECT parent AS item_id, uuid from item where $identifier = '%s' and uid = %d $item_normal
- AND item_wall = 1 $sql_extra limit 1",
+ $r = q("SELECT *, parent AS item_id from item where $identifier = '%s' and uid = %d $item_normal
+ AND item_wall = 1 $permission_sql $sql_extra limit 1",
dbesc($mid),
intval(App::$profile['profile_uid'])
);
@@ -392,13 +396,18 @@ class Channel extends Controller {
}
}
else {
- $r = q("SELECT DISTINCT item.parent AS item_id, $ordering FROM item
- left join abook on ( item.author_xchan = abook.abook_xchan $abook_uids )
- WHERE true and item.uid = %d $item_normal
+ $r = q("SELECT parent AS item_id, $ordering FROM item
+ LEFT JOIN abook ON (item.author_xchan = abook.abook_xchan $abook_uids)
+ WHERE item.uid = %d
+ AND item.id = item.parent
AND (abook.abook_blocked = 0 or abook.abook_flags is null)
- AND item.item_wall = 1 AND item.item_thread_top = 1
- $sql_extra $sql_extra2
- ORDER BY $ordering DESC, item_id $pager_sql ",
+ AND item.item_wall = 1
+ $item_normal
+ $permission_sql
+ $sql_extra
+ $sql_extra2
+ ORDER BY $ordering DESC, item_id
+ $pager_sql",
intval(App::$profile['profile_uid'])
);
}
@@ -408,19 +417,15 @@ class Channel extends Controller {
}
}
if ($r) {
- $parents_str = ids_to_querystr($r, 'item_id');
-
- $r = q("SELECT item.*, item.id AS item_id
- FROM item
- WHERE item.uid = %d $item_normal
- AND item.parent IN ( %s )
- $sql_extra ",
- intval(App::$profile['profile_uid']),
- dbesc($parents_str)
- );
+ $thr_parents = null;
+ if ($mid) {
+ $thr_parents = get_recursive_thr_parents($r[0]);
+ }
- xchan_query($r);
- $items = fetch_post_tags($r, true);
+ $items = items_by_parent_ids($r, $thr_parents, $permission_sql, $blog_mode);
+
+ xchan_query($items);
+ $items = fetch_post_tags($items, true);
$items = conv_sort($items, $ordering);
if ($load && $mid && (!count($items))) {
@@ -434,11 +439,8 @@ class Channel extends Controller {
$items = [];
}
-
-
$mode = (($search) ? 'search' : 'channel');
-
if ((!$update) && (!$load)) {
// This is ugly, but we can't pass the profile_uid through the session to the ajax updater,
diff --git a/Zotlabs/Module/Cloud.php b/Zotlabs/Module/Cloud.php
index 510f91c1e..f9abd767a 100644
--- a/Zotlabs/Module/Cloud.php
+++ b/Zotlabs/Module/Cloud.php
@@ -7,6 +7,7 @@ namespace Zotlabs\Module;
* Module for accessing the DAV storage area.
*/
+use App;
use Sabre\DAV as SDAV;
use Zotlabs\Web\Controller;
use Zotlabs\Storage\BasicAuth;
@@ -32,6 +33,15 @@ class Cloud extends Controller {
*/
function init() {
+ // TODO: why is this required?
+ // if we arrived at this path with any query parameters in the url, build a clean url without
+ // them and redirect.
+
+ $parsed = parse_url(App::$query_string);
+ if (!empty($parsed['query'])) {
+ goaway(z_root() . '/' . $parsed['path']);
+ }
+
if (! is_dir('store'))
os_mkdir('store', STORAGE_DEFAULT_PERMISSIONS, false);
@@ -44,15 +54,13 @@ class Cloud extends Controller {
if ($which)
profile_load( $which, $profile);
-
-
$auth = new BasicAuth();
$ob_hash = get_observer_hash();
if ($ob_hash) {
if (local_channel()) {
- $channel = \App::get_channel();
+ $channel = App::get_channel();
$auth->setCurrentUser($channel['channel_address']);
$auth->channel_account_id = $channel['channel_account_id'];
$auth->channel_id = $channel['channel_id'];
@@ -63,19 +71,12 @@ class Cloud extends Controller {
$auth->observer = $ob_hash;
}
- // if we arrived at this path with any query parameters in the url, build a clean url without
- // them and redirect.
-
if(! array_key_exists('cloud_sort',$_SESSION)) {
$_SESSION['cloud_sort'] = 'name';
}
$_SESSION['cloud_sort'] = ((isset($_REQUEST['sort']) && $_REQUEST['sort']) ? trim(notags($_REQUEST['sort'])) : $_SESSION['cloud_sort']);
- $x = clean_query_string();
- if($x !== \App::$query_string)
- goaway(z_root() . '/' . $x);
-
$rootDirectory = new Directory('/', [], $auth);
// A SabreDAV server-object
@@ -116,16 +117,16 @@ class Cloud extends Controller {
function DAVException($err) {
if($err instanceof \Sabre\DAV\Exception\NotFound) {
- \App::$page['content'] = '<h2>404 Not found</h2>';
+ App::$page['content'] = '<h2>404 Not found</h2>';
}
elseif($err instanceof \Sabre\DAV\Exception\Forbidden) {
- \App::$page['content'] = '<h2>403 Forbidden</h2>';
+ App::$page['content'] = '<h2>403 Forbidden</h2>';
}
elseif($err instanceof \Sabre\DAV\Exception\NotImplemented) {
- goaway(z_root() . '/' . \App::$query_string);
+ goaway(z_root() . '/' . App::$query_string);
}
else {
- \App::$page['content'] = '<h2>Unknown error</h2>';
+ App::$page['content'] = '<h2>Unknown error</h2>';
}
construct_page();
diff --git a/Zotlabs/Module/Display.php b/Zotlabs/Module/Display.php
index 090e0c92e..15aff9a84 100644
--- a/Zotlabs/Module/Display.php
+++ b/Zotlabs/Module/Display.php
@@ -81,7 +81,7 @@ class Display extends Controller {
'default_location' => $channel['channel_location'],
'nickname' => $channel['channel_address'],
'lockstate' => (($channel['channel_allow_cid'] || $channel['channel_allow_gid'] || $channel['channel_deny_cid'] || $channel['channel_deny_gid']) ? 'lock' : 'unlock'),
- 'acl' => populate_acl($channel_acl,true, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_stream'), get_post_aclDialogDescription(), 'acl_dialog_post'),
+ 'acl' => populate_acl($channel_acl,true, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_stream'), get_post_aclDialogDescription(), 'member/permissions'),
'permissions' => $channel_acl,
'bang' => '',
'visitor' => true,
@@ -212,7 +212,7 @@ class Display extends Controller {
$observer_hash = get_observer_hash();
$item_normal = item_normal();
$item_normal_update = item_normal_update();
- $sql_extra = '';
+ $permission_sql = '';
$r = [];
if($noscript_content || $load) {
@@ -231,7 +231,7 @@ class Display extends Controller {
}
if(!$r) {
- $sql_extra = item_permissions_sql(0, $observer_hash);
+ $permission_sql = item_permissions_sql(0, $observer_hash);
$r = q("SELECT item.id AS item_id FROM item
WHERE ((mid = '%s'
@@ -239,7 +239,7 @@ class Display extends Controller {
AND item.deny_gid = '' AND item_private = 0 )
AND uid IN ( " . stream_perms_api_uids(($observer_hash) ? (PERMS_NETWORK|PERMS_PUBLIC) : PERMS_PUBLIC) . " ))
OR uid = %d ))) OR
- (mid = '%s' $sql_extra ))
+ (mid = '%s' $permission_sql ))
$item_normal
limit 1",
dbesc($target_item['parent_mid']),
@@ -269,7 +269,7 @@ class Display extends Controller {
}
if(!$r) {
- $sql_extra = item_permissions_sql(0, $observer_hash);
+ $permission_sql = item_permissions_sql(0, $observer_hash);
$r = q("SELECT item.id as item_id from item
WHERE ((parent_mid = '%s'
@@ -277,7 +277,7 @@ class Display extends Controller {
AND item.deny_gid = '' AND item_private = 0 )
and uid in ( " . stream_perms_api_uids(($observer_hash) ? (PERMS_NETWORK|PERMS_PUBLIC) : PERMS_PUBLIC) . " ))
OR uid = %d ))) OR
- (parent_mid = '%s' $sql_extra ))
+ (parent_mid = '%s' $permission_sql ))
$item_normal
limit 1",
dbesc($target_item['parent_mid']),
@@ -288,17 +288,12 @@ class Display extends Controller {
}
if($r) {
- $parents_str = ids_to_querystr($r,'item_id');
- if($parents_str) {
- $items = q("SELECT item.*, item.id AS item_id
- FROM item
- WHERE parent in ( %s ) $sql_extra $item_normal ",
- dbesc($parents_str)
- );
- xchan_query($items);
- $items = fetch_post_tags($items,true);
- $items = conv_sort($items,'created');
- }
+ $thr_parents = get_recursive_thr_parents($target_item);
+ $items = items_by_parent_ids($r, $thr_parents, $permission_sql);
+
+ xchan_query($items);
+ $items = fetch_post_tags($items,true);
+ $items = conv_sort($items,'created');
}
else {
$items = array();
diff --git a/Zotlabs/Module/File_upload.php b/Zotlabs/Module/File_upload.php
index 8956ce16f..2586265f8 100644
--- a/Zotlabs/Module/File_upload.php
+++ b/Zotlabs/Module/File_upload.php
@@ -11,39 +11,42 @@ require_once('include/photos.php');
class File_upload extends \Zotlabs\Web\Controller {
function post() {
- logger('file upload: ' . print_r($_REQUEST,true));
+ logger('file upload: ' . print_r($_POST,true));
logger('file upload: ' . print_r($_FILES,true));
- $channel = (($_REQUEST['channick']) ? channelx_by_nick($_REQUEST['channick']) : null);
+ $channel = (($_POST['channick']) ? channelx_by_nick($_POST['channick']) : null);
- if(! $channel) {
+ if (!$channel) {
logger('channel not found');
- killme();
+ is_ajax() ? killme() : goaway(z_root() . '/' . $_POST['return_url']);
}
- $_REQUEST['source'] = 'file_upload';
+ $_POST['source'] = 'file_upload';
if($channel['channel_id'] != local_channel()) {
- $_REQUEST['contact_allow'] = expand_acl($channel['channel_allow_cid']);
- $_REQUEST['group_allow'] = expand_acl($channel['channel_allow_gid']);
- $_REQUEST['contact_deny'] = expand_acl($channel['channel_deny_cid']);
- $_REQUEST['group_deny'] = expand_acl($channel['channel_deny_gid']);
+ $_POST['contact_allow'] = expand_acl($channel['channel_allow_cid']);
+ $_POST['group_allow'] = expand_acl($channel['channel_allow_gid']);
+ $_POST['contact_deny'] = expand_acl($channel['channel_deny_cid']);
+ $_POST['group_deny'] = expand_acl($channel['channel_deny_gid']);
}
- $_REQUEST['allow_cid'] = ((isset($_REQUEST['contact_allow'])) ? perms2str($_REQUEST['contact_allow']) : '');
- $_REQUEST['allow_gid'] = ((isset($_REQUEST['group_allow'])) ? perms2str($_REQUEST['group_allow']) : '');
- $_REQUEST['deny_cid'] = ((isset($_REQUEST['contact_deny'])) ? perms2str($_REQUEST['contact_deny']) : '');
- $_REQUEST['deny_gid'] = ((isset($_REQUEST['group_deny'])) ? perms2str($_REQUEST['group_deny']) : '');
-
- if(isset($_REQUEST['filename']) && strlen($_REQUEST['filename'])) {
- $r = attach_mkdir($channel, get_observer_hash(), $_REQUEST);
- if($r['success']) {
- $hash = $r['data']['hash'];
- $sync = attach_export_data($channel,$hash);
- if($sync) {
- Libsync::build_sync_packet($channel['channel_id'],array('file' => array($sync)));
- }
- goaway(z_root() . '/' . $_REQUEST['return_url']);
+ $_POST['allow_cid'] = ((isset($_POST['contact_allow'])) ? perms2str($_POST['contact_allow']) : '');
+ $_POST['allow_gid'] = ((isset($_POST['group_allow'])) ? perms2str($_POST['group_allow']) : '');
+ $_POST['deny_cid'] = ((isset($_POST['contact_deny'])) ? perms2str($_POST['contact_deny']) : '');
+ $_POST['deny_gid'] = ((isset($_POST['group_deny'])) ? perms2str($_POST['group_deny']) : '');
+
+ if(isset($_POST['filename']) && strlen($_POST['filename'])) {
+ $r = attach_mkdir($channel, get_observer_hash(), $_POST);
+
+ if (!$r['success']) {
+ notice($r['message'] . EOL);
+ is_ajax() ? killme() : goaway(z_root() . '/' . $_POST['return_url']);
+ }
+
+ $hash = $r['data']['hash'];
+ $sync = attach_export_data($channel,$hash);
+ if ($sync) {
+ Libsync::build_sync_packet($channel['channel_id'], ['file' => [$sync]]);
}
}
else {
@@ -90,19 +93,19 @@ class File_upload extends \Zotlabs\Web\Controller {
}
}
- $r = attach_store($channel, get_observer_hash(), '', $_REQUEST);
- if($r['success']) {
- $sync = attach_export_data($channel,$r['data']['hash']);
- if($sync)
- Libsync::build_sync_packet($channel['channel_id'],array('file' => array($sync)));
+ $r = attach_store($channel, get_observer_hash(), '', $_POST);
+ if (!$r['success']) {
+ notice($r['message'] . EOL);
+ is_ajax() ? killme() : goaway(z_root() . '/' . $_POST['return_url']);
+ }
+ $sync = attach_export_data($channel,$r['data']['hash']);
+ if ($sync) {
+ Libsync::build_sync_packet($channel['channel_id'], ['file' => [$sync]]);
}
}
- if(is_ajax())
- killme();
-
- goaway(z_root() . '/' . $_REQUEST['return_url']);
+ is_ajax() ? killme() : goaway(z_root() . '/' . $_POST['return_url']);
}
diff --git a/Zotlabs/Module/Help.php b/Zotlabs/Module/Help.php
index fc0ef2708..52e23e4e0 100644
--- a/Zotlabs/Module/Help.php
+++ b/Zotlabs/Module/Help.php
@@ -30,7 +30,7 @@ class Help extends \Zotlabs\Web\Controller {
$this->determine_help_language();
if (empty($_REQUEST['search']) && argc() === 1) {
- goaway("/help/about/about");
+ goaway("/help/about");
killme();
}
}
@@ -85,7 +85,7 @@ class Help extends \Zotlabs\Web\Controller {
}
- if(argc() > 2 && argv(argc()-2) === 'assets') {
+ if(argc() > 2 && argv(argc()-2) === 'pic') {
$path = '';
for($x = 1; $x < argc(); $x ++) {
if(strlen($path))
diff --git a/Zotlabs/Module/Home.php b/Zotlabs/Module/Home.php
index 3ac445f9f..0dec432d0 100644
--- a/Zotlabs/Module/Home.php
+++ b/Zotlabs/Module/Home.php
@@ -24,9 +24,13 @@ class Home extends Controller {
$key = Config::Get('system', 'prvkey');
$ret = json_encode(Libzot::site_info());
- $headers = ['Content-Type' => 'application/x-zot+json', 'Digest' => HTTPSig::generate_digest_header($ret)];
- $headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI'];
- $h = HTTPSig::create_sig($headers, $key, z_root());
+ $headers = [
+ 'Content-Type' => 'application/x-zot+json',
+ 'Digest' => HTTPSig::generate_digest_header($ret),
+ 'Date' => datetime_convert('UTC','UTC', 'now', 'D, d M Y H:i:s \\G\\M\\T')
+ ];
+
+ $h = HTTPSig::create_sig($headers, $key, z_root());
HTTPSig::set_headers($h);
echo $ret;
@@ -68,9 +72,9 @@ class Home extends Controller {
$o = '';
- if (x($_SESSION, 'theme'))
+ if (isset($_SESSION['theme']))
unset($_SESSION['theme']);
- if (x($_SESSION, 'mobile_theme'))
+ if (isset($_SESSION['mobile_theme']))
unset($_SESSION['mobile_theme']);
$splash = ((argc() > 1 && argv(1) === 'splash') ? true : false);
diff --git a/Zotlabs/Module/Hq.php b/Zotlabs/Module/Hq.php
index 51caa179c..31faf9dfc 100644
--- a/Zotlabs/Module/Hq.php
+++ b/Zotlabs/Module/Hq.php
@@ -3,6 +3,7 @@ namespace Zotlabs\Module;
use App;
use Zotlabs\Widget\Messages;
+use Zotlabs\Lib\Config;
class Hq extends \Zotlabs\Web\Controller {
@@ -50,12 +51,13 @@ class Hq extends \Zotlabs\Web\Controller {
// select the target item with a bias to our own item
$sql_order = ((local_channel() > $sys['channel_id']) ? 'DESC' : 'ASC');
- $r = q("select id, uid, mid, parent_mid, thr_parent, verb, item_type, item_deleted, item_blocked from item where uid in (%d, %d) and $identifier = '%s' order by uid $sql_order limit 2",
+ $r = q("select id, uid, mid, parent, parent_mid, thr_parent, verb, item_type, item_deleted, item_blocked from item where uid in (%d, %d) and $identifier = '%s' order by uid $sql_order limit 2",
intval(local_channel()),
intval($sys['channel_id']),
dbesc($item_hash)
);
+
if($r) {
$target_item = $r[0];
if (intval($target_item['uid']) === intval($sys['channel_id'])) {
@@ -86,7 +88,7 @@ class Hq extends \Zotlabs\Web\Controller {
'default_location' => $channel['channel_location'],
'nickname' => $channel['channel_address'],
'lockstate' => (($channel['channel_allow_cid'] || $channel['channel_allow_gid'] || $channel['channel_deny_cid'] || $channel['channel_deny_gid']) ? 'lock' : 'unlock'),
- 'acl' => populate_acl($channel_acl,true, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_stream'), get_post_aclDialogDescription(), 'acl_dialog_post'),
+ 'acl' => populate_acl($channel_acl,true, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_stream'), get_post_aclDialogDescription(), 'member/permissions'),
'permissions' => $channel_acl,
'bang' => '',
'visitor' => true,
@@ -129,7 +131,7 @@ class Hq extends \Zotlabs\Web\Controller {
'$nouveau' => '0',
'$wall' => '0',
'$page' => '1',
- '$list' => ((x($_REQUEST,'list')) ? intval($_REQUEST['list']) : 0),
+ '$list' => ((!empty($_REQUEST['list'])) ? intval($_REQUEST['list']) : 0),
'$search' => '',
'$xchan' => '',
'$order' => '',
@@ -145,7 +147,6 @@ class Hq extends \Zotlabs\Web\Controller {
}
if($load && $target_item) {
-
if (!$sys_item) {
$r = q("SELECT item.id AS item_id FROM item
WHERE uid = %d
@@ -199,11 +200,8 @@ class Hq extends \Zotlabs\Web\Controller {
}
if($r) {
- $items = q("SELECT item.*, item.id AS item_id
- FROM item
- WHERE parent = '%s' $item_normal $sql_extra",
- dbesc($r[0]['item_id'])
- );
+ $thr_parents = get_recursive_thr_parents($target_item);
+ $items = items_by_parent_ids($r, $thr_parents);
xchan_query($items,true,(($sys_item) ? local_channel() : 0));
$items = fetch_post_tags($items,true);
diff --git a/Zotlabs/Module/Id.php b/Zotlabs/Module/Id.php
index e08568d00..004cad6e7 100644
--- a/Zotlabs/Module/Id.php
+++ b/Zotlabs/Module/Id.php
@@ -6,8 +6,8 @@ namespace Zotlabs\Module;
*
* Controller for responding to x-zot: protocol requests
* x-zot:_jkfRG85nJ-714zn-LW_VbTFW8jSjGAhAydOcJzHxqHkvEHWG2E0RbA_pbch-h4R63RG1YJZifaNzgccoLa3MQ/453c1678-1a79-4af7-ab65-6b012f6cab77
- *
- */
+ *
+ */
use Zotlabs\Lib\Activity;
use Zotlabs\Lib\ActivityStreams;
@@ -104,7 +104,7 @@ class Id extends Controller {
$headers['Content-Type'] = 'application/x-zot+json' ;
$ret = json_encode($x, JSON_UNESCAPED_SLASHES);
$headers['Digest'] = HTTPSig::generate_digest_header($ret);
- $headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI'];
+ $headers['Date'] = datetime_convert('UTC','UTC', 'now', 'D, d M Y H:i:s \\G\\M\\T');
$h = HTTPSig::create_sig($headers,$chan['channel_prvkey'],channel_url($chan));
HTTPSig::set_headers($h);
echo $ret;
diff --git a/Zotlabs/Module/Invite.php b/Zotlabs/Module/Invite.php
index 3e1e98f89..aff0e9340 100644
--- a/Zotlabs/Module/Invite.php
+++ b/Zotlabs/Module/Invite.php
@@ -310,9 +310,9 @@ class Invite extends Controller {
function get() {
- // zai1
+ $channel_id = local_channel();
- if(! local_channel()) {
+ if ($channel_id === false || $channel_id < 1) {
notice( 'ZAI0101E,' . t('Permission denied.') . EOL);
return;
}
@@ -330,15 +330,15 @@ class Invite extends Controller {
return $o;
}
- // invitation_by_user may still not configured, the default 'na' will tell this
- // if configured, 0 disables invitations by users, other numbers are how many invites a user may propagate
- $invuser = Config::Get('system','invitation_by_user', 'na');
+ $ihave = $this->count_invites_by_user($channel_id);
- // if the mortal user drives the invitation
- If (! is_site_admin()) {
-
- // when not configured, 4 is the default
- $invuser = ($invuser === 'na') ? 4 : $invuser;
+ if (is_site_admin()) {
+ // Admins have unlimited invites
+ $invuser = '∞';
+ } else {
+ // invitation_by_user may still not configured, the default 'na' will tell this
+ // if configured, 0 disables invitations by users, other numbers are how many invites a user may propagate
+ $invuser = Config::Get('system','invitation_by_user', 4);
// a config value 0 disables invitation by users
if (!$invuser) {
@@ -350,12 +350,6 @@ class Invite extends Controller {
notice( 'ZAI0105W,' . t('You have no more invitations available') . EOL);
return '';
}
-
- } else {
- // general deity admin invite limit infinite (theoretical)
- if ($invuser === 'na') Config::Set('system','invitation_by_user', 4);
- // for display only
- $invuser = '∞';
}
// xchan record of the page observer
@@ -394,17 +388,6 @@ class Invite extends Controller {
}
}
- if ($wehave > $invmaxau) {
- if (! is_site_admin()) {
- $feedbk .= 'ZAI0200E,' . t('All users invitation limit exceeded.') . $eol;
- }
- }
-
- // let see how many invites currently used by the user
- $r = q("SELECT count(reg_id) AS n FROM register WHERE reg_vital = 1 AND reg_byc = %d",
- intval(local_channel()));
- $ihave = $r ? $r[0]['n'] : 0;
-
$tpl = get_markup_template('invite.tpl');
$inv_rabots = array(
@@ -420,11 +403,11 @@ class Invite extends Controller {
'field' => array(
'name' => 'expire',
'title' => t('duration up from now'),
- 'value' => ($invexpire_n ? $invexpire_n : 2),
+ 'value' => 2,
'min' => '1',
'max' => '99',
'size' => '2',
- 'default' => ($invexpire_u ? $invexpire_u : 'd')
+ 'default' => 'd',
),
'rabot' => $inv_rabots
)
@@ -583,5 +566,18 @@ class Invite extends Controller {
}
return false;
}
+
+ /**
+ * Find how many invites the given channel is currently using.
+ *
+ * @param int $channel_id The id of the channel
+ *
+ * @return int Number of invites this channel is currently using.
+ */
+ private function count_invites_by_user(int $channel): int {
+ $r = q("SELECT count(reg_id) AS n FROM register WHERE reg_vital = 1 AND reg_byc = %d", $channel);
+
+ return $r ? $r[0]['n'] : 0;
+ }
}
diff --git a/Zotlabs/Module/Item.php b/Zotlabs/Module/Item.php
index ea561ee25..83e8d609e 100644
--- a/Zotlabs/Module/Item.php
+++ b/Zotlabs/Module/Item.php
@@ -78,7 +78,7 @@ class Item extends Controller {
// This will change. Figure out who the observer is and whether or not
// they have permission to post here. Else ignore the post.
- if ((!local_channel()) && (!remote_channel()) && (!x($_REQUEST, 'anonname')))
+ if ((!local_channel()) && (!remote_channel()) && (empty($_POST['anonname'])))
return;
$uid = local_channel();
@@ -107,12 +107,13 @@ class Item extends Controller {
* Is this a reply to something?
*/
- $parent = ((x($_REQUEST, 'parent')) ? intval($_REQUEST['parent']) : 0);
- $parent_mid = ((x($_REQUEST, 'parent_mid')) ? trim($_REQUEST['parent_mid']) : '');
- $mode = ((isset($_REQUEST['conv_mode']) && $_REQUEST['conv_mode'] === 'channel') ? 'channel' : 'network');
+ $parent = ((!empty($_POST['parent'])) ? intval($_POST['parent']) : 0);
+ $thr_parent_id = $parent;
+ $parent_mid = ((!empty($_POST['parent_mid'])) ? trim($_POST['parent_mid']) : '');
+ $mode = ((isset($_POST['conv_mode']) && $_POST['conv_mode'] === 'channel') ? 'channel' : 'network');
- $remote_xchan = ((x($_REQUEST, 'remote_xchan')) ? trim($_REQUEST['remote_xchan']) : false);
- $r = q("select * from xchan where xchan_hash = '%s' limit 1",
+ $remote_xchan = ((!empty($_POST['remote_xchan'])) ? trim($_POST['remote_xchan']) : false);
+ $r = q("select * from xchan where xchan_hash = '%s' limit 1",
dbesc($remote_xchan)
);
if ($r)
@@ -120,7 +121,7 @@ class Item extends Controller {
else
$remote_xchan = $remote_observer = false;
- $profile_uid = ((x($_REQUEST, 'profile_uid')) ? intval($_REQUEST['profile_uid']) : 0);
+ $profile_uid = ((!empty($_POST['profile_uid'])) ? intval($_POST['profile_uid']) : 0);
require_once('include/channel.php');
$sys = get_sys_channel();
@@ -130,25 +131,25 @@ class Item extends Controller {
$observer = $sys;
}
- if (x($_REQUEST, 'dropitems')) {
+ if (!empty($_POST['dropitems'])) {
require_once('include/items.php');
- $arr_drop = explode(',', $_REQUEST['dropitems']);
+ $arr_drop = explode(',', $_POST['dropitems']);
drop_items($arr_drop);
$json = ['success' => 1];
echo json_encode($json);
killme();
}
- call_hooks('post_local_start', $_REQUEST);
+ call_hooks('post_local_start', $_POST);
- // logger('postvars ' . print_r($_REQUEST,true), LOGGER_DATA);
+ // logger('postvars ' . print_r($_POST,true), LOGGER_DATA);
- $api_source = ((x($_REQUEST, 'api_source') && $_REQUEST['api_source']) ? true : false);
+ $api_source = ((!empty($_POST['api_source'])) ? true : false);
- $consensus = $_REQUEST['consensus'] ?? 0;
- $nocomment = $_REQUEST['nocomment'] ?? 0;
+ $consensus = $_POST['consensus'] ?? 0;
+ $nocomment = $_POST['nocomment'] ?? 0;
- $is_poll = ((isset($_REQUEST['poll_answers'][0]) && $_REQUEST['poll_answers'][0]) && (isset($_REQUEST['poll_answers'][1]) && $_REQUEST['poll_answers'][1]));
+ $is_poll = ((isset($_POST['poll_answers'][0]) && $_POST['poll_answers'][0]) && (isset($_POST['poll_answers'][1]) && $_POST['poll_answers'][1]));
// 'origin' (if non-zero) indicates that this network is where the message originated,
// for the purpose of relaying comments to other conversation members.
@@ -159,43 +160,43 @@ class Item extends Controller {
// If you are unsure, it is prudent (and important) to leave it unset.
- $origin = (($api_source && array_key_exists('origin', $_REQUEST)) ? intval($_REQUEST['origin']) : 1);
+ $origin = (($api_source && array_key_exists('origin', $_POST)) ? intval($_REQU_POSTEST['origin']) : 1);
// To represent message-ids on other networks - this will create an iconfig record
- $namespace = (($api_source && array_key_exists('namespace', $_REQUEST)) ? strip_tags($_REQUEST['namespace']) : '');
- $remote_id = (($api_source && array_key_exists('remote_id', $_REQUEST)) ? strip_tags($_REQUEST['remote_id']) : '');
+ $namespace = (($api_source && array_key_exists('namespace', $_POST)) ? strip_tags($_POST['namespace']) : '');
+ $remote_id = (($api_source && array_key_exists('remote_id', $_POST)) ? strip_tags($_POST['remote_id']) : '');
$owner_hash = null;
- $message_id = ((x($_REQUEST, 'message_id') && $api_source) ? strip_tags($_REQUEST['message_id']) : null);
- $created = ((x($_REQUEST, 'created')) ? datetime_convert(date_default_timezone_get(), 'UTC', $_REQUEST['created']) : datetime_convert());
- $post_id = ((x($_REQUEST, 'post_id')) ? intval($_REQUEST['post_id']) : 0);
- $app = ((x($_REQUEST, 'source')) ? strip_tags($_REQUEST['source']) : '');
- $return_path = ((x($_REQUEST, 'return')) ? $_REQUEST['return'] : '');
- $preview = ((x($_REQUEST, 'preview')) ? intval($_REQUEST['preview']) : 0);
- $categories = ((x($_REQUEST, 'category')) ? escape_tags($_REQUEST['category']) : '');
- $webpage = ((x($_REQUEST, 'webpage')) ? intval($_REQUEST['webpage']) : 0);
- $item_obscured = ((x($_REQUEST, 'obscured')) ? intval($_REQUEST['obscured']) : 0);
- $item_delayed = ((x($_REQUEST, 'delayed')) ? intval($_REQUEST['delayed']) : 0);
- $pagetitle = ((x($_REQUEST, 'pagetitle')) ? escape_tags($_REQUEST['pagetitle']) : '');
- $layout_mid = ((x($_REQUEST, 'layout_mid')) ? escape_tags($_REQUEST['layout_mid']) : '');
- $plink = ((x($_REQUEST, 'permalink')) ? escape_tags($_REQUEST['permalink']) : '');
- $obj_type = ((x($_REQUEST, 'obj_type')) ? escape_tags($_REQUEST['obj_type']) : 'Note');
+ $message_id = ((!empty($_POST['message_id']) && $api_source) ? strip_tags($_POST['message_id']) : null);
+ $created = ((!empty($_POST['created'])) ? datetime_convert(date_default_timezone_get(), 'UTC', $_POST['created']) : datetime_convert());
+ $post_id = ((!empty($_POST['post_id'])) ? intval($_POST['post_id']) : 0);
+ $app = ((!empty($_POST['source'])) ? strip_tags($_POST['source']) : '');
+ $return_path = ((!empty($_POST['return'])) ? $_POST['return'] : '');
+ $preview = ((!empty($_POST['preview'])) ? intval($_POST['preview']) : 0);
+ $categories = ((!empty($_POST['category'])) ? escape_tags($_POST['category']) : '');
+ $item_type = ((!empty($_POST['webpage'])) ? intval($_POST['webpage']) : ITEM_TYPE_POST);
+ $item_obscured = ((!empty($_POST['obscured'])) ? intval($_POST['obscured']) : 0);
+ $item_delayed = ((!empty($_POST['delayed'])) ? intval($_POST['delayed']) : 0);
+ $pagetitle = ((!empty($_POST['pagetitle'])) ? escape_tags($_POST['pagetitle']) : '');
+ $layout_mid = ((!empty($_POST['layout_mid'])) ? escape_tags($_POST['layout_mid']) : '');
+ $plink = ((!empty($_POST['permalink'])) ? escape_tags($_POST['permalink']) : null);
+ $obj_type = ((!empty($_POST['obj_type'])) ? escape_tags($_POST['obj_type']) : 'Note');
// allow API to bulk load a bunch of imported items with sending out a bunch of posts.
- $nopush = ((x($_REQUEST, 'nopush')) ? intval($_REQUEST['nopush']) : 0);
+ $nopush = ((!empty($_POST['nopush'])) ? intval($_POST['nopush']) : 0);
/*
* Check service class limits
*/
- if ($uid && !(x($_REQUEST, 'parent')) && !(x($_REQUEST, 'post_id'))) {
- $ret = $this->item_check_service_class($uid, (($_REQUEST['webpage'] == ITEM_TYPE_WEBPAGE) ? true : false));
+ if ($uid && empty($_POST['parent']) && empty($_POST['post_id'])) {
+ $ret = $this->item_check_service_class($uid, (($_POST['webpage'] == ITEM_TYPE_WEBPAGE) ? true : false));
if (!$ret['success']) {
notice(t($ret['message']) . EOL);
if ($api_source)
return (['success' => false, 'message' => 'service class exception']);
- if (x($_REQUEST, 'return'))
+ if (!empty($_POST['return']))
goaway(z_root() . "/" . $return_path);
killme();
}
@@ -216,8 +217,8 @@ class Item extends Controller {
if ($parent || $parent_mid) {
- if (!x($_REQUEST, 'type'))
- $_REQUEST['type'] = 'net-comment';
+ if (empty($_POST['type']))
+ $_POST['type'] = 'net-comment';
if ($parent) {
$r = q("SELECT * FROM item WHERE id = %d LIMIT 1",
@@ -253,7 +254,7 @@ class Item extends Controller {
notice(t('Unable to locate original post.') . EOL);
if ($api_source)
return (['success' => false, 'message' => 'invalid post id']);
- if (x($_REQUEST, 'return'))
+ if (!empty($_POST['return']))
goaway(z_root() . "/" . $return_path);
killme();
}
@@ -276,7 +277,7 @@ class Item extends Controller {
if (!$observer) {
$observer = App::get_observer();
if (!$observer) {
- $observer = anon_identity_init($_REQUEST);
+ $observer = anon_identity_init($_POST);
if ($observer) {
$moderated = true;
$remote_xchan = $remote_observer = $observer;
@@ -288,7 +289,7 @@ class Item extends Controller {
notice(t('Permission denied.') . EOL);
if ($api_source)
return (['success' => false, 'message' => 'permission denied']);
- if (x($_REQUEST, 'return'))
+ if (!empty($_POST['return']))
goaway(z_root() . "/" . $return_path);
killme();
}
@@ -307,17 +308,17 @@ class Item extends Controller {
notice(t('Permission denied.') . EOL);
if ($api_source)
return (['success' => false, 'message' => 'permission denied']);
- if (x($_REQUEST, 'return'))
+ if (!empty($_POST['return']))
goaway(z_root() . "/" . $return_path);
killme();
}
}
else {
- if (!perm_is_allowed($profile_uid, $observer['xchan_hash'], ($webpage) ? 'write_pages' : 'post_wall')) {
+ if (!perm_is_allowed($profile_uid, $observer['xchan_hash'], (intval($item_type) === ITEM_TYPE_POST) ? 'post_wall' : 'write_pages')) {
notice(t('Permission denied.') . EOL);
if ($api_source)
return (['success' => false, 'message' => 'permission denied']);
- if (x($_REQUEST, 'return'))
+ if (!empty($_POST['return']))
goaway(z_root() . "/" . $return_path);
killme();
}
@@ -373,7 +374,7 @@ class Item extends Controller {
logger("mod_item: no channel.");
if ($api_source)
return (['success' => false, 'message' => 'no channel']);
- if (x($_REQUEST, 'return'))
+ if (!empty($_POST['return']))
goaway(z_root() . "/" . $return_path);
killme();
}
@@ -391,7 +392,7 @@ class Item extends Controller {
logger("mod_item: no owner.");
if ($api_source)
return (['success' => false, 'message' => 'no owner']);
- if (x($_REQUEST, 'return'))
+ if (!empty($_POST['return']))
goaway(z_root() . "/" . $return_path);
killme();
}
@@ -425,17 +426,21 @@ class Item extends Controller {
$view_policy = \Zotlabs\Access\PermissionLimits::Get($channel['channel_id'], 'view_stream');
$comment_policy = \Zotlabs\Access\PermissionLimits::Get($channel['channel_id'], 'post_comments');
- $public_policy = ((x($_REQUEST, 'public_policy')) ? escape_tags($_REQUEST['public_policy']) : map_scope($view_policy, true));
- if ($webpage)
- $public_policy = '';
- if ($public_policy)
+ $public_policy = '';
+
+ if (intval($item_type) === ITEM_TYPE_POST) {
+ $public_policy = ((!empty($_POST['public_policy'])) ? escape_tags($_POST['public_policy']) : map_scope($view_policy, true));
+ }
+
+ if ($public_policy) {
$private = 1;
+ }
if ($orig_post) {
$private = 0;
- // webpages are allowed to change ACLs after the fact. Normal conversation items aren't.
- if ($webpage) {
- $acl->set_from_array($_REQUEST);
+ // Normal conversation items are not allowed to change ACL.
+ if (intval($item_type) !== ITEM_TYPE_POST) {
+ $acl->set_from_array($_POST);
}
else {
$acl->set($orig_post);
@@ -451,9 +456,9 @@ class Item extends Controller {
$coord = $orig_post['coord'];
$verb = $orig_post['verb'];
$app = $orig_post['app'];
- $title = escape_tags(trim($_REQUEST['title']));
- $summary = escape_tags(trim($_REQUEST['summary']));
- $body = trim($_REQUEST['body']);
+ $title = escape_tags(trim($_POST['title']));
+ $summary = escape_tags(trim($_POST['summary']));
+ $body = trim($_POST['body']);
$item_flags = $orig_post['item_flags'];
$item_origin = $orig_post['item_origin'];
$item_unseen = $orig_post['item_unseen'];
@@ -491,11 +496,11 @@ class Item extends Controller {
}
else {
if (!$walltowall) {
- if ((array_key_exists('contact_allow', $_REQUEST))
- || (array_key_exists('group_allow', $_REQUEST))
- || (array_key_exists('contact_deny', $_REQUEST))
- || (array_key_exists('group_deny', $_REQUEST))) {
- $acl->set_from_array($_REQUEST);
+ if ((array_key_exists('contact_allow', $_POST))
+ || (array_key_exists('group_allow', $_POST))
+ || (array_key_exists('contact_deny', $_POST))
+ || (array_key_exists('group_deny', $_POST))) {
+ $acl->set_from_array($_POST);
}
elseif (!$api_source) {
@@ -510,16 +515,16 @@ class Item extends Controller {
}
- $location = ((isset($_REQUEST['location'])) ? notags(trim($_REQUEST['location'])) : '');
- $coord = ((isset($_REQUEST['coord'])) ? notags(trim($_REQUEST['coord'])) : '');
- $verb = ((isset($_REQUEST['verb'])) ? notags(trim($_REQUEST['verb'])) : '');
- $title = ((isset($_REQUEST['title'])) ? escape_tags(trim($_REQUEST['title'])) : '');
- $summary = ((isset($_REQUEST['summary'])) ? escape_tags(trim($_REQUEST['summary'])) : '');
- $body = ((isset($_REQUEST['body'])) ? trim($_REQUEST['body']) : '');
- $body .= ((isset($_REQUEST['attachment'])) ? trim($_REQUEST['attachment']) : '');
+ $location = ((isset($_POST['location'])) ? notags(trim($_POST['location'])) : '');
+ $coord = ((isset($_POST['coord'])) ? notags(trim($_POST['coord'])) : '');
+ $verb = ((isset($_POST['verb'])) ? notags(trim($_POST['verb'])) : '');
+ $title = ((isset($_POST['title'])) ? escape_tags(trim($_POST['title'])) : '');
+ $summary = ((isset($_POST['summary'])) ? escape_tags(trim($_POST['summary'])) : '');
+ $body = ((isset($_POST['body'])) ? trim($_POST['body']) : '');
+ $body .= ((isset($_POST['attachment'])) ? trim($_POST['attachment']) : '');
$postopts = '';
- $allow_empty = ((array_key_exists('allow_empty', $_REQUEST)) ? intval($_REQUEST['allow_empty']) : 0);
+ $allow_empty = ((array_key_exists('allow_empty', $_POST)) ? intval($_POST['allow_empty']) : 0);
$private = ((isset($private) && $private) ? $private : intval($acl->is_private() || ($public_policy)));
@@ -530,7 +535,7 @@ class Item extends Controller {
$private = intval($parent_item['item_private']);
$public_policy = $parent_item['public_policy'];
$owner_hash = $parent_item['owner_xchan'];
- $webpage = $parent_item['item_type'];
+ $item_type = $parent_item['item_type'];
}
@@ -541,7 +546,7 @@ class Item extends Controller {
info(t('Empty post discarded.') . EOL);
if ($api_source)
return (['success' => false, 'message' => 'no content']);
- if (x($_REQUEST, 'return'))
+ if (!empty($_POST['return']))
goaway(z_root() . "/" . $return_path);
killme();
}
@@ -549,15 +554,15 @@ class Item extends Controller {
if (feature_enabled($profile_uid, 'content_expire')) {
- if (x($_REQUEST, 'expire')) {
- $expires = datetime_convert(date_default_timezone_get(), 'UTC', $_REQUEST['expire']);
+ if (!empty($_POST['expire'])) {
+ $expires = datetime_convert(date_default_timezone_get(), 'UTC', $_POST['expire']);
if ($expires <= datetime_convert())
$expires = NULL_DATE;
}
}
- $mimetype = ((isset($_REQUEST['mimetype'])) ? notags(trim($_REQUEST['mimetype'])) : '');
+ $mimetype = ((isset($_POST['mimetype'])) ? notags(trim($_POST['mimetype'])) : '');
if (!$mimetype)
$mimetype = 'text/bbcode';
@@ -591,7 +596,7 @@ class Item extends Controller {
$is_group = get_pconfig($profile_uid, 'system', 'group_actor');
- if ($is_group && $walltowall && !$walltowall_comment && !$webpage) {
+ if ($is_group && $walltowall && !$walltowall_comment && (intval($item_type) === ITEM_TYPE_POST)) {
$groupww = true;
$str_contact_allow = $owner_xchan['xchan_hash'];
$str_group_allow = '';
@@ -790,7 +795,7 @@ class Item extends Controller {
}
$item_unseen = ((local_channel() != $profile_uid) ? 1 : 0);
- $item_wall = ((isset($_REQUEST['type']) && ($_REQUEST['type'] === 'wall' || $_REQUEST['type'] === 'wall-comment')) ? 1 : 0);
+ $item_wall = ((isset($_POST['type']) && ($_POST['type'] === 'wall' || $_POST['type'] === 'wall-comment')) ? 1 : 0);
$item_origin = (($origin) ? 1 : 0);
$item_consensus = (($consensus) ? 1 : 0);
$item_nocomment = (($nocomment) ? 1 : 0);
@@ -798,15 +803,13 @@ class Item extends Controller {
// determine if this is a wall post
+ if (in_array($item_type, [ITEM_TYPE_POST, ITEM_TYPE_CARD, ITEM_TYPE_ARTICLE])) {
+ $item_wall = 1;
+ }
+
if ($parent) {
$item_wall = $parent_item['item_wall'];
}
- else {
- if (!$webpage) {
- $item_wall = 1;
- }
- }
-
if ($moderated) {
$item_blocked = ITEM_MODERATED;
@@ -847,10 +850,10 @@ class Item extends Controller {
if ($is_poll) {
$poll = [
'question' => $body,
- 'answers' => $_REQUEST['poll_answers'],
- 'multiple_answers' => $_REQUEST['poll_multiple_answers'],
- 'expire_value' => $_REQUEST['poll_expire_value'],
- 'expire_unit' => $_REQUEST['poll_expire_unit']
+ 'answers' => $_POST['poll_answers'],
+ 'multiple_answers' => $_POST['poll_multiple_answers'],
+ 'expire_value' => $_POST['poll_expire_value'],
+ 'expire_unit' => $_POST['poll_expire_unit']
];
$obj = $this->extract_poll_data($poll, ['item_private' => $private, 'allow_cid' => $str_contact_allow, 'allow_gid' => $str_contact_deny]);
}
@@ -929,7 +932,7 @@ class Item extends Controller {
$datarray['item_unseen'] = intval($item_unseen);
$datarray['item_wall'] = intval($item_wall);
$datarray['item_origin'] = intval($item_origin);
- $datarray['item_type'] = $webpage;
+ $datarray['item_type'] = $item_type;
$datarray['item_private'] = intval($private);
$datarray['item_thread_top'] = intval($item_thread_top);
$datarray['item_starred'] = intval($item_starred);
@@ -1008,14 +1011,14 @@ class Item extends Controller {
call_hooks('post_local', $datarray);
- if (x($datarray, 'cancel')) {
+ if (!empty($datarray['cancel'])) {
logger('mod_item: post cancelled by plugin or duplicate suppressed.');
if ($return_path)
goaway(z_root() . "/" . $return_path);
if ($api_source)
return (['success' => false, 'message' => 'operation cancelled']);
$json = ['cancel' => 1];
- $json['reload'] = z_root() . '/' . $_REQUEST['jsreload'];
+ $json['reload'] = z_root() . '/' . $_POST['jsreload'];
echo json_encode($json);
killme();
}
@@ -1024,8 +1027,8 @@ class Item extends Controller {
if (mb_strlen($datarray['title']) > 191)
$datarray['title'] = mb_substr($datarray['title'], 0, 191);
- if ($webpage) {
- IConfig::Set($datarray, 'system', webpage_to_namespace($webpage),
+ if (intval($item_type) !== ITEM_TYPE_POST) {
+ IConfig::Set($datarray, 'system', item_type_to_namespace($item_type),
(($pagetitle) ? $pagetitle : basename($datarray['mid'])), true);
}
elseif ($namespace) {
@@ -1065,7 +1068,7 @@ class Item extends Controller {
if ($api_source)
return ($x);
- if ((x($_REQUEST, 'return')) && strlen($return_path)) {
+ if ((!empty($_POST['return'])) && strlen($return_path)) {
logger('return: ' . $return_path);
if ($return_path === 'hq') {
@@ -1218,11 +1221,12 @@ class Item extends Controller {
$json = [
'success' => 1,
'id' => $post_id,
+ 'thr_parent_id' => $thr_parent_id,
'html' => conversation($item, $mode, true, 'r_preview'),
];
- if (x($_REQUEST, 'jsreload') && strlen($_REQUEST['jsreload']))
- $json['reload'] = z_root() . '/' . $_REQUEST['jsreload'];
+ if (!empty($_POST['jsreload']))
+ $json['reload'] = z_root() . '/' . $_POST['jsreload'];
logger('post_json: ' . print_r($json, true), LOGGER_DEBUG);
diff --git a/Zotlabs/Module/Lang.php b/Zotlabs/Module/Lang.php
index fe185ebea..1eeb29363 100644
--- a/Zotlabs/Module/Lang.php
+++ b/Zotlabs/Module/Lang.php
@@ -65,8 +65,22 @@ class Lang extends Controller {
}
nav_set_selected('Language');
- return lang_selector();
+ return $this->lang_selector();
}
+ private function lang_selector(): string
+ {
+ $lang_options = language_list();
+ array_unshift($lang_options, t('default'));
+
+ $tpl = get_markup_template('lang_selector.tpl');
+
+ return replace_macros($tpl, [
+ '$title' => t('Select an alternate language'),
+ '$langs' => array($lang_options, App::$language),
+
+ ]);
+ }
+
}
diff --git a/Zotlabs/Module/Like.php b/Zotlabs/Module/Like.php
index 2fb3fab83..52c559a17 100644
--- a/Zotlabs/Module/Like.php
+++ b/Zotlabs/Module/Like.php
@@ -22,9 +22,9 @@ class Like extends Controller {
'like' => 'Like',
'dislike' => 'Dislike',
'announce' => ACTIVITY_SHARE,
- 'attendyes' => 'Accept',
- 'attendno' => 'Reject',
- 'attendmaybe' => 'TentativeAccept'
+ 'accept' => 'Accept',
+ 'reject' => 'Reject',
+ 'tentativeaccept' => 'TentativeAccept'
];
// unlike (etc.) reactions are an undo of positive reactions, rather than a negative action.
@@ -52,43 +52,31 @@ class Like extends Controller {
profile_load($parts[0]);
}
- $item_normal = item_normal();
-
if ($page_mode === 'list') {
+ $item_normal = item_normal();
+
$items = q("SELECT item.*, item.id AS item_id FROM item
WHERE uid = %d $item_normal
AND parent = %d",
intval($arr['item']['uid']),
intval($arr['item']['parent'])
);
+
xchan_query($items, true);
$items = fetch_post_tags($items, true);
$items = conv_sort($items, 'commented');
}
else {
- $activities = q("SELECT item.*, item.id AS item_id FROM item
- WHERE uid = %d $item_normal
- AND thr_parent = '%s'
- AND verb IN ('%s', '%s', '%s', '%s', '%s', '%s', 'Accept', 'Reject', 'TentativeAccept')",
- intval($arr['item']['uid']),
- dbesc($arr['item']['mid']),
- dbesc('Like'),
- dbesc('Dislike'),
- dbesc(ACTIVITY_SHARE),
- dbesc(ACTIVITY_ATTEND),
- dbesc(ACTIVITY_ATTENDNO),
- dbesc(ACTIVITY_ATTENDMAYBE)
- );
- xchan_query($activities, true);
- $items = array_merge([$arr['item']], $activities);
- $items = fetch_post_tags($items, true);
+ $item = item_by_item_id($arr['item']['id'], $arr['item']['parent']);
+ xchan_query($item, true);
+ $item = fetch_post_tags($item, true);
}
$ret = [
'success' => 1,
'orig_id' => $arr['orig_item_id'], //this is required for pubstream items where $item_id != $item['id']
'id' => $arr['item']['id'],
- 'html' => conversation($items, $conv_mode, true, $page_mode),
+ 'html' => conversation($item, $conv_mode, true, $page_mode),
];
// mod photos
@@ -486,11 +474,11 @@ class Like extends Controller {
$bodyverb = t('%1$s likes %2$s\'s %3$s');
if ($verb === 'dislike')
$bodyverb = t('%1$s doesn\'t like %2$s\'s %3$s');
- if ($verb === 'attendyes')
+ if ($verb === 'accept')
$bodyverb = t('%1$s is attending %2$s\'s %3$s');
- if ($verb === 'attendno')
+ if ($verb === 'reject')
$bodyverb = t('%1$s is not attending %2$s\'s %3$s');
- if ($verb === 'attendmaybe')
+ if ($verb === 'tentativeaccept')
$bodyverb = t('%1$s may attend %2$s\'s %3$s');
if (!isset($bodyverb))
@@ -573,7 +561,7 @@ class Like extends Controller {
call_hooks('post_local_end', $arr);
- if ($is_rsvp && in_array($verb, ['attendyes', 'attendmaybe'])) {
+ if ($is_rsvp && in_array($verb, ['accept', 'tentativeaccept'])) {
event_addtocal($item_id, local_channel());
}
diff --git a/Zotlabs/Module/Login.php b/Zotlabs/Module/Login.php
index 269990a54..f5a83a91a 100644
--- a/Zotlabs/Module/Login.php
+++ b/Zotlabs/Module/Login.php
@@ -5,10 +5,17 @@ namespace Zotlabs\Module;
class Login extends \Zotlabs\Web\Controller {
function get() {
- if(local_channel())
+ if (local_channel()) {
goaway(z_root());
- if(remote_channel() && $_SESSION['atoken'])
+ }
+
+ if (remote_channel() && $_SESSION['atoken']) {
goaway(z_root());
+ }
+
+ if (!empty($_GET['retry'])) {
+ notice( t('Login failed.') . EOL );
+ }
$o = '<div class="generic-content-wrapper">';
$o .= '<div class="section-title-wrapper">';
diff --git a/Zotlabs/Module/Network.php b/Zotlabs/Module/Network.php
index 5573ed469..18f52591d 100644
--- a/Zotlabs/Module/Network.php
+++ b/Zotlabs/Module/Network.php
@@ -70,17 +70,19 @@ class Network extends \Zotlabs\Web\Controller {
$dm = ((x($_REQUEST,'dm')) ? $_REQUEST['dm'] : 0);
- $order = get_pconfig(local_channel(), 'mod_network', 'order', 0);
+ $order = get_pconfig(local_channel(), 'mod_network', 'order', 'created');
switch($order) {
- case 0:
- $order = 'comment';
+ case 'commented':
+ $ordering = 'commented';
break;
- case 1:
- $order = 'post';
+ case 'created':
+ $ordering = 'created';
break;
- case 2:
+ case 'unthreaded':
$nouveau = true;
break;
+ default:
+ $ordering = 'created';
}
$search = $_GET['search'] ?? '';
@@ -92,7 +94,7 @@ class Network extends \Zotlabs\Web\Controller {
}
if($datequery)
- $order = 'post';
+ $order = 'created';
// filter by collection (e.g. group)
@@ -201,7 +203,7 @@ class Network extends \Zotlabs\Web\Controller {
'default_location' => $channel['channel_location'],
'nickname' => $channel['channel_address'],
'lockstate' => (($private_editing || $channel['channel_allow_cid'] || $channel['channel_allow_gid'] || $channel['channel_deny_cid'] || $channel['channel_deny_gid']) ? 'lock' : 'unlock'),
- 'acl' => populate_acl((($private_editing) ? $def_acl : $channel_acl), true, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_stream'), get_post_aclDialogDescription(), 'acl_dialog_post'),
+ 'acl' => populate_acl((($private_editing) ? $def_acl : $channel_acl), true, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_stream'), get_post_aclDialogDescription(), 'member/permissions'),
'permissions' => (($private_editing) ? $def_acl : $channel_acl),
'bang' => (($private_editing) ? $bang : ''),
'visitor' => true,
@@ -274,8 +276,10 @@ class Network extends \Zotlabs\Web\Controller {
elseif($pf && $unseen && $nouveau) {
$vnotify = get_pconfig(local_channel(), 'system', 'vnotify');
- if(! ($vnotify & VNOTIFY_LIKE))
+ $likes_sql = '';
+ if (!($vnotify & VNOTIFY_LIKE)) {
$likes_sql = " AND verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
+ }
// This is for nouveau view public forum cid queries (if a forum notification is clicked)
$sql_extra = " AND item.parent IN (SELECT DISTINCT parent FROM item WHERE uid = " . intval(local_channel()) . " AND ( author_xchan = '" . dbesc($cid_r[0]['abook_xchan']) . "' OR owner_xchan = '" . dbesc($cid_r[0]['abook_xchan']) . "' ) $item_normal) AND item_unseen = 1 AND verb != 'Announce' $likes_sql ";
@@ -373,10 +377,10 @@ class Network extends \Zotlabs\Web\Controller {
}
if ($dm) {
- $sql_extra .= ' AND item_private = 2 ';
+ $sql_extra .= ' AND item.item_private = 2 ';
}
else {
- $sql_extra .= ' AND item_private IN (0, 1) ';
+ $sql_extra .= ' AND item.item_private IN (0, 1) ';
}
@@ -425,10 +429,12 @@ class Network extends \Zotlabs\Web\Controller {
$abook_uids = ' and abook.abook_channel = ' . local_channel() . ' ';
$uids = ' and item.uid = ' . local_channel() . ' ';
- if(feature_enabled(local_channel(), 'network_list_mode'))
+ $page_mode = 'client';
+
+ $blog_mode = feature_enabled(local_channel(), 'network_list_mode');
+ if ($blog_mode) {
$page_mode = 'list';
- else
- $page_mode = 'client';
+ }
$parents_str = '';
@@ -457,6 +463,7 @@ class Network extends \Zotlabs\Web\Controller {
$net_query
WHERE true $uids $item_normal
and (abook.abook_blocked = 0 or abook.abook_flags is null)
+ AND item.verb NOT IN ('Add', 'Remove')
$sql_extra $sql_options $sql_nets
$net_query2
ORDER BY item.created DESC $pager_sql "
@@ -472,13 +479,6 @@ class Network extends \Zotlabs\Web\Controller {
}
elseif($update) {
- // Normal conversation view
-
- if($order === 'post')
- $ordering = 'created';
- else
- $ordering = 'commented';
-
if($load) {
// Fetch a page full of parent items for this page
$r = dbq("SELECT item.parent AS item_id FROM item
@@ -507,12 +507,7 @@ class Network extends \Zotlabs\Web\Controller {
// Then fetch all the children of the parents that are on this page
if($r) {
- $parents_str = ids_to_querystr($r, 'item_id');
- $items = dbq("SELECT item.*, item.id AS item_id FROM item
- WHERE true $uids $item_normal
- AND item.parent IN ( $parents_str )
- $sql_extra "
- );
+ $items = items_by_parent_ids($r, blog_mode: $blog_mode);
xchan_query($items, true);
$items = fetch_post_tags($items, true);
diff --git a/Zotlabs/Module/Oep.php b/Zotlabs/Module/Oep.php
index 37a46a23e..201e5a06f 100644
--- a/Zotlabs/Module/Oep.php
+++ b/Zotlabs/Module/Oep.php
@@ -23,6 +23,7 @@ class Oep extends \Zotlabs\Web\Controller {
if(! $url)
http_status_exit(404, 'Not found');
+ $arr = [];
$maxwidth = $_REQUEST['maxwidth'] ?? 0;
$maxheight = $_REQUEST['maxheight'] ?? 0;
$format = $_REQUEST['format'] ?? '';
diff --git a/Zotlabs/Module/Photos.php b/Zotlabs/Module/Photos.php
index 215f0b08b..5f6162ba7 100644
--- a/Zotlabs/Module/Photos.php
+++ b/Zotlabs/Module/Photos.php
@@ -561,7 +561,6 @@ class Photos extends \Zotlabs\Web\Controller {
$like = null;
$dislike = null;
-
$owner_uid = \App::$data['channel']['channel_id'];
$owner_aid = \App::$data['channel']['channel_account_id'];
@@ -1105,20 +1104,8 @@ class Photos extends \Zotlabs\Web\Controller {
$alike = array();
$dlike = array();
- $like = '';
- $dislike = '';
-
- $conv_responses = array(
- 'like' => array('title' => t('Likes','title')),'dislike' => array('title' => t('Dislikes','title')),
- 'attendyes' => array('title' => t('Attending','title')), 'attendno' => array('title' => t('Not attending','title')), 'attendmaybe' => array('title' => t('Might attend','title'))
- );
-
if($r) {
- foreach($r as $item) {
- builtin_activity_puller($item, $conv_responses);
- }
-
$like_count = ((x($alike,$link_item['mid'])) ? $alike[$link_item['mid']] : '');
$like_list = ((x($alike,$link_item['mid'])) ? $alike[$link_item['mid'] . '-l'] : '');
@@ -1221,14 +1208,14 @@ class Photos extends \Zotlabs\Web\Controller {
$paginate = paginate();
$responses = [];
- if ($linkitem) {
+ if ($link_item) {
$response_verbs = ['like'];
if(feature_enabled($owner_uid,'dislike')) {
$response_verbs[] = 'dislike';
}
- $responses = get_responses($conv_responses,$response_verbs,'',$link_item);
+ $responses = get_responses($response_verbs, $link_item);
}
$hookdata = [
diff --git a/Zotlabs/Module/Pin.php b/Zotlabs/Module/Pin.php
index de3c75622..14a45c10d 100644
--- a/Zotlabs/Module/Pin.php
+++ b/Zotlabs/Module/Pin.php
@@ -29,8 +29,9 @@ class Pin extends \Zotlabs\Web\Controller {
if(! $observer)
http_status_exit(403, 'Forbidden');
- $r = q("SELECT * FROM item WHERE id = %d AND id = parent AND item_private = 0 LIMIT 1",
- $item_id
+ $r = q("SELECT * FROM item WHERE id = %d AND uid = %d AND id = parent AND item_private = 0 LIMIT 1",
+ intval($item_id),
+ intval(local_channel())
);
if(! $r) {
notice(t('Unable to locate original post.'));
diff --git a/Zotlabs/Module/Pubstream.php b/Zotlabs/Module/Pubstream.php
index 234e73792..879d98216 100644
--- a/Zotlabs/Module/Pubstream.php
+++ b/Zotlabs/Module/Pubstream.php
@@ -84,7 +84,7 @@ class Pubstream extends \Zotlabs\Web\Controller {
'default_location' => $channel['channel_location'],
'nickname' => $channel['channel_address'],
'lockstate' => (($channel['channel_allow_cid'] || $channel['channel_allow_gid'] || $channel['channel_deny_cid'] || $channel['channel_deny_gid']) ? 'lock' : 'unlock'),
- 'acl' => populate_acl($channel_acl,true, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_stream'), get_post_aclDialogDescription(), 'acl_dialog_post'),
+ 'acl' => populate_acl($channel_acl,true, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_stream'), get_post_aclDialogDescription(), 'member/permissions'),
'permissions' => $channel_acl,
'bang' => '',
'visitor' => true,
@@ -183,6 +183,7 @@ class Pubstream extends \Zotlabs\Web\Controller {
$sql_extra .= protect_sprintf(term_query('item', $hashtags, TERM_HASHTAG, TERM_COMMUNITYTAG));
$sql_extra_order = " ORDER BY item.created DESC ";
$thread_top = '';
+
}
$net_query2 = (($net) ? " and xchan_network = '" . protect_sprintf(dbesc($net)) . "' " : '');
@@ -196,7 +197,7 @@ class Pubstream extends \Zotlabs\Web\Controller {
if($update) {
- $ordering = Config::Get('system', 'pubstream_ordering', 'commented');
+ $ordering = Config::Get('system', 'pubstream_ordering', 'created');
if($load) {
if($mid) {
@@ -250,15 +251,7 @@ class Pubstream extends \Zotlabs\Web\Controller {
$parents_str = '';
if($r) {
-
- $parents_str = ids_to_querystr($r,'item_id');
-
- $items = dbq("SELECT item.*, item.id AS item_id FROM item
- WHERE true $uids $item_normal
- AND item.parent IN ( $parents_str )
- $sql_extra $sql_extra_order"
- );
-
+ $items = items_by_parent_ids($r);
// use effective_uid param of xchan_query to help sort out comment permission
// for sys_channel owned items.
diff --git a/Zotlabs/Module/Regate.php b/Zotlabs/Module/Regate.php
index c67f45a88..956c5e2ea 100644
--- a/Zotlabs/Module/Regate.php
+++ b/Zotlabs/Module/Regate.php
@@ -375,7 +375,7 @@ class Regate extends \Zotlabs\Web\Controller {
]);
$reonar = json_decode( $r['reg_stuff'], true);
- $reonar['deny'] = $now . ',' . $ip . ' ' . $did2 . ' ' . $msg;
+ $reonar['deny'] = $now . ',' . $ip . ' ' . $did2;
$flags = ( $r['reg_flags'] &= ( $r['reg_flags'] ^ ACCOUNT_UNVERIFIED) )
| ( $r['reg_flags'] |= REGISTER_DENIED);
$rd = q("UPDATE register SET reg_stuff='%s', reg_vital=0, reg_flags=%d WHERE reg_id = %d ",
@@ -456,7 +456,7 @@ class Regate extends \Zotlabs\Web\Controller {
// $log = ' from § ' . $ip . ' §' . ' (' . dbesc($did2) . ')';
zar_log($msg);
$o = replace_macros(get_markup_template('plain.tpl'), [
- '$title' => $title,
+ '$title' => $msg,
'$now' => $nowfmt,
'$infos' => $msg
]);
diff --git a/Zotlabs/Module/Request.php b/Zotlabs/Module/Request.php
new file mode 100644
index 000000000..439f56282
--- /dev/null
+++ b/Zotlabs/Module/Request.php
@@ -0,0 +1,89 @@
+<?php
+namespace Zotlabs\Module;
+
+use Zotlabs\Web\Controller;
+
+class Request extends Controller
+{
+
+ private function mapVerb(string $verb) : string
+ {
+ $verbs = [
+ 'like' => 'Like',
+ 'dislike' => 'Dislike',
+ 'announce' => 'Announce',
+ 'accept' => 'Accept',
+ 'reject' => 'Reject',
+ 'tentativeaccept' => 'TentativeAccept'
+ ];
+
+ if (array_key_exists($verb, $verbs)) {
+ return $verbs[$verb];
+ }
+
+ return EMPTY_STR;
+ }
+
+
+ private function processSubthreadRequest() : string
+ {
+ $mid = $_GET['mid'];
+ $parent = intval($_GET['parent']);
+
+ $offset = null;
+ if ($_GET['verb'] === 'load') {
+ $offset = intval($_GET['offset']);
+ }
+
+ $module = strip_tags($_GET['module']);
+
+ $items = items_by_thr_parent($mid, $parent, $offset);
+ xchan_query($items);
+
+ $items = fetch_post_tags($items,true);
+
+ if ($module === 'channel') {
+ $parts = explode('@', $items[0]['owner']['xchan_addr']);
+ profile_load($parts[0]);
+ }
+
+ $ret['html'] = conversation($items, $module, true, 'r_preview');
+
+ json_return_and_die($ret);
+ }
+
+ public function get() : string
+ {
+
+ if (in_array($_GET['verb'], ['comment', 'load'])) {
+ return self::processSubthreadRequest();
+ }
+
+ $verb = self::mapVerb($_GET['verb']);
+
+ if (!$verb) {
+ killme();
+ }
+
+ $text = get_response_button_text($_GET['verb']);
+ $mid = strip_tags($_GET['mid']);
+ $parent = intval($_GET['parent']);
+ $observer_hash = get_observer_hash();
+
+ $ret['result'] = item_activity_xchans($mid, $parent, $verb);
+
+ $commentable = $ret['result']['is_commentable'];
+ unset($ret['result']['is_commentable']);
+
+ if ($commentable) {
+ $ret['action'] = (($verb === 'Announce') ? 'jotShare' : 'dolike');
+ $ret['action_label'] = ((find_xchan_in_array($observer_hash, $ret['result'])) ? (($verb === 'Announce') ? t('+ Repeat again') : t('- Remove yours')) : t('+ Add yours'));
+ }
+
+ $ret['title'] = $text['label'];
+
+ json_return_and_die($ret);
+
+ }
+
+}
diff --git a/Zotlabs/Module/Rpost.php b/Zotlabs/Module/Rpost.php
index 45f19d7e7..09513a44e 100644
--- a/Zotlabs/Module/Rpost.php
+++ b/Zotlabs/Module/Rpost.php
@@ -94,7 +94,7 @@ class Rpost extends \Zotlabs\Web\Controller {
'default_location' => $channel['channel_location'],
'nickname' => $channel['channel_address'],
'lockstate' => (($acl->is_private()) ? 'lock' : 'unlock'),
- 'acl' => populate_acl($channel_acl, true, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_stream'), get_post_aclDialogDescription(), 'acl_dialog_post'),
+ 'acl' => populate_acl($channel_acl, true, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_stream'), get_post_aclDialogDescription(), 'member/permissions'),
'permissions' => $channel_acl,
'bang' => '',
'visitor' => true,
diff --git a/Zotlabs/Module/Settings/Display.php b/Zotlabs/Module/Settings/Display.php
index a7fccea47..98c3d7543 100644
--- a/Zotlabs/Module/Settings/Display.php
+++ b/Zotlabs/Module/Settings/Display.php
@@ -24,7 +24,7 @@ class Display {
$theme = 'redbasic';
- $preload_images = ((x($_POST,'preload_images')) ? intval($_POST['preload_images']) : 0);
+ $thread_allow = ((!empty($_POST['thread_allow'])) ? intval($_POST['thread_allow']) : 0);
$user_scalable = ((x($_POST,'user_scalable')) ? intval($_POST['user_scalable']) : 0);
$nosmile = ((x($_POST,'nosmile')) ? intval($_POST['nosmile']) : 0);
$title_tosource = ((x($_POST,'title_tosource')) ? intval($_POST['title_tosource']) : 0);
@@ -40,7 +40,7 @@ class Display {
$itemspage = 30;
- set_pconfig(local_channel(),'system','preload_images',$preload_images);
+ set_pconfig(local_channel(), 'system', 'thread_allow', $thread_allow);
set_pconfig(local_channel(),'system','user_scalable',$user_scalable);
set_pconfig(local_channel(),'system','update_interval', $browser_update);
set_pconfig(local_channel(),'system','itemspage', $itemspage);
@@ -146,8 +146,7 @@ class Display {
$start_menu = get_pconfig(local_channel(), 'system', 'start_menu', 0);
}
- $preload_images = get_pconfig(local_channel(),'system','preload_images');
- $preload_images = (($preload_images===false)? '0': $preload_images); // default if not set: 0
+ $thread_allow = get_pconfig(local_channel(), 'system', 'thread_allow', true);
$user_scalable = get_pconfig(local_channel(),'system','user_scalable');
$user_scalable = (($user_scalable===false)? '0': $user_scalable); // default if not set: 0
@@ -192,7 +191,7 @@ class Display {
'$theme' => (($themes) ? array('theme', t('Display Theme:'), $theme_selected, '', $themes, 'preview') : false),
'$schema' => (($schemas) ? array('schema', t('Select scheme'), $existing_schema, '' , $schemas) : false),
- '$preload_images' => array('preload_images', t("Preload images before rendering the page"), $preload_images, t("The subjective page load time will be longer but the page will be ready when displayed"), $yes_no),
+ '$thread_allow' => ['thread_allow', t('Threaded conversation view'), $thread_allow, t('Display replies below their parent message (default yes)'), $yes_no],
'$user_scalable' => array('user_scalable', t("Enable user zoom on mobile devices"), $user_scalable, '', $yes_no),
'$ajaxint' => array('browser_update', t("Update browser every xx seconds"), $browser_update, t('Minimum of 10 seconds, no maximum')),
'$itemspage' => array('itemspage', t("Maximum number of conversations to load at any time:"), $itemspage, t('Maximum of 30 items')),
diff --git a/Zotlabs/Module/Sse_bs.php b/Zotlabs/Module/Sse_bs.php
index 8847ff242..c457363c0 100644
--- a/Zotlabs/Module/Sse_bs.php
+++ b/Zotlabs/Module/Sse_bs.php
@@ -42,7 +42,7 @@ class Sse_bs extends Controller {
self::$offset = 0;
self::$xchans = '';
- if (isset($_REQUEST['sse_rmids'])) {
+ if (!empty($_REQUEST['sse_rmids'])) {
self::mark_read(explode(',', $_REQUEST['sse_rmids']));
}
@@ -118,7 +118,6 @@ class Sse_bs extends Controller {
}
function mark_read($arr) {
-
$mids = [];
$str = '';
$slice = 0;
@@ -142,24 +141,51 @@ class Sse_bs extends Controller {
}
}
- $_SESSION['sse_mids_all'] = serialise($mids_all);
+ $str = implode(',', $mids);
+
+ $sys = get_sys_channel();
+ $sql_order = ((self::$uid > $sys['channel_id']) ? 'DESC' : 'ASC');
+
+ $r = q("SELECT uid, uuid FROM item
+ WHERE uid in (%d, %d)
+ AND verb IN ('Like', 'Dislike', 'Announce', 'Accept', 'Reject', 'TentativeAccept')
+ AND thr_parent IN (
+ SELECT mid FROM item WHERE uid IN (%d, %d) AND uuid IN (%s) ORDER BY uid $sql_order
+ )
+ GROUP BY uid, uuid
+ ORDER BY uid $sql_order",
+ intval(self::$uid),
+ intval($sys['channel_id']),
+ intval(self::$uid),
+ intval($sys['channel_id']),
+ $str // this is dbesc() in the above foreach loop
+ );
+
+ if ($r) {
+ $activities_str = ids_to_querystr($r, 'uuid', true);
+ $str .= ',' . $activities_str;
+ $activities_arr = explode(',', $activities_str);
+ $mids_all = array_merge($mids_all, $activities_arr);
+ }
+
+ $_SESSION['sse_mids_all'] = serialise(array_unique($mids_all));
if(! self::$uid) {
return;
}
- $str = implode(',', $mids);
-
$x = [ 'channel_id' => self::$uid, 'update' => 'unset' ];
call_hooks('update_unseen',$x);
- if($x['update'] === 'unset' || intval($x['update'])) {
- q("UPDATE item SET item_unseen = 0 WHERE uid = %d AND uuid in (%s) AND item_unseen = 1",
+ if ($x['update'] === 'unset' || intval($x['update'])) {
+ q("UPDATE item SET item_unseen = 0
+ WHERE uid = %d
+ AND uuid in (%s)
+ AND item_unseen = 1",
intval(self::$uid),
$str // this is dbesc() in the above foreach loop
);
}
-
}
function bs_network($notifications) {
@@ -182,32 +208,34 @@ class Sse_bs extends Controller {
$sql_extra = '';
if (!(self::$vnotify & VNOTIFY_LIKE)) {
- $sql_extra = " AND verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
+ $sql_extra = " AND item.verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
}
elseif (!feature_enabled(self::$uid, 'dislike')) {
- $sql_extra = " AND verb NOT IN ('Dislike', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
+ $sql_extra = " AND item.verb NOT IN ('Dislike', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
}
$sql_extra2 = '';
if(self::$xchans)
- $sql_extra2 = " AND CASE WHEN verb = '" . dbesc(ACTIVITY_SHARE) . "' THEN owner_xchan ELSE author_xchan END IN (" . self::$xchans . ") ";
+ $sql_extra2 = " AND CASE WHEN item.verb = '" . dbesc(ACTIVITY_SHARE) . "' THEN item.owner_xchan ELSE item.author_xchan END IN (" . self::$xchans . ") ";
$item_normal = item_normal();
// Filter internal follow activities and strerams add/remove activities
- $item_normal .= " AND verb NOT IN ('Add', 'Remove', 'Follow', 'Ignore', '" . dbesc(ACTIVITY_FOLLOW) . "') ";
+ $item_normal .= " AND item.verb NOT IN ('Add', 'Remove', 'Follow', 'Ignore', '" . dbesc(ACTIVITY_FOLLOW) . "') ";
if ($notifications) {
- $items = q("SELECT * FROM item
- WHERE uid = %d
- AND created <= '%s'
- AND item_unseen = 1 AND item_wall = 0 AND item_private IN (0, 1)
- AND obj_type NOT IN ('Document', 'Video', 'Audio', 'Image')
- AND author_xchan != '%s'
+ $items = q("SELECT item.*, tp.uuid AS thr_parent_uuid FROM item
+ LEFT JOIN item tp ON item.thr_parent = tp.mid AND item.uid = tp.uid
+ WHERE item.uid = %d
+ AND item.created <= '%s'
+ AND item.item_unseen = 1 AND item.item_wall = 0 AND item.item_private IN (0, 1)
+ AND item.obj_type NOT IN ('Document', 'Video', 'Audio', 'Image')
+ AND NOT (item.verb = 'Announce' AND item.item_thread_top = 1) -- only show the announce activity and not the resulting item
+ AND NOT item.author_xchan = '%s'
$item_normal
$sql_extra
$sql_extra2
- ORDER BY created DESC LIMIT $limit OFFSET $offset",
+ ORDER BY item.created DESC LIMIT $limit OFFSET $offset",
intval(self::$uid),
dbescdate($_SESSION['sse_loadtime']),
dbesc(self::$ob_hash)
@@ -265,28 +293,30 @@ class Sse_bs extends Controller {
$sql_extra = '';
if (!(self::$vnotify & VNOTIFY_LIKE)) {
- $sql_extra = " AND verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
+ $sql_extra = " AND item.verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
}
elseif (!feature_enabled(self::$uid, 'dislike')) {
- $sql_extra = " AND verb NOT IN ('Dislike', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
+ $sql_extra = " AND item.verb NOT IN ('Dislike', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
}
$sql_extra2 = '';
if(self::$xchans)
- $sql_extra2 = " AND CASE WHEN verb = '" . ACTIVITY_SHARE . "' THEN owner_xchan ELSE author_xchan END IN (" . self::$xchans . ") ";
+ $sql_extra2 = " AND CASE WHEN item.verb = '" . ACTIVITY_SHARE . "' THEN item.owner_xchan ELSE item.author_xchan END IN (" . self::$xchans . ") ";
$item_normal = item_normal();
// Filter internal follow activities and strerams add/remove activities
- $item_normal .= " AND verb NOT IN ('Add', 'Remove', 'Follow', 'Ignore', '" . dbesc(ACTIVITY_FOLLOW) . "') ";
+ $item_normal .= " AND item.verb NOT IN ('Add', 'Remove', 'Follow', 'Ignore', '" . dbesc(ACTIVITY_FOLLOW) . "') ";
if ($notifications) {
- $items = q("SELECT * FROM item
- WHERE uid = %d
- AND created <= '%s'
- AND item_unseen = 1 AND item_private = 2
- AND obj_type NOT IN ('Document', 'Video', 'Audio', 'Image')
- AND author_xchan != '%s'
+ $items = q("SELECT item.*, tp.uuid AS thr_parent_uuid FROM item
+ LEFT JOIN item tp ON item.thr_parent = tp.mid AND item.uid = tp.uid
+ WHERE item.uid = %d
+ AND item.created <= '%s'
+ AND item.item_unseen = 1 AND item.item_private = 2
+ AND item.obj_type NOT IN ('Document', 'Video', 'Audio', 'Image')
+ AND NOT (item.verb = 'Announce' AND item.item_thread_top = 1) -- only show the announce activity and not the resulting item
+ AND NOT item.author_xchan = '%s'
$item_normal
$sql_extra
$sql_extra2
@@ -347,33 +377,35 @@ class Sse_bs extends Controller {
$sql_extra = '';
if (!(self::$vnotify & VNOTIFY_LIKE)) {
- $sql_extra = " AND verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
+ $sql_extra = " AND item.verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
}
elseif (!feature_enabled(self::$uid, 'dislike')) {
- $sql_extra = " AND verb NOT IN ('Dislike', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
+ $sql_extra = " AND item.verb NOT IN ('Dislike', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
}
$sql_extra2 = '';
if(self::$xchans)
- $sql_extra2 = " AND CASE WHEN verb = '" . ACTIVITY_SHARE . "' THEN owner_xchan ELSE author_xchan END IN (" . self::$xchans . ") ";
+ $sql_extra2 = " AND CASE WHEN item.verb = '" . ACTIVITY_SHARE . "' THEN item.owner_xchan ELSE item.author_xchan END IN (" . self::$xchans . ") ";
$item_normal = item_normal();
// Filter internal follow activities and strerams add/remove activities
- $item_normal .= " AND verb NOT IN ('Add', 'Remove', 'Follow', 'Ignore', '" . dbesc(ACTIVITY_FOLLOW) . "') ";
+ $item_normal .= " AND item.verb NOT IN ('Add', 'Remove', 'Follow', 'Ignore', '" . dbesc(ACTIVITY_FOLLOW) . "') ";
if ($notifications) {
- $items = q("SELECT * FROM item
- WHERE uid = %d
- AND created <= '%s'
- AND item_unseen = 1 AND item_wall = 1 AND item_private IN (0, 1)
- AND obj_type NOT IN ('Document', 'Video', 'Audio', 'Image')
- AND author_xchan != '%s'
+ $items = q("SELECT item.*, tp.uuid AS thr_parent_uuid FROM item
+ LEFT JOIN item tp ON item.thr_parent = tp.mid AND item.uid = tp.uid
+ WHERE item.uid = %d
+ AND item.created <= '%s'
+ AND item.item_unseen = 1 AND item.item_wall = 1 AND item.item_private IN (0, 1)
+ AND item.obj_type NOT IN ('Document', 'Video', 'Audio', 'Image')
+ AND NOT (item.verb = 'Announce' AND item.item_thread_top = 1) -- only show the announce activity and not the resulting item
+ AND NOT item.author_xchan = '%s'
$item_normal
$sql_extra
$sql_extra2
- ORDER BY created DESC LIMIT $limit OFFSET $offset",
+ ORDER BY item.created DESC LIMIT $limit OFFSET $offset",
intval(self::$uid),
dbescdate($_SESSION['sse_loadtime']),
dbesc(self::$ob_hash)
@@ -442,49 +474,51 @@ class Sse_bs extends Controller {
$sys = get_sys_channel();
$sql_extra = '';
if (!(self::$vnotify & VNOTIFY_LIKE)) {
- $sql_extra = " AND verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
+ $sql_extra = " AND item.verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
}
elseif (!feature_enabled(self::$uid, 'dislike')) {
- $sql_extra = " AND verb NOT IN ('Dislike', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
+ $sql_extra = " AND item.verb NOT IN ('Dislike', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
}
$sql_extra2 = '';
if(self::$xchans)
- $sql_extra2 = " AND CASE WHEN verb = '" . ACTIVITY_SHARE . "' THEN owner_xchan ELSE author_xchan END IN (" . self::$xchans . ") ";
+ $sql_extra2 = " AND CASE WHEN item.verb = '" . ACTIVITY_SHARE . "' THEN item.owner_xchan ELSE item.author_xchan END IN (" . self::$xchans . ") ";
$sql_extra3 = '';
- $sse_mids_all = unserialise($_SESSION['sse_mids_all']) ?? [];
+ $sse_mids_all = isset($_SESSION['sse_mids_all']) ? unserialise($_SESSION['sse_mids_all']) : [];
if ($sse_mids_all) {
- $sql_extra3 = " AND uuid NOT IN (" . protect_sprintf(implode(',', $sse_mids_all)) . ") ";
+ $sql_extra3 = " AND item.uuid NOT IN (" . protect_sprintf(implode(',', $sse_mids_all)) . ") ";
}
- $uids = " AND uid IN ( " . $sys['channel_id'] . " ) ";
+ $uids = " AND item.uid IN ( " . $sys['channel_id'] . " ) ";
$site_firehose = Config::Get('system', 'site_firehose', 0);
if($site_firehose) {
- $uids = " AND uid IN ( " . stream_perms_api_uids(PERMS_PUBLIC) . " ) AND item_private = 0 AND item_wall = 1 ";
+ $uids = " AND item.uid IN ( " . stream_perms_api_uids(PERMS_PUBLIC) . " ) AND item.item_private = 0 AND item.item_wall = 1 ";
}
$item_normal = item_normal();
// Filter internal follow activities and strerams add/remove activities
- $item_normal .= " AND verb NOT IN ('Add', 'Remove', 'Follow', 'Ignore', '" . dbesc(ACTIVITY_FOLLOW) . "') ";
+ $item_normal .= " AND item.verb NOT IN ('Add', 'Remove', 'Follow', 'Ignore', '" . dbesc(ACTIVITY_FOLLOW) . "') ";
if ($notifications) {
- $items = q("SELECT * FROM item
+ $items = q("SELECT item.*, tp.uuid AS thr_parent_uuid FROM item
+ LEFT JOIN item tp ON item.thr_parent = tp.mid AND item.uid = tp.uid
WHERE true $uids
- AND created <= '%s'
- AND obj_type NOT IN ('Document', 'Video', 'Audio', 'Image')
- AND author_xchan != '%s'
- AND created > '%s'
+ AND item.created <= '%s'
+ AND item.created > '%s'
+ AND item.obj_type NOT IN ('Document', 'Video', 'Audio', 'Image')
+ AND NOT (item.verb = 'Announce' AND item.item_thread_top = 1) -- only show the announce activity not the resulting item
+ AND NOT item.author_xchan = '%s'
$item_normal
$sql_extra
$sql_extra2
$sql_extra3
- ORDER BY created DESC LIMIT $limit OFFSET $offset",
+ ORDER BY item.created DESC LIMIT $limit OFFSET $offset",
dbescdate($_SESSION['sse_loadtime']),
- dbesc(self::$ob_hash),
- dbescdate($_SESSION['last_login_date'] ?? $_SESSION['static_loadtime'])
+ dbescdate($_SESSION['last_login_date'] ?? $_SESSION['static_loadtime']),
+ dbesc(self::$ob_hash)
);
if($items) {
diff --git a/Zotlabs/Module/Viewsrc.php b/Zotlabs/Module/Viewsrc.php
index 3e49b9db4..cfc184a9d 100644
--- a/Zotlabs/Module/Viewsrc.php
+++ b/Zotlabs/Module/Viewsrc.php
@@ -6,34 +6,34 @@ namespace Zotlabs\Module;
class Viewsrc extends \Zotlabs\Web\Controller {
function get() {
-
+
$o = '';
-
+
$sys = get_sys_channel();
-
+
$item_id = ((argc() > 1) ? intval(argv(1)) : 0);
$json = ((argc() > 2 && argv(2) === 'json') ? true : false);
$dload = ((argc() > 2 && argv(2) === 'download') ? true : false);
-
+
if(! local_channel()) {
notice( t('Permission denied.') . EOL);
}
-
-
+
+
if(! $item_id) {
\App::$error = 404;
notice( t('Item not found.') . EOL);
}
-
+
$item_normal = item_normal_search();
-
+
if(local_channel() && $item_id) {
- $r = q("select id, mid, item_flags, mimetype, item_obscured, body, llink, plink from item where uid in (%d , %d) and id = %d $item_normal limit 1",
+ $r = q("select id, mid, uuid, item_flags, mimetype, item_obscured, body, llink, plink from item where uid in (%d , %d) and id = %d $item_normal limit 1",
intval(local_channel()),
intval($sys['channel_id']),
intval($item_id)
);
-
+
if($r) {
if(intval($r[0]['item_obscured']))
$dload = true;
@@ -50,18 +50,18 @@ class Viewsrc extends \Zotlabs\Web\Controller {
$o = (($json) ? json_encode($content) : $content);
}
}
-
+
if(is_ajax()) {
echo '<div class="p-1">';
- echo '<div>id: ' . $r[0]['id'] . ' | <a href="' . $r[0]['plink'] . '" target="_blank">plink</a> | <a href="' . $r[0]['llink'] . '" target="_blank">llink</a><br>mid: ' . $r[0]['mid'] . '</div>';
+ echo '<div>id: ' . $r[0]['id'] . ' | <a href="' . $r[0]['plink'] . '" target="_blank">plink</a> | <a href="' . $r[0]['llink'] . '" target="_blank">llink</a><br>mid: ' . $r[0]['mid'] . '<br>uuid: ' . $r[0]['uuid'] . '</div>';
echo '<hr>';
echo '<pre class="p-1">' . $o . '</pre>';
echo '</div>';
killme();
- }
-
+ }
+
return $o;
}
-
-
+
+
}
diff --git a/Zotlabs/Module/Wall_attach.php b/Zotlabs/Module/Wall_attach.php
index e354f58f1..84c76f8dd 100644
--- a/Zotlabs/Module/Wall_attach.php
+++ b/Zotlabs/Module/Wall_attach.php
@@ -10,7 +10,7 @@ class Wall_attach extends \Zotlabs\Web\Controller {
function init() {
logger('request_method: ' . $_SERVER['REQUEST_METHOD'],LOGGER_DATA,LOG_INFO);
- logger('wall_attach: ' . print_r($_REQUEST,true),LOGGER_DEBUG,LOG_INFO);
+ logger('wall_attach: ' . print_r($_POST,true),LOGGER_DEBUG,LOG_INFO);
logger('wall_attach files: ' . print_r($_FILES,true),LOGGER_DEBUG,LOG_INFO);
// for testing without actually storing anything
// http_status_exit(200,'OK');
@@ -18,12 +18,11 @@ class Wall_attach extends \Zotlabs\Web\Controller {
function post() {
-
$using_api = false;
$result = [];
- if($_REQUEST['api_source'] && array_key_exists('media',$_FILES)) {
+ if($_POST['api_source'] && array_key_exists('media',$_FILES)) {
$using_api = true;
}
@@ -98,8 +97,8 @@ class Wall_attach extends \Zotlabs\Web\Controller {
$r = attach_store($channel, get_observer_hash(), '', $data);
- if(! $r['success']) {
- notice( $r['message'] . EOL);
+ if (!$r['success']) {
+ notice($r['message'] . EOL);
killme();
}
diff --git a/Zotlabs/Module/Xref.php b/Zotlabs/Module/Xref.php
deleted file mode 100644
index e9d494da4..000000000
--- a/Zotlabs/Module/Xref.php
+++ /dev/null
@@ -1,26 +0,0 @@
-<?php
-namespace Zotlabs\Module;
-
-
-class Xref extends \Zotlabs\Web\Controller {
-
- function init() {
- // Sets a referral URL using an xchan directly
- // Link format: example.com/xref/[xchan]/[TargetURL]
- // Target URL is optional.
- // Cookie lasts 24 hours to survive a browser restart. Contains no personal
- // information at all - just somebody else's xchan.
- $referrer = argv(1);
- $expire=time()+60*60*2;
- $path = 'xref';
- setcookie($path, $referrer, $expire, "/");
- $url = '';
-
- if (argc() > 2)
- $url = argv(2);
-
- goaway (z_root() . '/' . $url);
-
- }
-
-}
diff --git a/Zotlabs/Photo/PhotoGd.php b/Zotlabs/Photo/PhotoGd.php
index a81d9cae2..8d46ae4d5 100644
--- a/Zotlabs/Photo/PhotoGd.php
+++ b/Zotlabs/Photo/PhotoGd.php
@@ -25,6 +25,8 @@ class PhotoGd extends PhotoDriver {
$t['image/gif'] = 'gif';
if(\imagetypes() & IMG_WEBP)
$t['image/webp'] = 'webp';
+ if(\imagetypes() & IMG_AVIF)
+ $t['image/avif'] = 'avif';
return $t;
}
@@ -184,6 +186,19 @@ class PhotoGd extends PhotoDriver {
break;
+ case 'image/avif':
+ $quality = Config::Get('system', 'avif_quality');
+
+ if((! $quality) || ($quality > 100)) {
+ $quality = AVIF_QUALITY;
+ }
+
+ if (function_exists('imageavif')) {
+ \imageavif($this->image, NULL, $quality);
+ }
+
+ break;
+
case 'image/jpeg':
// gd can lack imagejpeg(), but we verify during installation it is available
diff --git a/Zotlabs/Render/SmartyInterface.php b/Zotlabs/Render/SmartyInterface.php
index a319a4881..003262939 100644
--- a/Zotlabs/Render/SmartyInterface.php
+++ b/Zotlabs/Render/SmartyInterface.php
@@ -19,7 +19,7 @@ class SmartyInterface extends Smarty {
// The order is thus very important here
$template_dirs = array('theme' => "view/theme/$thname/tpl/");
- if ( x(App::$theme_info,"extends") ) {
+ if (!empty(App::$theme_info['extends'])) {
$template_dirs = $template_dirs + array('extends' => "view/theme/" . App::$theme_info["extends"] . "/tpl/");
}
$template_dirs = $template_dirs + array('base' => 'view/tpl/');
diff --git a/Zotlabs/Render/Theme.php b/Zotlabs/Render/Theme.php
index a42b65e03..2faf3631a 100644
--- a/Zotlabs/Render/Theme.php
+++ b/Zotlabs/Render/Theme.php
@@ -26,9 +26,9 @@ class Theme {
*/
static public function current() {
- self::$system_theme = ((isset(App::$config['system']['theme']))
+ self::$system_theme = ((!empty(App::$config['system']['theme']))
? App::$config['system']['theme'] : '');
- self::$session_theme = ((isset($_SESSION) && x($_SESSION, 'theme'))
+ self::$session_theme = ((!empty($_SESSION['theme']))
? $_SESSION['theme'] : self::$system_theme);
$page_theme = null;
@@ -87,8 +87,13 @@ class Theme {
// Find any theme at all and use it.
$fallback = array_merge(glob('view/theme/*/css/style.css'), glob('view/theme/*/php/style.php'));
- if(count($fallback))
- return(array(str_replace('view/theme/', '', substr($fallback[0], 0, -14))));
+
+ if (empty($fallback)) {
+ logger('Unable to find a theme');
+ http_status_exit(500, 'internal server error');
+ }
+
+ return(array(str_replace('view/theme/', '', substr($fallback[0], 0, -14))));
}
@@ -111,7 +116,7 @@ class Theme {
$opts = '';
$opts = (($uid) ? '?puid=' . $uid : '');
- $schema_str = ((x(App::$layout,'schema')) ? '&schema=' . App::$layout['schema'] : '');
+ $schema_str = ((!empty(App::$layout['schema'])) ? '&schema=' . App::$layout['schema'] : '');
if(($s) && (! $schema_str))
$schema_str = '&schema=' . $s;
diff --git a/Zotlabs/Storage/Browser.php b/Zotlabs/Storage/Browser.php
index 6a9cd61ef..6ea6031d3 100644
--- a/Zotlabs/Storage/Browser.php
+++ b/Zotlabs/Storage/Browser.php
@@ -5,6 +5,7 @@ namespace Zotlabs\Storage;
use Sabre\DAV;
use App;
use Zotlabs\Lib\Config;
+use Zotlabs\Lib\Text;
/**
* @brief Provides a DAV frontend for the webbrowser.
@@ -260,13 +261,16 @@ class Browser extends DAV\Browser\Plugin {
}
}
+ $display_path_encoded = Text::rawurlencode_parts($data['display_path']);
+ $href_encoded = Text::rawurlencode_parts($href);
+
// put the array for this file together
$ft['attach_id'] = $id;
// $ft['icon'] = $icon;
$ft['photo_icon'] = $photo_icon;
$ft['is_creator'] = $is_creator;
- $ft['rel_path'] = (($data) ? '/cloud/' . $nick .'/' . $data['display_path'] : $href);
- $ft['full_path'] = z_root() . (($data) ? '/cloud/' . $nick .'/' . $data['display_path'] : $href);
+ $ft['rel_path'] = (($data) ? '/cloud/' . $nick .'/' . $display_path_encoded : $href_encoded);
+ $ft['full_path'] = z_root() . (($data) ? '/cloud/' . $nick .'/' . $display_path_encoded : $href_encoded);
$ft['name'] = $name;
$ft['type'] = $type;
$ft['size'] = $size;
diff --git a/Zotlabs/Web/HTTPSig.php b/Zotlabs/Web/HTTPSig.php
index 7c289ff5f..e53bcb3e9 100644
--- a/Zotlabs/Web/HTTPSig.php
+++ b/Zotlabs/Web/HTTPSig.php
@@ -2,6 +2,7 @@
namespace Zotlabs\Web;
+use App;
use DateTime;
use DateTimeZone;
use Zotlabs\Lib\Activity;
@@ -11,6 +12,9 @@ use Zotlabs\Lib\Keyutils;
use Zotlabs\Lib\Webfinger;
use Zotlabs\Lib\Zotfinger;
use Zotlabs\Lib\Libzot;
+use HttpSignature\HttpMessageSigner;
+use HttpSignature\UnProcessableSignatureException;
+
/**
* @brief Implements HTTP Signatures per draft-cavage-http-signatures-10.
@@ -88,7 +92,7 @@ class HTTPSig {
// See draft-cavage-http-signatures-10
- static function verify($data, $key = '', $keytype = '') {
+ public static function verify($data, $key = '', $keytype = '') {
$body = $data;
$headers = null;
@@ -102,11 +106,59 @@ class HTTPSig {
'content_valid' => false
];
-
$headers = self::find_headers($data, $body);
- if (!$headers)
+ if (!$headers) {
return $result;
+ }
+
+ if (App::$request && array_key_exists('signature-input', $headers) && array_key_exists('signature', $headers)) {
+ $found = preg_match('/keyid="(.*?)"/', $headers['signature-input'], $matches);
+ $keyId = ($found) ? $matches[1] : '';
+
+ if (!$keyId) {
+ return $result;
+ }
+
+ $found = preg_match('/alg="(.*?)"/', $headers['signature-input'], $matches);
+ $alg = ($found) ? $matches[1] : null;
+
+ $keyInfo = self::get_key($key, $keytype, $keyId);
+ $publicKey = $keyInfo['public_key'];
+
+ $messageSigner = new HttpMessageSigner();
+
+ $messageSigner->setPublicKey($publicKey);
+ $messageSigner->setAlgorithm($alg);
+ $messageSigner->setKeyId($keyId);
+
+ $messageSigner->setNonce(preg_match('/nonce="(.*?)"/', $headers['signature-input'], $matches) ? $matches[1] : '');
+ $messageSigner->setTag(preg_match('/tag="(.*?)"/', $headers['signature-input'], $matches) ? $matches[1] : '');
+ $messageSigner->setCreated(preg_match('/created=([0-9]+)/', $headers['signature-input'], $matches) ? $matches[1] : '');
+ $messageSigner->setExpires(preg_match('/expires=([0-9]+)/', $headers['signature-input'], $matches) ? $matches[1] : '');
+
+ try {
+ $verified = $messageSigner->verifyRequest(App::$request);
+ if (!$verified) {
+ btlogger('RFC9421: Unable to verify request: ' . print_r($headers, true), LOGGER_DATA);
+ }
+ }
+ catch (\Exception $exception) {
+ btlogger($exception->getMessage(), LOGGER_DATA);
+ $verified = false;
+ }
+
+ logger('verified (RFC9421): ' . (($verified) ? 'true' : 'false'), LOGGER_DEBUG);
+
+ return [
+ 'signer' => $keyId,
+ 'portable_id' => $keyInfo['portable_id'] ?? '',
+ 'header_signed' => true,
+ 'header_valid' => $verified,
+ 'content_signed' => array_key_exists('content-digest', $headers),
+ 'content_valid' => $verified
+ ];
+ }
if (is_array($body)) {
btlogger('body is array:' . print_r($body, true));
diff --git a/Zotlabs/Web/Router.php b/Zotlabs/Web/Router.php
index 122e7ff73..9f4cb4b4a 100644
--- a/Zotlabs/Web/Router.php
+++ b/Zotlabs/Web/Router.php
@@ -4,6 +4,7 @@ namespace Zotlabs\Web;
use App;
use Zotlabs\Extend\Route;
+use Zotlabs\Render\Theme;
use Zotlabs\Lib\Config;
use Exception;
@@ -33,6 +34,7 @@ use Exception;
* so within the module init and/or post functions and then invoke killme() to terminate
* further processing.
*/
+
class Router {
private $modname = '';
@@ -49,7 +51,7 @@ class Router {
$module = App::$module;
$modname = "Zotlabs\\Module\\" . ucfirst($module);
- if(strlen($module)) {
+ if(!empty($module)) {
/*
* We will always have a module name.
@@ -57,11 +59,11 @@ class Router {
*/
$routes = Route::get();
- if($routes) {
+ if ($routes) {
foreach($routes as $route) {
- if(is_array($route) && file_exists($route[0]) && strtolower($route[1]) === $module) {
+ if (is_array($route) && file_exists($route[0]) && strtolower($route[1]) === $module) {
include_once($route[0]);
- if(class_exists($modname)) {
+ if (class_exists($modname)) {
$this->controller = new $modname;
$this->module_loaded = true;
}
@@ -71,14 +73,14 @@ class Router {
// legacy plugins - this can be removed when they have all been converted
- if(! ($this->module_loaded)) {
- if(is_array(App::$plugins) && in_array($module, App::$plugins) && file_exists("addon/{$module}/{$module}.php")) {
+ if (!$this->module_loaded) {
+ if (is_array(App::$plugins) && in_array($module, App::$plugins) && file_exists("addon/{$module}/{$module}.php")) {
include_once("addon/{$module}/{$module}.php");
- if(class_exists($modname)) {
+ if (class_exists($modname)) {
$this->controller = new $modname;
$this->module_loaded = true;
}
- elseif(function_exists($module . '_module')) {
+ elseif (function_exists($module . '_module')) {
$this->module_loaded = true;
}
}
@@ -89,7 +91,7 @@ class Router {
* Otherwise, look for the standard program module
*/
- if(! ($this->module_loaded)) {
+ if (!$this->module_loaded) {
try {
$filename = 'Zotlabs/SiteModule/'. ucfirst($module). '.php';
if(file_exists($filename)) {
@@ -105,15 +107,16 @@ class Router {
$this->module_loaded = true;
}
}
- if(! $this->module_loaded)
+ if (!$this->module_loaded) {
throw new Exception('Module not found');
+ }
}
- catch(Exception $e) {
- if(file_exists("mod/site/{$module}.php")) {
+ catch (Exception $e) {
+ if (file_exists("mod/site/{$module}.php")) {
include_once("mod/site/{$module}.php");
$this->module_loaded = true;
}
- elseif(file_exists("mod/{$module}.php")) {
+ elseif (file_exists("mod/{$module}.php")) {
include_once("mod/{$module}.php");
$this->module_loaded = true;
}
@@ -125,6 +128,7 @@ class Router {
'installed' => $this->module_loaded,
'controller' => $this->controller
];
+
/**
* @hooks module_loaded
* Called when a module has been successfully locate to server a URL request.
@@ -138,7 +142,8 @@ class Router {
* * \e mixed \b controller - The initialized module object
*/
call_hooks('module_loaded', $x);
- if($x['installed']) {
+
+ if ($x['installed']) {
$this->module_loaded = true;
$this->controller = $x['controller'];
}
@@ -147,7 +152,7 @@ class Router {
* The URL provided does not resolve to a valid module.
*/
- if(! ($this->module_loaded)) {
+ if (!$this->module_loaded) {
// undo the setting of a letsencrypt acme-challenge rewrite rule
// which blocks access to our .well-known routes.
@@ -155,8 +160,8 @@ class Router {
// for a custom .htaccess in the .well-known directory; but they should
// make the file read-only so letsencrypt doesn't modify it
- if(strpos($_SERVER['REQUEST_URI'],'/.well-known/') === 0) {
- if(file_exists('.well-known/.htaccess') && Config::Get('system','fix_apache_acme',true)) {
+ if (strpos($_SERVER['REQUEST_URI'],'/.well-known/') === 0) {
+ if (file_exists('.well-known/.htaccess') && Config::Get('system','fix_apache_acme',true)) {
rename('.well-known/.htaccess','.well-known/.htaccess.old');
}
}
@@ -166,16 +171,17 @@ class Router {
'installed' => $this->module_loaded,
'controller' => $this->controller
];
+
call_hooks('page_not_found',$x);
// Stupid browser tried to pre-fetch our Javascript img template.
// Don't log the event or return anything - just quietly exit.
- if((x($_SERVER, 'QUERY_STRING')) && preg_match('/{[0-9]}/', $_SERVER['QUERY_STRING']) !== 0) {
+ if (!empty($_SERVER['QUERY_STRING']) && preg_match('/{[0-9]}/', $_SERVER['QUERY_STRING']) !== 0) {
killme();
}
- if(Config::Get('system','log_404',true)) {
+ if (Config::Get('system','log_404',true)) {
logger("Module {$module} not found.", LOGGER_DEBUG, LOG_WARNING);
logger('index.php: page not found: ' . $_SERVER['REQUEST_URI']
. ' ADDRESS: ' . $_SERVER['REMOTE_ADDR'] . ' QUERY: '
@@ -206,7 +212,7 @@ class Router {
* Call module functions
*/
- if($this->module_loaded) {
+ if ($this->module_loaded) {
App::$page['page_title'] = App::$module;
$placeholder = '';
@@ -218,13 +224,18 @@ class Router {
* to over-ride them.
*/
- $arr = array('init' => true, 'replace' => false);
+ $arr = [
+ 'init' => true,
+ 'replace' => false
+ ];
+
call_hooks(App::$module . '_mod_init', $arr);
- if(! $arr['replace']) {
- if($this->controller && method_exists($this->controller,'init')) {
+
+ if (!$arr['replace']) {
+ if ($this->controller && method_exists($this->controller,'init')) {
$this->controller->init();
}
- elseif(function_exists(App::$module . '_init')) {
+ elseif (function_exists(App::$module . '_init')) {
$func = App::$module . '_init';
$func();
}
@@ -250,52 +261,58 @@ class Router {
* load current theme info
*/
- $current_theme = \Zotlabs\Render\Theme::current();
+ $current_theme = Theme::current();
$theme_info_file = 'view/theme/' . $current_theme[0] . '/php/theme.php';
if (file_exists($theme_info_file)){
require_once($theme_info_file);
}
- if(function_exists(str_replace('-', '_', $current_theme[0]) . '_init')) {
+ if (function_exists(str_replace('-', '_', $current_theme[0]) . '_init')) {
$func = str_replace('-', '_', $current_theme[0]) . '_init';
$func();
}
- elseif (x(App::$theme_info, 'extends') && file_exists('view/theme/' . App::$theme_info['extends'] . '/php/theme.php')) {
+ elseif (!empty(App::$theme_info['extends']) && file_exists('view/theme/' . App::$theme_info['extends'] . '/php/theme.php')) {
require_once('view/theme/' . App::$theme_info['extends'] . '/php/theme.php');
- if(function_exists(str_replace('-', '_', App::$theme_info['extends']) . '_init')) {
+ if (function_exists(str_replace('-', '_', App::$theme_info['extends']) . '_init')) {
$func = str_replace('-', '_', App::$theme_info['extends']) . '_init';
$func();
}
}
- if(($_SERVER['REQUEST_METHOD'] === 'POST') && (! App::$error) && (! x($_POST, 'auth-params'))) {
+ if ($_SERVER['REQUEST_METHOD'] === 'POST' && !App::$error && empty($_POST['auth-params'])) {
call_hooks(App::$module . '_mod_post', $_POST);
- if($this->controller && method_exists($this->controller,'post')) {
+ if ($this->controller && method_exists($this->controller, 'post')) {
$this->controller->post();
}
- elseif(function_exists(App::$module . '_post')) {
+ elseif (function_exists(App::$module . '_post')) {
$func = App::$module . '_post';
$func();
}
}
- if(! App::$error) {
- $arr = array('content' => App::$page['content'], 'replace' => false);
+ if (!App::$error) {
+ $arr = [
+ 'content' => App::$page['content'],
+ 'replace' => false
+ ];
+
call_hooks(App::$module . '_mod_content', $arr);
- if(! $arr['replace']) {
- if($this->controller && method_exists($this->controller,'get')) {
+ if (!$arr['replace']) {
+ if ($this->controller && method_exists($this->controller, 'get')) {
$arr = array('content' => $this->controller->get());
}
- elseif(function_exists(App::$module . '_content')) {
+ elseif (function_exists(App::$module . '_content')) {
$func = App::$module . '_content';
$arr = array('content' => $func());
}
}
+
call_hooks(App::$module . '_mod_aftercontent', $arr);
- App::$page['content'] = ((isset($arr['replace'])) ? $arr['content'] : App::$page['content'] . $arr['content']);
+
+ App::$page['content'] = ((empty($arr['replace'])) ? App::$page['content'] . $arr['content'] : $arr['content']);
}
}
}
diff --git a/Zotlabs/Web/Session.php b/Zotlabs/Web/Session.php
index ec26e6b0d..1762d3832 100644
--- a/Zotlabs/Web/Session.php
+++ b/Zotlabs/Web/Session.php
@@ -132,7 +132,7 @@ class Session {
else
logger('no session handler');
- if (x($_COOKIE, 'jsdisabled')) {
+ if (!empty($_COOKIE['jsdisabled'])) {
setcookie(
'jsdisabled',
$_COOKIE['jsdisabled'],
diff --git a/Zotlabs/Web/WebServer.php b/Zotlabs/Web/WebServer.php
index d59effc88..89ef755d9 100644
--- a/Zotlabs/Web/WebServer.php
+++ b/Zotlabs/Web/WebServer.php
@@ -2,7 +2,9 @@
namespace Zotlabs\Web;
+use App;
use Zotlabs\Lib\Text;
+use GuzzleHttp\Psr7\ServerRequest;
class WebServer {
@@ -17,9 +19,10 @@ class WebServer {
$installed = sys_boot();
+ App::$request = ServerRequest::fromGlobals();
- \App::$language = get_best_language();
- load_translation_table(\App::$language, !$installed);
+ App::$language = get_best_language();
+ load_translation_table(App::$language, !$installed);
/**
@@ -33,8 +36,8 @@ class WebServer {
*
*/
- if(\App::$session) {
- \App::$session->start();
+ if(App::$session) {
+ App::$session->start();
}
else {
session_start();
@@ -53,13 +56,13 @@ class WebServer {
unset($_SESSION['language']);
}
- if ((x($_SESSION, 'language')) && ($_SESSION['language'] !== \App::$language)) {
- \App::$language = $_SESSION['language'];
+ if ((!empty($_SESSION['language'])) && ($_SESSION['language'] !== App::$language)) {
+ App::$language = $_SESSION['language'];
load_translation_table(\App::$language);
}
- if (x($_GET,'zid') && $installed) {
- \App::$query_string = strip_zids(\App::$query_string);
+ if (!empty($_GET['zid']) && $installed) {
+ App::$query_string = strip_zids(App::$query_string);
if(! local_channel()) {
if (!isset($_SESSION['my_address'])) {
$_SESSION['my_address'] = Text::escape_tags($_GET['zid']);
@@ -71,26 +74,28 @@ class WebServer {
}
}
- if (x($_GET,'zat') && $installed) {
- \App::$query_string = strip_zats(\App::$query_string);
+ if (!empty($_GET['zat']) && $installed) {
+ App::$query_string = strip_zats(App::$query_string);
if(! local_channel()) {
zat_init();
}
}
- if (x($_REQUEST,'owt') && $installed) {
+ if (!empty($_REQUEST['owt']) && $installed) {
$token = $_REQUEST['owt'];
- \App::$query_string = strip_query_param(\App::$query_string,'owt');
+ App::$query_string = strip_query_param(App::$query_string,'owt');
owt_init($token);
}
- if((x($_SESSION, 'authenticated')) || (x($_POST, 'auth-params')) || (\App::$module === 'login'))
+ if(!empty($_SESSION['authenticated']) || !empty($_POST['auth-params']) || App::$module === 'login') {
require('include/auth.php');
+ }
if (!$installed) {
/* Allow an exception for the view module so that pcss will be interpreted during installation */
- if(\App::$module != 'view')
- \App::$module = 'setup';
+ if(App::$module != 'view') {
+ App::$module = 'setup';
+ }
}
else {
@@ -111,17 +116,7 @@ class WebServer {
$Router->Dispatch();
- // TODO: this is not used for anything atm and messes up comanche templates by adding some javascript
- //$this->set_homebase();
-
- // now that we've been through the module content, see if the page reported
- // a permission problem and if so, a 403 response would seem to be in order.
-
- if(isset($_SESSION['sysmsg']) && is_array($_SESSION['sysmsg']) && stristr(implode("", $_SESSION['sysmsg']), t('Permission denied'))) {
- header($_SERVER['SERVER_PROTOCOL'] . ' 403 ' . t('Permission denied.'));
- }
-
- call_hooks('page_end', \App::$page['content']);
+ call_hooks('page_end', App::$page['content']);
construct_page();
@@ -133,10 +128,11 @@ class WebServer {
/* initialise content region */
- if(! x(\App::$page, 'content'))
- \App::$page['content'] = '';
+ if(empty(App::$page['content'])) {
+ App::$page['content'] = '';
+ }
- call_hooks('page_content_top', \App::$page['content']);
+ call_hooks('page_content_top', App::$page['content']);
}
@@ -148,44 +144,24 @@ class WebServer {
* to all protocol drivers; thus doing it here avoids duplication.
*/
- if (( \App::$module === 'channel' ) && argc() > 1) {
- \App::$channel_links = [
+ if (App::$module === 'channel' && argc() > 1) {
+ App::$channel_links = [
[
'rel' => 'lrdd',
'type' => 'application/xrd+xml',
- 'url' => z_root() . '/xrd?f=&uri=acct%3A' . argv(1) . '%40' . \App::get_hostname()
+ 'url' => z_root() . '/xrd?f=&uri=acct%3A' . argv(1) . '%40' . App::get_hostname()
],
[
'rel' => 'jrd',
'type' => 'application/jrd+json',
- 'url' => z_root() . '/.well-known/webfinger?f=&resource=acct%3A' . argv(1) . '%40' . \App::get_hostname()
+ 'url' => z_root() . '/.well-known/webfinger?f=&resource=acct%3A' . argv(1) . '%40' . App::get_hostname()
],
];
- $x = [ 'channel_address' => argv(1), 'channel_links' => \App::$channel_links ];
+ $x = [ 'channel_address' => argv(1), 'channel_links' => App::$channel_links ];
call_hooks('channel_links', $x );
- \App::$channel_links = $x['channel_links'];
+ App::$channel_links = $x['channel_links'];
header('Link: ' . \App::get_channel_links());
}
}
-
- private function set_homebase() {
-
- // If you're just visiting, let javascript take you home
-
- if(x($_SESSION, 'visitor_home')) {
- $homebase = $_SESSION['visitor_home'];
- }
- elseif(local_channel()) {
- $homebase = z_root() . '/channel/' . \App::$channel['channel_address'];
- }
-
- if(isset($homebase)) {
- \App::$page['content'] .= '<script>var homebase = "' . $homebase . '";</script>';
- }
-
- }
-
-
-
}
diff --git a/Zotlabs/Widget/Activity_order.php b/Zotlabs/Widget/Activity_order.php
index e8ee11508..8d47370db 100644
--- a/Zotlabs/Widget/Activity_order.php
+++ b/Zotlabs/Widget/Activity_order.php
@@ -16,7 +16,7 @@ class Activity_order {
return '';
if(! feature_enabled(local_channel(),'order_tab')) {
- set_pconfig(local_channel(), 'mod_network', 'order', 0);
+ set_pconfig(local_channel(), 'mod_network', 'order', 'created');
return '';
}
@@ -26,17 +26,17 @@ class Activity_order {
if(x($_GET, 'order')) {
switch($_GET['order']){
- case 'post':
+ case 'created':
$postord_active = 'active';
- set_pconfig(local_channel(), 'mod_network', 'order', 1);
+ set_pconfig(local_channel(), 'mod_network', 'order', 'created');
break;
- case 'comment':
+ case 'commented':
$commentord_active = 'active';
- set_pconfig(local_channel(), 'mod_network', 'order', 0);
+ set_pconfig(local_channel(), 'mod_network', 'order', 'commented');
break;
case 'unthreaded':
$unthreaded_active = 'active';
- set_pconfig(local_channel(), 'mod_network', 'order', 2);
+ set_pconfig(local_channel(), 'mod_network', 'order', 'unthreaded');
break;
default:
$commentord_active = 'active';
@@ -44,19 +44,19 @@ class Activity_order {
}
}
else {
- $order = get_pconfig(local_channel(), 'mod_network', 'order', 0);
+ $order = get_pconfig(local_channel(), 'mod_network', 'order', 'created');
switch($order) {
- case 0:
+ case 'commented':
$commentord_active = 'active';
break;
- case 1:
+ case 'created':
$postord_active = 'active';
break;
- case 2:
+ case 'unthreaded':
$unthreaded_active = 'active';
break;
default:
- $commentord_active = 'active';
+ $postord_active = 'active';
}
}
@@ -90,26 +90,26 @@ class Activity_order {
// tabs
- $tabs = [];
+ $tabs[] = [
+ 'label' => t('Posted Date'),
+ 'icon' => '',
+ 'url'=>z_root() . '/' . $cmd . '?order=created' . $filter,
+ 'sel'=> $postord_active,
+ 'title' => t('Order by last posted date'),
+ ];
$tabs[] = [
'label' => t('Commented Date'),
'icon' => '',
- 'url'=>z_root() . '/' . $cmd . '?f=&order=comment' . $filter,
+ 'url'=>z_root() . '/' . $cmd . '?order=commented' . $filter,
'sel'=> $commentord_active,
'title' => t('Order by last commented date'),
];
- $tabs[] = [
- 'label' => t('Posted Date'),
- 'icon' => '',
- 'url'=>z_root() . '/' . $cmd . '?f=&order=post' . $filter,
- 'sel'=> $postord_active,
- 'title' => t('Order by last posted date'),
- ];
+
$tabs[] = array(
'label' => t('Date Unthreaded'),
'icon' => '',
- 'url' => z_root() . '/' . $cmd . '?f=&order=unthreaded' . $filter,
+ 'url' => z_root() . '/' . $cmd . '?order=unthreaded' . $filter,
'sel' => $unthreaded_active,
'title' => t('Order unthreaded by date'),
);
diff --git a/Zotlabs/Widget/Album.php b/Zotlabs/Widget/Album.php
index f1fa69182..667952360 100644
--- a/Zotlabs/Widget/Album.php
+++ b/Zotlabs/Widget/Album.php
@@ -59,6 +59,9 @@ class Album {
//edit album name
$album_edit = null;
+ $ph = photo_factory('');
+ $phototypes = $ph->supportedTypes();
+
$photos = array();
if($r) {
$twist = 'rotright';
diff --git a/Zotlabs/Widget/Channel_activities.php b/Zotlabs/Widget/Channel_activities.php
index debaf20d4..2677f82c3 100644
--- a/Zotlabs/Widget/Channel_activities.php
+++ b/Zotlabs/Widget/Channel_activities.php
@@ -223,7 +223,7 @@ class Channel_activities {
$i[] = [
'url' => z_root() . '/manage/' . $rr['channel_id'],
'title' => '',
- 'summary' => '<div class="text-truncate lh-sm"><img src="' . $rr['xchan_photo_s'] . '" class="menu-img-2">' . '<strong>' . $rr['channel_name'] . '</strong><br><small class="opacity-75">' . $rr['xchan_addr'] . '</small></div>',
+ 'summary' => '<div class="text-truncate lh-sm"><img src="' . $rr['xchan_photo_s'] . '" class="menu-img-2">' . '<strong>' . $rr['channel_name'] . '</strong><br><small class="text-body-secondary">' . $rr['xchan_addr'] . '</small></div>',
'footer' => $footer
];
diff --git a/Zotlabs/Widget/Messages.php b/Zotlabs/Widget/Messages.php
index f90b4f99e..cc1811ffc 100644
--- a/Zotlabs/Widget/Messages.php
+++ b/Zotlabs/Widget/Messages.php
@@ -28,6 +28,8 @@ class Messages {
intval(TERM_FILE)
);
+ $file_tags = [];
+
if ($r) {
foreach($r as $rr) {
$file_tags[] = $rr['term'];
@@ -42,14 +44,14 @@ class Messages {
'$feature_file' => feature_enabled(local_channel(), 'filing'),
'$file_tags' => $file_tags,
'$strings' => [
- 'messages_title' => t('Public and restricted messages'),
- 'direct_messages_title' => t('Direct messages'),
- 'starred_messages_title' => t('Starred messages'),
+ 'messages_title' => t('Public and restricted conversations'),
+ 'direct_messages_title' => t('Private conversations'),
+ 'starred_messages_title' => t('Starred conversations'),
'filed_messages_title' => t('Filed messages'),
- 'notice_messages_title' => t('Notices'),
+ 'notice_messages_title' => t('Notifications'),
'loading' => t('Loading'),
- 'empty' => t('No messages'),
- 'unseen_count' => t('Unseen'),
+ 'empty' => t('No conversations'),
+ 'unseen_count' => t('Unseen reactions'),
'filter' => t('Filter by name or address'),
'file_filter' => t('Filter by file name')
]
@@ -84,8 +86,6 @@ class Messages {
$entries = [];
$limit = 30;
$order_sql = 'i.created DESC';
- $dummy_order_sql = '';
- $filter_sql = '';
$loadtime = (($offset) ? $_SESSION['messages_loadtime'] : datetime_convert());
$vnotify = get_pconfig(local_channel(), 'system', 'vnotify', -1);
@@ -101,14 +101,18 @@ class Messages {
$vnotify_sql_i = " AND i.verb NOT IN ('Dislike', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
}
+ $filter_sql = '';
if($type !== 'filed' && $author) {
$filter_sql = " AND (i.owner_xchan = '" . protect_sprintf(dbesc($author)) . "') ";
}
+ $filed_filter_sql = '';
if($type === 'filed' && $file) {
$filed_filter_sql = " AND (term.term = '" . protect_sprintf(dbesc($file)) . "') ";
}
+ $dummy_order_sql = '';
+
switch($type) {
case 'direct':
$type_sql = ' AND i.item_private = 2 AND i.item_thread_top = 1 ';
diff --git a/Zotlabs/Widget/Notifications.php b/Zotlabs/Widget/Notifications.php
index 225403535..f9cee6e71 100644
--- a/Zotlabs/Widget/Notifications.php
+++ b/Zotlabs/Widget/Notifications.php
@@ -21,16 +21,16 @@ class Notifications {
'icon' => 'grid-3x3',
'severity' => 'secondary',
'label' => t('Network'),
- 'title' => t('New network activity notifications'),
+ 'title' => t('Unseen network activity'),
'viewall' => [
'url' => 'network',
'label' => t('Network stream')
],
'markall' => [
- 'label' => t('Mark all notifications read')
+ 'label' => t('Mark all read')
],
'filter' => [
- 'posts_label' => t('Show new posts only'),
+ 'posts_label' => t('Conversation starters'),
'name_label' => t('Filter by name or address')
]
];
@@ -40,17 +40,17 @@ class Notifications {
'type' => 'home',
'icon' => 'house',
'severity' => 'danger',
- 'label' => t('Home'),
- 'title' => t('New home activity notifications'),
+ 'label' => t('Channel'),
+ 'title' => t('Unseen channel activity'),
'viewall' => [
'url' => 'channel/' . $channel['channel_address'],
- 'label' => t('Home stream')
+ 'label' => t('Channel stream')
],
'markall' => [
- 'label' => t('Mark all notifications seen')
+ 'label' => t('Mark all seen')
],
'filter' => [
- 'posts_label' => t('Show new posts only'),
+ 'posts_label' => t('Conversation starters'),
'name_label' => t('Filter by name or address')
]
];
@@ -59,17 +59,17 @@ class Notifications {
'type' => 'dm',
'icon' => 'envelope',
'severity' => 'danger',
- 'label' => t('Direct Messages'),
- 'title' => t('New direct messages notifications'),
+ 'label' => t('Private'),
+ 'title' => t('Unseen private activity'),
'viewall' => [
'url' => 'network/?dm=1',
- 'label' => t('Direct messages stream')
+ 'label' => t('Private stream')
],
'markall' => [
- 'label' => t('Mark all notifications read')
+ 'label' => t('Mark all read')
],
'filter' => [
- 'posts_label' => t('Show new posts only'),
+ 'posts_label' => t('Conversation starters'),
'name_label' => t('Filter by name or address')
]
];
@@ -79,13 +79,13 @@ class Notifications {
'icon' => 'calendar-date',
'severity' => 'secondary',
'label' => t('Events'),
- 'title' => t('New events notifications'),
+ 'title' => t('Unseen events activity'),
'viewall' => [
'url' => 'cdav/calendar',
'label' => t('View events')
],
'markall' => [
- 'label' => t('Mark all events seen')
+ 'label' => t('Mark all seen')
]
];
@@ -94,10 +94,10 @@ class Notifications {
'icon' => 'people',
'severity' => 'danger',
'label' => t('New Connections'),
- 'title' => t('New connections notifications'),
+ 'title' => t('New connections'),
'viewall' => [
'url' => 'connections',
- 'label' => t('View all connections')
+ 'label' => t('View all')
]
];
@@ -106,21 +106,21 @@ class Notifications {
'icon' => 'folder',
'severity' => 'danger',
'label' => t('Files'),
- 'title' => t('New files notifications'),
+ 'title' => t('Useen files activity'),
];
$notifications[] = [
'type' => 'notify',
'icon' => 'exclamation-circle',
'severity' => 'danger',
- 'label' => t('Notices'),
- 'title' => t('Notices'),
+ 'label' => t('Notifications'),
+ 'title' => t('Unseen notifications'),
'viewall' => [
'url' => 'notifications/system',
- 'label' => t('View all notices')
+ 'label' => t('View all')
],
'markall' => [
- 'label' => t('Mark all notices seen')
+ 'label' => t('Mark all seen')
]
];
@@ -129,7 +129,7 @@ class Notifications {
'icon' => 'chat-quote',
'severity' => 'secondary',
'label' => t('Forums'),
- 'title' => t('Forums'),
+ 'title' => t('Unseen forums activity'),
'filter' => [
'name_label' => t('Filter by name or address')
]
@@ -142,7 +142,7 @@ class Notifications {
'icon' => 'person-exclamation',
'severity' => 'danger',
'label' => t('Registrations'),
- 'title' => t('New registrations notifications'),
+ 'title' => t('Unseen registration activity'),
];
}
@@ -152,7 +152,7 @@ class Notifications {
'icon' => 'globe',
'severity' => 'secondary',
'label' => t('Public Stream'),
- 'title' => t('New public stream notifications'),
+ 'title' => t('Unseen public stream activity'),
'viewall' => [
'url' => 'pubstream',
'label' => t('Public stream')
@@ -163,7 +163,7 @@ class Notifications {
],
*/
'filter' => [
- 'posts_label' => t('Show new posts only'),
+ 'posts_label' => t('Conversation starters'),
'name_label' => t('Filter by name or address')
]
];
diff --git a/Zotlabs/Widget/Pinned.php b/Zotlabs/Widget/Pinned.php
index be6b98434..7b95d3bc6 100644
--- a/Zotlabs/Widget/Pinned.php
+++ b/Zotlabs/Widget/Pinned.php
@@ -67,19 +67,19 @@ class Pinned {
$attend = null;
$canvote = false;
- $conv_responses = [];
-
- if(in_array($item['obj_type'], ['Event', ACTIVITY_OBJ_EVENT])) {
- $conv_responses['attendyes'] = [ 'title' => t('Attending','title') ];
- $conv_responses['attendno'] = [ 'title' => t('Not attending','title') ];
- $conv_responses['attendmaybe'] = [ 'title' => t('Might attend','title') ];
- if($commentable && $observer) {
- $attend = [ t('I will attend'), t('I will not attend'), t('I might attend') ];
- $isevent = true;
- }
+ $response_verbs[] = 'like';
+
+ if(feature_enabled(\App::$profile['profile_uid'],'dislike')) {
+ $response_verbs[] = 'dislike';
+ }
+
+ $response_verbs[] = 'announce';
+
+ if ($item['obj_type'] === 'Question') {
+ $response_verbs[] = 'answer';
}
- $this->activity($item, $conv_responses);
+ $responses = get_responses($response_verbs, $item);
$verified = (intval($item['item_verified']) ? t('Message signature validated') : '');
$forged = ((! intval($item['item_verified']) && $item['sig']) ? t('Message signature incorrect') : '');
@@ -110,6 +110,9 @@ class Pinned {
'text' => strip_tags($body['html']),
'id' => $item['id'],
'mids' => json_encode([ $midb64 ]),
+ 'mid' => $item['uuid'],
+ 'rawmid' => $item['mid'],
+ 'parent' => $item['parent'],
'isevent' => $isevent,
'attend' => $attend,
'conlabels' => [],
@@ -151,7 +154,7 @@ class Pinned {
'hide' => (! $is_new && isset($observer['xchan_hash']) && $observer['xchan_hash'] != $owner['xchan_hash'] ? t("Don't show") : ''),
// end toolbar buttons
'modal_dismiss' => t('Close'),
- 'responses' => $conv_responses,
+ 'responses' => $responses,
'author_id' => (($author['xchan_addr']) ? $author['xchan_addr'] : $author['xchan_url'])
];
@@ -193,74 +196,14 @@ class Pinned {
if(empty($mids_list))
return [];
- $r = q("SELECT * FROM item WHERE uuid IN ( '%s' ) AND uid = %d AND id = parent AND item_private = 0 ORDER BY created DESC",
+
+ $r = q("SELECT parent AS item_id FROM item WHERE uuid IN ( '%s' ) AND uid = %d AND id = parent AND item_private = 0",
dbesc(implode(",", $mids_list)),
intval($this->uid)
);
- if($r)
- return $r;
-
- return [];
- }
+ return items_by_parent_ids($r, blog_mode: true);
- /*
- * @brief List activities on item
- *
- * @param array $item
- * @param array $conv_responses
- * @return array
- *
- */
- private function activity($item, &$conv_responses) {
-
- foreach(array_keys($conv_responses) as $verb) {
- $verb_sql = '';
-
- switch($verb) {
- case 'like':
- $verb_sql = " AND verb IN ('Like', '" . ACTIVITY_LIKE . "') ";
- break;
- case 'dislike':
- $verb_sql = " AND verb IN ('Dislike', '" . ACTIVITY_DISLIKE . "') ";
- break;
- case 'attendyes':
- $verb_sql = " AND verb IN ('Accept', '" . ACTIVITY_ATTEND . "') ";
- break;
- case 'attendno':
- $verb_sql = " AND verb IN ('Reject', '" . ACTIVITY_ATTENDNO . "') ";
- break;
- case 'attendmaybe':
- $verb_sql = " AND verb IN ('TentativeAccept', '" . ACTIVITY_ATTENDMAYBE . "') ";
- break;
- default:
- break;
- }
-
- $r = q("SELECT * FROM item WHERE parent = %d AND id <> parent $verb_sql AND item_deleted = 0",
- intval($item['id'])
- );
- if(! $r) {
- unset($conv_responses[$verb]);
- continue;
- }
-
- $conv_responses[$verb]['count'] = count($r);
- $conv_responses[$verb]['button'] = get_response_button_text($verb, $conv_responses[$verb]['count']);
-
- foreach($r as $rr) {
-
- $author = q("SELECT * FROM xchan WHERE xchan_hash = '%s' LIMIT 1",
- dbesc($rr['author_xchan'])
- );
- $name = (($author && $author[0]['xchan_name']) ? $author[0]['xchan_name'] : t('Unknown'));
- $conv_responses[$verb]['list'][] = (($rr['author_xchan'] && $author && $author[0]['xchan_photo_s']) ?
- '<a class="dropdown-item" href="' . chanlink_hash($rr['author_xchan']) . '">' . '<img class="menu-img-1" src="' . zid($author[0]['xchan_photo_s']) . '" alt="' . urlencode($name) . '" /> ' . $name . '</a>' :
- '<a class="dropdown-item" href="#" class="disabled">' . $name . '</a>'
- );
- }
- }
-
- $conv_responses['count'] = count($conv_responses);
}
+
}
diff --git a/Zotlabs/Widget/Portfolio.php b/Zotlabs/Widget/Portfolio.php
index bde1c7d6a..1c9dc162a 100644
--- a/Zotlabs/Widget/Portfolio.php
+++ b/Zotlabs/Widget/Portfolio.php
@@ -66,6 +66,10 @@ class Portfolio {
//edit album name
$album_edit = null;
+
+ $ph = photo_factory('');
+ $phototypes = $ph->supportedTypes();
+
$photos = array();
if($r) {
$twist = 'rotright';
diff --git a/Zotlabs/Zot6/Zot6Handler.php b/Zotlabs/Zot6/Zot6Handler.php
index 2ef9e3b8d..1716ff84b 100644
--- a/Zotlabs/Zot6/Zot6Handler.php
+++ b/Zotlabs/Zot6/Zot6Handler.php
@@ -12,7 +12,7 @@ class Zot6Handler implements IHandler {
}
function Rekey($sender, $data, $hub) {
- return self::reply_rekey_request($sender, $data, $hub);
+ return self::rekey_request($sender, $data, $hub);
}
function Refresh($sender, $recipients, $hub, $force) {
diff --git a/boot.php b/boot.php
index f85cd114d..4f0a7c8ed 100644
--- a/boot.php
+++ b/boot.php
@@ -36,6 +36,9 @@ use Zotlabs\Daemon\Master;
use Zotlabs\Lib\DB_Upgrade;
use Zotlabs\Lib\Libzot;
use Zotlabs\Lib\Config;
+use Zotlabs\Render\SmartyTemplate;
+use Zotlabs\Render\Theme;
+use Zotlabs\Web\HttpMeta;
require_once('vendor/autoload.php');
@@ -46,6 +49,7 @@ require_once('include/text.php');
require_once('include/datetime.php');
require_once('include/language.php');
require_once('include/nav.php');
+require_once('include/observer.php');
require_once('include/permissions.php');
require_once('include/features.php');
require_once('include/taxonomy.php');
@@ -66,7 +70,7 @@ require_once('include/security.php');
define('PLATFORM_NAME', 'hubzilla');
-define('STD_VERSION', '10.3.5');
+define('STD_VERSION', '10.5');
define('ZOT_REVISION', '6.0');
define('DB_UPDATE_VERSION', 1263);
@@ -134,6 +138,12 @@ define('PNG_QUALITY', 8);
define('WEBP_QUALITY', 80);
/**
+ * App::$config['system']['avif_quality'] from 1 (maximum compressed) to 100 (uncompressed)
+ */
+define('AVIF_QUALITY', 80);
+
+
+/**
* Language detection parameters
*/
define('LANGUAGE_DETECT_MIN_LENGTH', 128);
@@ -175,6 +185,13 @@ if (!defined('STORAGE_DEFAULT_PERMISSIONS')) {
*/
define('MAX_IMAGE_LENGTH', -1);
+/**
+ * Those are the current limits we can store in the DB
+ */
+
+define('MAX_FILENAME_LENGTH', 191);
+define('MAX_FOLDER_LENGTH', 64);
+
/**
* log levels
@@ -640,8 +657,10 @@ function sys_boot(): bool {
// allow somebody to set some initial settings just in case they can't
// install without special fiddling
- if (App::$install && file_exists('.htpreconfig.php'))
+ if (App::$install && file_exists('.htpreconfig.php')) {
+ // @phpstan-ignore include.fileNotFound
@include('.htpreconfig.php');
+ }
if (array_key_exists('default_timezone', get_defined_vars())) {
App::$config['system']['timezone'] = $default_timezone;
@@ -654,7 +673,6 @@ function sys_boot(): bool {
App::$timezone = ((App::$config['system']['timezone']) ? App::$config['system']['timezone'] : 'UTC');
date_default_timezone_set(App::$timezone);
-
if (!defined('DEFAULT_PLATFORM_ICON')) {
define('DEFAULT_PLATFORM_ICON', '/images/hz-32.png');
}
@@ -756,7 +774,7 @@ class miniApp {
*
*/
class App {
-
+ public static $request = null;
public static $install = false; // true if we are installing the software
public static $account = null; // account record of the logged-in account
public static $channel = null; // channel record of the current channel of the logged-in account
@@ -854,6 +872,9 @@ class App {
*/
public static $template_engine_instance = [];
+ /// Page layouts for comanche
+ public static array $page_layouts = [];
+
private static $ldelim = [
'internal' => '',
'smarty3' => '{{'
@@ -896,46 +917,43 @@ class App {
set_include_path(
'include' . PATH_SEPARATOR
. 'library' . PATH_SEPARATOR
- . 'library/langdet' . PATH_SEPARATOR
. '.');
self::$scheme = 'http';
- if (x($_SERVER, 'HTTPS') && $_SERVER['HTTPS'])
+
+ if (!empty($_SERVER['HTTPS'])) {
self::$scheme = 'https';
- elseif (x($_SERVER, 'SERVER_PORT') && (intval($_SERVER['SERVER_PORT']) == 443))
+ }
+ elseif (!empty($_SERVER['SERVER_PORT']) && intval($_SERVER['SERVER_PORT']) == 443) {
self::$scheme = 'https';
+ }
- if (x($_SERVER, 'SERVER_NAME')) {
+ if (!empty($_SERVER['SERVER_NAME'])) {
self::$hostname = punify($_SERVER['SERVER_NAME']);
- if (x($_SERVER, 'SERVER_PORT') && $_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443)
+ if (!empty($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) {
self::$hostname .= ':' . $_SERVER['SERVER_PORT'];
+ }
/*
* Figure out if we are running at the top of a domain
* or in a sub-directory and adjust accordingly
*/
$path = trim(dirname($_SERVER['SCRIPT_NAME']), '/\\');
- if (isset($path) && strlen($path) && ($path != self::$path))
+
+ if (isset($path) && strlen($path) && ($path != self::$path)) {
self::$path = $path;
+ }
}
- if ((x($_SERVER, 'QUERY_STRING')) && substr($_SERVER['QUERY_STRING'], 0, 2) === "q=") {
- self::$query_string = str_replace(['<', '>'], ['&lt;', '&gt;'], substr($_SERVER['QUERY_STRING'], 2));
-
- // removing trailing / - maybe a nginx problem
- if (substr(self::$query_string, 0, 1) == "/")
- self::$query_string = substr(self::$query_string, 1);
-
- // trim trailing '&' if no extra args are present
- self::$query_string = rtrim(self::$query_string, '&');
-
- // change the first & to ?
- self::$query_string = preg_replace('/&/', '?', self::$query_string, 1);
+ if (!empty($_SERVER['REQUEST_URI'])) {
+ self::$query_string = str_replace(['<', '>'], ['&lt;', '&gt;'], $_SERVER['REQUEST_URI']);
+ self::$query_string = ltrim(self::$query_string, '/');
}
- if (x($_GET, 'q'))
+ if (!empty($_GET['q'])) {
self::$cmd = escape_tags(trim($_GET['q'], '/\\'));
+ }
// Serve raw files from the file system in certain cases.
$filext = pathinfo(self::$cmd, PATHINFO_EXTENSION);
@@ -985,8 +1003,9 @@ class App {
// unix style "homedir"
- if ((substr(self::$cmd, 0, 1) === '~') || (substr(self::$cmd, 0, 1) === '@'))
+ if (substr(self::$cmd, 0, 1) === '~' || substr(self::$cmd, 0, 1) === '@') {
self::$cmd = 'channel/' . substr(self::$cmd, 1);
+ }
/*
* Break the URL path into C style argc/argv style arguments for our
@@ -1006,7 +1025,7 @@ class App {
self::$argv = explode('/', self::$cmd);
self::$argc = count(self::$argv);
- if ((array_key_exists('0', self::$argv)) && strlen(self::$argv[0])) {
+ if (array_key_exists('0', self::$argv) && strlen(self::$argv[0])) {
if (strpos(self::$argv[0], '.')) {
$_REQUEST['module_format'] = substr(self::$argv[0], strpos(self::$argv[0], '.') + 1);
self::$argv[0] = substr(self::$argv[0], 0, strpos(self::$argv[0], '.'));
@@ -1014,8 +1033,9 @@ class App {
self::$module = str_replace(".", "_", self::$argv[0]);
self::$module = str_replace("-", "_", self::$module);
- if (strpos(self::$module, '_') === 0)
+ if (strpos(self::$module, '_') === 0) {
self::$module = substr(self::$module, 1);
+ }
}
else {
self::$argc = 1;
@@ -1029,22 +1049,26 @@ class App {
* pagination
*/
- self::$pager['page'] = ((x($_GET, 'page') && intval($_GET['page']) > 0) ? intval($_GET['page']) : 1);
+ self::$pager['page'] = ((!empty($_GET['page']) && intval($_GET['page']) > 0) ? intval($_GET['page']) : 1);
self::$pager['itemspage'] = 60;
self::$pager['start'] = (self::$pager['page'] * self::$pager['itemspage']) - self::$pager['itemspage'];
- if (self::$pager['start'] < 0)
+
+ if (self::$pager['start'] < 0) {
self::$pager['start'] = 0;
+ }
+
self::$pager['total'] = 0;
/*
* register template engines
*/
- self::$meta = new Zotlabs\Web\HttpMeta();
+ self::$meta = new HttpMeta();
// create an instance of the smarty template engine so we can register it.
- $smarty = new Zotlabs\Render\SmartyTemplate();
+ $smarty = new SmartyTemplate();
+
/// @todo validate if this is still the desired behavior
self::register_template_engine(get_class($smarty));
@@ -1089,10 +1113,12 @@ class App {
self::$scheme = $parsed['scheme'];
self::$hostname = punify($parsed['host']);
- if (x($parsed, 'port'))
+ if (!empty($parsed['port'])) {
self::$hostname .= ':' . $parsed['port'];
- if (x($parsed, 'path'))
+ }
+ if (!empty($parsed['path'])) {
self::$path = trim($parsed['path'], '\\/');
+ }
}
}
@@ -1197,28 +1223,22 @@ class App {
public static function build_pagehead() {
- $user_scalable = ((local_channel()) ? get_pconfig(local_channel(), 'system', 'user_scalable') : 0);
- if ($user_scalable === false)
- $user_scalable = 0;
-
- $preload_images = ((local_channel()) ? get_pconfig(local_channel(), 'system', 'preload_images') : 0);
- if ($preload_images === false)
- $preload_images = 0;
+ $user_scalable = ((local_channel()) ? get_pconfig(local_channel(), 'system', 'user_scalable', 0) : 0);
$interval = ((local_channel()) ? get_pconfig(local_channel(), 'system', 'update_interval') : 80000);
- if ($interval < 10000)
+ if ($interval < 10000) {
$interval = 80000;
-
- $theme_color = ((local_channel()) ? get_pconfig(local_channel(), 'redbasic', 'nav_bg') : App::$theme_info['theme_color']);
- if (!$theme_color) {
- $theme_color = App::$theme_info['theme_color'];
}
- if (!isset(self::$page['title']) && isset(self::$config['system']['sitename']))
+ $theme_color = ((local_channel()) ? get_pconfig(local_channel(), 'redbasic', 'nav_bg', App::$theme_info['theme_color']) : App::$theme_info['theme_color']);
+
+ if (empty(self::$page['title']) && !empty(self::$config['system']['sitename'])) {
self::$page['title'] = self::$config['system']['sitename'];
+ }
- if (isset(self::$page['title']))
+ if (!empty(self::$page['title'])) {
$pagemeta = ['og:title' => self::$page['title']];
+ }
call_hooks('page_meta', $pagemeta);
@@ -1255,7 +1275,6 @@ class App {
self::$page['htmlhead'] = replace_macros(get_markup_template('head.tpl'),
[
- '$preload_images' => $preload_images,
'$user_scalable' => $user_scalable,
'$query' => urlencode(self::$query_string),
'$baseurl' => self::get_baseurl(),
@@ -1291,7 +1310,7 @@ class App {
public static function register_template_engine($class, $name = '') {
if (!$name) {
$v = get_class_vars($class);
- if (x($v, "name")) {
+ if (!empty($v['name'])) {
$name = $v['name'];
}
}
@@ -1317,7 +1336,7 @@ class App {
}
else {
$template_engine = 'smarty3';
- if (x(self::$theme, 'template_engine')) {
+ if (!empty(self::$theme['template_engine'])) {
$template_engine = self::$theme['template_engine'];
}
}
@@ -1377,11 +1396,11 @@ class App {
public static function head_get_icon() {
$icon = self::$data['pageicon'];
- if (strpos($icon, '://') === false) {
- $icon = z_root() . $icon;
+ if (str_contains($icon, '://')) {
+ return $icon;
}
- return $icon;
+ return z_root() . $icon;
}
} // End App class
@@ -1434,21 +1453,6 @@ function system_unavailable() {
}
-function clean_urls() {
-
- // if(App::$config['system']['clean_urls'])
- return true;
- // return false;
-}
-
-function z_path() {
- $base = z_root();
- if (!clean_urls())
- $base .= '/?q=';
-
- return $base;
-}
-
/**
* @brief Returns the baseurl.
*
@@ -1460,19 +1464,6 @@ function z_root() {
return App::get_baseurl();
}
-/**
- * @brief Return absolute URL for given $path.
- *
- * @param string $path
- *
- * @return string
- */
-function absurl($path) {
- if (strpos($path, '/') === 0)
- return z_path() . $path;
-
- return $path;
-}
function os_mkdir($path, $mode = 0777, $recursive = false) {
$oldumask = @umask(0);
@@ -1525,10 +1516,11 @@ function is_ajax() {
function check_config() {
$saved = Config::Get('system', 'urlverify');
- if (!$saved)
+ if (!$saved) {
Config::Set('system', 'urlverify', bin2hex(z_root()));
+ }
- if (($saved) && ($saved != bin2hex(z_root()))) {
+ if ($saved && $saved !== bin2hex(z_root())) {
// our URL changed. Do something.
$oldurl = hex2bin($saved);
@@ -1540,12 +1532,13 @@ function check_config() {
$is_ip_addr = ((preg_match("/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/", $host)) ? true : false);
$was_ip_addr = ((preg_match("/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/", $oldhost)) ? true : false);
// only change the url to an ip address if it was already an ip and not a dns name
- if ((!$is_ip_addr) || ($is_ip_addr && $was_ip_addr)) {
+ if (!$is_ip_addr || ($is_ip_addr && $was_ip_addr)) {
fix_system_urls($oldurl, z_root());
Config::Set('system', 'urlverify', bin2hex(z_root()));
}
- else
+ else {
logger('Attempt to change baseurl from a DNS name to an IP address was refused.');
+ }
}
// This will actually set the url to the one stored in .htconfig, and ignore what
@@ -1792,7 +1785,6 @@ function login($register = false, $form_id = 'main_login', $hiddens = false, $lo
* @brief Used to end the current process, after saving session state.
*/
function killme() {
-
register_shutdown_function('shutdown');
exit;
}
@@ -1810,25 +1802,6 @@ function shutdown() {
}
/**
- * @brief Returns the entity id of locally logged in account or false.
- *
- * Returns numeric account_id if authenticated or 0. It is possible to be
- * authenticated and not connected to a channel.
- *
- * @return int|bool account_id or false
- */
-function get_account_id() {
-
- if (isset($_SESSION['account_id']))
- return intval($_SESSION['account_id']);
-
- if (App::$account)
- return intval(App::$account['account_id']);
-
- return false;
-}
-
-/**
* @brief Returns the entity id (channel_id) of locally logged in channel or false.
*
* Returns authenticated numeric channel_id if authenticated and connected to
@@ -1842,8 +1815,9 @@ function get_account_id() {
function local_channel() {
if (session_id()
&& array_key_exists('authenticated', $_SESSION) && $_SESSION['authenticated']
- && array_key_exists('uid', $_SESSION) && intval($_SESSION['uid']))
+ && array_key_exists('uid', $_SESSION) && intval($_SESSION['uid'])) {
return intval($_SESSION['uid']);
+ }
return false;
}
@@ -1863,8 +1837,9 @@ function local_channel() {
function remote_channel() {
if (session_id()
&& array_key_exists('authenticated', $_SESSION) && $_SESSION['authenticated']
- && array_key_exists('visitor_id', $_SESSION) && $_SESSION['visitor_id'])
+ && array_key_exists('visitor_id', $_SESSION) && $_SESSION['visitor_id']) {
return $_SESSION['visitor_id'];
+ }
return false;
}
@@ -1905,27 +1880,6 @@ function can_view_public_stream() {
*/
function notice($s) {
-/*
-
- if (!session_id()) {
- return;
- }
-
- if (!isset($_SESSION['sysmsg'])) {
- $_SESSION['sysmsg'] = [];
- }
-
- // ignore duplicated error messages which haven't yet been displayed
-
- if (in_array($s, $_SESSION['sysmsg'])) {
- return;
- }
-
- if (App::$interactive) {
- $_SESSION['sysmsg'][] = $s;
- }
-*/
-
$hash = get_observer_hash();
$sse_id = false;
@@ -1974,25 +1928,6 @@ function notice($s) {
* @param string $s Text to display
*/
function info($s) {
-/*
- if (!session_id()) {
- return;
- }
-
- if (!isset($_SESSION['sysmsg_info'])) {
- $_SESSION['sysmsg_info'] = [];
- }
-
- // ignore duplicated error messages which haven't yet been displayed
-
- if (in_array($s, $_SESSION['sysmsg_info'])) {
- return;
- }
-
- if (App::$interactive) {
- $_SESSION['sysmsg_info'][] = $s;
- }
-*/
$hash = get_observer_hash();
$sse_id = false;
@@ -2079,7 +2014,7 @@ function proc_run() {
return;
if (count($args) && $args[0] === 'php') {
- $args[0] = ((x(App::$config, 'system')) && (x(App::$config['system'], 'php_path')) && (strlen(App::$config['system']['php_path'])) ? App::$config['system']['php_path'] : 'php');
+ $args[0] = ((!empty(App::$config['system']['php_path'])) ? App::$config['system']['php_path'] : 'php');
}
$args = array_map('escapeshellarg', $args);
@@ -2091,10 +2026,12 @@ function proc_run() {
proc_close(proc_open($cmd, [], $foo));
}
else {
- if (Config::Get('system', 'use_proc_open'))
+ if (Config::Get('system', 'use_proc_open')) {
proc_close(proc_open($cmdline . " &", [], $foo));
- else
+ }
+ else {
exec($cmdline . ' > /dev/null &');
+ }
}
}
@@ -2119,15 +2056,17 @@ function is_windows() {
* @return bool true if user is an admin
*/
function is_site_admin() {
-
- if (!session_id())
+ if (!session_id()) {
return false;
+ }
- if (isset($_SESSION['delegate']))
+ if (isset($_SESSION['delegate'])) {
return false;
+ }
- if (isset($_SESSION['authenticated']) && is_array(App::$account) && (App::$account['account_roles'] & ACCOUNT_ROLE_ADMIN))
+ if (isset($_SESSION['authenticated']) && is_array(App::$account) && (App::$account['account_roles'] & ACCOUNT_ROLE_ADMIN)) {
return true;
+ }
return false;
}
@@ -2141,13 +2080,15 @@ function is_site_admin() {
*/
function is_developer() {
- if (!session_id())
+ if (!session_id()) {
return false;
+ }
- if ((intval($_SESSION['authenticated']))
- && (is_array(App::$account))
- && (App::$account['account_roles'] & ACCOUNT_ROLE_DEVELOPER))
+ if (intval($_SESSION['authenticated'])
+ && is_array(App::$account)
+ && (App::$account['account_roles'] & ACCOUNT_ROLE_DEVELOPER)) {
return true;
+ }
return false;
}
@@ -2157,8 +2098,9 @@ function load_contact_links($uid) {
$ret = [];
- if (!$uid || x(App::$contacts, 'empty'))
+ if (!$uid || !empty(App::$contacts['empty'])) {
return;
+ }
// logger('load_contact_links');
@@ -2170,44 +2112,11 @@ function load_contact_links($uid) {
$ret[$rv['xchan_hash']] = $rv;
}
}
- else
+ else {
$ret['empty'] = true;
-
- App::$contacts = $ret;
-}
-
-
-/**
- * @brief Returns querystring as string from a mapped array.
- *
- * @param array $params mapped array with query parameters
- * @param string $name of parameter, default null
- *
- * @return string
- */
-function build_querystring($params, $name = null) {
- $ret = '';
- foreach ($params as $key => $val) {
- if (is_array($val)) {
- if ($name === null) {
- $ret .= build_querystring($val, $key);
- }
- else {
- $ret .= build_querystring($val, $name . "[$key]");
- }
- }
- else {
- $val = urlencode($val);
- if ($name != null) {
- $ret .= $name . "[$key]" . "=$val&";
- }
- else {
- $ret .= "$key=$val&";
- }
- }
}
- return $ret;
+ App::$contacts = $ret;
}
@@ -2219,8 +2128,9 @@ function argc() {
}
function argv($x) {
- if (array_key_exists($x, App::$argv))
+ if (array_key_exists($x, App::$argv)) {
return App::$argv[$x];
+ }
return '';
}
@@ -2230,21 +2140,6 @@ function dba_timer() {
}
/**
- * @brief Returns xchan_hash from the observer.
- *
- * Observer can be a local or remote channel.
- *
- * @return string xchan_hash from observer, otherwise empty string if no observer
- */
-function get_observer_hash() {
- $observer = App::get_observer();
- if (is_array($observer))
- return $observer['xchan_hash'];
-
- return '';
-}
-
-/**
* @brief Returns the complete URL of the current page, e.g.: http(s)://something.com/network
*
* Taken from http://webcheatsheet.com/php/get_current_page_url.php
@@ -2267,22 +2162,6 @@ function curPageURL() {
return $pageURL;
}
-/**
- * @brief Returns a custom navigation by name???
- *
- * If no $navname provided load default page['nav']
- *
- * @param string $navname
- *
- * @return mixed
- * @todo not fully implemented yet
- *
- */
-function get_custom_nav($navname) {
- if (!$navname)
- return App::$page['nav'];
- // load custom nav menu by name here
-}
/**
* @brief Loads a page definition file for a module.
@@ -2382,30 +2261,36 @@ function construct_page() {
nav($navbar);
}
- $current_theme = Zotlabs\Render\Theme::current();
+ $current_theme = Theme::current();
// logger('current_theme: ' . print_r($current_theme,true));
// Zotlabs\Render\Theme::debug();
- if (($p = theme_include($current_theme[0] . '.js')) != '')
+ if (($p = theme_include($current_theme[0] . '.js')) != '') {
head_add_js('/' . $p);
+ }
- if (($p = theme_include('mod_' . App::$module . '.php')) != '')
+ if (($p = theme_include('mod_' . App::$module . '.php')) != '') {
require_once($p);
+ }
require_once('include/js_strings.php');
- if (x(App::$page, 'template_style'))
+ if (!empty(App::$page['template_style'])) {
head_add_css(App::$page['template_style'] . '.css');
- else
- head_add_css(((x(App::$page, 'template')) ? App::$page['template'] : 'default') . '.css');
+ }
+ else {
+ head_add_css(((!empty(App::$page['template'])) ? App::$page['template'] : 'default') . '.css');
+ }
- if (($p = theme_include('mod_' . App::$module . '.css')) != '')
+ if (($p = theme_include('mod_' . App::$module . '.css')) != '') {
head_add_css('mod_' . App::$module . '.css');
+ }
- head_add_css(Zotlabs\Render\Theme::url());
+ head_add_css(Theme::url());
- if (($p = theme_include('mod_' . App::$module . '.js')) != '')
+ if (($p = theme_include('mod_' . App::$module . '.js')) != '') {
head_add_js('mod_' . App::$module . '.js');
+ }
App::build_pagehead();
@@ -2437,7 +2322,6 @@ function construct_page() {
call_hooks('construct_page', $arr);
App::$layout = $arr['layout'];
-
foreach (App::$layout as $k => $v) {
if ((strpos($k, 'region_') === 0) && strlen($v)) {
if (strpos($v, '$region_') !== false) {
@@ -2472,8 +2356,9 @@ function construct_page() {
// security headers - see https://securityheaders.io
- if (App::get_scheme() === 'https' && isset(App::$config['system']['transport_security_header']) && intval(App::$config['system']['transport_security_header']) == 1)
+ if (App::get_scheme() === 'https' && isset(App::$config['system']['transport_security_header']) && intval(App::$config['system']['transport_security_header']) == 1) {
header("Strict-Transport-Security: max-age=31536000");
+ }
if (isset(App::$config['system']['content_security_policy']) && intval(App::$config['system']['content_security_policy']) == 1) {
$cspsettings = [
@@ -2525,7 +2410,7 @@ function construct_page() {
}
require_once(theme_include(
- ((x(App::$page, 'template')) ? App::$page['template'] : 'default') . '.php')
+ ((!empty(App::$page['template'])) ? App::$page['template'] : 'default') . '.php')
);
}
@@ -2544,9 +2429,7 @@ function appdirpath() {
* @param string $icon
*/
function head_set_icon($icon) {
-
App::$data['pageicon'] = $icon;
-
}
/**
@@ -2555,8 +2438,9 @@ function head_set_icon($icon) {
* @return string
*/
function get_directory_realm() {
- if ($x = Config::Get('system', 'directory_realm'))
+ if ($x = Config::Get('system', 'directory_realm')) {
return $x;
+ }
return DIRECTORY_REALM;
}
@@ -2574,8 +2458,9 @@ function get_directory_primary() {
return z_root();
}
- if ($x = Config::Get('system', 'directory_primary'))
+ if ($x = Config::Get('system', 'directory_primary')) {
return $x;
+ }
return DIRECTORY_FALLBACK_MASTER;
}
@@ -2594,18 +2479,22 @@ function get_poller_runtime() {
function z_get_upload_dir() {
$upload_dir = Config::Get('system', 'uploaddir');
- if (!$upload_dir)
+ if (!$upload_dir) {
$upload_dir = ini_get('upload_tmp_dir');
- if (!$upload_dir)
+ }
+
+ if (!$upload_dir) {
$upload_dir = sys_get_temp_dir();
+ }
return $upload_dir;
}
function z_get_temp_dir() {
$temp_dir = Config::Get('system', 'tempdir');
- if (!$temp_dir)
+ if (!$temp_dir) {
$temp_dir = sys_get_temp_dir();
+ }
return $temp_dir;
}
@@ -2622,8 +2511,9 @@ function z_check_cert() {
if (!$x['success']) {
$recurse = 0;
$y = z_fetch_url(z_root() . '/siteinfo.json', false, $recurse, ['novalidate' => true]);
- if ($y['success'])
+ if ($y['success']) {
cert_bad_email();
+ }
}
}
}
@@ -2656,8 +2546,9 @@ function check_for_new_perms() {
// Do not execute if we are in the middle of a git update and the relevant versions don't match
- if (Permissions::version() != PermissionRoles::version())
+ if (Permissions::version() != PermissionRoles::version()) {
return;
+ }
$pregistered = Config::Get('system', 'perms');
@@ -2720,8 +2611,9 @@ function check_for_new_perms() {
}
// We should probably call perms_refresh here, but this should get pushed in 24 hours and there is no urgency
- if ($found_new_perm)
+ if ($found_new_perm) {
Config::Set('system', 'perms', $pcurrent);
+ }
}
diff --git a/composer.json b/composer.json
index 62793d7a5..66023d7de 100644
--- a/composer.json
+++ b/composer.json
@@ -48,7 +48,7 @@
"twbs/bootstrap": "^5.3",
"blueimp/jquery-file-upload": "^10.3",
"desandro/imagesloaded": "^4.1",
- "phpseclib/phpseclib": "^2.0.47",
+ "phpseclib/phpseclib": "^3.0.46",
"jbroadway/urlify": "^1.2",
"chillerlan/php-qrcode": "^5.0.3",
"spomky-labs/otphp": "^11.1",
@@ -56,7 +56,8 @@
"stephenhill/base58": "^1.1",
"mmccook/php-json-canonicalization-scheme": "^1.0",
"scssphp/scssphp": "^2.0.1",
- "twbs/bootstrap-icons": "^1.11"
+ "twbs/bootstrap-icons": "^1.11",
+ "macgirvin/http-message-signer": "^0.2"
},
"require-dev": {
"ext-yaml": "*",
diff --git a/composer.lock b/composer.lock
index edd07c234..d09aabf44 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,9 +4,91 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "2eb30d4399172ceafecf36238f69d828",
+ "content-hash": "fc5da708517d924a34e28c262f9ef799",
"packages": [
{
+ "name": "bakame/http-structured-fields",
+ "version": "2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/bakame-php/http-structured-fields.git",
+ "reference": "d0fc193e5b173a4e90f2fa589d5b97b2b653b323"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/bakame-php/http-structured-fields/zipball/d0fc193e5b173a4e90f2fa589d5b97b2b653b323",
+ "reference": "d0fc193e5b173a4e90f2fa589d5b97b2b653b323",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.1"
+ },
+ "require-dev": {
+ "bakame/aide-base32": "dev-main",
+ "friendsofphp/php-cs-fixer": "^3.65.0",
+ "httpwg/structured-field-tests": "*@dev",
+ "phpbench/phpbench": "^1.3.1",
+ "phpstan/phpstan": "^2.0.3",
+ "phpstan/phpstan-deprecation-rules": "^2.0.1",
+ "phpstan/phpstan-phpunit": "^2.0.1",
+ "phpstan/phpstan-strict-rules": "^2.0",
+ "phpunit/phpunit": "^10.5.38 || ^11.5.0",
+ "symfony/var-dumper": "^6.4.15 || ^v7.2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-develop": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Bakame\\Http\\StructuredFields\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ignace Nyamagana Butera",
+ "email": "nyamsprod@gmail.com",
+ "homepage": "https://github.com/nyamsprod/",
+ "role": "Developer"
+ }
+ ],
+ "description": "A PHP library that parses, validates and serializes HTTP structured fields according to RFC9561 and RFC8941",
+ "keywords": [
+ "headers",
+ "http",
+ "http headers",
+ "http trailers",
+ "parser",
+ "rfc8941",
+ "rfc9651",
+ "serializer",
+ "structured fields",
+ "structured headers",
+ "structured trailers",
+ "structured values",
+ "trailers",
+ "validation"
+ ],
+ "support": {
+ "docs": "https://github.com/bakame-php/http-structured-fields",
+ "issues": "https://github.com/bakame-php/http-structured-fields/issues",
+ "source": "https://github.com/bakame-php/http-structured-fields"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sponsors/nyamsprod",
+ "type": "github"
+ }
+ ],
+ "time": "2024-12-12T08:25:42+00:00"
+ },
+ {
"name": "blueimp/jquery-file-upload",
"version": "v10.32.0",
"source": {
@@ -508,6 +590,122 @@
"time": "2024-11-01T03:51:45+00:00"
},
{
+ "name": "guzzlehttp/psr7",
+ "version": "2.7.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/psr7.git",
+ "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16",
+ "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5 || ^8.0",
+ "psr/http-factory": "^1.0",
+ "psr/http-message": "^1.1 || ^2.0",
+ "ralouphie/getallheaders": "^3.0"
+ },
+ "provide": {
+ "psr/http-factory-implementation": "1.0",
+ "psr/http-message-implementation": "1.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "http-interop/http-factory-tests": "0.9.0",
+ "phpunit/phpunit": "^8.5.39 || ^9.6.20"
+ },
+ "suggest": {
+ "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
+ },
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Psr7\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "George Mponos",
+ "email": "gmponos@gmail.com",
+ "homepage": "https://github.com/gmponos"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://github.com/sagikazarmark"
+ },
+ {
+ "name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
+ "homepage": "https://github.com/Tobion"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://sagikazarmark.hu"
+ }
+ ],
+ "description": "PSR-7 message implementation that also provides common utility methods",
+ "keywords": [
+ "http",
+ "message",
+ "psr-7",
+ "request",
+ "response",
+ "stream",
+ "uri",
+ "url"
+ ],
+ "support": {
+ "issues": "https://github.com/guzzle/psr7/issues",
+ "source": "https://github.com/guzzle/psr7/tree/2.7.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/Nyholm",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2025-03-27T12:30:47+00:00"
+ },
+ {
"name": "jbroadway/urlify",
"version": "1.2.5-stable",
"source": {
@@ -882,6 +1080,49 @@
"time": "2016-09-22T15:10:54+00:00"
},
{
+ "name": "macgirvin/http-message-signer",
+ "version": "v0.2.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/macgirvin/HTTP-Message-Signer.git",
+ "reference": "47604de860b822cd202dcd8b1da910d6c84720ab"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/macgirvin/HTTP-Message-Signer/zipball/47604de860b822cd202dcd8b1da910d6c84720ab",
+ "reference": "47604de860b822cd202dcd8b1da910d6c84720ab",
+ "shasum": ""
+ },
+ "require": {
+ "bakame/http-structured-fields": "^2.0",
+ "ext-openssl": "*",
+ "guzzlehttp/psr7": "^2.0",
+ "php": "^8.1",
+ "phpseclib/phpseclib": "~3.0",
+ "phpseclib/phpseclib2_compat": "^1.0",
+ "psr/http-message": "^2.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "HttpSignature\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "description": "RFC 9421 HTTP Message Signer and Verifier for PSR-7 requests",
+ "support": {
+ "issues": "https://github.com/macgirvin/HTTP-Message-Signer/issues",
+ "source": "https://github.com/macgirvin/HTTP-Message-Signer/tree/v0.2.2"
+ },
+ "time": "2025-07-10T01:13:05+00:00"
+ },
+ {
"name": "michelf/php-markdown",
"version": "2.0.0",
"source": {
@@ -1063,6 +1304,56 @@
"time": "2024-05-08T12:36:18+00:00"
},
{
+ "name": "paragonie/random_compat",
+ "version": "v9.99.100",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/paragonie/random_compat.git",
+ "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
+ "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">= 7"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "4.*|5.*",
+ "vimeo/psalm": "^1"
+ },
+ "suggest": {
+ "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
+ },
+ "type": "library",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Paragon Initiative Enterprises",
+ "email": "security@paragonie.com",
+ "homepage": "https://paragonie.com"
+ }
+ ],
+ "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
+ "keywords": [
+ "csprng",
+ "polyfill",
+ "pseudorandom",
+ "random"
+ ],
+ "support": {
+ "email": "info@paragonie.com",
+ "issues": "https://github.com/paragonie/random_compat/issues",
+ "source": "https://github.com/paragonie/random_compat"
+ },
+ "time": "2020-10-15T08:29:30+00:00"
+ },
+ {
"name": "patrickschur/language-detection",
"version": "v5.3.1",
"source": {
@@ -1163,32 +1454,32 @@
},
{
"name": "phpseclib/phpseclib",
- "version": "2.0.48",
+ "version": "3.0.46",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
- "reference": "eaa7be704b8b93a6913b69eb7f645a59d7731b61"
+ "reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/eaa7be704b8b93a6913b69eb7f645a59d7731b61",
- "reference": "eaa7be704b8b93a6913b69eb7f645a59d7731b61",
+ "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6",
+ "reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6",
"shasum": ""
},
"require": {
- "php": ">=5.3.3"
+ "paragonie/constant_time_encoding": "^1|^2|^3",
+ "paragonie/random_compat": "^1.4|^2.0|^9.99.99",
+ "php": ">=5.6.1"
},
"require-dev": {
- "phing/phing": "~2.7",
- "phpunit/phpunit": "^4.8.35|^5.7|^6.0|^9.4",
- "squizlabs/php_codesniffer": "~2.0"
+ "phpunit/phpunit": "*"
},
"suggest": {
+ "ext-dom": "Install the DOM extension to load XML formatted public keys.",
"ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
"ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
- "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations.",
- "ext-xml": "Install the XML extension to load XML formatted public keys."
+ "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
},
"type": "library",
"autoload": {
@@ -1196,7 +1487,7 @@
"phpseclib/bootstrap.php"
],
"psr-4": {
- "phpseclib\\": "phpseclib/"
+ "phpseclib3\\": "phpseclib/"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -1253,7 +1544,7 @@
],
"support": {
"issues": "https://github.com/phpseclib/phpseclib/issues",
- "source": "https://github.com/phpseclib/phpseclib/tree/2.0.48"
+ "source": "https://github.com/phpseclib/phpseclib/tree/3.0.46"
},
"funding": [
{
@@ -1269,7 +1560,55 @@
"type": "tidelift"
}
],
- "time": "2024-12-14T21:03:54+00:00"
+ "time": "2025-06-26T16:29:55+00:00"
+ },
+ {
+ "name": "phpseclib/phpseclib2_compat",
+ "version": "1.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpseclib/phpseclib2_compat.git",
+ "reference": "90976f25d6c2ff936878624b9cfaa322db11dde7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpseclib/phpseclib2_compat/zipball/90976f25d6c2ff936878624b9cfaa322db11dde7",
+ "reference": "90976f25d6c2ff936878624b9cfaa322db11dde7",
+ "shasum": ""
+ },
+ "require": {
+ "phpseclib/phpseclib": "^3.0"
+ },
+ "provide": {
+ "phpseclib/phpseclib": "2.0.47"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5.7|^6.0|^9.4"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "phpseclib\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jim Wigginton",
+ "email": "terrafrost@php.net",
+ "role": "Lead Developer"
+ }
+ ],
+ "description": "phpseclib 2.0 polyfill built with phpseclib 3.0",
+ "homepage": "https://github.com/phpseclib/phpseclib2_compat",
+ "support": {
+ "issues": "https://github.com/phpseclib/phpseclib2_compat/issues",
+ "source": "https://github.com/phpseclib/phpseclib2_compat"
+ },
+ "time": "2024-02-26T14:37:15+00:00"
},
{
"name": "psr/clock",
@@ -1478,6 +1817,50 @@
"time": "2024-09-11T13:17:53+00:00"
},
{
+ "name": "ralouphie/getallheaders",
+ "version": "3.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ralouphie/getallheaders.git",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "^2.1",
+ "phpunit/phpunit": "^5 || ^6.5"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/getallheaders.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ralph Khattar",
+ "email": "ralph.khattar@gmail.com"
+ }
+ ],
+ "description": "A polyfill for getallheaders.",
+ "support": {
+ "issues": "https://github.com/ralouphie/getallheaders/issues",
+ "source": "https://github.com/ralouphie/getallheaders/tree/develop"
+ },
+ "time": "2019-03-08T08:55:37+00:00"
+ },
+ {
"name": "ramsey/collection",
"version": "2.1.1",
"source": {
diff --git a/doc/LICENSE b/doc/LICENSE
new file mode 100644
index 000000000..2d58298e6
--- /dev/null
+++ b/doc/LICENSE
@@ -0,0 +1,428 @@
+Attribution-ShareAlike 4.0 International
+
+=======================================================================
+
+Creative Commons Corporation ("Creative Commons") is not a law firm and
+does not provide legal services or legal advice. Distribution of
+Creative Commons public licenses does not create a lawyer-client or
+other relationship. Creative Commons makes its licenses and related
+information available on an "as-is" basis. Creative Commons gives no
+warranties regarding its licenses, any material licensed under their
+terms and conditions, or any related information. Creative Commons
+disclaims all liability for damages resulting from their use to the
+fullest extent possible.
+
+Using Creative Commons Public Licenses
+
+Creative Commons public licenses provide a standard set of terms and
+conditions that creators and other rights holders may use to share
+original works of authorship and other material subject to copyright
+and certain other rights specified in the public license below. The
+following considerations are for informational purposes only, are not
+exhaustive, and do not form part of our licenses.
+
+ Considerations for licensors: Our public licenses are
+ intended for use by those authorized to give the public
+ permission to use material in ways otherwise restricted by
+ copyright and certain other rights. Our licenses are
+ irrevocable. Licensors should read and understand the terms
+ and conditions of the license they choose before applying it.
+ Licensors should also secure all rights necessary before
+ applying our licenses so that the public can reuse the
+ material as expected. Licensors should clearly mark any
+ material not subject to the license. This includes other CC-
+ licensed material, or material used under an exception or
+ limitation to copyright. More considerations for licensors:
+ wiki.creativecommons.org/Considerations_for_licensors
+
+ Considerations for the public: By using one of our public
+ licenses, a licensor grants the public permission to use the
+ licensed material under specified terms and conditions. If
+ the licensor's permission is not necessary for any reason--for
+ example, because of any applicable exception or limitation to
+ copyright--then that use is not regulated by the license. Our
+ licenses grant only permissions under copyright and certain
+ other rights that a licensor has authority to grant. Use of
+ the licensed material may still be restricted for other
+ reasons, including because others have copyright or other
+ rights in the material. A licensor may make special requests,
+ such as asking that all changes be marked or described.
+ Although not required by our licenses, you are encouraged to
+ respect those requests where reasonable. More considerations
+ for the public:
+ wiki.creativecommons.org/Considerations_for_licensees
+
+=======================================================================
+
+Creative Commons Attribution-ShareAlike 4.0 International Public
+License
+
+By exercising the Licensed Rights (defined below), You accept and agree
+to be bound by the terms and conditions of this Creative Commons
+Attribution-ShareAlike 4.0 International Public License ("Public
+License"). To the extent this Public License may be interpreted as a
+contract, You are granted the Licensed Rights in consideration of Your
+acceptance of these terms and conditions, and the Licensor grants You
+such rights in consideration of benefits the Licensor receives from
+making the Licensed Material available under these terms and
+conditions.
+
+
+Section 1 -- Definitions.
+
+ a. Adapted Material means material subject to Copyright and Similar
+ Rights that is derived from or based upon the Licensed Material
+ and in which the Licensed Material is translated, altered,
+ arranged, transformed, or otherwise modified in a manner requiring
+ permission under the Copyright and Similar Rights held by the
+ Licensor. For purposes of this Public License, where the Licensed
+ Material is a musical work, performance, or sound recording,
+ Adapted Material is always produced where the Licensed Material is
+ synched in timed relation with a moving image.
+
+ b. Adapter's License means the license You apply to Your Copyright
+ and Similar Rights in Your contributions to Adapted Material in
+ accordance with the terms and conditions of this Public License.
+
+ c. BY-SA Compatible License means a license listed at
+ creativecommons.org/compatiblelicenses, approved by Creative
+ Commons as essentially the equivalent of this Public License.
+
+ d. Copyright and Similar Rights means copyright and/or similar rights
+ closely related to copyright including, without limitation,
+ performance, broadcast, sound recording, and Sui Generis Database
+ Rights, without regard to how the rights are labeled or
+ categorized. For purposes of this Public License, the rights
+ specified in Section 2(b)(1)-(2) are not Copyright and Similar
+ Rights.
+
+ e. Effective Technological Measures means those measures that, in the
+ absence of proper authority, may not be circumvented under laws
+ fulfilling obligations under Article 11 of the WIPO Copyright
+ Treaty adopted on December 20, 1996, and/or similar international
+ agreements.
+
+ f. Exceptions and Limitations means fair use, fair dealing, and/or
+ any other exception or limitation to Copyright and Similar Rights
+ that applies to Your use of the Licensed Material.
+
+ g. License Elements means the license attributes listed in the name
+ of a Creative Commons Public License. The License Elements of this
+ Public License are Attribution and ShareAlike.
+
+ h. Licensed Material means the artistic or literary work, database,
+ or other material to which the Licensor applied this Public
+ License.
+
+ i. Licensed Rights means the rights granted to You subject to the
+ terms and conditions of this Public License, which are limited to
+ all Copyright and Similar Rights that apply to Your use of the
+ Licensed Material and that the Licensor has authority to license.
+
+ j. Licensor means the individual(s) or entity(ies) granting rights
+ under this Public License.
+
+ k. Share means to provide material to the public by any means or
+ process that requires permission under the Licensed Rights, such
+ as reproduction, public display, public performance, distribution,
+ dissemination, communication, or importation, and to make material
+ available to the public including in ways that members of the
+ public may access the material from a place and at a time
+ individually chosen by them.
+
+ l. Sui Generis Database Rights means rights other than copyright
+ resulting from Directive 96/9/EC of the European Parliament and of
+ the Council of 11 March 1996 on the legal protection of databases,
+ as amended and/or succeeded, as well as other essentially
+ equivalent rights anywhere in the world.
+
+ m. You means the individual or entity exercising the Licensed Rights
+ under this Public License. Your has a corresponding meaning.
+
+
+Section 2 -- Scope.
+
+ a. License grant.
+
+ 1. Subject to the terms and conditions of this Public License,
+ the Licensor hereby grants You a worldwide, royalty-free,
+ non-sublicensable, non-exclusive, irrevocable license to
+ exercise the Licensed Rights in the Licensed Material to:
+
+ a. reproduce and Share the Licensed Material, in whole or
+ in part; and
+
+ b. produce, reproduce, and Share Adapted Material.
+
+ 2. Exceptions and Limitations. For the avoidance of doubt, where
+ Exceptions and Limitations apply to Your use, this Public
+ License does not apply, and You do not need to comply with
+ its terms and conditions.
+
+ 3. Term. The term of this Public License is specified in Section
+ 6(a).
+
+ 4. Media and formats; technical modifications allowed. The
+ Licensor authorizes You to exercise the Licensed Rights in
+ all media and formats whether now known or hereafter created,
+ and to make technical modifications necessary to do so. The
+ Licensor waives and/or agrees not to assert any right or
+ authority to forbid You from making technical modifications
+ necessary to exercise the Licensed Rights, including
+ technical modifications necessary to circumvent Effective
+ Technological Measures. For purposes of this Public License,
+ simply making modifications authorized by this Section 2(a)
+ (4) never produces Adapted Material.
+
+ 5. Downstream recipients.
+
+ a. Offer from the Licensor -- Licensed Material. Every
+ recipient of the Licensed Material automatically
+ receives an offer from the Licensor to exercise the
+ Licensed Rights under the terms and conditions of this
+ Public License.
+
+ b. Additional offer from the Licensor -- Adapted Material.
+ Every recipient of Adapted Material from You
+ automatically receives an offer from the Licensor to
+ exercise the Licensed Rights in the Adapted Material
+ under the conditions of the Adapter's License You apply.
+
+ c. No downstream restrictions. You may not offer or impose
+ any additional or different terms or conditions on, or
+ apply any Effective Technological Measures to, the
+ Licensed Material if doing so restricts exercise of the
+ Licensed Rights by any recipient of the Licensed
+ Material.
+
+ 6. No endorsement. Nothing in this Public License constitutes or
+ may be construed as permission to assert or imply that You
+ are, or that Your use of the Licensed Material is, connected
+ with, or sponsored, endorsed, or granted official status by,
+ the Licensor or others designated to receive attribution as
+ provided in Section 3(a)(1)(A)(i).
+
+ b. Other rights.
+
+ 1. Moral rights, such as the right of integrity, are not
+ licensed under this Public License, nor are publicity,
+ privacy, and/or other similar personality rights; however, to
+ the extent possible, the Licensor waives and/or agrees not to
+ assert any such rights held by the Licensor to the limited
+ extent necessary to allow You to exercise the Licensed
+ Rights, but not otherwise.
+
+ 2. Patent and trademark rights are not licensed under this
+ Public License.
+
+ 3. To the extent possible, the Licensor waives any right to
+ collect royalties from You for the exercise of the Licensed
+ Rights, whether directly or through a collecting society
+ under any voluntary or waivable statutory or compulsory
+ licensing scheme. In all other cases the Licensor expressly
+ reserves any right to collect such royalties.
+
+
+Section 3 -- License Conditions.
+
+Your exercise of the Licensed Rights is expressly made subject to the
+following conditions.
+
+ a. Attribution.
+
+ 1. If You Share the Licensed Material (including in modified
+ form), You must:
+
+ a. retain the following if it is supplied by the Licensor
+ with the Licensed Material:
+
+ i. identification of the creator(s) of the Licensed
+ Material and any others designated to receive
+ attribution, in any reasonable manner requested by
+ the Licensor (including by pseudonym if
+ designated);
+
+ ii. a copyright notice;
+
+ iii. a notice that refers to this Public License;
+
+ iv. a notice that refers to the disclaimer of
+ warranties;
+
+ v. a URI or hyperlink to the Licensed Material to the
+ extent reasonably practicable;
+
+ b. indicate if You modified the Licensed Material and
+ retain an indication of any previous modifications; and
+
+ c. indicate the Licensed Material is licensed under this
+ Public License, and include the text of, or the URI or
+ hyperlink to, this Public License.
+
+ 2. You may satisfy the conditions in Section 3(a)(1) in any
+ reasonable manner based on the medium, means, and context in
+ which You Share the Licensed Material. For example, it may be
+ reasonable to satisfy the conditions by providing a URI or
+ hyperlink to a resource that includes the required
+ information.
+
+ 3. If requested by the Licensor, You must remove any of the
+ information required by Section 3(a)(1)(A) to the extent
+ reasonably practicable.
+
+ b. ShareAlike.
+
+ In addition to the conditions in Section 3(a), if You Share
+ Adapted Material You produce, the following conditions also apply.
+
+ 1. The Adapter's License You apply must be a Creative Commons
+ license with the same License Elements, this version or
+ later, or a BY-SA Compatible License.
+
+ 2. You must include the text of, or the URI or hyperlink to, the
+ Adapter's License You apply. You may satisfy this condition
+ in any reasonable manner based on the medium, means, and
+ context in which You Share Adapted Material.
+
+ 3. You may not offer or impose any additional or different terms
+ or conditions on, or apply any Effective Technological
+ Measures to, Adapted Material that restrict exercise of the
+ rights granted under the Adapter's License You apply.
+
+
+Section 4 -- Sui Generis Database Rights.
+
+Where the Licensed Rights include Sui Generis Database Rights that
+apply to Your use of the Licensed Material:
+
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right
+ to extract, reuse, reproduce, and Share all or a substantial
+ portion of the contents of the database;
+
+ b. if You include all or a substantial portion of the database
+ contents in a database in which You have Sui Generis Database
+ Rights, then the database in which You have Sui Generis Database
+ Rights (but not its individual contents) is Adapted Material,
+ including for purposes of Section 3(b); and
+
+ c. You must comply with the conditions in Section 3(a) if You Share
+ all or a substantial portion of the contents of the database.
+
+For the avoidance of doubt, this Section 4 supplements and does not
+replace Your obligations under this Public License where the Licensed
+Rights include other Copyright and Similar Rights.
+
+
+Section 5 -- Disclaimer of Warranties and Limitation of Liability.
+
+ a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
+ EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
+ AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
+ ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
+ IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
+ WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
+ ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
+ KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
+ ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
+
+ b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
+ TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
+ NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
+ INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
+ COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
+ USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
+ DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
+ IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
+
+ c. The disclaimer of warranties and limitation of liability provided
+ above shall be interpreted in a manner that, to the extent
+ possible, most closely approximates an absolute disclaimer and
+ waiver of all liability.
+
+
+Section 6 -- Term and Termination.
+
+ a. This Public License applies for the term of the Copyright and
+ Similar Rights licensed here. However, if You fail to comply with
+ this Public License, then Your rights under this Public License
+ terminate automatically.
+
+ b. Where Your right to use the Licensed Material has terminated under
+ Section 6(a), it reinstates:
+
+ 1. automatically as of the date the violation is cured, provided
+ it is cured within 30 days of Your discovery of the
+ violation; or
+
+ 2. upon express reinstatement by the Licensor.
+
+ For the avoidance of doubt, this Section 6(b) does not affect any
+ right the Licensor may have to seek remedies for Your violations
+ of this Public License.
+
+ c. For the avoidance of doubt, the Licensor may also offer the
+ Licensed Material under separate terms or conditions or stop
+ distributing the Licensed Material at any time; however, doing so
+ will not terminate this Public License.
+
+ d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
+ License.
+
+
+Section 7 -- Other Terms and Conditions.
+
+ a. The Licensor shall not be bound by any additional or different
+ terms or conditions communicated by You unless expressly agreed.
+
+ b. Any arrangements, understandings, or agreements regarding the
+ Licensed Material not stated herein are separate from and
+ independent of the terms and conditions of this Public License.
+
+
+Section 8 -- Interpretation.
+
+ a. For the avoidance of doubt, this Public License does not, and
+ shall not be interpreted to, reduce, limit, restrict, or impose
+ conditions on any use of the Licensed Material that could lawfully
+ be made without permission under this Public License.
+
+ b. To the extent possible, if any provision of this Public License is
+ deemed unenforceable, it shall be automatically reformed to the
+ minimum extent necessary to make it enforceable. If the provision
+ cannot be reformed, it shall be severed from this Public License
+ without affecting the enforceability of the remaining terms and
+ conditions.
+
+ c. No term or condition of this Public License will be waived and no
+ failure to comply consented to unless expressly agreed to by the
+ Licensor.
+
+ d. Nothing in this Public License constitutes or may be interpreted
+ as a limitation upon, or waiver of, any privileges and immunities
+ that apply to the Licensor or You, including from the legal
+ processes of any jurisdiction or authority.
+
+
+=======================================================================
+
+Creative Commons is not a party to its public
+licenses. Notwithstanding, Creative Commons may elect to apply one of
+its public licenses to material it publishes and in those instances
+will be considered the “Licensor.” The text of the Creative Commons
+public licenses is dedicated to the public domain under the CC0 Public
+Domain Dedication. Except for the limited purpose of indicating that
+material is shared under a Creative Commons public license or as
+otherwise permitted by the Creative Commons policies published at
+creativecommons.org/policies, Creative Commons does not authorize the
+use of the trademark "Creative Commons" or any other trademark or logo
+of Creative Commons without its prior written consent including,
+without limitation, in connection with any unauthorized modifications
+to any of its public licenses or any other arrangements,
+understandings, or agreements concerning use of licensed material. For
+the avoidance of doubt, this paragraph does not form part of the
+public licenses.
+
+Creative Commons may be contacted at creativecommons.org.
+
diff --git a/doc/de/SUMMARY.md b/doc/de/SUMMARY.md
new file mode 100644
index 000000000..74e5a442a
--- /dev/null
+++ b/doc/de/SUMMARY.md
@@ -0,0 +1,142 @@
+# Summary
+
+- [Über](./about.md)
+ - [Grundlegende Konzepte von Hubzilla](./basicconcepts.md)
+ - [Funktionen](./functions.md)
+ - [Glossar](./glossary.md)
+ - [Oberfläche / Bezeichnungen](ui.md)
+- [Benutzerhandbuch](./usermanual/overview.md)
+ - [Anmeldung / Registrierung](./usermanual/registration.md)
+ - [Konten, Profile, Kanäle](./usermanual/accounts_profiles_channels_basics.md)
+ - [Kanäle](./usermanual/channels.md)
+ - [Kanäle erstellen](./usermanual/create_channels.md)
+ - [Kanalrollen](./usermanual/channel_roles.md)
+ - [Profile](./usermanual/profiles.md)
+ - [Einstellungen](./usermanual/settings.md)
+ - [Konto-Einstellungen](./usermanual/account_settings.md)
+ - [Kanal-Einstellungen](./usermanual/channel_settings.md)
+ - [Privacy-Einstellungen](./usermanual/privacy_settings.md)
+ - [Anzeige-Einstellungen](./usermanual/display_settings.md)
+ - [Klon-Adressen verwalten](./usermanual/channel_locations.md)
+ - [Stream-Einstellungen](./usermanual/stream_settings.md)
+ - [Zusätzliche Funktionen (verborgene Einstellungen)](./usermanual/additional_features.md)
+ - [Verbinden mit Kanälen](./usermanual/connecting_with_channels.md)
+ - [Posten](./usermanual/posting.md)
+ - [Kommentieren](./usermanual/commenting.md)
+ - [Bilder einfügen](./usermanual/insert_images.md)
+ - [bbCode](./usermanual/bbcode.md)
+ - [Das Grid](./usermanual/the_grid.md)
+ - [Der Stream](./usermanual/the_stream.md)
+ - [Mit Postings interagieren](./usermanual/interact.md)
+ - [Repeat](./usermanual/repeat.md)
+ - [Teilen](./usermanual/share.md)
+ - [Link zur Quelle](./usermanual/link_to_source.md)
+ - [In Ordner speichern](./usermanual/save_to_folder.md)
+ - [Markierungsstatus (Stern) umschalten](./usermanual/toggle_star_status.md)
+ - [Quellcode anzeigen](./usermanual/show_source_code.md)
+ - [Unterhaltung folgen / nicht mehr folgen](./usermanual/follow_conversation.md)
+ - [Löschen](./usermanual/delete.md)
+ - [Konversationsmerkmale](./usermanual/conversation_features.md)
+ - [Inhaltsfilter](./usermanual/content_filters.md)
+ - [Öffentlicher Beitrags-Stream](./usermanual/public_stream.md)
+ - [Apps](./usermanual/apps.md)
+ - [wichtige Apps](./usermanual/important_apps.md)
+ - [Verbindungen](./usermanual/connections.md)
+ - [Verbindungs-Editor](./usermanual/connection_editor.md)
+ - [Diaspora-Kompatibilität](./usermanual/diaspora_compat.md)
+ - [Verzeichnis](./usermanual/directory.md)
+ - [Erweiterte Verzeichnissuche](./usermanual/AdvancedSearch.md)
+ - [Kanäle blockieren/ignorieren/archivieren/verstecken](./usermanual/blocking_channels.md)
+ - [Superblock](./usermanual/superblock.md)
+ - [Berechtigungen](./usermanual/permissions.md)
+ - [Berechtigungen für Inhalte](./usermanual/permissions_content.md)
+ - [Berechtigungen - Benutzerdefinierte Kanalrollen](./usermanual/permissions_channel_roles.md)
+ - [Berechtigungen - Kontaktrollen](./usermanual/permissions_contact_roles.md)
+ - [Direktnachrichten](./usermanual/direct_messages.md)
+ - [Erwähnungen](./usermanual/mentions.md)
+ - [Tags](./usermanual/tags.md)
+ - [Bookmarks](./usermanual/bookmarks.md)
+ - [Suche](./usermanual/search.md)
+ - [Artikel](./usermanual/article.md)
+ - [Dateien](./usermanual/files.md)
+ - [Fotos](./usermanual/photos.md)
+ - [Cloudspeicher](./usermanual/cloud_storage.md)
+ - [Galerie](./usermanual/gallery.md)
+ - [Chaträume](./usermanual/chat_rooms.md)
+ - [Gastzugang](./usermanual/guest_access.md)
+ - [Privacy Gruppen](./usermanual/privacy_groups.md)
+ - [Kalender](./usermanual/calendar.md)
+ - [Adressbuch](./usermanual/addressbook.md)
+ - [Wikis](./usermanual/wikis.md)
+ - [Inhaltswarnung/NSFW](./usermanual/NSFW.md)
+ - [Klonen](./usermanual/clone.md)
+ - [Webseiten](./usermanual/websites.md)
+ - [Comanche Seitenbeschreibungssprache](./usermanual/comanche.md)
+ - [Verschlüsselung](./usermanual/encryption.md)
+ - [Tipps zum Schutz der Privatsphäre](./usermanual/protection_of_privacy.md)
+ - [Kanal löschen](./usermanual/deleting_channel.md)
+ - [Account löschen](./usermanual/delete_account.md)
+- [Tutorials](./tutorials/tutorials.md)
+ - [Mit Hubzilla Schritt für Schritt ins Fediverse](./tutorials/step_with_hubzilla.md)
+ - [Hubzilla optisch anpassen](./tutorials/customise_look.md)
+ - [Ein abgeleitetes Thema erstellen](./tutorials/DerivedTheme1.md)
+- [Handbuch für Administratoren](./adminmanual/manual_for_administrators.md)
+ - [Wo Sie weitere Hilfe finden](./adminmanual/further_help.md)
+ - [Bevor Sie beginnen](./adminmanual/before_you_start.md)
+ - [Installation](./adminmanual/installation.md)
+ - [Anforderungen](./adminmanual/installation_requirements.md)
+ - [Manuelle Installation](./adminmanual/manual_installation.md)
+ - [Automatisierte Installation über das Shell-Skript .homeinstall](./adminmanual/automated_installation.md)
+ - [Installation mittels Docker](./adminmanual/Installation_using_docker.md)
+ - [Empfohlene Addons](./adminmanual/recommended_addons.md)
+ - [Föderations Addons](./adminmanual/federation_addons.md)
+ - [Probleme nach einer Aktualisierung](./adminmanual/problems-following-an-update.md)
+ - [Service-Klassen](./adminmanual/service_classes.md)
+ - [Verwaltung der Themen](./adminmanual/theme_management.md)
+ - [Erstellen von Seitenvorlagen](./adminmanual/Creating-Templates.md)
+ - [Hubzilla-Entwicklung - ein Leitfaden für das Schemasystem](./adminmanual/Schema-development.md)
+ - [Grundlegende Widgets](./adminmanual/Widgets.md)
+ - [Kanal-Verzeichnis](./adminmanual/channel_directory.md)
+ - [Primäres Verzeichnis](./adminmanual/Primary-Directory.md)
+ - [Administration](./adminmanual/administration.md)
+ - [Fehlersuche](./adminmanual/troubleshooting.md)
+ - [Hub-Snapshot-Tools](./adminmanual/hub_snapshot_tools.md)
+ - [Datenbank](./adminmanual/database.md)
+ - [Erweiterte Konfigurationen für Administratoren](./adminmanual/advanced_configurations.md)
+ - [CLI-Tools (utils)](./adminmanual/CLI_tools.md)
+ - [Datei-Synchronisation und Klonen](./adminmanual/filesync.md)
+ - [Nomad - Ein Überblick auf oberer Ebene](./adminmanual/Nomad---A-High-Level-Overview.md)
+ - [Admin FAQ](./adminmanual/faq_admins.md)
+- [Entwickler](./develmanual/developers_guide.md)
+ - [Wer ist ein Hubzilla-Entwickler? Sollte ich das lesen?](./develmanual/who_is_a_hubzilla_developer.md)
+ - [Sie möchten Code beisteuern?](./develmanual/dev_beginner.md)
+ - [Versionen und Releases](./develmanual/versions.md)
+ - [Git-Repository-Zweige](./develmanual/git_repository.md)
+ - [Git für Nicht-Entwickler](./develmanual/git_for_non_developers.md)
+ - [Tools und Arbeitsabläufe für Entwickler](./develmanual/tools_workflows.md)
+ - [Dokumentation erstellen](./develmanual/doco.md)
+ - [Übersetzungen](./develmanual/translations.md)
+ - [Lizenzvergabe](./develmanual/licensing.md)
+ - [Codierungsstil](./develmanual/coding_style.md)
+ - [Dateisystem-Layout](./develmanual/file_system_layout.md)
+ - [Hubzilla-Entwicklung - einige nützliche Grundfunktionen](./develmanual/dev-function-overview.md)
+ - [Erstellen von Plugins/Addons für Hubzilla](./develmanual/Plugins.md)
+ - [Testen](./develmanual/testing.md)
+ - [Erstellen von Protokoll- Föderationsdiensten](./develmanual/federate.md)
+ - [Verhaltenskodex für Mitwirkende](./develmanual/code_of_conduct.md)
+ - [Unser Versprechen](./develmanual/our_pledge.md)
+ - [Unsere Standards](./develmanual/our_standards.md)
+ - [Unsere Verantwortlichkeiten](./develmanual/our_responsibilities.md)
+ - [Geltungsbereich](./develmanual/scope.md)
+ - [Durchsetzung](./develmanual/enforcement.md)
+ - [Namensnennung](./develmanual/attribution.md)
+ - [Das Nomad Protokoll](./develmanual/nomad_protocol.md)
+ - [Technische Einführung](./develmanual/technical_introduction.md)
+ - [Magic Auth](./develmanual/magic_auth.md)
+ - [Nomad-Strukturen](./develmanual/nomad_structures.md)
+ - [Zot API](./develmanual/API.md)
+ - [Hooks](./develmanual/hooks.md)
+ - [Noch zu organisierende Informationen](./develmanual/unorganized.md)
+- [Das Hubzilla-Projekt](./the_hubzilla_project.md)
+- [Credits](./credits.md)
+- [Über diesen Hub](./platzhalter.md)
diff --git a/doc/de/about.md b/doc/de/about.md
new file mode 100644
index 000000000..7ca94690a
--- /dev/null
+++ b/doc/de/about.md
@@ -0,0 +1,88 @@
+# Was ist Hubzilla?
+
+Hubzilla ist ein dezentralisiertes Kommunikationsnetzwerk mit dem Ziel, Kommunikationsmöglichkeiten bereitzustellen, die Zensur umgehen, die Privatsphäre respektieren und somit frei sind von den Einschränkungen, die die heutigen kommerziellen Kommunikationsgiganten uns auferlegen. Diese stellen in erster Linie Spionagenetzwerke für zahlende Kunden aller Art zur Verfügung und monopolisieren und zentralisieren das ganze Internet – was ursprünglich eben gerade nicht unter den revolutionären Zielen war, die einst zum World Wide Web führten.
+
+Hubzilla ist frei, kostenlos und Open Source. Es wurde entwickelt, um auf einem Raspberry Pi ebenso zu laufen wie auf den größten AMD- und Intel-Xeon-Multiprozessor-Servern. Es kann für die Kommunikation zwischen einigen wenigen Einzelpersonen genutzt werden oder viele tausend Leute und mehr miteinander verbinden.
+
+Ein weiteres Ziel ist es, von Können und Ressourcen unabhängig zu sein. Hubzilla ist für den einfachen Computernutzer ebenso leicht bedienbar wie für Systemadministratoren und Entwickler.
+
+Wie Du es benutzt hängt davon ab, wie Du es benutzen *willst.*
+
+Hubzilla ist in PHP geschrieben, dadurch ist es einfach, sie auf jedweder heutigen Hosting-Plattform zu installieren, inklusive Self-Hosting zu Hause, auf Shared Servern oder auf virtuellen und dedizierten Servern.
+
+Mit anderen Worten, Hubzilla kann auf jeder Plattform laufen, die einen Web-Server, eine MySQL-kompatible Datenbank und PHP mitbringt.
+
+---
+
+## Grundlegende Konzepte und Funktionen von Hubzilla
+
+
+
+### Kanäle (im Sinne von Identitäten im Gegensatz zu einer App)
+
+Als Nutzer (d. h. als Privatperson oder als Verein) können Sie eine oder mehrere Web-**Identitäten** erstellen. Die Webpräsenz einer Identität wird innerhalb von Hubzilla gebündelt: Ein Besucher der Web-Identität sieht Inhalte, die mit dieser Identität in Verbindung stehen, an einem Ort, der über das Hauptmenü *Apps* für diese Identität verfügbar ist.
+
+Intern, innerhalb der Software, wird eine Identität als „Kanal“ bezeichnet. Für einen Besucher bedeutet das Wort „Kanal“ jedoch **eine** bestimmte App, die zu dieser Identität gehört, nämlich die **Pinnwand**, auf der die (möglicherweise) föderierten Beiträge der Identität in einer zeitachsenbasierten Timeline angezeigt werden.
+
+Im Folgenden verstehen wir Kanäle als Identitäten: Als angemeldeter Benutzer können Sie zwischen Ihren Kanälen wechseln, um die Inhalte für jeden Kanal zu bearbeiten, d. h. Beiträge zu veröffentlichen oder ein oder mehrere Kanalprofile, Websites, Wiki-Seiten und mehr zu erstellen. Pro Kanal können Sie auch Dateien in einer Cloud verwalten, Fotos mit Tags versehen und benennen und die Fotos in einer Webgalerie anzeigen. Ereignisse des Kanals können in einem Kalender angezeigt werden.
+
+
+
+### Dezentrales Netzwerk: Zugriff über Grenzen hinweg (Zot/Nomad) vs. Bereitstellung über Grenzen hinweg (Zot/Nomad, ActivityPub und Diaspora)
+
+Wenn dies erlaubt ist, verbinden sich Identitäten auf Hubzilla über Server- und Verwaltungsgrenzen hinweg über das Kommunikationsprotokoll Zot/Nomad (und, sofern von Ihrem Server bereitgestellt, auch über die Protokolle ActivityPub und Diaspora).
+
+Mit Ausnahme von Beiträgen/Nachrichten bleiben alle veröffentlichten Inhalte lokal auf Ihrem Server. Sie können Inhalte zwar öffentlich veröffentlichen, aber auch lokale Inhalte nur mit bestimmten Verbindungen teilen. Letzteres ist nur mit Verbindungen über Zot/Nomad (also mit Identitäten auf Hubzilla und Streams) möglich.
+
+
+
+### Zugriffskontrolle
+
+Ihre lokalen Inhalte können von den Zot/Nomad-basierten Verbindungen „besucht“ werden, denen Sie die entsprechende Berechtigung erteilt haben. Indem Sie Ihre Verbindungen Verbindungslisten (sogenannten „Datenschutzgruppen“) zuordnen, können Sie allen Mitgliedern dieser Liste Zugriff auf bestimmte Inhalte gewähren.
+
+Sie können Beiträge/Nachrichten (auch über die Protokolle ActivityPub oder Disaspora) an eine Verbindung, an eine Datenschutzgruppe oder an die Öffentlichkeit senden, indem Sie eine Zugriffskontrollliste verwenden.
+
+Es ist möglich, eine Dauer für das Ablaufen eines Beitrags/einer Nachricht festzulegen. Auf diese Weise können Sie den Zugriff zeitlich begrenzen.
+
+
+
+### Fernauthentifizierung
+
+„Kennst du mich? – Ja, ich kenne dich, willkommen!“
+Der Zugriff auf nicht öffentliche Inhalte auf einem Remote-Server sollte nur möglich sein, wenn du dich gegenüber diesem Server authentifizieren kannst. Fediverse-Software, die nur das ActivityPub- oder das Diaspora-Protokoll verwendet, kann jedoch nur Konten vom lokalen Server authentifizieren.
+Das Zot-Protokoll hingegen verfügt über einen integrierten Mechanismus namens MagicAuth, der es einem Server ermöglicht, Identitäten, die auf einem anderen (entfernten) Server registriert sind, Zugriff auf Inhalte und Aktionen zu gewähren oder zu verweigern.
+
+
+
+### Nomadische Identität
+
+Das Zot/Nomad-Protokoll ermöglicht es, Ihre Kanäle von dem Hub zu trennen, auf dem Sie sie erstellt haben. Sie können sie auf einen anderen Hub übertragen oder klonen. In diesem Fall existieren die Identität und die Daten des Kanals gleichzeitig an mehreren Orten. Dies sorgt für Ausfallsicherheit der Kanäle, falls ein Hub abgeschaltet wird oder nicht mehr verfügbar ist.
+
+
+
+### Modulares Ökosystem durch Apps
+
+ Hubzilla stellt für jede Funktion eine separate **App** zur Verfügung, d. h. die Cloud, Fotos, Galerie, Chat, Wiki, Kalender, Kontakte oder die Verbindungs-App. Ein Serveradministrator entscheidet, welche Apps für die Benutzer dieses Servers verfügbar sein sollen. Ein Benutzer kann dann die verfügbaren Apps installieren oder deinstallieren.
+
+
+
+### Zusammenarbeit
+
+Die folgenden gemeinsamen privaten Bereiche ermöglichen die Arbeit im Team mit Hubzilla:
+- Als Benutzer erlauben Sie ausgewählten Verbindungen, Ihre Webseiten und Wiki-Seiten zu lesen und zu bearbeiten.
+- Erlauben Sie ihnen, Ihre Dateien, Ihren Kalender und Ihre Kontakte über die Weboberfläche (die Cloud-, Kalender- und Kontakt-Apps) oder WebDAV, CalDAV und CardDAV zu lesen und zu bearbeiten.
+- Verwenden Sie Konversations-Threads (Beiträge), die nur für Ihre Kollaborationsgruppe sichtbar sind, indem Sie die Zugriffskontrollliste entsprechend einstellen.
+
+
+
+### Cloud-Speicher
+
+Sie können Dokumente direkt auf Ihrer Website speichern und mit anderen teilen. Öffentlicher, privater oder eingeschränkter Zugriff. Da die Dateien auf Ihrem Domainnamen gespeichert sind, wissen die Empfänger, dass sie von Ihnen stammen.
+
+
+
+### Teilen vs. Boosten
+
+Mit Hubzilla können Sie Beiträge anderer Fediverse-Nutzer (erneut) teilen. Das (erneute) Teilen in Hubzilla ist so, als würden Sie Ihren Freunden Jennie und Omar erzählen, was Giaco gesagt hat. Und dann sagen sie: „Cool, das gefällt mir“.
+
+Hubzilla ermöglicht auch das Boosten. Etwas zu boosten ist so, als würdest du deinen Freunden Jennie und Omar erzählen, was Giaco gesagt hat, während Giaco und alle seine Freunde und seine Familie (die du nicht kennst) zuhören. Giaco und alle seine Freunde und seine Familie können jetzt mit dir sprechen.
diff --git a/doc/de/about/.gitkeep b/doc/de/about/.gitkeep
deleted file mode 100644
index e69de29bb..000000000
--- a/doc/de/about/.gitkeep
+++ /dev/null
diff --git a/doc/de/about/about.bb b/doc/de/about/about.bb
deleted file mode 100644
index 27ea4ca9a..000000000
--- a/doc/de/about/about.bb
+++ /dev/null
@@ -1,23 +0,0 @@
-[h3]Was ist $Projectname?[/h3]
-
-$Projectname ist ein dezentralisiertes Kommunikationsnetzwerk mit dem Ziel, Kommunikationsmöglichkeiten bereitzustellen, die Zensur umgehen, die Privatsphäre respektieren und somit frei sind von den Einschränkungen, die die heutigen kommerziellen Kommunikationsgiganten uns auferlegen. Diese stellen in erster Linie Spionagenetzwerke für zahlende Kunden aller Art zur Verfügung und monopolisieren und zentralisieren das ganze Internet – was ursprünglich eben gerade nicht unter den revolutionären Zielen war, die einst zum World Wide Web führten.
-
-$Projectname ist frei, kostenlos und Open Source. Sie wurde entwickelt, um auf einem Raspberry Pi für € 30,– ebenso zu laufen wie auf den größten AMD- und Intel-Xeon-Multiprozessor-Servern. Es kann für die Kommunikation zwischen einigen wenigen Einzelpersonen genutzt werden oder viele tausend Leute und mehr miteinander verbinden.
-
-Ein weiteres Ziel ist es, von Können und Ressourcen unabhängig zu sein. $Projectname ist für den einfachen Computernutzer ebenso leicht bedienbar wie für Systemadministratoren und Entwickler.
-
-Wie Du es benutzt hängt davon ab, wie Du es benutzen [i]willst.[/i]
-
-$Projectname ist in PHP geschrieben, dadurch ist es einfach, sie auf jedweder heutigen Hosting-Plattform zu installieren, inklusive Self-Hosting zu Hause, auf Shared Servern wie bei [url=https://uberspace.de/]Uberspace[/url], [url=http://mediatemple.com/]Media Temple[/url] und [url=http://www.dreamhost.com/]Dreamhost[/url], oder auf virtuellen und dedizierten Servern, wie es sie zum Beispiel bei [url=https://www.linode.com]Linode[/url], [url=http://greenqloud.com]GreenQloud[/url] oder [url=https://aws.amazon.com]Amazon AWS[/url] gibt.
-
-Mit anderen Worten, $Projectname kann auf jeder Plattform laufen, die einen Web-Server, eine MySQL-kompatible Datenbank und PHP mitbringt.
-
-Dabei bietet $Projectname einige einzigartige Leckerbissen:
-
-[b]Ein-Klick-Identifikation:[/b] Du kannst auf andere Server im $Projectname-Netzwerk zugreifen, indem Du einfach auf einen Link dorthin klickst. Die Authentifizierung wird ganz einfach automatisch hinter den Kulissen durchgeführt. Vergiss viele verschiedene Usernamen für verschiedene Seiten und die Passwörter dazu – das tut alles $Projectname für Dich.
-
-[b]Klone:[/b] Du kannst Deine Online-Identität (oder, wie wir sagen, einen Kanal) klonen. Sie ist nicht mehr länger an einen bestimmten Server, eine Domain oder eine IP-Adresse gebunden. Importiere sie einfach auf einem anderen $Projectname-Server (oder $Projectname-Hub, wie es bei uns heißt) – direkt online oder mit Hilfe eines vorher generierten Exports. Wenn Dein primärer Hub plötzlich nicht mehr online ist, kein Problem, Deine Kontakte, Posts* und Nachrichten* sind automagisch weiterhin unter Deiner geklonten Identität verfügbar und zugreifbar. [i](*: nur Posts und Nachrichten, die nach dem Moment des Klonens erstellt wurden)[/i]
-
-[b]Privatsphäre:[/b] $Projectname-Identitäten (Zot-IDs) können gelöscht, gesichert/heruntergeladen und geklont werden. Du hast volle Kontrolle über Deine Daten. Wenn Du Dich entscheidest, all Deine Daten und Deine Zot-ID zu löschen, musst Du nur auf einen Link klicken, und sie werden sofort von dem Server gelöscht. Keine Fragen, keine Umstände.
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/about/about_hub.bb b/doc/de/about_hub.bb
index 0c1082f51..0c1082f51 100644
--- a/doc/en/about/about_hub.bb
+++ b/doc/de/about_hub.bb
diff --git a/doc/de/adminmanual/CLI_tools.md b/doc/de/adminmanual/CLI_tools.md
new file mode 100644
index 000000000..7bd27b5ca
--- /dev/null
+++ b/doc/de/adminmanual/CLI_tools.md
@@ -0,0 +1,103 @@
+### CLI Tools (utils)
+
+Wer als Administrator Zugang zur Shell hat, kann weitere kleine CLI Tools verwenden, welche sich im Verzeichnis "utils" finden.
+
+#### addons
+
+Mit dem Script addons können Sie sich anzeigen lassen, welche Addons installiert und welche vorhanden sind. Außerdem können Sie Addons installieren und deinstallieren, sowie alle Addons neu installieren.
+
+- `util/addons list`
+ Anzeige aller installierter Addons
+- `util/addons list all`
+ Anzeige aller Addons , die installiert sind (*) und solcher, die aufgrund von Inkompatibilität deaktiviert sind (!)
+- `util/addons install foo`
+ Installiere das Addon mit dem Namen "foo"
+- `util/addons uninstall foo`
+ Deinstalliere das Addon mit dem Namen "foo"
+- `util/addons reinstall`
+ Reinstalliere alle Addons
+
+#### admins
+
+Das Script admins ermöglicht es Ihnen, sich sämtliche Admins des Hub anzeigen zu lassen, sowie weitere Admins hinzuzufügen und bestehende Admins zu entfernen.
+
+- `util/admins`
+- `util/admins list`
+- `util/admins add <account_id>`
+- `util/admins remove <account_id>`
+
+#### config / pconfig
+
+Siehe: [Erweiterte Konfigurationen für Administratoren](/help/de/adminmanual/advanced_configurations.md)
+
+#### connect
+
+Mit connect können Sie eine Verbindung zwischen einem Kanal Ihres Hubs mit einem anderen Kanal herstellen.
+
+- `util/connect <channel_id> <channel@hub>`
+- `util/connect <channel_address> <channel@hub>`
+
+#### dcp
+
+Mit dcp können Sie Dateien in den Store-Bereich eines Kanals auf Ihrem Hub kopieren.
+
+- `util/dcp <quelle> <zielverzeichnis>` wobei Zielverzeichnis `store/$nickname/path` oder `$nickname/path` sein muss.
+
+#### dmkdir
+
+Mit dmkdir können Sie im Store-Bereich eines Kanals auf Ihrem Hub ein Unterverzeichnis anlegen.
+
+- `util/dmkdir <directory>` wobei Directory `store/$nickname/path/<directory>` oder `$nickname/path/<directory>` sein muss.
+
+#### fresh (The Freaking REd Shell)
+
+Dies funktioniert nur unter Unix/Linux. Wenn das readline-Modul installiert ist, verwenden es dieses für die Eingabe, ansonsten liest es nur von stdin und schreibt nach stdout.
+Die Befehle werden der Reihe nach abgearbeitet, bis der Befehl „exit“, „quit“ oder das Ende der Datei erreicht ist.
+
+Kommandos:
+
+- `version`
+ Zeigt die aktuelle Fresh-Version an.
+- `login <E-Mail-Adresse>`
+ Fragt nach einem Passwort und authentifiziert `<E-Mail-Adresse>` als den aktuellen
+ Benutzer.
+- `finger <Kanal-Adresse>`
+ Führt einen Lookup von `<Kanal-Adresse>` durch und meldet das Ergebnis.
+- `channel <Kanal-Nickname>`
+ Schaltet den aktuellen Kanal auf den Kanal mit dem angegebenen Spitznamen um.
+- `conn [<id1> <id2> ...]`
+- Ohne Argumente listet dies alle Verbindungen des aktuellen Kanals mit einer ID auf.
+ Wenn IDs angegeben werden, werden die Details der einzelnen Verbindungen angezeigt.
+
+#### hz
+
+Einfaches, minimalistisches Kommandozeilentool, um den Status über die API an hubzilla zu senden. Erfordert curl.
+
+`hz [<Konfigurationsdatei>]`
+
+hz benötigt eine Konfigurationsdatei. Sie können entweder eine Datei `.hubzilla` verwenden und den Parameter `<Konfigurationsdatei>` weglassen oder eine Konfigurationsdatei unter einem beliebigen Namen im Hubzilla-Verzeichnis erstellen, deren Namen Sie dann bei Aufruf von hz angeben.
+
+Format der Konfigurationsdatei:
+
+```
+USER=<Ihr Benutzername>
+PASS=<Ihr Passwort>
+HUB=<Domain Ihres Hubs>
+```
+
+Anschließend können Sie Ihr Posting eingeben und die Eingabe mit Strg-D abschließen.
+
+#### storageconf
+
+Mit storageconf können Sie den Speicherort für Thumbnails festlegen (Dateisystem oder Datenbank), sowie die aktuelle Konfiguration abfragen.
+
+- `util/storageconv stats`
+ Zeigt den aktuell eingestellten Status
+- `util/storageconv fs`
+ Verschiebt die Thumbnails von der Datenbank ins Dateisystem
+- `util/storageconv db`
+ Verschiebt die Thumbnails vom Dateisystem in die Datenbank
+
+#### thumbrepair
+
+thumbrepair erstellt die lokalen Thumbnails neu. \ No newline at end of file
diff --git a/doc/de/adminmanual/Creating-Templates.md b/doc/de/adminmanual/Creating-Templates.md
new file mode 100644
index 000000000..61b596e1f
--- /dev/null
+++ b/doc/de/adminmanual/Creating-Templates.md
@@ -0,0 +1,79 @@
+### Erstellen von Seitenvorlagen
+
+Für eine Seitenvorlage zur Verwendung mit Comanche sind zwei Dateien erforderlich - eine PHP-Vorlage und eine CSS-Datei. Die Seitenvorlagen müssen vom Systemadministrator Ihrer Website installiert werden.
+Wählen Sie zunächst einen Namen. In diesem Fall werden wir eine Vorlage erstellen und sie „demo“ nennen.
+Sie müssen die Dateien „view/php/demo.php“ und „view/css/demo.css“ erstellen, um die PHP-Vorlage bzw. das CSS zu speichern.
+Um eine bessere Vorstellung von diesem Prozess zu bekommen, schauen wir uns eine bestehende Vorlage an - die „Standard“-Vorlage. Diese wird standardmäßig in der gesamten Anwendung verwendet.
+
+#### **view/php/default.php**
+
+```
+<!DOCTYPE html >
+<html>
+<head>
+ <title><?php if(x($page,'title')) echo $page['title'] ?></title>
+ <script>var baseurl=„<?php echo z_root() ?>“;</script>
+ <?php if(x($page,'htmlhead')) echo $page['htmlhead'] ?>
+</head>
+<body>
+ <?php if(x($page,'nav')) echo $page['nav']; ?>
+ <aside id=„region_1“><?php if(x($page,'aside')) echo $page['aside']; ?></aside>
+ <section id=„region_2“><?php if(x($page,'content')) echo $page['content']; ?>
+ <div id=„page-footer“></div>
+ <div id=„pause“></div>
+ </abschnitt>
+<aside id=„region_3“><?php if(x($page,'right_aside')) echo $page['right_aside']; ?></aside>
+<footer><?php if(x($page,'footer')) echo $page['footer']; ?></footer>
+</body>
+</html>
+```
+
+Hier ist die entsprechende CSS-Datei
+
+#### **view/php/default.css**
+
+```
+aside#region_1 {
+ Anzeige: block;
+ Breite: 210px;
+ Position: absolut;
+ oben: 65px;
+ links: 0;
+ margin-left: 10px;
+}
+
+beiseite input[type='text'] {
+ Breite: 174px;
+}
+section {
+ Position: absolut;
+ top: 65px;
+ links: 250px;
+ Anzeige: block;
+ rechts: 15px;
+ padding-bottom: 350px;
+}
+```
+
+Wenn Sie sich diese Definitionen ansehen, fallen Ihnen vielleicht einige Dinge auf:
+
+- Wir haben kein CSS für die Bereiche „nav“, „right_aside“ oder „footer“ angegeben. In dieser Vorlage werden „nav“ und „footer“ die volle Seitenbreite einnehmen, und wir lassen die Größe und Platzierung dieser Elemente durch das Thema steuern. „right_aside“ wird derzeit nicht verwendet.
+- Es gibt Elemente auf der Seite wie „page-footer“ und „pause“, für die es keinen sichtbaren Inhalt gibt. Dieser Inhalt wird von Javascript-Elementen stammen.
+- Unsere Standardvorlage verwendet eine absolute Positionierung. Modernes Webdesign verwendet häufig „float“-Div-Container, so dass bei der Anzeige auf Geräten mit kleinen Bildschirmen in der Regel keine Bildlaufleisten erforderlich sind.
+
+Um eine neue Vorlage zu entwerfen, ist es am besten, mit einer bestehenden Vorlage zu beginnen und sie nach Wunsch zu ändern. Genau das werden wir hier tun.
+Die Art und Weise, wie Comanche Inhalte innerhalb einer bestimmten Region bereitstellt, ist die Verwendung eines Region-Tags.
+
+```
+[region=aside][widget=profile][/widget][/region]
+```
+
+In diesem Beispiel wird ein „Profil“-Widget in der Region „aside“ platziert. In Wirklichkeit wird jedoch der HTML-Code für das Widget in der Code-Variablen **$page['aside']** abgelegt. Unsere Standardseitenvorlage definiert einen Bereich auf der Seite (das CSS positioniert diesen als absolute Seitenleiste) und fügt dann den Inhalt von $page['aside'] ein (falls vorhanden).
+Wenn Sie also eine Vorlage mit einem Bereich namens „foo“ erstellen wollten, würden Sie einen Platz dafür auf der Seite vorsehen, dann den Inhalt von $page['foo'] dort einfügen, wo Sie ihn verwenden wollen, und dann mit Comanche angeben
+
+```
+[region=foo][widget=profile][/widget][/region]
+```
+
+und dies würde ein Profil-Widget in der von Ihnen erstellten „foo“-Region platzieren.
+Verwenden Sie die CSS-Datei, um den Bereich an der gewünschten Stelle auf der Seite zu positionieren und optional seine Größe zu steuern. \ No newline at end of file
diff --git a/doc/de/adminmanual/Installation_using_docker.md b/doc/de/adminmanual/Installation_using_docker.md
new file mode 100644
index 000000000..0f59847a6
--- /dev/null
+++ b/doc/de/adminmanual/Installation_using_docker.md
@@ -0,0 +1,56 @@
+### Installation mittels Docker
+
+Es besteht die Möglichkeit, Hubzilla komfortabel und bequem als Docker-Container zu installieren. Dafür bietet Saiwal ([sk@hub.utsukta.org](https://hub.utsukta.org/channel/sk)) ein vorkonfigurierte Umgebung für einen Hubzilla-Container an.
+
+Hauptfeatures sind:
+
+- Einfache Bereitstellung: Verwenden Sie Docker Compose, um eine voll funktionsfähige Hubzilla-Instanz mit nur wenigen Befehlen einzurichten.
+- Benutzerdefinierte Konfiguration: Passen Sie Ihre Hubzilla-Installation ganz einfach mit Umgebungsvariablen für SMTP, Datenbank und mehr an.
+- Kontinuierliche Updates: Das Docker-Image ist so aufgebaut, dass es einfach aktualisiert werden kann, wenn neue Änderungen am Hubzilla-Kern oder seinen Abhängigkeiten vorgenommen werden.
+- SMTP-Integration: Integrierte Unterstützung für den Versand von E-Mails mit ssmtp, wodurch die Konfiguration von E-Mail-Benachrichtigungen für Ihre Hubzilla-Instanz erleichtert wird.
+
+Das Repository für den Container befindet sich hier: [**skprg/hubzilla-docker**](https://github.com/skprg/hubzilla-docker)
+
+#### Das Image von Grund auf neu erstellen
+
+- Klonen Sie das Repository:
+
+```
+git clone https://github.com/skprg/hubzilla-docker.git
+cd hubzilla-docker
+```
+
+- Konfigurieren Sie Ihre Umgebung: Aktualisieren Sie die Datei `docker-compose.yml` mit Ihren SMTP- und anderen Einstellungen.
+- Erstellen und führen Sie den Container aus:
+
+```
+docker-compose up --build -d
+```
+
+#### Vorgefertigtes Image verwenden
+
+- Ersetzen Sie die folgenden Zeilen in `docker-compose.yml`
+
+```
+build:
+ context: .
+ Dockerfile: Dockerfile
+```
+
+mit
+
+```
+image: ghcr.io/skprg/hubzilla-docker:latest
+```
+
+- Starten Sie den Container:
+
+```
+docker compose up -d
+```
+
+Greifen Sie auf Ihre Hubzilla-Instanz zu: Navigieren Sie zu http://localhost (oder der entsprechenden URL), um Ihre Hubzilla-Instanz anzuzeigen.
+
+#### Weitere Hinweise / Update / Tipps
+
+Weitere Hinweise, Upgrade-Anweisungen und Tipps finden Sie im oben verlinkten Repository. \ No newline at end of file
diff --git a/doc/de/adminmanual/Nomad---A-High-Level-Overview.md b/doc/de/adminmanual/Nomad---A-High-Level-Overview.md
new file mode 100644
index 000000000..c86febf6f
--- /dev/null
+++ b/doc/de/adminmanual/Nomad---A-High-Level-Overview.md
@@ -0,0 +1,111 @@
+### Nomad - Ein Überblick auf oberer Ebene
+
+Hier ist eine allgemeine Beschreibung der Funktionsweise von Nomad.
+
+In diesem Beispiel wird „Indigo“ eine öffentliche Nachricht von seiner Website „podunk.edu“ aus versenden. „Nickordo„ ist ein Empfänger auf einer anderen Website (“example.com").
+
+Indigo stellt seine Nachricht zuerst auf podunk.edu ein. podunk.edu schaut nach, wer die Nachricht erhalten soll und findet Nickordo. Nickordo postet normalerweise von example.com aus, also fügen wir dieses Ziel zu unserer Liste der Empfänger hinzu. Wir können auch andere Ziele für Nickordo und alle anderen, die Indigos Beiträge verfolgen, hinzufügen.
+
+In diesem Beispiel stellen wir fest, dass wir nur einen bekannten Empfänger an einem bekannten Ort haben.
+
+Wir senden ein Paket an example.com:
+
+
+
+ {
+ "type":"notify",
+ "sender":{
+ "guid":"kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA",
+ "guid_sig":"PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK-R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q",
+ "url":"http:\/\/podunk.edu",
+ "url_sig":"T8Bp7j5DHHhQDCFcAHXfuhUfGk2P3inPbImwaXXF1xJd3TGgluoXyyKDx6WDm07x0hqbupoAoZB1qBP3_WfvWiJVAK4N1FD77EOYttUEHZ7L43xy5PCpojJQmkppGbPJc2jnTIc_F1vvGvw5fv8gBWZvPqTdb6LWF6FLrzwesZpi7j2rsioZ3wyUkqb5TDZaNNeWQrIEYXrEnWkRI_qTSOzx0dRTsGO6SpU1fPWuOOYMZG8Nh18nay0kLpxReuHCiCdxjXRVvk5k9rkcMbDBJcBovhiSioPKv_yJxcZVBATw3z3TTE95kGi4wxCEenxwhSpvouwa5b0hT7NS4Ay70QaxoKiLb3ZjhZaUUn4igCyZM0h6fllR5I6J_sAQxiMYD0v5ouIlb0u8YVMni93j3zlqMWdDUZ4WgTI7NNbo8ug9NQDHd92TPmSE1TytPTgya3tsFMzwyq0LZ0b-g-zSXWIES__jKQ7vAtIs9EwlPxqJXEDDniZ2AJ6biXRYgE2Kd6W_nmI7w31igwQTms3ecXe5ENI3ckEPUAq__llNnND7mxp5ZrdXzd5HHU9slXwDShYcW3yDeQLEwAVomTGSFpBrCX8W77n9hF3JClkWaeS4QcZ3xUtsSS81yLrp__ifFfQqx9_Be89WVyIOoF4oydr08EkZ8zwlAsbZLG7eLXY"
+ },
+ "callback":"\/post",
+ "version":1,
+ "secret":"1eaa6613699be6ebb2adcefa5379c61a3678aa0df89025470fac871431b70467"
+ }
+
+In diesem Paket steht das Folgende:
+
+Ich bin Indigo und hier ist der Beweis. Ich poste von podunk.edu und hier ist der Beweis. Ich habe ein Paket für Sie. Die Kontrollnummer lautet „1eaa6613....“.
+
+Example.com nimmt dieses Paket an und sagt: „Moment mal - ich kenne Sie nicht. Ich möchte beweisen, wer du bist.“ Example.com stellt also eine Verbindung zu podunk.edu über eine „bekannte URL“ her, die wir für diesen Zweck verwenden, und sucht nach der oben genannten „guid“. Es sollte eine Reihe von Informationen zurückgeben, darunter auch einen öffentlichen Schlüssel. Example.com verwendet diesen Schlüssel, um die Signaturen in der Nachricht zu überprüfen und festzustellen, ob es tatsächlich eine Person namens Indigo bei podunk.edu gibt. Wir müssen dies nur einmal tun. (Beachten Sie, dass Indigo von jedem Ort aus posten kann. Wir müssen nur beweisen, dass es sich um Indigo handelt und dass Indigo beweisen kann, dass er von einem anderen Standort aus postet).
+
+Dann trennt example.com die Verbindung und weist darauf hin, dass eine Nachricht auf podunk.edu wartet. Entweder sofort oder immer dann, wenn der Drang besteht (je nachdem, wie wichtig Indigo für jemanden auf dieser Website ist), „ruft“ example.com podunk.edu an. Die Nachricht lautet in etwa so:
+
+
+
+ {
+ "type":"pickup",
+ "url":"http:\/\/example.com",
+ "callback_sig":"teE1_fLIqfyeCuZY4iS7sNU8jUlUuqYOYBiHLarkC99I9K-uSr8DAwVW8ZPZRK-uYdxRMuKFb6cumF_Gt9XjecCPBM8HkoXHOi_VselzJkxPwor4ZPtWYWWaFtRfcAm794LrWjdz62zdESTQd2JJIZWbrli1sUhK801BF3n0Ye6-X1MWhy9EUTVlNimOeRipcuD_srMhUcAXOEbLlrugZ8ovy2YBe6YOXkS8jj0RSFjsOduXAoVhQmNpcobSYsDvaQS3e3MvE6-oXE602zGQhuNLr7DIMt9PCdAeQo-ZM-DHlZGCkGk4O2oQFCXFzGPqLUMWDACGJfTfIWGoh_EJqT_SD5b_Yi_Wk9S1lj7vb-lmxe5JuIf7ezWzHoBT8vswnZxPYlidH2i9wapdzij9il_qqcCWWHIp7q_XkY_Zj52Z4r4gdmiqM-8y1c_1SDX7hrJFRwqL_PKFbEvyi5nMWTEzqp55Tay5Woiv19STK_H_8ufFfD9AOkYnk6rIOMsk9dn3a5tAFpDRyRndXkBWAXwiJjiND2zjue7BFu7Ty40THXcfYRh1a5XrAXcaGeYuagg-8J9tAufu9_LY3qGazFg8kRBVMOn4M8DRKSIhKj7z4MnbYL0s09gREojy4jqWO3VkaOjP2jUGzoPuUDLasudE1ehWFq0K_MTQNavgmp8",
+ "callback":"http:\/\/example.com\/post",
+ "secret":"1eaa6613699be6ebb2adcefa5379c61a3678aa0df89025470fac871431b70467",
+ "secret_sig":"O7nB4_UJHBXi28Suwl9LBZF9hI_9KGVTgehnUlWF1oYMNRnBbVHB9lzUfAoalvp3STbU3xJbtD_S58tv6MfV7J5j2V_S1W5ex3dulmDGB8Pt_7Fe5mbEPmjQFcfv3Eg5dUjYIuDl0TDScfrHyImj7RZIWHbwd7wWVoMzzDa_o33klpYmKZCBvObCh55bRrlFkXZs_dRuOiPwkfX0C6_XES4OyOIYl45V30rdhmf-STrf4L9dKYy_axQ12RIwRcKychvVLwlUJn3bn9lgNXRRU_HTne-09OPcJbUOdcD3DkFoKOxMULBNKPHzsCau0ICYug7S0EP6LpCom_mW78s08LyVA1vYeFZjevBCiGecj57yIAQDYi6_rpWJfihYaWHRN0oqtScUR4Bdf0bQbEHxMs4zAtrOAxfyJCbi6U1pfnGgzXzB9ulOYGnVGNTF7Ey4K7FOZIBtk0ILY2JfvBUaVvVs8ttagOOHmhWhnbCvrnOFlkNdlce7zoJCSUJENUOCYmTRfwB_Jno5fAzRnrsYU3_Z-l1mzniU_OmUPz8mPEh7PwhkqAiVlyaM-q15gn7l2lAIDk9kp2X_iCme7v4V0ADN_DbpaI_0-6mPw5HLbKrCsA-sxlSMB4DO4lDCHYkauj0l25sbfroRWB_hax1O4Q0oWyOlVJLUqEC5nuUJCCE"
+ }
+
+Was diese Nachricht aussagt, ist: Hier ist example.com, ich habe einen Beweis, und ich bin hier, um ein Paket abzuholen. Hier ist die Sendungsverfolgungsnummer, und hier ist der Beweis, dass dies die Sendungsverfolgungsnummer ist, die Sie vermutlich an example.com gesendet haben.
+
+Gut genug. Podunk.edu überprüft die Geschichte und tatsächlich, es ist example.com, und ja, da wartet ein Paket mit dieser Kontrollnummer. Hier ist das Paket...
+
+
+
+ {
+ "success":1,
+ "pickup":{
+ "notify":{
+ "type":"notify",
+ "sender":{
+ "guid":"kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA",
+ "guid_sig":"PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK-R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q",
+ "url":"http:\/\/z.podunk.edu",
+ "url_sig":"T8Bp7j5DHHhQDCFcAHXfuhUfGk2P3inPbImwaXXF1xJd3TGgluoXyyKDx6WDm07x0hqbupoAoZB1qBP3_WfvWiJVAK4N1FD77EOYttUEHZ7L43xy5PCpojJQmkppGbPJc2jnTIc_F1vvGvw5fv8gBWZvPqTdb6LWF6FLrzwesZpi7j2rsioZ3wyUkqb5TDZaNNeWQrIEYXrEnWkRI_qTSOzx0dRTsGO6SpU1fPWuOOYMZG8Nh18nay0kLpxReuHCiCdxjXRVvk5k9rkcMbDBJcBovhiSioPKv_yJxcZVBATw3z3TTE95kGi4wxCEenxwhSpvouwa5b0hT7NS4Ay70QaxoKiLb3ZjhZaUUn4igCyZM0h6fllR5I6J_sAQxiMYD0v5ouIlb0u8YVMni93j3zlqMWdDUZ4WgTI7NNbo8ug9NQDHd92TPmSE1TytPTgya3tsFMzwyq0LZ0b-g-zSXWIES__jKQ7vAtIs9EwlPxqJXEDDniZ2AJ6biXRYgE2Kd6W_nmI7w31igwQTms3ecXe5ENI3ckEPUAq__llNnND7mxp5ZrdXzd5HHU9slXwDShYcW3yDeQLEwAVomTGSFpBrCX8W77n9hF3JClkWaeS4QcZ3xUtsSS81yLrp__ifFfQqx9_Be89WVyIOoF4oydr08EkZ8zwlAsbZLG7eLXY"
+ },
+ "callback":"\/post",
+ "version":1,
+ "secret":"1eaa6613699be6ebb2adcefa5379c61a3678aa0df89025470fac871431b70467"
+ },
+ "message":{
+ "message_id":"10b049ce384cbb2da9467319bc98169ab36290b8bbb403aa0c0accd9cb072e76@podunk.edu",
+ "message_top":"10b049ce384cbb2da9467319bc98169ab36290b8bbb403aa0c0accd9cb072e76@podunk.edu",
+ "message_parent":"10b049ce384cbb2da9467319bc98169ab36290b8bbb403aa0c0accd9cb072e76@podunk.edu",
+ "created":"2012-11-20 04:04:16",
+ "edited":"2012-11-20 04:04:16",
+ "title":"",
+ "body":"Hi Nickordo",
+ "app":"",
+ "verb":"post",
+ "object_type":"",
+ "target_type":"",
+ "permalink":"",
+ "location":"",
+ "longlat":"",
+ "owner":{
+ "name":"Indigo",
+ "address":"indigo@podunk.edu",
+ "url":"http:\/\/podunk.edu",
+ "photo":{
+ "mimetype":"image\/jpeg",
+ "src":"http:\/\/podunk.edu\/photo\/profile\/m\/5"
+ },
+ "guid":"kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA",
+ "guid_sig":"PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK-R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q"
+ },
+ "author":{
+ "name":"Indigo",
+ "address":"indigo@podunk.edu",
+ "url":"http:\/\/podunk.edu",
+ "photo":{
+ "mimetype":"image\/jpeg",
+ "src":"http:\/\/podunk.edu\/photo\/profile\/m\/5"
+ },
+ "guid":"kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA",
+ "guid_sig":"PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK-R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q"
+ }
+ }
+ }
+ }
+
+
+
+Und das ist das Paket (die ursprüngliche Nachricht). Example.com wandelt diese in eine Form um, die von Nickordo eingesehen werden kann, und benachrichtigt Nickordo, dass es eine neue Nachricht gibt. Podunk.edu **könnte** feststellen, dass noch andere Pakete auf example.com warten. Wenn dies der Fall ist, kann es auch alle anderen wartenden Pakete zu diesem Zeitpunkt versenden. Jedes dieser Pakete ist mit der ursprünglichen Sendungsnummer versehen. \ No newline at end of file
diff --git a/doc/de/adminmanual/Primary-Directory.md b/doc/de/adminmanual/Primary-Directory.md
new file mode 100644
index 000000000..9de1d6c98
--- /dev/null
+++ b/doc/de/adminmanual/Primary-Directory.md
@@ -0,0 +1,35 @@
+### Primäres Verzeichnis
+
+Standardmäßig verwendet Hubzilla verfügbare Verzeichnisse im Internet, die Ihnen weltweit verfügbare Kanäle anzeigen.
+
+Es gibt bestimmte Szenarien, in denen Sie Ihren eigenen Verzeichnis-Server benötigen, mit dem Sie mehrere Hubs verbinden können. Dadurch werden die Kanäle, die in allen Ihren Hubs erscheinen, auf die Kanäle der Hubs beschränkt, die mit Ihrem Verzeichnis-Server verbunden sind.
+
+#### Anleitungen zum Einrichten eines Hubs als primäres Verzeichnis für eine Reihe von privaten Hubs.
+
+- Öffnen Sie auf dem Hub, der der Directory Server sein wird, die Datei .htconfig.php und setzen Sie
+ `App::$config['system']['directory_mode'] = DIRECTORY_MODE_PRIMARY;`
+
+```
+Standardmäßig sollte dies bereits **DIRECTORY_MODE_NORMAL** sein, also ändern Sie diese Zeile einfach in **DIRECTORY_MODE_PRIMARY**.
+```
+
+- Als Nächstes gehen Sie für jeden Hub (einschließlich des Verzeichnisservers) von einem Terminal aus in den Ordner, in dem er installiert ist, und führen Folgendes aus:
+
+ `util/config system directory_realm YOURREALMNAME`
+ (**YOURREALMNAME** kann jeder beliebige Name für Ihren Realm sein)
+
+ dann:
+
+ `util/config system realm_token THEPASSWORD`
+ (**THEPASSWORD** ist das Passwort, das Sie für Ihren Realm verwenden möchten)
+ **HINWEIS:** Verwenden Sie für jeden Hub denselben Realm-Namen und dasselbe Passwort.
+
+- Führen Sie schließlich für jeden „Client“-Hub (von einem Terminal aus) aus:
+
+ `util/config system directory_server https://theaddressofyourdirectoryserver.com`
+
+Wenn Sie sich nun das Verzeichnis jedes Hubs ansehen, sollten nur die Kanäle angezeigt werden, die auf den Hubs in Ihrem Realm existieren. Ich habe bisher mit zwei Hubs getestet, und es scheint gut zu funktionieren. Channels, die in jedem Hub erstellt wurden, werden im Primärverzeichnis und anschließend im Verzeichnis aller Client-Hubs angezeigt.
+
+#### Probleme
+
+Als ich den ersten Hub erstellte, lief er etwa eine Stunde lang, bevor ich ihn auf PRIMARY_MODE umstellte, und nachdem ich ihn umgestellt hatte, waren noch einige Kanäle aus der gesamten Matrix im Verzeichnis vorhanden. Ich habe sie aus der xchan-Tabelle gelöscht, und das scheint das Problem behoben zu haben. \ No newline at end of file
diff --git a/doc/de/adminmanual/Schema-development.md b/doc/de/adminmanual/Schema-development.md
new file mode 100644
index 000000000..5e2304e61
--- /dev/null
+++ b/doc/de/adminmanual/Schema-development.md
@@ -0,0 +1,45 @@
+### Hubzilla-Entwicklung - ein Leitfaden für das Schemasystem
+
+Ein Schema ist, kurz gesagt, eine Sammlung von Einstellungen für eine Reihe von Variablen, die bestimmte Elemente eines Themes definieren. Ein Schema wird so geladen, als wäre es Teil der config.php und hat Zugang zu allen Informationen. Das bedeutet, dass es identitätsbewusst ist und für einige interessante Dinge verwendet werden kann. Man könnte z. B. die Optionen nach Dienstklassen einschränken oder verschiedenen Mitgliedern unterschiedliche Optionen anbieten.
+
+Standardmäßig filtern wir nur danach, ob der Expertenmodus aktiviert ist oder nicht. Wenn der Expertenmodus aktiviert ist, werden dem Mitglied alle Optionen angezeigt. Ist dies nicht der Fall, stehen nur Schema, Hintergrundbild, Schriftart und Iconset zur Auswahl.
+
+Ein Schema wird *erst nach* den persönlichen Einstellungen des Mitglieds geladen. Um einem Mitglied die Möglichkeit zu geben, einen bestimmten Aspekt eines Schemas zu überschreiben, würden Sie daher die folgende Syntax verwenden:
+
+```
+ if (! $foo)
+ $foo = 'bar';
+```
+
+Es gibt jedoch Umstände - insbesondere bei positionalen Elementen -, unter denen es wünschenswert (oder notwendig) sein kann, die Einstellungen eines Mitglieds zu überschreiben. In diesem Fall ist die Syntax noch einfacher:
+
+```
+ $foo = 'bar';
+```
+
+Die Mitglieder werden Ihnen dies jedoch nicht danken, also verwenden Sie es nur, wenn es erforderlich ist.
+Wenn keine persönlichen Optionen eingestellt sind und kein Schema ausgewählt ist, wird zunächst versucht, ein Schema mit dem Dateinamen „default.php“ zu laden. Diese Datei sollte niemals in ein Thema eingebunden werden. Wenn dies der Fall ist, wird es zu Konflikten kommen, wenn andere ihren Code aktualisieren. Vielmehr sollte dies von den Administratoren für jede Seite einzeln festgelegt werden. default.php und default.css MÜSSEN Symlinks zu bestehenden Schemadateien sein.
+
+Ihr Schema muss - und sollte - nicht alle diese Werte enthalten. Nur die Werte, die von den Standardwerten abweichen, sollten aufgeführt werden. Dies gibt Ihnen einige sehr mächtige Optionen mit sehr wenigen Zeilen Code.
+
+Beachten Sie, dass die verfügbaren Optionen von Thema zu Thema unterschiedlich sind. Für das Redbasic-Theme sind folgende Optionen verfügbar:
+
+- nav_colour Die Farbe der Navigationsleiste. Die Optionen sind rot, schwarz und silber. Alternativ kann man $nav_bg_1, $nav_bg_2, $nav_bg_3 und $nav_bg_4 einstellen, um Farbverläufe und Hover-Effekte zu erzielen.
+- banner_colour Die Schriftfarbe des Bannerelements. Akzeptiert einen RGB- oder Hex-Wert.
+- bgcolour Legt die Hintergrundfarbe des Körpers fest. Akzeptiert einen RGB- oder Hex-Wert.
+- background_image Legt ein Hintergrundbild fest. Akzeptiert eine URL oder einen Pfad.
+- item_colour Legt die Hintergrundfarbe von Elementen fest. Akzeptiert einen RGB- oder Hex-Wert.
+- item_opacity Legt die Deckkraft der Elemente fest. Akzeptiert einen Wert von 0,01 bis 1
+- toolicon_colour Legt die Farbe der Werkzeugsymbole fest. Akzeptiert einen RGB- oder Hex-Wert.
+- toolicon_activecolour Legt die Farbe der aktiven oder mit dem Mauszeiger gehaltenen Werkzeugsymbole fest.
+- font_size Legt die Größe der Schriftarten in Artikeln und Beiträgen fest. Akzeptiert px oder em.
+- body_font_size Legt die Größe der Schriftarten auf der Body-Ebene fest. Akzeptiert px oder em.
+- font_colour Legt die Farbe der Schrift fest. Akzeptiert einen RGB- oder Hex-Wert.
+- radius Legt den Radius der Ecken fest. Akzeptiert einen Zahlenwert und ist immer in px.
+- shadow Legt die Größe der Schatten fest, die bei Inline-Bildern angezeigt werden. Akzeptiert einen numerischen Wert. Hinweis: Schatten werden nicht auf Smileys angewendet.
+- converse_width Legt die maximale Breite des Inhaltsbereichs in px fest.
+- nav_min_opacity
+- top_photo
+- antwort_foto
+
+Wenn eine Datei your_schema_name.css gefunden wird, wird der Inhalt dieser Datei an das Ende von style.css angehängt. Dies gibt dem Schema-Entwickler die Möglichkeit, jede Style-Komponente zu überschreiben. \ No newline at end of file
diff --git a/doc/de/adminmanual/Widgets.md b/doc/de/adminmanual/Widgets.md
new file mode 100644
index 000000000..a222a0533
--- /dev/null
+++ b/doc/de/adminmanual/Widgets.md
@@ -0,0 +1,99 @@
+### Grundlegende Widgets
+
+Einige/viele dieser Widgets haben Einschränkungen, die die Art der Seite, auf der sie erscheinen können, einschränken oder eine Anmeldung erfordern können
+
+- clock - zeigt die aktuelle Zeit an
+ - args: military (1 oder 0) - verwendet 24-Stunden-Zeit anstelle von AM/PM
+- profile - zeigt eine Profil-Seitenleiste auf Seiten an, die Profile laden (Seiten mit Nickname in der URL)
+- tagcloud - zeigt eine Tagcloud von Webseiten-Elementen an
+ - args: count - Anzahl der Elemente, die zurückgegeben werden sollen (Standardwert 24)
+- collections - Auswahl der Privatsphärengruppe für den aktuell eingeloggten Kanal
+ - args: mode - eine der Optionen „conversation“, „group“, „abook“ je nach Modul
+- suggestions - Freundesvorschläge für den aktuell angemeldeten Kanal
+- follow - zeigt ein Textfeld zum Folgen eines anderen Channels an
+- notes - privater Notizbereich für den aktuell eingeloggten Kanal, wenn die Funktion private_notes aktiviert ist
+- savedsearch - Netzwerk-/Matrixsuche mit Speicherung - muss eingeloggt und die savedsearch-Funktion aktiviert sein
+- filer - Auswahl abgelegter Elemente aus dem Netzwerk/Matrix-Stream - muss eingeloggt sein
+- archive - Datumsbereichswähler für Netzwerk- und Kanalseiten
+ - args: 'wall' - 1 oder 0, Begrenzung auf Wall-Posts oder Netzwerk-/Matrix-Posts (Standard)
+- fullprofile - wie das aktuelle Profil
+- categories - Kategorien-Filter (Channel-Seite)
+- tagcloud_wall - Tagcloud nur für Channelseite
+ - args: 'limit' - Anzahl der Tags, die zurückgegeben werden sollen (Standardwert 50)
+- catcloud_wall - Tagcloud für die Kategorien der Kanalseite
+ - args: 'limit' - Anzahl der zurückzugebenden Kategorien (Standardwert 50)
+- affinity - Affinitäts-Schieberegler für die Netzwerk-Seite - muss eingeloggt sein
+- settings_menu - Seitenleistenmenü für die Einstellungsseite, muss eingeloggt sein
+- mailmenu - Seitenleistenmenü für die Seite mit den privaten Nachrichten - muss eingeloggt sein
+- design_tools - Menü für Design-Tools für Seiten zur Erstellung von Webseiten, muss eingeloggt sein
+- findpeople - Werkzeuge, um andere Kanäle zu finden
+- photo_albums - Liste der Fotoalben des aktuellen Seitenbesitzers mit einem Auswahlmenü
+- vcard - Mini-Profil-Seitenleiste für die Person von Interesse (Seitenbesitzer, was auch immer)
+- dirsafemode - Werkzeug zur Verzeichnisauswahl - nur auf Verzeichnisseiten
+- dirsort - Werkzeug zur Verzeichnisauswahl - nur auf Verzeichnisseiten
+- dirtags - Verzeichnis-Werkzeug - nur auf Verzeichnisseiten
+- menu_preview - Vorschau eines Menüs - nur auf Menübearbeitungsseiten
+- chatroom_list - Liste der Chaträume für den Seitenbesitzer
+- bookmarkedchats - Liste der Chaträume mit Lesezeichen, die auf dieser Seite für den aktuellen Betrachter gesammelt wurden
+- suggestedchats - „interessante“ Chaträume für den aktuellen Betrachter ausgewählt
+- item - zeigt ein einzelnes Webpage-Element nach Mitte oder Seitentitel an
+ - args:
+ - channel_id - Kanal, dem der Inhalt gehört, Standard ist die profile_uid
+ - mid - message_id der anzuzeigenden Webseite (muss eine Webseite sein, kein Konversationselement)
+ - title - URL-Seitentitel der Webseite (muss entweder title oder mid enthalten)
+- photo - Anzeige eines einzelnen Fotos
+ - args:
+ - src - URL des Fotos, muss http oder https sein
+ - zrl - zid-authentifizierter Link verwenden
+ - style - CSS-Style-String
+- cover_photo - zeigt das Titelbild für den ausgewählten Kanal an
+ - args:
+ - channel_id - zu verwendender Kanal, Standard ist die profile_uid
+ - style - CSS-Style-String (standardmäßig wird die Größe dynamisch an die Breite des Bereichs angepasst)
+
+- photo_rand - zeigt ein zufälliges Foto aus einem Ihrer Fotoalben an. Fotoberechtigungen werden beachtet
+ - args:
+ - album - Name des Albums (sehr zu empfehlen, wenn Sie viele Fotos haben)
+ - scale - typischerweise 0 (Originalgröße), 1 (1024px), 2, (640px) oder 3 (320px)
+ - style - CSS-Style-String
+ - channel_id - wenn nicht Ihre eigene
+- random_block - zeigt ein zufälliges Blockelement aus Ihrer Sammlung von Webpage Design Tools an. Erlaubnisse werden beachtet.
+ - args:
+ - contains - gibt nur Blöcke zurück, die den String contains im Blocknamen enthalten
+ - channel_id - wenn nicht Ihre eigene
+- tasklist - liefert eine Aufgaben- oder To-Do-Liste für den aktuell eingeloggten Channel.
+ - args:
+ - all - zeigt erledigte Aufgaben an, wenn all ungleich Null ist.
+- forums - liefert eine Liste der verbundenen öffentlichen Foren mit ungesehenen Beiträgen für den aktuell eingeloggten Channel.
+- activity - liefert eine Liste der Autoren von ungelesenen Netzwerkinhalten für den aktuell eingeloggten Channel.
+- album - stellt ein Widget zur Verfügung, das ein komplettes Fotoalbum aus den Alben des Seitenbesitzers enthält; dies kann zu groß sein, um in einem Seitenleistenbereich dargestellt zu werden, und wird am besten als Widget für einen Inhaltsbereich implementiert.
+ - args:
+ - album - Name des Albums
+ - title - optionaler Titel, wenn nicht vorhanden wird der Albumname verwendet
+
+#### **Neue Widgets erstellen**
+
+#### **Klassen-Widgets**
+
+Um ein klassenbasiertes Widget namens 'slugfish' zu erstellen, erstellen Sie eine Datei mit folgendem Inhalt:
+
+```php
+<?php
+namespace Zotlabs\Widget;
+class Slugfish {
+
+ function widget($args) {
+
+ ... der Widget-Code kommt hier hin.
+ ... Die Funktion gibt einen String zurück, der den HTML-Inhalt des Widgets darstellt.
+ ... $args ist ein benanntes Array, dem alle [var]-Variablen aus dem Layout-Editor übergeben werden
+ ... Zum Beispiel füllt [widget=slugfish][var=count]3[/var][/widget] $args mit
+ ... [ 'count' => 3 ]
+ }
+
+}
+
+?>
+```
+
+Die resultierende Datei kann in Zotlabs/Widgets/Slugfish.php abgelegt werden. Sie kann auch von einem Git-Repository mit util/add_widget_repo verlinkt werden. \ No newline at end of file
diff --git a/doc/de/adminmanual/Zot---A-High-Level-Overview.md b/doc/de/adminmanual/Zot---A-High-Level-Overview.md
new file mode 100644
index 000000000..c86febf6f
--- /dev/null
+++ b/doc/de/adminmanual/Zot---A-High-Level-Overview.md
@@ -0,0 +1,111 @@
+### Nomad - Ein Überblick auf oberer Ebene
+
+Hier ist eine allgemeine Beschreibung der Funktionsweise von Nomad.
+
+In diesem Beispiel wird „Indigo“ eine öffentliche Nachricht von seiner Website „podunk.edu“ aus versenden. „Nickordo„ ist ein Empfänger auf einer anderen Website (“example.com").
+
+Indigo stellt seine Nachricht zuerst auf podunk.edu ein. podunk.edu schaut nach, wer die Nachricht erhalten soll und findet Nickordo. Nickordo postet normalerweise von example.com aus, also fügen wir dieses Ziel zu unserer Liste der Empfänger hinzu. Wir können auch andere Ziele für Nickordo und alle anderen, die Indigos Beiträge verfolgen, hinzufügen.
+
+In diesem Beispiel stellen wir fest, dass wir nur einen bekannten Empfänger an einem bekannten Ort haben.
+
+Wir senden ein Paket an example.com:
+
+
+
+ {
+ "type":"notify",
+ "sender":{
+ "guid":"kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA",
+ "guid_sig":"PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK-R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q",
+ "url":"http:\/\/podunk.edu",
+ "url_sig":"T8Bp7j5DHHhQDCFcAHXfuhUfGk2P3inPbImwaXXF1xJd3TGgluoXyyKDx6WDm07x0hqbupoAoZB1qBP3_WfvWiJVAK4N1FD77EOYttUEHZ7L43xy5PCpojJQmkppGbPJc2jnTIc_F1vvGvw5fv8gBWZvPqTdb6LWF6FLrzwesZpi7j2rsioZ3wyUkqb5TDZaNNeWQrIEYXrEnWkRI_qTSOzx0dRTsGO6SpU1fPWuOOYMZG8Nh18nay0kLpxReuHCiCdxjXRVvk5k9rkcMbDBJcBovhiSioPKv_yJxcZVBATw3z3TTE95kGi4wxCEenxwhSpvouwa5b0hT7NS4Ay70QaxoKiLb3ZjhZaUUn4igCyZM0h6fllR5I6J_sAQxiMYD0v5ouIlb0u8YVMni93j3zlqMWdDUZ4WgTI7NNbo8ug9NQDHd92TPmSE1TytPTgya3tsFMzwyq0LZ0b-g-zSXWIES__jKQ7vAtIs9EwlPxqJXEDDniZ2AJ6biXRYgE2Kd6W_nmI7w31igwQTms3ecXe5ENI3ckEPUAq__llNnND7mxp5ZrdXzd5HHU9slXwDShYcW3yDeQLEwAVomTGSFpBrCX8W77n9hF3JClkWaeS4QcZ3xUtsSS81yLrp__ifFfQqx9_Be89WVyIOoF4oydr08EkZ8zwlAsbZLG7eLXY"
+ },
+ "callback":"\/post",
+ "version":1,
+ "secret":"1eaa6613699be6ebb2adcefa5379c61a3678aa0df89025470fac871431b70467"
+ }
+
+In diesem Paket steht das Folgende:
+
+Ich bin Indigo und hier ist der Beweis. Ich poste von podunk.edu und hier ist der Beweis. Ich habe ein Paket für Sie. Die Kontrollnummer lautet „1eaa6613....“.
+
+Example.com nimmt dieses Paket an und sagt: „Moment mal - ich kenne Sie nicht. Ich möchte beweisen, wer du bist.“ Example.com stellt also eine Verbindung zu podunk.edu über eine „bekannte URL“ her, die wir für diesen Zweck verwenden, und sucht nach der oben genannten „guid“. Es sollte eine Reihe von Informationen zurückgeben, darunter auch einen öffentlichen Schlüssel. Example.com verwendet diesen Schlüssel, um die Signaturen in der Nachricht zu überprüfen und festzustellen, ob es tatsächlich eine Person namens Indigo bei podunk.edu gibt. Wir müssen dies nur einmal tun. (Beachten Sie, dass Indigo von jedem Ort aus posten kann. Wir müssen nur beweisen, dass es sich um Indigo handelt und dass Indigo beweisen kann, dass er von einem anderen Standort aus postet).
+
+Dann trennt example.com die Verbindung und weist darauf hin, dass eine Nachricht auf podunk.edu wartet. Entweder sofort oder immer dann, wenn der Drang besteht (je nachdem, wie wichtig Indigo für jemanden auf dieser Website ist), „ruft“ example.com podunk.edu an. Die Nachricht lautet in etwa so:
+
+
+
+ {
+ "type":"pickup",
+ "url":"http:\/\/example.com",
+ "callback_sig":"teE1_fLIqfyeCuZY4iS7sNU8jUlUuqYOYBiHLarkC99I9K-uSr8DAwVW8ZPZRK-uYdxRMuKFb6cumF_Gt9XjecCPBM8HkoXHOi_VselzJkxPwor4ZPtWYWWaFtRfcAm794LrWjdz62zdESTQd2JJIZWbrli1sUhK801BF3n0Ye6-X1MWhy9EUTVlNimOeRipcuD_srMhUcAXOEbLlrugZ8ovy2YBe6YOXkS8jj0RSFjsOduXAoVhQmNpcobSYsDvaQS3e3MvE6-oXE602zGQhuNLr7DIMt9PCdAeQo-ZM-DHlZGCkGk4O2oQFCXFzGPqLUMWDACGJfTfIWGoh_EJqT_SD5b_Yi_Wk9S1lj7vb-lmxe5JuIf7ezWzHoBT8vswnZxPYlidH2i9wapdzij9il_qqcCWWHIp7q_XkY_Zj52Z4r4gdmiqM-8y1c_1SDX7hrJFRwqL_PKFbEvyi5nMWTEzqp55Tay5Woiv19STK_H_8ufFfD9AOkYnk6rIOMsk9dn3a5tAFpDRyRndXkBWAXwiJjiND2zjue7BFu7Ty40THXcfYRh1a5XrAXcaGeYuagg-8J9tAufu9_LY3qGazFg8kRBVMOn4M8DRKSIhKj7z4MnbYL0s09gREojy4jqWO3VkaOjP2jUGzoPuUDLasudE1ehWFq0K_MTQNavgmp8",
+ "callback":"http:\/\/example.com\/post",
+ "secret":"1eaa6613699be6ebb2adcefa5379c61a3678aa0df89025470fac871431b70467",
+ "secret_sig":"O7nB4_UJHBXi28Suwl9LBZF9hI_9KGVTgehnUlWF1oYMNRnBbVHB9lzUfAoalvp3STbU3xJbtD_S58tv6MfV7J5j2V_S1W5ex3dulmDGB8Pt_7Fe5mbEPmjQFcfv3Eg5dUjYIuDl0TDScfrHyImj7RZIWHbwd7wWVoMzzDa_o33klpYmKZCBvObCh55bRrlFkXZs_dRuOiPwkfX0C6_XES4OyOIYl45V30rdhmf-STrf4L9dKYy_axQ12RIwRcKychvVLwlUJn3bn9lgNXRRU_HTne-09OPcJbUOdcD3DkFoKOxMULBNKPHzsCau0ICYug7S0EP6LpCom_mW78s08LyVA1vYeFZjevBCiGecj57yIAQDYi6_rpWJfihYaWHRN0oqtScUR4Bdf0bQbEHxMs4zAtrOAxfyJCbi6U1pfnGgzXzB9ulOYGnVGNTF7Ey4K7FOZIBtk0ILY2JfvBUaVvVs8ttagOOHmhWhnbCvrnOFlkNdlce7zoJCSUJENUOCYmTRfwB_Jno5fAzRnrsYU3_Z-l1mzniU_OmUPz8mPEh7PwhkqAiVlyaM-q15gn7l2lAIDk9kp2X_iCme7v4V0ADN_DbpaI_0-6mPw5HLbKrCsA-sxlSMB4DO4lDCHYkauj0l25sbfroRWB_hax1O4Q0oWyOlVJLUqEC5nuUJCCE"
+ }
+
+Was diese Nachricht aussagt, ist: Hier ist example.com, ich habe einen Beweis, und ich bin hier, um ein Paket abzuholen. Hier ist die Sendungsverfolgungsnummer, und hier ist der Beweis, dass dies die Sendungsverfolgungsnummer ist, die Sie vermutlich an example.com gesendet haben.
+
+Gut genug. Podunk.edu überprüft die Geschichte und tatsächlich, es ist example.com, und ja, da wartet ein Paket mit dieser Kontrollnummer. Hier ist das Paket...
+
+
+
+ {
+ "success":1,
+ "pickup":{
+ "notify":{
+ "type":"notify",
+ "sender":{
+ "guid":"kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA",
+ "guid_sig":"PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK-R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q",
+ "url":"http:\/\/z.podunk.edu",
+ "url_sig":"T8Bp7j5DHHhQDCFcAHXfuhUfGk2P3inPbImwaXXF1xJd3TGgluoXyyKDx6WDm07x0hqbupoAoZB1qBP3_WfvWiJVAK4N1FD77EOYttUEHZ7L43xy5PCpojJQmkppGbPJc2jnTIc_F1vvGvw5fv8gBWZvPqTdb6LWF6FLrzwesZpi7j2rsioZ3wyUkqb5TDZaNNeWQrIEYXrEnWkRI_qTSOzx0dRTsGO6SpU1fPWuOOYMZG8Nh18nay0kLpxReuHCiCdxjXRVvk5k9rkcMbDBJcBovhiSioPKv_yJxcZVBATw3z3TTE95kGi4wxCEenxwhSpvouwa5b0hT7NS4Ay70QaxoKiLb3ZjhZaUUn4igCyZM0h6fllR5I6J_sAQxiMYD0v5ouIlb0u8YVMni93j3zlqMWdDUZ4WgTI7NNbo8ug9NQDHd92TPmSE1TytPTgya3tsFMzwyq0LZ0b-g-zSXWIES__jKQ7vAtIs9EwlPxqJXEDDniZ2AJ6biXRYgE2Kd6W_nmI7w31igwQTms3ecXe5ENI3ckEPUAq__llNnND7mxp5ZrdXzd5HHU9slXwDShYcW3yDeQLEwAVomTGSFpBrCX8W77n9hF3JClkWaeS4QcZ3xUtsSS81yLrp__ifFfQqx9_Be89WVyIOoF4oydr08EkZ8zwlAsbZLG7eLXY"
+ },
+ "callback":"\/post",
+ "version":1,
+ "secret":"1eaa6613699be6ebb2adcefa5379c61a3678aa0df89025470fac871431b70467"
+ },
+ "message":{
+ "message_id":"10b049ce384cbb2da9467319bc98169ab36290b8bbb403aa0c0accd9cb072e76@podunk.edu",
+ "message_top":"10b049ce384cbb2da9467319bc98169ab36290b8bbb403aa0c0accd9cb072e76@podunk.edu",
+ "message_parent":"10b049ce384cbb2da9467319bc98169ab36290b8bbb403aa0c0accd9cb072e76@podunk.edu",
+ "created":"2012-11-20 04:04:16",
+ "edited":"2012-11-20 04:04:16",
+ "title":"",
+ "body":"Hi Nickordo",
+ "app":"",
+ "verb":"post",
+ "object_type":"",
+ "target_type":"",
+ "permalink":"",
+ "location":"",
+ "longlat":"",
+ "owner":{
+ "name":"Indigo",
+ "address":"indigo@podunk.edu",
+ "url":"http:\/\/podunk.edu",
+ "photo":{
+ "mimetype":"image\/jpeg",
+ "src":"http:\/\/podunk.edu\/photo\/profile\/m\/5"
+ },
+ "guid":"kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA",
+ "guid_sig":"PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK-R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q"
+ },
+ "author":{
+ "name":"Indigo",
+ "address":"indigo@podunk.edu",
+ "url":"http:\/\/podunk.edu",
+ "photo":{
+ "mimetype":"image\/jpeg",
+ "src":"http:\/\/podunk.edu\/photo\/profile\/m\/5"
+ },
+ "guid":"kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA",
+ "guid_sig":"PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK-R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q"
+ }
+ }
+ }
+ }
+
+
+
+Und das ist das Paket (die ursprüngliche Nachricht). Example.com wandelt diese in eine Form um, die von Nickordo eingesehen werden kann, und benachrichtigt Nickordo, dass es eine neue Nachricht gibt. Podunk.edu **könnte** feststellen, dass noch andere Pakete auf example.com warten. Wenn dies der Fall ist, kann es auch alle anderen wartenden Pakete zu diesem Zeitpunkt versenden. Jedes dieser Pakete ist mit der ursprünglichen Sendungsnummer versehen. \ No newline at end of file
diff --git a/doc/de/adminmanual/administration.md b/doc/de/adminmanual/administration.md
new file mode 100644
index 000000000..ab8cd21c6
--- /dev/null
+++ b/doc/de/adminmanual/administration.md
@@ -0,0 +1,13 @@
+### Administration
+
+#### Website-Verwaltung
+
+Die Verwaltung der Website erfolgt in der Regel über die Verwaltungsseite, die sich unter /admin auf Ihrer Website befindet. Um auf diese Seite zugreifen zu können, müssen Sie über Verwaltungsrechte für den Server verfügen. Administrationsrechte werden dem ersten Konto gewährt, das sich auf Ihrer Website registriert, **vorausgesetzt,** die E-Mail-Adresse dieses Kontos stimmt genau mit der E-Mail-Adresse überein, die Sie bei der Einrichtung als E-Mail-Adresse des Administrators angegeben haben.
+
+![adm01](/help/de/pic/adm01.png)
+
+Es gibt mehrere Möglichkeiten, wie dies fehlschlagen und das System ohne ein Administratorkonto bleiben kann, z. B. wenn das erste Konto, das erstellt wurde, eine andere E-Mail-Adresse als die bei der Einrichtung angegebene Administrator-E-Mail-Adresse angegeben hat.
+
+Aus Sicherheitsgründen gibt es auf dem System keine Webseite oder Schnittstelle, die Ihnen Administratorrechte verleiht. Wenn Sie eine Situation korrigieren müssen, in der ein System kein Administratorkonto hat, **muss** dies durch Bearbeiten der Kontotabelle in der Datenbank geschehen. Es gibt keine andere Möglichkeit. Dazu müssen Sie den Eintrag in der Kontotabelle finden, der zu dem gewünschten Administrator gehört, und „account_roles“ für diesen Eintrag auf 4096 setzen. Anschließend können Sie die Administratorseite über das Profilmenü Ihres Systems oder direkt über /admin aufrufen.
+
+Ein Hub kann mehrere Administratoren haben und die Anzahl der Administratoren ist nicht begrenzt. Wiederholen Sie den obigen Vorgang für jedes Konto, das Sie mit Administrationsrechten ausstatten möchten. \ No newline at end of file
diff --git a/doc/de/adminmanual/advanced_configurations.md b/doc/de/adminmanual/advanced_configurations.md
new file mode 100644
index 000000000..fb4644384
--- /dev/null
+++ b/doc/de/adminmanual/advanced_configurations.md
@@ -0,0 +1,326 @@
+### Erweiterte Konfigurationen für Administratoren
+
+*Dieses Dokument setzt voraus, dass Sie ein Administrator sind.*
+
+Hubzilla enthält viele Konfigurationsoptionen, die im Hauptverwaltungsbereich verborgen sind. Im Allgemeinen handelt es sich dabei um Optionen, die als zu nischenartig, fortgeschritten oder verwirrend angesehen werden.
+
+Diese Einstellungen können über die Shell vom Webverzeichnis der obersten Ebene aus mit folgender Syntax geändert werden
+
+`util/config cat key value`
+für eine Website-Konfiguration, oder
+
+`util/pconfig channel_id cat key value`
+für eine Mitgliedskonfiguration.
+
+Für eine Site-Konfiguration ist eine weitere Möglichkeit, eine Zeile in .htconfig.php einzufügen, mit der Syntax:
+`App::$config['cat']['Schlüssel'] = 'Wert';`
+
+
+
+#### Mitgliederkonfiguration (pconfig)
+
+- system.always_my_theme
+
+ Verwende immer dein eigenes Theme, wenn du Kanäle im selben Hub anschaust. Dies führt zu einigen recht einfallsreichen Problemen, wenn Kanäle mit themenabhängigen Comanche angezeigt werden.
+
+- system.blocked
+
+ Ein Array von Xchans, die von diesem Channel blockiert werden. Technisch gesehen ist dies eine versteckte Konfiguration und gehört hierher, aber Addons (insbesondere Superblock) haben dies in der Benutzeroberfläche verfügbar gemacht.
+
+- system.default_cipher
+
+ Setzt den Standardchiffre, der für E2EE-Elemente verwendet wird.
+
+- system.display_friend_count
+
+ Legt die Anzahl der Verbindungen fest, die im Widget des Verbindungsprofils angezeigt werden sollen.
+
+- system.do_not_track
+
+ Wie der Browser-Header. Dies wird viele identitätsbasierte Funktionen unterbrechen. Sie sollten wirklich nur Berechtigungen setzen, die Sinn machen.
+
+- system.forcepublicuploads
+
+ Erzwingt, dass hochgeladene Fotos öffentlich sind, wenn sie als Wandelemente hochgeladen werden. Es ist viel sinnvoller, die Berechtigungen von vornherein richtig zu setzen. Tun Sie das stattdessen.
+
+- system.network_page_default
+
+ Legt die Standardparameter für die Anzeige der Netzwerkseite fest. Dies sollte den gleichen Querystring enthalten wie die manuelle Filterung.
+
+- system.paranoia
+
+ Legt die Sicherheitsstufe der IP-Überprüfung fest. Wenn sich die IP-Adresse einer angemeldeten Sitzung ändert, wird diese Stufe angewendet, um festzustellen, ob das Konto als Sicherheitsverletzung abgemeldet werden sollte. Die Optionen sind: 0 - keine IP-Überprüfung 1 - 3 Oktette prüfen 2 - 2 Oktette prüfen 3 - Prüfung auf alle Unterschiede
+
+- system.prevent_tag_hijacking
+
+ Verhindert, dass fremde Netzwerke Hashtags in Ihren Beiträgen klauen und sie auf ihre eigenen Ressourcen leiten.
+
+- system.startpage
+
+ Eine weitere dieser technisch versteckten Konfigurationen, die von Addons zur Verfügung gestellt werden. Legt die Standardseite fest, die beim Einloggen angezeigt wird. Diese wird der Benutzeroberfläche durch das startpage-Addon zur Verfügung gestellt.
+
+- system.taganyone
+
+ Erfordert, dass die gleichnamige Konfiguration aktiviert ist. Erlaubt das @mention tagging von jedem, egal ob man verbunden ist oder nicht. Dies skaliert nicht.
+
+- system.anonymous_kommentare
+
+ Standardmäßig oder wenn auf 1 gesetzt, können benutzerdefinierte Berechtigungen gesetzt werden, um anonyme (moderierte) Kommentare wie bei WordPress zu erlauben, die vom Channel-Besitzer moderiert werden. Ist der Wert auf 0 gesetzt, kann kein Mitglied Ihrer Website dies auswählen oder aktivieren.
+
+- system.user_scalable
+
+ Bestimmt, ob die App auf Touchscreens skalierbar ist. Standardmäßig auf on, zum Deaktivieren auf zero setzen - real zero, nicht nur false.
+
+#### Konfiguration der Website
+
+- randprofile.check
+
+ Bei der Anforderung eines Zufallsprofils zuerst prüfen, ob es tatsächlich existiert
+
+- randprofile.retry
+
+ Anzahl der Versuche, ein zufälliges Profil zu erhalten
+
+- system.admin_email
+
+ Gibt die E-Mail-Adresse des Administrators für diese Site an. Diese wird bei der Installation festgelegt.
+
+- system.authlog
+
+ Logdatei, die für die Protokollierung von Authentifizierungsfehlern verwendet wird. Wird verwendet, um eine Verbindung zu serverseitiger Software wie fail2ban herzustellen. Auth-Fehler werden auch in den Hauptprotokollen protokolliert.
+
+- system.auto_channel_create
+
+ Fügt die notwendigen Formularelemente hinzu, um den ersten Kanal auf der Kontoregistrierungsseite zu erstellen, und erstellt ihn (möglicherweise nach einer E-Mail-Validierung oder der Genehmigung des Administrators). Dies schließt die Möglichkeit aus, einen Channel von einer anderen Website als ersten auf dieser Website für ein neues Konto erstellten Channel zu importieren. Verwendung mit system.default_permissions_role zur Rationalisierung der Registrierung.
+
+- system.auto_follow
+
+ Der erste Channel eines Accounts folgt automatisch den hier aufgelisteten Channels - kommagetrennte Liste von Webbies (member@hub Adressen).
+
+- system.blacklisted_sites
+
+ Ein Array spezifischer Hubs, die von diesem Hub komplett blockiert werden sollen.
+
+- system.block_public_search
+
+ Ähnlich wie block_public, mit dem Unterschied, dass nur der öffentliche Zugang zu den Suchfunktionen blockiert wird. Nützlich für Seiten, die öffentlich sein wollen, aber von Suchmaschinen überrannt werden.
+
+- system.cron_hour
+
+ Geben Sie eine Stunde an, in der cron_daily ausgeführt werden soll. Standardmäßig, ohne Konfiguration, wird dies um Mitternacht UTC ausgeführt.
+
+- system.default_permissions_role
+
+ Wenn dieser Wert auf einen gültigen Namen für eine Berechtigungsrolle gesetzt ist, wird diese Rolle für den ersten Channel verwendet, der von einem neuen Konto erstellt wird, und es wird nicht nach dem „Channel-Typ“ im Formular zur Channel-Erstellung gefragt. Beispiele für gültige Namen sind: 'social', 'social_restricted', 'social_private', 'forum', 'forum_restricted' und 'forum_private'. Lesen Sie [hier](https://hub.hubzilla.hu/help/roles) mehr über Berechtigungsrollen.
+
+- system.default_profile_photo
+
+ Legt das Profilfoto fest, mit dem neue Channels beginnen. Dies sollte den Namen eines Verzeichnisses enthalten, das sich unter images/default_profile_photos/ befindet, oder es sollte nicht gesetzt werden. Wenn es nicht gesetzt ist, wird 'rainbow_man' angenommen.
+
+- system.directorytags
+
+ Legt die Anzahl der Schlüsselwort-Tags fest, die auf der Verzeichnisseite angezeigt werden. Die Voreinstellung ist 50, wenn sie nicht auf eine positive ganze Zahl gesetzt wird.
+
+- system.disable_directory_keywords
+
+ Wenn '1', werden keine Verzeichnisschlüsselwörter angezeigt. Wenn der Hub ein Verzeichnisserver ist, verhindern Sie die Rückgabe von Schlüsselwörtern an alle Verzeichnis-Clients. Bitte setzen Sie dies nicht für Verzeichnisserver im Bereich RED_GLOBAL.
+
+- system.disable_discover_tab
+
+ Hiermit können Sie die Fähigkeit, öffentliche Inhalte von externen Sites zu entdecken, vollständig deaktivieren.
+
+- system.disable_dreport
+
+ Wenn '1', werden keine Zustellungsberichte gespeichert oder verlinkt.
+
+- system.dlogfile
+
+ Logdatei, die für die Protokollierung von Entwicklungsfehlern verwendet wird. Genau dasselbe wie logger sonst. Dies ist keine Magie und erfordert Ihre eigenen Logging-Anweisungen. Entwickler-Tool.
+
+- system.email_notify_icon_url
+
+ URL des Bildes (32x32), das in E-Mail-Benachrichtigungen (HTML-Bodies) angezeigt werden soll.
+
+- system.expire_delivery_reports
+
+ Verfallsdatum in Tagen für Zustellungsberichte - Standardwert 10
+
+- system.expire_limit
+
+ Nicht mehr als diese Anzahl von Beiträgen pro Kanal pro Ablauflauf ablaufen lassen, um den Speicher nicht zu erschöpfen. Voreinstellung 5000.
+
+- system.photo_storage_type
+
+ Wenn '1', wird das Dateisystem anstelle der SQL-Datenbank zum Speichern der Thumbnails verwendet. Voreinstellung ist '0'. Eingeführt in 4.2
+
+- system.hidden_version_siteinfo
+
+ Wenn 'true', wird die Softwareversion nicht auf den Seiten von siteinfo angezeigt (system.hide_version verbirgt die Version auch auf diesen Seiten, diese Einstellung verbirgt *nur* die Version auf den Seiten von siteinfo).
+
+- system.hide_help
+
+ Link zur Hilfedokumentation nicht in der Navigationsleiste anzeigen
+
+- system.hide_in_statistiken
+
+ Weist die roten Statistikserver an, diesen Hub in Hublisten komplett auszublenden.
+
+- system.hide_version
+
+ Wenn true, wird die Softwareversion auf Webseiten und Tools nicht angezeigt. (*) Muss in .htconfig.php eingestellt werden.
+
+- system.ignore_imagick
+
+ Ignoriert imagick und verwendet GD, auch wenn imagick auf dem Server installiert ist. Verhindert einige Probleme mit PNG-Dateien in älteren Versionen von imagick.
+
+- system.max_daily_registrations
+
+ Legt die maximale Anzahl der an einem Tag erlaubten Neuanmeldungen fest. Nützlich, um eine Überzeichnung zu verhindern, wenn das Projekt in der Öffentlichkeit bekannt gemacht wird.
+
+- system.max_import_size
+
+ Falls konfiguriert, die maximale Länge einer importierten Textnachricht. Diese wird normalerweise auf 200 KByte oder mehr belassen, um private Friendica-Fotos, die eingebettet sind, unterzubringen.
+
+- system.max_tagged_forums
+
+ Spam-Schutz. Begrenzt die Anzahl der getaggten Foren, die in jedem Beitrag erkannt werden. Standard ist 2. Nur die ersten 'n' Tags werden als Foren zugestellt, die anderen verursachen keine Zustellung.
+
+- system.minimum_feedcheck_minutes
+
+ Das minimale Intervall zwischen der Abfrage von RSS-Feeds. Wenn dieses Intervall kleiner als das Cron-Intervall ist, werden die Feeds bei jedem Cronjob abgefragt. Der Standardwert ist 60, wenn er nicht festgelegt wurde. Die Site-Einstellung kann auch für jeden einzelnen Kanal durch eine Serviceklassen-Einstellung mit dem treffenden Namen „minimum_feedcheck_minutes“ außer Kraft gesetzt werden.
+
+- system.no_age_restriction
+
+ Beschränken Sie die Registrierung nicht auf Personen über 13 Jahren. In vielen Ländern ist es gesetzlich vorgeschrieben, dass das Alter angegeben werden muss und dass alle persönlichen Daten von Minderjährigen gesperrt werden müssen.
+
+- system.object_cache_days
+
+ Legt fest, wie lange zwischengespeicherte eingebettete Inhalte ohne erneutes Abrufen verwendet werden können. Die Voreinstellung ist 30 Tage.
+
+- system.openssl_conf_file
+
+ Geben Sie eine Datei an, die die OpenSSL-Konfiguration enthält. Wird in einigen Windows-Installationen benötigt, um die openssl-Konfigurationsdatei auf dem System zu finden. Lesen Sie zuerst den Code. Wenn Sie den Code nicht lesen können, spielen Sie nicht mit ihm.
+
+- system.openssl_encrypt
+
+ Verschlüsselungs-Engine von openssl verwenden, Standard ist false (verwendet mcrypt für AES-Verschlüsselung)
+
+- system.optimize_items
+
+ Führt optimise_table während einiger Aufgaben aus, um Ihre Datenbank sauber und defragmentiert zu halten. Dies geht zu Lasten der Leistung, während die Operationen laufen, sorgt aber auch dafür, dass die Dinge etwas schneller laufen, wenn sie nicht laufen. Es gibt auch CLI-Hilfsprogramme zur Durchführung dieser Operation, die Sie vielleicht bevorzugen, besonders wenn Sie eine große Site haben.
+
+- system.override_poll_lockfile
+
+ Ignoriert die Sperrdatei im Poller-Prozess, damit mehr als ein Prozess gleichzeitig laufen kann.
+
+- system.paranoia
+
+ Wie pconfig, aber auf einer site-weiten Basis. Kann durch Mitgliedereinstellungen überschrieben werden.
+
+- system.pin_types
+
+ Array der zulässigen Elementtypen für die Anheftung. Die Standardwerte hängen vom Modul ab, können aber hier geändert werden.
+
+- system.photo_cache_time
+
+ Wie lange die Fotos zwischengespeichert werden sollen, in Sekunden. Standardwert ist 86400 (1 Tag). Eine längere Zeit erhöht die Leistung, bedeutet aber auch, dass es länger dauert, bis geänderte Berechtigungen gelten.
+
+- system.plattform_name
+
+ Was als Plattformname auf Webseiten und in Statistiken angezeigt werden soll. (*) Muss in .htconfig.php eingestellt werden.
+
+- system.rating_enabled
+
+ Verteilte Reputationsberichte und Datenerfassung. Diese Funktion wird derzeit überarbeitet.
+
+- system.poke_basic
+
+ Reduziert die Anzahl der poke-Verben auf genau 1 („poke“). Deaktivieren Sie andere Verben.
+
+- system.proc_run_use_exec
+
+ Wenn 1, wird der Systemaufruf exec in proc_run verwendet, um Hintergrundaufgaben auszuführen. Standardmäßig verwenden wir proc_open und proc_close. Auf einigen (derzeit seltenen) Systemen funktioniert dies nicht gut.
+
+- system.projecthome
+
+ Zeigt die Projektseite auf Ihrer Homepage für abgemeldete Betrachter an.
+
+- system.projekthome
+
+ Legt die Projekthomepage als Startseite des Hubs fest. (Veraltet)
+
+- system.pubstream_bestellung
+
+ Legt die Pubstream-Reihenfolge fest. Mögliche Werte 'commented' (Standard), 'created' und 'edited'.
+
+- system.register_link
+
+ Pfad, auf den der „register“-Link im Anmeldeformular verweisen soll. Bei geschlossenen Sites wird dies auf 'pubsites' verweisen. Bei offenen Sites wird er normalerweise auf 'register' umgeleitet, aber Sie können dies auf eine benutzerdefinierte Site-Seite ändern, die Abonnements oder ähnliches anbietet.
+
+- system.reserved_channels
+
+ Erlaube den Mitgliedern nicht, Kanäle mit dieser durch Kommata getrennten Liste von Namen (keine Leerzeichen) zu registrieren.
+
+- system.sellpage
+
+ Eine URL, die in der Liste der öffentlichen Sites angezeigt wird, um Ihren Hub zu verkaufen - Serviceklassen anzeigen, usw.
+
+- system.startpage
+
+ Legt die Standardseite fest, die nach einer Anmeldung für alle Channels auf dieser Website angezeigt wird. Kann durch Benutzereinstellungen überschrieben werden.
+
+- system.sys_expire_days
+
+ Wie viele Tage sollen entdeckte öffentliche Inhalte von anderen Websites aufbewahrt werden?
+
+- system.taganyone
+
+ Erlaubt das @mention-Tagging von jedem, egal ob Sie verbunden sind oder nicht.
+
+- system.tempdir
+
+ Ort, an dem temporäre Dateien gespeichert werden (derzeit unbenutzt), Standard ist in der PHP-Konfiguration definiert.
+
+- system.tos_url
+
+ Setzt einen alternativen Link für den ToS-Speicherort.
+
+- system.transport_security_header
+
+ wenn ungleich Null und SSL verwendet wird, wird ein strict-transport-security-Header auf den Webseiten eingefügt
+
+- system.uploaddir
+
+ Speicherort für das Hochladen von Dateien (Standard ist system.tempdir, derzeit nur vom js_upload-Plugin verwendet)
+
+- system.workflow_channel_next
+
+ Die Seite, zu der neue Mitglieder unmittelbar nach der Erstellung eines Channels weitergeleitet werden sollen.
+
+- system.arbeitsablauf_register_next
+
+ Die Seite, auf die Mitglieder direkt nach dem Erstellen eines Kontos geleitet werden (nur wenn auto_channel_create oder UNO aktiviert ist).
+
+#### Verzeichnis-Konfiguration
+
+##### Standardwerte für die Verzeichnissuche
+
+- directory.globaldir
+
+ 0 oder 1. Standardwert 0. Wenn Sie das Verzeichnis auf einer Site besuchen, sehen Sie standardmäßig nur die Mitglieder dieser Site. Man muss einen zusätzlichen Schritt machen, um die Leute im Rest des Netzwerks zu sehen; und indem man das tut, gibt es eine klare Abgrenzung, dass diese Leute *nicht* Mitglieder dieser Site sind, sondern eines größeren Netzwerks.
+
+- directory.pubforums
+
+ 0 oder 1. Öffentliche Foren *sollten* standardmäßig 0 sein.
+
+- directory.safemode
+
+ 0 oder 1.
+
+##### Konfiguration des Verzeichnisservers
+
+- system.directory_mode
+- system.directory_primary
+- system.directory_realm
+- system.verzeichnis_server
+- system.realm_token \ No newline at end of file
diff --git a/doc/de/adminmanual/automated_installation.md b/doc/de/adminmanual/automated_installation.md
new file mode 100644
index 000000000..0cb440717
--- /dev/null
+++ b/doc/de/adminmanual/automated_installation.md
@@ -0,0 +1,26 @@
+### Automatisierte Installation über das Shell-Skript .homeinstall
+
+Es gibt ein Shell-Skript in (`.homeinstall/hubzilla-setup.sh`), das Hubzilla und seine Abhängigkeiten auf einer frischen Installation von Debian stable installiert. Es sollte auf ähnlichen Linux-Systemen funktionieren, aber Ihre Ergebnisse können variieren.
+
+#### Anforderungen
+
+Das Installationsskript wurde ursprünglich für einen kleinen Hardwareserver hinter Ihrem Heimrouter entwickelt. Es wurde jedoch auf mehreren Systemen mit Debian 9 getestet:
+
+- Home-PC (Debian-9.2-amd64) und Rapberry-Pi 3 (Rasbian = Debian 9.3)
+ - Internetanschluss und Router zu Hause
+ - Mini-PC / Raspi an den Router angeschlossen
+ - USB-Laufwerk für Backups
+ - Frische Installation von Debian auf Ihrem Mini-PC
+ - Router mit offenen Ports 80 und 443 für Ihr Debian
+
+#### Überblick über die Installationsschritte
+
+1. `apt-get install git`
+2. `mkdir -p /var/www/html`
+3. `cd /var/www/html`
+4. `git clone https://framagit.org/hubzilla/core.git .`
+5. `nano .homeeinstall/hubzilla-config.txt`
+6. `cd .homeeinstall/`
+7. `./hubzilla-setup.sh`
+8. `service apache2 neu laden`
+9. Öffnen Sie Ihre Domain mit einem Browser und gehen Sie durch die anfängliche Konfiguration von Hubzilla. \ No newline at end of file
diff --git a/doc/de/adminmanual/before_you_start.md b/doc/de/adminmanual/before_you_start.md
new file mode 100644
index 000000000..1bc425b9c
--- /dev/null
+++ b/doc/de/adminmanual/before_you_start.md
@@ -0,0 +1,17 @@
+### Bevor Sie beginnen
+
+Wählen Sie einen Domänennamen oder einen Subdomänennamen für Ihren Server.
+
+Die Software kann nur im Stammverzeichnis einer Domäne oder Subdomäne installiert werden und kann nicht über alternative TCP-Ports installiert werden. Diese Beschränkungen können in Zukunft gelockert werden, sind aber unbequem, so dass wir Ihnen weiterhin DRINGEND empfehlen, sich daran zu halten.
+
+Entscheiden Sie, ob Sie SSL verwenden wollen, und besorgen Sie sich vor der Softwareinstallation ein SSL-Zertifikat. Sie SOLLTEN SSL verwenden. Wenn Sie SSL verwenden, MÜSSEN Sie ein „browsergültiges“ Zertifikat verwenden. Sie KÖNNEN KEINE selbstsignierten Zertifikate verwenden!
+
+Bitte testen Sie Ihr Zertifikat vor der Installation. Ein Webtool zum Testen Ihres Zertifikats finden Sie unter „http://www.digicert.com/help/“. Wenn Sie Ihre Website zum ersten Mal besuchen, verwenden Sie bitte die SSL-URL („https://“), wenn SSL verfügbar ist. Dadurch werden spätere Probleme vermieden. Die Installationsroutine erlaubt es Ihnen nicht, ein nicht browsergültiges Zertifikat zu verwenden.
+
+Diese Einschränkung wird vorgenommen, weil öffentliche Beiträge von Ihnen Verweise auf Bilder in Ihrem eigenen Hub enthalten können. Andere Mitglieder, die ihren Stream auf anderen Hubs betrachten, erhalten Warnungen, wenn Ihr Zertifikat von ihrem Webbrowser nicht als vertrauenswürdig eingestuft wird. Dies wird viele Leute verwirren, da es sich um ein dezentralisiertes Netzwerk handelt, und sie werden die Warnung über Ihren Hub erhalten, während sie ihren eigenen Hub ansehen, und könnten denken, dass ihr eigener Hub ein Problem hat. Diese Warnungen sind sehr technisch und beängstigend für einige Leute, von denen viele nicht wissen, wie sie weiter vorgehen sollen, außer den Ratschlägen des Browsers zu folgen. Das ist für die Gemeinschaft störend. Abgesehen davon erkennen wir die Probleme im Zusammenhang mit der derzeitigen Zertifikatsinfrastruktur an und stimmen zu, dass es viele Probleme gibt, aber das ändert nichts an der Anforderung.
+
+Kostenlose „browsergültige“ Zertifikate gibt es von Anbietern wie ZeroSSL, LetsEncrypt und einigen anderen.
+
+Wenn Sie NICHT SSL verwenden, kann es beim ersten Installationsskript zu einer Verzögerung von bis zu einer Minute kommen - während wir den SSL-Port überprüfen, um zu sehen, ob dort etwas antwortet. Bei der Kommunikation mit neuen Sites versucht Hubzilla immer zuerst eine Verbindung über den SSL-Port herzustellen, bevor es auf eine weniger sichere Verbindung zurückgreift. Wenn Sie kein SSL verwenden, MUSS Ihr Webserver überhaupt nicht auf Port 443 lauschen.
+
+Wenn Sie LetsEncrypt für die Bereitstellung von Zertifikaten verwenden und eine Datei unter .well-known/acme-challenge erstellen, damit LetsEncrypt Ihre Domaineigentümerschaft überprüfen kann, entfernen Sie bitte das Verzeichnis .well-known oder benennen Sie es um, sobald das Zertifikat generiert wurde. Die Software stellt bei der Installation einen eigenen Handler für „.well-known“-Dienste zur Verfügung, und ein bestehendes Verzeichnis an diesem Ort kann verhindern, dass einige dieser Dienste korrekt funktionieren. Dies sollte bei Apache kein Problem sein, kann aber bei nginx oder anderen Webserver-Plattformen ein Problem darstellen. \ No newline at end of file
diff --git a/doc/de/adminmanual/channel_directory.md b/doc/de/adminmanual/channel_directory.md
new file mode 100644
index 000000000..745a20272
--- /dev/null
+++ b/doc/de/adminmanual/channel_directory.md
@@ -0,0 +1,15 @@
+### Kanal-Verzeichnis
+
+#### Schlüsselwörter
+
+Es gibt eine „Schlagwortwolke“ mit Schlüsselwörtern, die auf der Kanalverzeichnisseite erscheinen können. Wenn Sie diese Schlüsselwörter, die vom Verzeichnisserver bezogen werden, ausblenden möchten, können Sie das *Konfigurationswerkzeug* verwenden:
+
+```
+util/config system disable_directory_keywords 1
+```
+
+Wenn sich Ihr Hub im Standalone-Modus befindet, weil Sie sich nicht mit dem globalen Netz verbinden möchten, können Sie stattdessen sicherstellen, dass die Option *directory_server* system leer ist:
+
+```
+util/config system directory_server „“
+``` \ No newline at end of file
diff --git a/doc/de/adminmanual/database.md b/doc/de/adminmanual/database.md
new file mode 100644
index 000000000..d46001880
--- /dev/null
+++ b/doc/de/adminmanual/database.md
@@ -0,0 +1,74 @@
+### Datenbank
+
+#### Datenbank-Updates
+
+Auf der Seite `/admin/dbsync` kann der Administrator überprüfen, ob eine Aktualisierung fehlgeschlagen ist, und sie gegebenenfalls erneut versuchen.
+
+Wenn eine Aktualisierung fehlgeschlagen ist, aber aus irgendeinem Grund nicht als fehlgeschlagen registriert wird, kann der Administrator versuchen, die Aktualisierung erneut auszuführen. Zum Beispiel für die DB-Aktualisierung #1999, indem er die Webseite besucht:
+
+`/admin/dbsync/1999`
+
+#### Datenbank-Tabellen
+
+| Tabelle | Beschreibung |
+| ------------------------------------------------------------ | ------------------------------------------------------------ |
+| [abconfig](/help/de/database/db_abconfig) | beliebiger Speicherplatz für Verbindungen von lokalen Kanälen |
+| [abook](/help/de/database/db_abook) | Verbindungen der lokalen Kanäle |
+| [account](/help/de/database/db_account) | Dienstanbieterkonto |
+| [addon](/help/de/database/db_addon) | registrierte Plugins |
+| [app](/help/de/database/db_app) | persönliche App-Daten |
+| [attach](/help/de/database/db_attach) | Dateianhänge |
+| [auth_codes](/help/de/database/db_auth_codes) | OAuth Benutzung |
+| [cache](/help/de/database/db_cache) | OEmbed Cache |
+| [cal](/help/de/database/db_cal) | CalDAV-Container für Ereignisse |
+| [channel](/help/de/database/db_channel) | lokale Kanäle |
+| [chat](/help/de/database/db_chat) | Chatraum-Inhalte |
+| [chatpresence](/help/de/database/db_chatpresence) | Kanalpräsenzinformationen für den Chat |
+| [chatroom](/help/de/database/db_chatroom) | Daten für den eigentlichen Chatraum |
+| [clients](/help/de/database/db_clients) | OAuth Benutzung |
+| [config](/help/de/database/db_config) | Hauptkonfigurationsspeicher |
+| [conv](/help/de/database/db_conv) | Meta-Konversationsstruktur für private Nachrichten in Diaspora |
+| [event](/help/de/database/db_event) | Events |
+| [pgrp_member](/help/de/database/db_pgrp_member) | Datenschutzgruppen (Sammlungen), Gruppeninformationen |
+| [pgrp](/help/de/database/db_pgrp) | Datenschutzgruppen (Sammlungen), Mitgliederinformationen |
+| [hook](/help/de/database/db_hook) | Plugin-Hook-Register |
+| [hubloc](/help/de/database/db_hubloc) | xchan-Standortspeicher, verknüpft einen Hub-Standort mit einem xchan |
+| [iconfig](/help/de/database/db_iconfig) | erweiterbarer, beliebiger Speicher für Elemente |
+| [issue](/help/de/database/db_issue) | künftige Fehler-/Problemdatenbank |
+| [item](/help/de/database/db_item) | alle Beiträge und Webseiten |
+| [item_id](/help/de/database/db_item_id) | (veraltet durch iconfig) andere Identifikatoren in anderen Diensten für Beiträge |
+| [likes](/help/de/database/db_likes) | "Dinge“ mögen |
+| [mail](/help/de/database/db_mail) | private Nachrichten |
+| [menu](/help/de/database/db_menu) | Webseiten-Menü-Daten |
+| [menu_item](/help/de/database/db_menu_item) | Einträge für Menüs auf Webseiten |
+| [notify](/help/de/database/db_notify) | Benachrichtigungen |
+| [obj](/help/de/database/db_obj) | Objektdaten für Dinge (x hat y) |
+| [outq](/help/de/database/db_outq) | Ausgangswarteschlange |
+| [pconfig](/help/de/database/db_pconfig) | persönlicher (pro Kanal) Konfigurationsspeicher |
+| [photo](/help/de/database/db_photo) | Fotospeicher |
+| [poll](/help/de/database/db_poll) | Daten für Umfragen |
+| [poll_elm](/help/de/database/db_poll_elm) | Daten für Abfrageelemente |
+| [profdef](/help/de/database/db_profdef) | Definitionen für benutzerdefinierte Profilfelder |
+| [profext](/help/de/database/db_profext) | benutzerdefinierte Profilfelddaten |
+| [profile](/help/de/database/db_profile) | Kanalprofile |
+| [profile_check](/help/de/database/db_profile_check) | DFRN-Fernautorisierung, kann veraltet sein |
+| [register](/help/de/database/db_register) | Registrierungen, die eine Verwaltungsgenehmigung erfordern |
+| [session](/help/de/database/db_session) | Speicherung von Websitzungen |
+| [shares](/help/de/database/db_shares) | Informationen über gemeinsame Elemente |
+| [sign](/help/de/database/db_sign) | Diaspora-Unterschriften. Wird schrittweise abgebaut. |
+| [site](/help/de/database/db_site) | Standorttabelle zum Auffinden von Verzeichnis-Peers |
+| [source](/help/de/database/db_source) | Daten aus Kanalquellen |
+| [sys_perms](/help/de/database/db_sys_perms) | erweiterbare Berechtigungen für OAuth |
+| [term](/help/de/database/db_term) | Tabelle der Artikeltaxonomie (Kategorien, Tags usw.) |
+| [tokens](/help/de/database/db_tokens) | OAuth Benutzung |
+| [updates](/help/de/database/db_updates) | Verzeichnis-Synchronisations-Updates |
+| [verify](/help/de/database/db_verify) | allgemeine Verifikationsstruktur |
+| [vote](/help/de/database/db_vote) | Abstimmungsdaten für Umfragen |
+| [xchan](/help/de/database/db_xchan) | Liste der bekannten Kanäle im Universum |
+| [xchat](/help/de/database/db_xchat) | Chaträume mit Lesezeichen |
+| [xconfig](/help/de/database/db_xconfig) | wie pconfig, aber für Kanäle ohne lokales Konto |
+| [xign](/help/de/database/db_xign) | von Freundschaftsvorschlägen ignorierte Kanäle |
+| [xlink](/help/de/database/db_xlink) | von poco abgeleitete „Freunde von Freunden“-Verknüpfungen, auch Speicherung von Bewertungen |
+| [xperm](/help/de/database/db_xperm) | OAuth/OpenID-Connect erweiterbare Berechtigungen Berechtigungsspeicher |
+| [xprof](/help/de/database/db_xprof) | wenn dieser Knotenpunkt ein Verzeichnisserver ist, enthält er grundlegende öffentliche Profilinformationen über jeden im Netz |
+| [xtag](/help/de/database/db_xtag) | wenn dieser Hub ein Verzeichnisserver ist, enthält er Tags oder Interessen von jedem im Netzwerk |
diff --git a/doc/de/adminmanual/directories.md b/doc/de/adminmanual/directories.md
new file mode 100644
index 000000000..0fab695ae
--- /dev/null
+++ b/doc/de/adminmanual/directories.md
@@ -0,0 +1,87 @@
+### **Verzeichnis-Konfiguration**
+
+Verzeichnisse in Hubzilla dienen dem Zweck, Mitglieder überall im Netzwerk zu suchen und zu finden. Sie werden auch verwendet, um „Bewertungen“ von Mitgliedern und Sites zu speichern und abzufragen. Die Verzeichnisdienste sind verteilt und gespiegelt, so dass ein Ausfall eines Dienstes nicht das gesamte Netzwerk lahmlegt oder stört.
+
+#### **Standard-Konfiguration**
+
+Neue Sites, die als Verzeichnis-Clients arbeiten, wählen bei ihrem ersten Verzeichniszugriff automatisch aus einer fest programmierten Liste von Verzeichnisservern aus. Sie können diese Einstellung überprüfen oder außer Kraft setzen, indem Sie
+
+```
+util/config system directory_server
+```
+
+eingeben.
+
+Um einen anderen Server einzustellen, verwenden Sie diesen Befehl:
+
+```
+util/config system directory_server https://newdirectory.something
+```
+
+#### **Eigenständige Konfiguration**
+
+Einige Hubs möchten vielleicht im „Standalone“-Modus arbeiten und keine Verbindung zu externen Verzeichnisdiensten herstellen. Dies ist nützlich für isolierte Sites („off the gird“) und Test-Sites, kann aber auch für kleine Organisationen nützlich sein, die keine Verbindung mit anderen Sites im Netzwerk herstellen wollen.
+
+Um dies zu konfigurieren, suchen Sie bitte in Ihrer Datei .htconfig.php nach dem folgenden Text und stellen Sie die Konfiguration entsprechend ein.
+
+```php
+// Configure how we communicate with directory servers.
+// DIRECTORY_MODE_NORMAL = directory client, we will find a directory
+// DIRECTORY_MODE_SECONDARY = caching directory or mirror
+// DIRECTORY_MODE_PRIMARY = main directory server
+// DIRECTORY_MODE_STANDALONE = "off the grid" or private directory services
+
+App::$config['system']['directory_mode'] = DIRECTORY_MODE_STANDALONE;
+```
+
+#### **Konfiguration des Sekundärservers**
+
+Sie können Ihre Site auch als Sekundärserver konfigurieren. Dieser arbeitet als Spiegel des primären Verzeichnisses und ermöglicht die Verteilung der Last auf die verfügbaren Server. Es gibt kaum funktionale Unterschiede zwischen einem primären und einem sekundären Server, allerdings darf es nur *einen* primären Verzeichnisserver pro Realm geben (Realms werden später in diesem Dokument behandelt).
+
+Bevor Sie sich für einen Verzeichnisserver entscheiden, sollten Sie ein aktives Mitglied des Netzwerks sein und über die Ressourcen und Zeit verfügen, um diese Dienste zu verwalten. In der Regel ist keine Verwaltung erforderlich, sondern die Anforderung dient eher der Stabilität, da der Ausfall eines Verzeichnisservers zu Problemen bei den Verzeichnis-Clients führen kann, die auf ihn angewiesen sind.
+
+#### **Wechseln des Verzeichnisservers**
+
+Wenn ein Verzeichnisserver anzeigt, dass er kein Verzeichnisserver mehr ist, sollte dies von der Software erkannt werden, und die Konfiguration für diesen Server wird entfernt (geleert). Wenn er ohne Vorwarnung dauerhaft offline geht, erfahren Sie das nur, wenn die Mitglieder der Website melden, dass die Verzeichnisdienste nicht mehr verfügbar sind. Derzeit kann dies nur manuell durch den Site-Administrator repariert werden, indem er ein neues Verzeichnis auswählt und den Befehl
+
+```
+util/config system directory_server https://newdirectory.something
+```
+
+Wir hoffen, dass diese Option in Zukunft in der Administrationsoberfläche der Website aktiviert werden kann.
+
+### **Verzeichnis-Bereiche**
+
+Große Organisationen möchten vielleicht lieber Verzeichnis-„Realms“ als ein einzelnes, unabhängiges Verzeichnis verwenden. Der Standard-Realm heißt RED_GLOBAL. Durch die Erstellung eines neuen Realms hat Ihre Organisation die Möglichkeit, ihre eigene Hierarchie von primären und sekundären Servern und Clients zu erstellen.
+
+```
+util/config system directory_realm MY_REALM
+```
+
+Ihr Realm *muss* ein primäres Verzeichnis haben. Erstellen Sie dieses zuerst. Setzen Sie dann den Realm auf allen Sites innerhalb Ihres Verzeichnisbereichs (Server und Clients) auf denselben Wert.
+
+Sie können auch einen „Sub-Realm“ einrichten, der unabhängig vom RED_GLOBAL-Realm (oder jedem anderen Realm) arbeitet, aber eine übergreifende Mitgliedschaft und die Möglichkeit bietet, Mitglieder des gesamten Verzeichnisraums zu suchen. Dies wurde bisher nur leicht getestet, also seien Sie bereit, mitzuhelfen und eventuell auftretende Probleme zu beheben. Ein Sub-Realm enthält seinen übergeordneten Realm im Realm-Namen.
+
+```
+util/config system directory_realm RED_GLOBAL:MY_REALM
+```
+
+#### **Realm-Zugang**
+
+Sie möchten vielleicht, dass Ihre Verzeichnisserver und -dienste nur von Mitgliedern Ihres Realms genutzt werden. Zu diesem Zweck muss ein Token oder Passwort für den Zugriff auf die Verzeichnisdienste des Realms angegeben werden. Dieses Token wird während der Übertragung nicht verschlüsselt, reicht aber aus, um den gelegentlichen Zugriff auf Ihre Verzeichnisserver zu verhindern. Folgendes muss für alle Sites (Clients und Verzeichnisserver) innerhalb des Realms konfiguriert werden:
+
+```
+util/config system realm_token my-secret-realm-password
+```
+
+### **Verzeichnis-Spiegelung**
+
+Die Spiegelung erfolgt mit einem täglichen Transaktionsprotokoll der Aktivitäten, das von den Verzeichnisservern gemeinsam genutzt wird. Bei Verzeichnis- und Profilaktualisierungen wird die Adresse des Kanals, der die Aktualisierung durchführt, übermittelt, und die anderen Verzeichnisserver prüfen diesen Kanal an seiner Quelle auf Änderungen. Wir können und sollten den Informationen, die uns von anderen Verzeichnisservern gegeben werden, nicht vertrauen. Wir überprüfen die Informationen immer an der Quelle.
+
+Bewertungen werden etwas anders gehandhabt - ein verschlüsseltes Paket (signiert von dem Kanal, der die Bewertung erstellt hat) wird zwischen den Servern übertragen. Diese Signatur muss überprüft werden, bevor die Bewertung akzeptiert wird. Bewertungen werden immer auf dem primären Verzeichnisserver veröffentlicht und von dort aus an alle anderen Verzeichnisserver weitergegeben. Aus diesem Grund kann es nur einen primären Server in einem Bereich geben. Wenn eine falsch konfigurierte Site behauptet, ein primäres Verzeichnis zu sein, wird sie im RED_GLOBAL-Bereich ignoriert. Für andere Realms gibt es derzeit keinen solchen Schutz. Seien Sie sich dessen bewusst, wenn Sie mit alternativen Realms arbeiten.
+
+Neu erstellte Verzeichnisserver erhalten keinen „Full Dump“, sondern werden aus Leistungsgründen und um die anderen Server im Netz möglichst wenig zu stören, langsam online gestellt. Es kann bis zu einem Monat dauern, bis ein neuer sekundärer Verzeichnisserver einen vollständigen Überblick über das Netzwerk bietet. Bitte fügen Sie keine sekundären Server zur fest kodierten Liste der Fallback-Verzeichnisserver hinzu, bevor er nicht mindestens einen Monat lang als Verzeichnis in Betrieb war.
+
+Alle Channels sind so konfiguriert, dass sie ihren Verzeichnisserver einmal im Monat „anpingen“, und zwar zu einem zufälligen Zeitpunkt im Monat. Auf diese Weise kann das Verzeichnis tote Channels und Sites entdecken (sie hören auf zu pingen). In der Folge werden sie als tot oder unerreichbar markiert und mit der Zeit aus den Verzeichnisergebnissen entfernt.
+
+Kanäle können so konfiguriert werden, dass sie vor dem Verzeichnis „versteckt“ werden. Diese Kanäle sind zwar noch im Verzeichnis vorhanden, aber nicht mehr durchsuchbar, und einige „sensible“ persönliche Informationen werden nicht gespeichert. \ No newline at end of file
diff --git a/doc/de/adminmanual/faq_admins.md b/doc/de/adminmanual/faq_admins.md
new file mode 100644
index 000000000..496a49f98
--- /dev/null
+++ b/doc/de/adminmanual/faq_admins.md
@@ -0,0 +1,89 @@
+### Hubzilla FAQ
+
+#### Gibt es eine Möglichkeit, das Admin-Konto zu ändern?
+
+#### Gibt es eine Möglichkeit, mehrere Administratoren zu haben?
+
+Ja, aber im Moment ist es noch etwas umständlich, da es noch nicht in der Benutzeroberfläche sichtbar ist. Um ein Konto zu einem administrativen Konto zu machen, muss man 4096 zum account_roles-Eintrag in der Kontotabelle der Datenbank hinzufügen. Um administrative Berechtigungen zu entfernen, muss man 4096 von den Kontorollen abziehen.
+
+#### Ich kann mich einloggen, aber es gibt keine Beiträge oder Webseiten
+
+Höchstwahrscheinlich ist Ihre Artikeltabelle abgestürzt. Führen Sie den MySQL-Befehl
+
+```
+repair table item;
+```
+
+#### Das Einloggen funktioniert nicht, sofort nach dem Einloggen wird die Seite neu geladen und ich werde ausgeloggt
+
+Höchstwahrscheinlich ist Ihre Sitzungstabelle abgestürzt. Führen Sie den MySQL-Befehl
+
+```
+repair table session;
+```
+
+aus.
+
+#### Wenn ich das Thema wechsle, werden manchmal Elemente des einen Themas über das andere gelegt
+
+a) store/[data]/smarty3 ist für den Webserver nicht beschreibbar. Machen Sie es möglich.
+b) Sie verwenden Midori, oder bei bestimmten Themes Konqueror im KHTML-Modus.
+
+#### Mein Netzwerk-Tab wird nicht geladen, es scheint durch ein Foto oder Video verursacht zu werden
+
+Ihr PHP-Speicherlimit ist zu niedrig. Erhöhen Sie die Größe der memory_limit-Anweisung in Ihrer php.ini
+Entgegen der landläufigen Meinung hat die Anzahl der Nutzer eines Hubs keinen Einfluss auf das benötigte Speicherlimit, vielmehr zählt der Inhalt einer individuellen Matrix. Streams mit vielen Fotos und Videos benötigen mehr Speicher als Streams mit viel Text.
+
+#### Ich habe keine Kommunikation mit jemandem
+
+Sie lauschen auf Port 443, haben aber kein gültiges SSL-Zertifikat. Dies gilt auch, wenn Ihre Basisurl http lautet.
+Lauschen Sie nicht auf Port 443, wenn Sie ihn nicht verwenden können. Es wird dringend empfohlen, dieses Problem durch die Installation eines gültigen SSL-Zertifikats für den Browser zu lösen, anstatt Port 443 zu deaktivieren.
+
+#### Wie kann ich eine Nicht-Git-Installation aktualisieren?
+
+1. Sichern Sie .htconfig.php
+2. Sichern Sie alles in store/
+3. Sichern Sie alle benutzerdefinierten Änderungen in mod/site/ und view/site
+4. Löschen Sie Ihre bestehende Installation
+5. Laden Sie die neue Version hoch.
+6. Laden Sie die neue Version der Themes und Addons hoch.
+7. Stellen Sie alles wieder her, was Sie zuvor gesichert haben.
+
+#### Was muss ich tun, wenn ich meinen Hub auf einen anderen Server verschiebe?
+
+1. Erstellen Sie einen Git-Klon auf dem neuen Server. Wiederholen Sie den Vorgang für alle benutzerdefinierten Themes und Addons.
+2. Rsync .htconfig.php
+3. Rsync alles in store/
+4. Rsync alles in mod/site/ und view/site (diese sind nur vorhanden, wenn Sie eigene Module haben)
+5. Dump und Wiederherstellung der DB.
+
+#### Wie installiere ich einen bestehenden Hub auf demselben Server neu?
+
+```
+git reset --hard HEAD
+```
+
+setzt alle Dateien auf ihre Upstream-Standardwerte zurück. Dabei werden keine lokalen Dateien zurückgesetzt, die nicht auch im Upstream existieren. Wenn Sie z.B. lokale Änderungen an mod/channel.php vorgenommen haben, werden diese zurückgesetzt - nicht aber die Änderungen in mod/site/channel.php.
+
+1. Wenn Sie unbedingt eine Neuinstallation durchführen müssen - z.B. wenn Sie das Betriebssystem aktualisieren müssen - befolgen Sie die Schritte für den Umzug auf einen anderen Server, aber anstatt rsync zu verwenden, erstellen Sie ein Backup und stellen Sie es auf andere Weise wieder her.
+
+Installieren Sie einen Hub nicht mit einer neuen Datenbank und einer neuen .htconfig.php neu, es sei denn, dies ist der allerletzte Ausweg. Das Anlegen eines temporären Accounts und das Anfordern von Hilfe über einen Support-Kanal für nicht-triviale Neuinstallationen ist einer Neuinstallation vorzuziehen.
+
+#### Wie kann ich die Standard-Homepage für abgemeldete Besucher festlegen?
+
+Verwenden Sie das Addon custom_home, das im Hauptrepository für Addons verfügbar ist.
+
+#### Was bedeuten die verschiedenen Einstellungen für den Verzeichnismodus?
+
+```
+// Konfigurieren Sie, wie wir mit Verzeichnisservern kommunizieren.
+// DIRECTORY_MODE_NORMAL = Verzeichnis-Client, wir finden ein Verzeichnis (alle Abfragen Ihrer Mitglieder werden an andere Stellen geleitet)
+// DIRECTORY_MODE_SECONDARY = Zwischenspeicherverzeichnis oder Mirror (hält sich synchron mit dem primären Realm [fügt erhebliche Cron-Ausführungszeit hinzu])
+// DIRECTORY_MODE_PRIMARY = Hauptverzeichnisserver (dies ist nur sinnvoll, wenn Sie einen eigenen Realm betreiben. einer pro Realm)
+// DIRECTORY_MODE_STANDALONE = „Off the grid“ oder private Verzeichnisdienste (nur lokale Site-Mitglieder im Verzeichnis)
+```
+
+- Die Voreinstellung ist NORMAL. Damit werden die meisten Verzeichnisdienste auf einen anderen Server verlagert. Der verwendete Server ist der config:system/directory_server. Diese Einstellung KANN durch den Code auf einen der Projektsekundärserver aktualisiert werden, wenn der aktuelle Server nicht erreichbar ist. Sie sollten entweder die Kontrolle über diesen anderen Server haben oder ihm vertrauen, dass er diese Einstellung verwendet.
+- SEKUNDÄR. Dies ermöglicht es Ihrer lokalen Site, als Verzeichnisserver zu fungieren, ohne die Abfragen Ihrer Mitglieder einem anderen Server zu überlassen. Es erfordert zusätzliche Verarbeitungszeit während der Cron-Abfrage und wird nicht empfohlen, auf einem gemeinsam genutzten Webhost ausgeführt zu werden.
+- PRIMÄR. Dies ermöglicht es Ihnen, ein komplett separates 'Netzwerk' von Verzeichnisservern mit einem eigenen Realm zu betreiben. Standardmäßig befinden sich alle Server im Realm RED_GLOBAL, es sei denn, die Einstellung config:system/directory_realm wird überschrieben. *Verwenden Sie dies nicht, wenn Sie keinen eigenen directory_realm haben.*
+- STANDALONE. Dies ist wie primary, außer dass es sich um ein eigenständiges „Netzwerk“ handelt, das mit keinem anderen Server kommuniziert. Verwenden Sie dies, wenn Sie nur einen Server haben und von den Red#Matrix-Verzeichnislisten getrennt sein wollen. \ No newline at end of file
diff --git a/doc/de/adminmanual/federation_addons.md b/doc/de/adminmanual/federation_addons.md
new file mode 100644
index 000000000..eda62a062
--- /dev/null
+++ b/doc/de/adminmanual/federation_addons.md
@@ -0,0 +1,8 @@
+### Föderations Addons
+
+Verschiedene Web-Communities haben begonnen, sich unter Verwendung gemeinsamer Protokolle zusammenzuschließen. Die beteiligten Protokolle sind in ihren Möglichkeiten etwas eingeschränkt. Das Diaspora-Protokoll ist etwas restriktiv, was die erlaubten Kommunikationsarten angeht. Alle Kommentare müssen vom ursprünglichen Autor auf eine sehr eindeutige Weise signiert werden. Das ActivityPub-Protokoll wird ebenfalls unterstützt. Kein anderes existierendes Protokoll unterstützt nomadische Standorte (es gibt inzwischen die Unterstützung nomadischer Identitäten bei ActivityPub), wie sie in diesem Projekt verwendet werden. Dies stellt einige Herausforderungen an die Unterstützung, da einige Funktionen mit einigen Netzen funktionieren und mit anderen nicht. Nichtsdestotrotz ermöglichen die Föderationsprotokolle die Herstellung von Verbindungen zu einer viel größeren Gemeinschaft von Menschen weltweit. Sie werden als Addons bereitgestellt.
+
+- Diaspora Protocol - Das Diaspora-Protokoll wird von Diaspora und Friendica verwendet. Sie sollten zuerst 'Diaspora Statistics' (Statistiken) aktivieren, um alle verfügbaren Funktionen zu nutzen.
+- PubCrawl - Das ActivityPub-Protokoll, das von Mastodon, MIsskey, Friendica und allen Fediverse-Diensten unterstützt wird.
+
+Jedes Mitglied Ihrer Website muss selbst entscheiden, ob es diese Protokolle zulassen will oder nicht, da sie mit einigen wünschenswerten Kernfunktionen und Fähigkeiten dieser Software (wie z.B. Channel-Migration und Klonen) in Konflikt geraten können. Sie tun dies auf ihrer Seite „Einstellungen -> Feature/Addon-Einstellungen“. \ No newline at end of file
diff --git a/doc/de/adminmanual/filesync.md b/doc/de/adminmanual/filesync.md
new file mode 100644
index 000000000..97cc388c6
--- /dev/null
+++ b/doc/de/adminmanual/filesync.md
@@ -0,0 +1,42 @@
+### Datei-Synchronisation und Klonen
+
+Das Klonen von Dateien über mehrere Instanzen eines Channels hinweg ist aufgrund der Natur der PHP-Speicherzuweisung ein sehr schwieriges Problem. Dies muss völlig anders gehandhabt werden als das Klonen oder Synchronisieren anderer Informationen. (Die Verarbeitung einer großen Videodatei oder von 40-50 Fotos kann den Speicher voll auslasten). Daher können wir nicht einfach alle Daten in eine Speicherauszugsdatei auslagern und sie dann sequentiell verarbeiten. Das Laden der Speicherauszugsdatei selbst wird wahrscheinlich den Speicherplatz erschöpfen.
+
+Außerdem gibt es zwei Hauptoperationen, die wir in Betracht ziehen. Die erste ist die schwierigste - das Speichern und anschließende Importieren aller Kanalinformationen in einen neuen Kanal-Klon. Der zweite ist die Synchronisierung von Datei-Änderungen, die in zwei oder mehr „aktiven“ Klonen auftreten.
+Beim ersten Versuch mit diesem Tool werden wir uns auf den zweiten Fall konzentrieren, während wir versuchen, ein gewisses Maß an Kompatibilität mit dem ersten Fall aufrechtzuerhalten, damit wir dieselben Tools wiederverwenden können.
+
+#### Metadaten
+
+Zunächst benötigen wir die Metadaten der Datei, um ihre Struktur auf einer anderen Website genau rekonstruieren zu können. Dazu sind die folgenden Informationen erforderlich:
+die „attach“-Struktur (ohne Dateiinhalt - das ist die Standardeinstellung) für die Datei selbst **und** ihre übergeordneten Verzeichnisse, damit wir ihren genauen Platz im Dateisystem wiederherstellen können, da wir nicht wissen, ob das übergeordnete Verzeichnis schon einmal importiert worden ist oder nicht.
+Foto"-Struktur für alle Fotoelemente, die durch das Hochladen dieser Datei in das System erstellt wurden. Diese enthält in der Regel mehrere verschiedene „Skalen“ oder Miniaturbilder, von denen einige für die Verwendung als Profilfoto oder Titelbild beschnitten werden können. Wir müssen die Informationen über den Bildausschnitt aufbewahren, die nicht in den Metadaten, sondern nur in den gespeicherten Daten enthalten sind. Die eigentlichen Miniaturbilddaten können in den Metadaten enthalten sein, müssen es aber nicht. Ein großformatiges Titelbild (Maßstab #7) könnte möglicherweise zu Speicherproblemen führen. Es ist zwar nicht so schlimm wie ein 100-Meter-Video, aber wenn Sie mehrere davon haben, können sie sich summieren.
+
+Objekteinträge, die mit dieser Datei verknüpft sind. Dabei kann es sich um Dateifreigabeaktivitäten, das „übergeordnete Element“, das mit Fotos verknüpft ist, und angehängte Konversationselemente (Foto-Likes, Kommentare usw.) handeln.
+
+Für alle diese Elemente muss die URL ersetzt und das Element neu signiert werden, wenn es auf eine andere Website verschoben wird.
+
+#### Dateidaten
+
+Dann haben wir die eigentlichen Dateidaten, die wir für die Rekonstruktion der Datei benötigen. Diese müssen getrennt von den Metadaten gespeichert werden, damit der Speicher bei der Verarbeitung nicht überlastet wird. Die eigentlichen Dateidaten können zur Rekonstruktion der Anhangsstruktur und der ersten vier Fotoskalen verwendet werden. Wenn es sich um ein Foto handelt, benötigen wir Zugriff auf die Skala „#4“ (Profilfoto) und die Skala #7 (Titelbild), so wie sie ursprünglich beschnitten wurden. Alle anderen Miniaturansichten können aus diesen generiert werden.
+
+#### Dateiabgleich
+
+Wir werden diesen Vorgang zuerst betrachten, weil er wahrscheinlich am einfachsten zu implementieren ist. Wenn ein Foto zum Quellsystem hinzugefügt, entfernt oder geändert wird, senden wir ein Klon-Synchronisierungspaket an alle bekannten Klone, das die Metadaten, aber **keine Dateidaten** enthält. Wir können nur ein Sync-Paket pro zu synchronisierendem Dateivorgang senden.
+
+Die empfangende Seite erstellt alle Metadatenstrukturen, führt eine URL-Übersetzung durch und speichert sie. Dann muss es die eigentlichen Daten abrufen. Unter der Voraussetzung, dass CURL Streaming unterstützt, wird eine authentifizierte Anfrage an die ursprüngliche Website gesendet und die Originaldatei wird angefordert und direkt auf die Festplatte gestreamt (unter Umgehung aller Verarbeitungsschritte). Wenn Fotomaßstab Nr. 4 oder Maßstab Nr. 7 erforderlich ist, werden diese angefordert und in den entsprechenden Strukturen gespeichert. Wir gehen in diesem Fall davon aus, dass der große Maßstab des Titelbildes den Speicher nicht erschöpft. Wenn CURL das Streaming nicht unterstützen kann, müssen die Anforderungspakete in eine Warteschlange gestellt und an den Ursprungsort gesendet werden, um „Chunks“ der Datei zu erhalten, und wieder zusammengesetzt werden, sobald alle Chunks abgerufen wurden.
+
+Die authentifizierte Anfrage hängt vom jeweiligen Mechanismus ab. Beim CURL-Streaming muss wahrscheinlich ein signiertes Geheimnis mit einem Zeitstempel erzeugt und an die Ursprungsseite der Datei gesendet werden. Dann können die Daten mit minimaler interner Verarbeitung abgerufen und unter Verwendung von stdio-Pufferung direkt auf die Festplatte ausgegeben werden. Im Falle einer Nomad-Anforderung wird das Nomad-Anforderungspaket validiert, aber die Planung von Chunk-Batches und deren Wiederzusammensetzung könnte sich als schwierig erweisen.
+
+#### Sicherung/Wiederherstellung von Dateien
+
+Dies ist viel komplizierter, da wir keinen authentifizierten Webserver haben, von dem wir Daten anfordern können. Die Metadaten können größtenteils identisch sein, aber wir brauchen eine Form der Signalisierung, dass wir die Datei nicht über das Web abrufen werden. Dies erfordert wahrscheinlich einen clientseitigen Prozess, um jede Metadaten-Datei zu analysieren und eine Datei auf der Festplatte zu finden, mit der sie verknüpft ist. Anschließend müssten die Daten mit einem speziellen Endpunkt, der für diese Aufgabe vorgesehen ist, an den Zielserver gestreamt werden. Eine Java-Applikation könnte hier die beste Option sein, um die Plattformneutralität zu wahren.
+
+Eine andere Möglichkeit wäre die Verwendung von WebDAV für diesen Schritt. Die Metadaten würden zuerst hochgeladen, dann die Datendateien. Wenn eine Datendatei mit einer vorhandenen Metadaten-Datei übereinstimmt, würden die Metadaten verarbeitet, die Datei entsprechend gespeichert und die Metadaten-Datei anschließend entfernt. In diesem Fall müssten in den Metadaten Fotos der Skalen 4 und 7 bereitgestellt werden.
+
+Optional könnte dieser Schritt auch mit einem lokalen Dateisystem auf dem Zielserver durchgeführt werden. Dies wäre die leistungsfähigste Lösung, und eine Reihe von Shell-basierten Tools (im Falle von Linux) könnte die „Client-Seite“ der Aufgabe übernehmen.
+
+Die Komplexität dieser Aufgabe erfordert eine sorgfältige Planung, wie die Daten organisiert und gespeichert und gegebenenfalls per Fernzugriff gesichert oder von der Quellwebsite zur Sicherung übertragen werden.
+
+#### Rückwärtskompatibilität
+
+Es gibt einige offensichtliche Probleme bei der Bereitstellung von Daten für die Sicherung oder das Klonen, die auf dem System vor der Existenz von Wiederherstellungs-/Synchronisierungstools vorhanden waren. Um die Werkzeuge selbst relativ unkompliziert zu halten (soweit dies angesichts der Beschränkungen möglich ist), muss die Abwärtskompatibilität möglicherweise durch ein spezielles Plugin oder Addon hergestellt werden. \ No newline at end of file
diff --git a/doc/de/adminmanual/further_help.md b/doc/de/adminmanual/further_help.md
new file mode 100644
index 000000000..cfd465b07
--- /dev/null
+++ b/doc/de/adminmanual/further_help.md
@@ -0,0 +1,3 @@
+### Wo Sie weitere Hilfe finden
+
+Wenn Sie auf Probleme stoßen oder Fragen haben, die in dieser Dokumentation nicht behandelt werden, lassen Sie es uns bitte über den [Issue Tracker](https://framagit.org/hubzilla/core/issues) wissen. Bitte beschreiben Sie Ihre Betriebsumgebung so genau wie möglich und geben Sie so viele Details wie möglich zu den Fehlermeldungen an, die Sie sehen, damit wir diese in Zukunft vermeiden können. Aufgrund der großen Vielfalt an Betriebssystemen und PHP-Plattformen haben wir nur begrenzte Möglichkeiten, Ihre PHP-Installation zu debuggen oder fehlende Module zu beschaffen, aber wir werden unser Bestes tun, um allgemeine Code-Probleme zu lösen.
diff --git a/doc/de/adminmanual/hub_snapshot_tools.md b/doc/de/adminmanual/hub_snapshot_tools.md
new file mode 100644
index 000000000..8e56a2168
--- /dev/null
+++ b/doc/de/adminmanual/hub_snapshot_tools.md
@@ -0,0 +1,127 @@
+### Hub-Snapshot-Tools
+
+Hubzilla-Entwickler müssen häufig zwischen Zweigen wechseln, die möglicherweise inkompatible Datenbankschemata oder Inhalte haben. Die folgenden beiden Skripte erstellen und restaurieren komplette Snapshots einer Hubzilla-Instanz, einschließlich des Hub-Web-Root und des gesamten Datenbankstatus. Jedes Skript benötigt eine Konfigurationsdatei namens `hub-snapshot.conf`, die sich im selben Ordner befindet und die spezifischen Verzeichnisse und Datenbankdetails Ihres Hubs enthält.
+
+### Konfiguration
+
+Das Format der Konfigurationsdatei ist sehr streng. Es dürfen keine Leerzeichen zwischen dem Variablennamen und dem Wert stehen. Ersetzen Sie nur den Inhalt innerhalb der Anführungszeichen durch Ihre Konfiguration. Speichern Sie diese Datei als `hub-snapshot.conf` zusammen mit den Skripten.
+
+```
+# Ort des Hub-Root. Normalerweise ist dies der Speicherort des Hubzilla Repo-Klons.
+HUBROOT=„/var/www/“
+# Name der MySQL-Datenbank
+DBNAME=„hubzilla“
+# MySQL-Datenbank-Benutzer
+DBUSER=„hubzilla“
+# MySQL-Datenbank-Passwort
+DBPWD=„akeufajeuwfb“
+# Der Ziel-Snapshot-Ordner, in dem das Git-Repository initialisiert werden soll
+SNAPSHOTROOT=„/root/snapshots/hubzilla/“
+```
+
+#### Snapshot
+
+Beispiel für die Verwendung:
+
+```
+sh hub-snapshot.sh my-hub.conf „Commit-Meldung für den Snapshot“
+```
+
+**hub-snapshot.sh**:
+
+```
+#!/bin/bash
+
+if ! [ -f "$1" ]; then
+ echo "$1 is not a valid file. Aborting..."
+ exit 1
+fi
+source "$1"
+#echo "$DBNAME"
+#echo "$DBUSER"
+#echo "$DBPWD"
+#echo "$HUBROOT"
+#echo "$SNAPSHOTROOT"
+MESSAGE="snapshot: $2"
+
+if [ "$DBPWD" == "" -o "$SNAPSHOTROOT" == "" -o "$DBNAME" == "" -o "$DBUSER" == "" -o "$HUBROOT" == "" ]; then
+ echo "Required variable is not set. Aborting..."
+ exit 1
+fi
+
+if [ ! -d "$SNAPSHOTROOT"/db/ ]; then
+ mkdir -p "$SNAPSHOTROOT"/db/
+fi
+if [ ! -d "$SNAPSHOTROOT"/www/ ]; then
+ mkdir -p "$SNAPSHOTROOT"/www/
+fi
+
+if [ ! -d "$SNAPSHOTROOT"/www/ ] || [ ! -d "$SNAPSHOTROOT"/db/ ]; then
+ echo "Error creating snapshot directories. Aborting..."
+ exit 1
+fi
+
+echo "Export database..."
+mysqldump -u "$DBUSER" -p"$DBPWD" "$DBNAME" > "$SNAPSHOTROOT"/db/"$DBNAME".sql
+echo "Copy hub root files..."
+rsync -va --delete --exclude=.git* "$HUBROOT"/ "$SNAPSHOTROOT"/www/
+
+cd "$SNAPSHOTROOT"
+
+if [ ! -d ".git" ]; then
+ git init
+fi
+if [ ! -d ".git" ]; then
+ echo "Cannot initialize git repo. Aborting..."
+ exit 1
+fi
+
+git add -A
+echo "Commit hub snapshot..."
+git commit -a -m "$MESSAGE"
+
+exit 0
+```
+
+#### Restore
+
+```
+#!/bin/bash
+# Restore hub to a previous state. Input hub config and commit hash
+
+if ! [ -f "$1" ]; then
+ echo "$1 is not a valid file. Aborting..."
+ exit 1
+fi
+source "$1"
+COMMIT=$2
+
+if [ "$DBPWD" == "" -o "$SNAPSHOTROOT" == "" -o "$DBNAME" == "" -o "$DBUSER" == "" -o "$HUBROOT" == "" ]; then
+ echo "Required variable is not set. Aborting..."
+ exit 1
+fi
+RESTOREDIR="$(mktemp -d)/"
+
+if [ ! -d "$RESTOREDIR" ]; then
+ echo "Cannot create restore directory. Aborting..."
+ exit 1
+fi
+echo "Cloning the snapshot repo..."
+git clone "$SNAPSHOTROOT" "$RESTOREDIR"
+cd "$RESTOREDIR"
+echo "Checkout requested snapshot..."
+git checkout "$COMMIT"
+echo "Restore hub root files..."
+rsync -a --delete --exclude=.git* "$RESTOREDIR"/www/ "$HUBROOT"/
+echo "Restore hub database..."
+mysql -u "$DBUSER" -p"$DBPWD" "$DBNAME" < "$RESTOREDIR"/db/"$DBNAME".sql
+
+chown -R www-data:www-data "$HUBROOT"/{store,extend,addon,.htlog,.htconfig.php}
+
+echo "Restored hub to snapshot $COMMIT"
+echo "Removing temporary files..."
+
+rm -rf "$RESTOREDIR"
+
+exit 0
+``` \ No newline at end of file
diff --git a/doc/de/adminmanual/installation.md b/doc/de/adminmanual/installation.md
new file mode 100644
index 000000000..4efbf4239
--- /dev/null
+++ b/doc/de/adminmanual/installation.md
@@ -0,0 +1,8 @@
+### Installation
+
+Es gibt mehrere Möglichkeiten, einen neuen Hub zu installieren.
+
+- Manuelle Installation auf einem bestehenden Server
+- Automatisierte Installation auf einem bestehenden Server mit einem Shell-Skript
+- Automatisierte Bereitstellung über einen OpenShift Virtual Private Server (VPS)
+- Verwendung eines Docker-Containers \ No newline at end of file
diff --git a/doc/de/adminmanual/installation_requirements.md b/doc/de/adminmanual/installation_requirements.md
new file mode 100644
index 000000000..b7f772add
--- /dev/null
+++ b/doc/de/adminmanual/installation_requirements.md
@@ -0,0 +1,14 @@
+### Anforderungen
+
+- Apache mit aktiviertem mod-rewrite und „AllowOverride All“, damit Sie eine lokale .htaccess-Datei verwenden können. Einige Leute haben erfolgreich nginx und lighttpd verwendet. Beispielkonfigurations-Skripte sind für diese Plattformen in doc/install verfügbar. Apache und nginx haben die meiste Unterstützung.
+- PHP 8.1 oder höher. Beachten Sie, dass in einigen Shared-Hosting-Umgebungen die *Kommandozeilenversion* von PHP von der *Webserverversion* abweichen kann
+- *PHP-Befehlszeilenzugriff*, wenn register_argc_argv in der Datei php.ini auf true gesetzt ist und der Hosting-Provider keine Einschränkungen für die Verwendung von exec() und proc_open() hat.
+- curl, gd (mit mindestens jpeg und png Unterstützung), pdo-mysql (oder pdo-postgres), mbstring, zip und openssl Erweiterungen. Die imagick-Erweiterung ist nicht erforderlich, wird aber empfohlen.
+- Die xml-Erweiterung ist erforderlich, wenn Sie webdav verwenden möchten.
+- eine Art von E-Mail-Server oder E-Mail-Gateway, so dass PHP mail() funktioniert.
+- Ein unterstützter Datenbankserver. Die unterstützten Datenbanken sind:
+ - Mysql Version 8.0.22 oder höher
+ - MariaDB Version 10.4 oder höher
+ - PostgreSQL Version 12 oder höher
+- Fähigkeit, Aufträge mit Cron zu planen.
+- Die Installation in einer Top-Level-Domain oder Sub-Domain (ohne Verzeichnis/Pfad-Komponente in der URL) ist ERFORDERLICH. \ No newline at end of file
diff --git a/doc/de/adminmanual/manual_for_administrators.md b/doc/de/adminmanual/manual_for_administrators.md
new file mode 100644
index 000000000..9b3237620
--- /dev/null
+++ b/doc/de/adminmanual/manual_for_administrators.md
@@ -0,0 +1,30 @@
+### Handbuch für Administratoren
+
+Hubzilla ist mehr als eine einfache Webanwendung. Es handelt sich um ein komplexes Kommunikationssystem, das eher einem E-Mail-Server als einem Webserver ähnelt. Aus Gründen der Zuverlässigkeit und Leistung werden Nachrichten im Hintergrund zugestellt und für eine spätere Zustellung in eine Warteschlange gestellt, wenn Websites nicht erreichbar sind. Diese Art von Funktionalität erfordert etwas mehr vom Hostsystem als der typische Blog. Nicht jeder PHP/MySQL-Hosting-Anbieter ist in der Lage, Hubzilla zu unterstützen. Viele können es, aber bitte prüfen Sie die Anforderungen und bestätigen Sie diese mit Ihrem Hosting-Anbieter vor der Installation.
+
+Wir haben uns sehr bemüht, sicherzustellen, dass Hubzilla auf Standard-Hosting-Plattformen läuft, wie sie für Wordpress-Blogs und Drupal-Websites verwendet werden. Es läuft auf den meisten Linux-Systemen.
+
+#include doc/de/adminmanual/further_help.md;
+#include doc/de/adminmanual/before_you_start.md;
+#include doc/de/adminmanual/installation.md;
+#include doc/de/adminmanual/automated_installation.md;
+#include doc/de/adminmanual/Installation_using_docker.md;
+#include doc/de/adminmanual/recommendec_addons.md;
+#include doc/de/adminmanual/problems-following-an-update.md;
+#include doc/de/adminmanual/federation_addons.md;
+#include doc/de/adminmanual/service_classes.md;
+#include doc/de/adminmanual/theme_management.md;
+#include doc/de/adminmanual/Creating-Templates.md;
+#include doc/de/adminmanual/Schema-development.md;
+#include doc/de/adminmanual/Widgets.md;
+#include doc/de/adminmanual/channel_directory.md;
+#include doc/de/adminmanual/Primary-Directory.md;
+#include doc/de/adminmanual/administration.md;
+#include doc/de/adminmanual/troubleshooting.md;
+#include doc/de/adminmanual/hub_snapshot_tools.md;
+#include doc/de/adminmanual/database.md;
+#include doc/de/adminmanual/advanced_configurations.md;
+#include doc/de/adminmanual/CLI_tools.md;
+#include doc/de/adminmanual/filesync.md;
+#include doc/de/adminmanual/Nomad---A-High-Level-Overview.md;
+#include doc/de/adminmanual/faq_admins.md;
diff --git a/doc/de/adminmanual/manual_installation.md b/doc/de/adminmanual/manual_installation.md
new file mode 100644
index 000000000..303b5d416
--- /dev/null
+++ b/doc/de/adminmanual/manual_installation.md
@@ -0,0 +1,60 @@
+### Manuelle Installation
+
+#### Entpacken Sie die Hubzilla-Dateien in das Stammverzeichnis Ihres Webserver-Dokumentbereichs
+
+Wenn Sie den Verzeichnisbaum auf Ihren Webserver kopieren, stellen Sie sicher, dass Sie die versteckten Dateien wie .htaccess mit einbeziehen.
+
+Wenn Sie dazu in der Lage sind, empfehlen wir, das Quellcode-Repository mit git zu klonen, anstatt eine gepackte tar- oder zip-Datei zu verwenden. Dadurch lässt sich die Software viel einfacher aktualisieren. Der Linux-Befehl zum Klonen des Repositorys in ein Verzeichnis „mywebsite“ lautet wie folgt:
+
+```
+git clone https://framagit.org/hubzilla/core.git mywebsite
+```
+
+und dann können Sie die neuesten Änderungen jederzeit mit:
+
+```
+git pull
+```
+
+Stellen Sie sicher, dass die Ordner `store/[data]/smarty3` und `store` existieren und vom Webserver beschreibbar sind:
+
+```
+mkdir -p „store/[data]/smarty3“
+chmod -R 777 store
+```
+
+Diese Berechtigung (777) ist sehr gefährlich und wenn Sie über ausreichende Privilegien und Kenntnisse haben, sollten Sie diese Verzeichnisse nur für den Webserver und, falls abweichend, für den Benutzer, der den cron-Job ausführt (siehe unten). In vielen Shared-Hosting-Umgebungen kann dies schwierig sein, ohne ein Trouble-Ticket bei Ihrem Provider zu eröffnen. Die obigen Berechtigungen ermöglichen das Funktionieren der Software, sind aber nicht optimal.
+
+Die folgenden Verzeichnisse müssen ebenfalls vom Webserver beschreibbar sein, damit bestimmte webbasierte Verwaltungstools funktionieren:
+
+- `addon`
+- `extend`
+- `view/theme`
+- `widget`
+
+#### Offizielle Addons
+
+##### Installation
+
+Navigieren Sie zu Ihrer Website. Dann sollten Sie das Addon-Repository klonen (separat). Wir geben diesem Repository den Spitznamen 'hzaddons'. Du kannst andere Hubzilla-Addon-Repositories einbinden, indem du ihnen andere Nicknames gibst:
+
+```
+cd mywebsite
+util/add_addon_repo https://framagit.org/hubzilla/addons.git hzaddons
+```
+
+##### Aktualisieren
+
+Um den Addon-Baum auf dem neuesten Stand zu halten, sollten Sie sich auf der obersten Ebene des Website-Verzeichnisses befinden und einen Update-Befehl für dieses Repository eingeben::
+
+```
+cd mywebsite
+util/update_addon_repo hzaddons
+```
+
+Erstellen Sie durchsuchbare Darstellungen der Online-Dokumentation. Sie können dies jedes Mal tun, wenn die Dokumentation aktualisiert wird:
+
+```
+cd mywebsite
+util/importdoc
+``` \ No newline at end of file
diff --git a/doc/de/adminmanual/pic/adm01.png b/doc/de/adminmanual/pic/adm01.png
new file mode 100644
index 000000000..5c5b1bab3
--- /dev/null
+++ b/doc/de/adminmanual/pic/adm01.png
Binary files differ
diff --git a/doc/de/adminmanual/problems-following-an-update.md b/doc/de/adminmanual/problems-following-an-update.md
new file mode 100644
index 000000000..2bfbaf83d
--- /dev/null
+++ b/doc/de/adminmanual/problems-following-an-update.md
@@ -0,0 +1,31 @@
+### Probleme nach einer Aktualisierung
+
+Gut 90 % aller Fehler, die unmittelbar nach der Aktualisierung des Codes auf die neueste Version auftreten, sind einfache Cache-Fehler der einen oder anderen Art. Wenn Sie ein Update durchführen und feststellen, dass etwas sehr Offensichtliches kaputt ist - z. B. dass Ihre Matrix-Seite nicht geladen wird, dass Benachrichtigungen fehlen oder dass Kommentarboxen fehlen - dann ist es wahrscheinlich gar kein Fehler. Das Brechen grundlegender Funktionen ist die Art von Dingen, die Entwickler eher bemerken.
+Wenn Ihnen das passiert, können Sie einige einfache Schritte unternehmen, bevor Sie sich an die Support-Foren wenden:
+
+**Browser-Cache**
+Symptome: Menüs lassen sich nicht ausklappen, die ACL-Auswahl öffnet sich nicht, die Fortschrittsanzeige wird nicht angezeigt (oder läuft in einer Endlosschleife), Matrix- und Kanalseiten werden nicht geladen.
+Erzwingen Sie das Neuladen der Seite. Gelegentlich, aber sehr, sehr selten, müssen Sie auch die Sitzungsdaten löschen, was durch einen Neustart des Browsers erreicht wird.
+
+**FastCGI**
+Symptome: Falsche Variablen. Die grundlegende Benutzeroberfläche funktioniert meistens, zeigt aber falsche Inhalte an oder es fehlen ganz Inhalte.
+Wenn Sie php-fpm verwenden, lässt sich dieses Problem in der Regel beheben mit
+
+```
+service php-fpm neu starten
+```
+
+**Smarty-Cache**
+Die Symptome:
+
+1. Weißer Bildschirm des Todes. Dies tritt vor allem auf den Einstellungs- und Verwaltungsseiten auf.
+2. Fehlende Icons, Registerkarten, Menüs oder Funktionen.
+
+Wir verwenden die Smarty3-Vorlagen-Engine, um Seiten zu generieren. Diese Vorlagen werden kompiliert, bevor sie angezeigt werden. Es kann vorkommen, dass eine neue oder geänderte Vorlage die alte kompilierte Version nicht überschreibt. Um den Smarty-Cache zu löschen, löschen Sie alle Dateien in store/[data]/smarty3/compiled **, aber nicht das Verzeichnis selbst**. Die Vorlagen werden dann bei ihrem nächsten Zugriff neu kompiliert.
+
+**Theme-Probleme**
+Es gibt viele Themes für Hubzilla. Nur Redbasic wird offiziell von den Kernentwicklern unterstützt. Dies gilt *auch dann, wenn ein Kernentwickler zufällig ein weiteres Theme unterstützt*. Das bedeutet, dass neue Funktionen garantiert nur in Redbasic funktionieren.
+
+Redbasic verwendet einige Javascript-Bibliotheken, die in anderen Themes anders oder gar nicht verwendet werden. Dies bedeutet, dass neue Funktionen möglicherweise nur in Redbasic richtig funktionieren. Bevor Sie ein Problem melden, sollten Sie daher zu Redbasic wechseln, um zu sehen, ob das Problem dort existiert. Wenn das Problem verschwindet, handelt es sich nicht um einen Fehler, sondern um ein Theme, das nicht auf dem neuesten Stand ist.
+
+Sollten Sie das Problem dann den Theme-Entwicklern melden? Nein. Theme-Entwickler verwenden ihre Themes. Die Chancen stehen gut, dass sie es wissen. Geben Sie ihnen zwei oder drei Tage Zeit, um sich auf den neuesten Stand zu bringen, und melden Sie das Problem, wenn es *dann* immer noch nicht behoben ist. Für diese Situation gibt es zwei Lösungsmöglichkeiten. Erstens können Sie vorübergehend Redbasic verwenden. Zweitens sind die meisten Themes auch Open Source - öffnen Sie einen Pull Request und machen Sie sich einen Freund. \ No newline at end of file
diff --git a/doc/de/adminmanual/recommended_addons.md b/doc/de/adminmanual/recommended_addons.md
new file mode 100644
index 000000000..d39325fed
--- /dev/null
+++ b/doc/de/adminmanual/recommended_addons.md
@@ -0,0 +1,7 @@
+### Empfohlene Addons
+
+Wir empfehlen, die folgenden Addons auf allen öffentlichen Seiten zu installieren:
+
+**nsfw** - unangemessene Beiträge/Kommentare ausblenden
+
+**superblock** - blockiert Inhalte von anstößigen Kanälen \ No newline at end of file
diff --git a/doc/de/adminmanual/service_classes.md b/doc/de/adminmanual/service_classes.md
new file mode 100644
index 000000000..ac79096d9
--- /dev/null
+++ b/doc/de/adminmanual/service_classes.md
@@ -0,0 +1,57 @@
+### Service-Klassen
+
+Mit Hilfe von Dienstklassen können Sie die Systemressourcen begrenzen, indem Sie die Möglichkeiten einzelner Konten einschränken, z. B. die Speicherung von Dateien und die Begrenzung von Posts auf der obersten Ebene. Definieren Sie benutzerdefinierte Serviceklassen entsprechend Ihren Anforderungen in der Datei `.htconfig.php`. Erstellen Sie zum Beispiel eine *Standard-* und eine *Premiumklasse* mit den folgenden Zeilen:
+
+```
+// Dienstklassen
+
+App::$config['system']['default_service_class']='standard'; // dies ist die Standard-Serviceklasse, die mit jedem neuen Konto verbunden wird
+
+// Konfiguration für die Standard-Serviceklasse
+App::$config['service_class']['standard'] =
+array('photo_upload_limit'=>2097152, // Gesamtspeichergrenze für Fotos pro Kanal (hier 2MB)
+'total_identities' =>1, // Anzahl der Kanäle, die ein Konto erstellen kann
+'total_items' =>0, // Anzahl der Beiträge der obersten Ebene, die ein Kanal erstellen kann. Gilt nur für Top-Level-Posts des Channel-Benutzers, andere Posts und Kommentare sind davon nicht betroffen
+'total_pages' =>100, // Anzahl der Seiten, die ein Channel erstellen kann
+'total_channels' =>100, // Anzahl der Channels, die der Benutzer hinzufügen kann, andere Benutzer können diesen Channel immer noch hinzufügen, auch wenn das Limit erreicht ist
+attach_upload_limit' =>2097152, // Gesamtspeicherplatz für Anhänge pro Kanal (hier 2MB)
+'chatters_inroom' =>20);
+
+// Konfiguration für Premium-Dienstklasse
+App::$config['service_class']['premium'] =
+array('photo_upload_limit'=>20000000000, // Gesamtspeichergrenze für Fotos pro Kanal (hier 20GB)
+'total_identities' =>20, // Anzahl der Kanäle, die ein Konto erstellen kann
+'total_items' =>20000, // Anzahl der Beiträge der obersten Ebene, die ein Kanal erstellen kann. Gilt nur für Beiträge der obersten Ebene des Kanalbenutzers, andere Beiträge und Kommentare sind davon nicht betroffen
+'total_pages' =>400, // Anzahl der Seiten, die ein Channel erstellen kann
+'total_channels' =>2000, // Anzahl der Channels, die der Nutzer hinzufügen kann, andere Nutzer können diesen Channel immer noch hinzufügen, auch wenn das Limit erreicht ist
+attach_upload_limit' =>20000000000, // Gesamtspeicherplatz für Anhänge pro Kanal (hier 20GB)
+'chatters_inroom' =>100);
+```
+
+Um eine Serviceklasse auf ein bestehendes Konto anzuwenden, verwenden Sie das Befehlszeilendienstprogramm aus dem Web-Root:
+
+`util/service_class` list service classes
+
+`util/config system default_service_class firstclass` setzt die Standarddienstklasse auf 'firstclass'
+
+`util/service_class firstclass` listet die Dienste auf, die Teil der Dienstklasse „firstclass“ sind
+
+`util/service_class firstclass photo_upload_limit 10000000` setzt die gesamte Fotoplattennutzung der firstclass auf 10 Millionen Bytes
+
+`util/service_class --account=5 firstclass` setzt die Konto-ID 5 auf die Serviceklasse 'firstclass' (mit Bestätigung)
+
+`util/service_class --channel=blogchan firstclass` setzt das Konto, dem der Kanal „blogchan“ gehört, auf die Serviceklasse „firstclass“ (mit Bestätigung)
+
+**Optionen zur Begrenzung der Dienstklasse**
+
+- photo_upload_limit - maximale Gesamtzahl an Bytes für Fotos
+- total_items - maximale Gesamtzahl an Beiträgen auf oberster Ebene
+- total_pages - maximale Anzahl von Seiten für Comanche
+- total_identities - maximale Anzahl von Kanälen im Besitz des Kontos
+- total_channels - maximale Anzahl von Verbindungen
+- total_feeds - Maximale Anzahl von RSS-Feed-Verbindungen
+- attach_upload_limit - maximaler Datei-Upload-Speicherplatz (Bytes)
+- minimum_feedcheck_minutes - niedrigste zulässige Einstellung für die Abfrage von RSS-Feeds
+- chatrooms - maximale Anzahl von Chaträumen
+- chatters_inroom - maximale Anzahl Chatter pro Raum
+- access_tokens - maximale Anzahl von Gastzugangstoken pro Kanal \ No newline at end of file
diff --git a/doc/de/adminmanual/theme_management.md b/doc/de/adminmanual/theme_management.md
new file mode 100644
index 000000000..bfdf4d261
--- /dev/null
+++ b/doc/de/adminmanual/theme_management.md
@@ -0,0 +1,7 @@
+### Verwaltung der Themen
+
+#### Beispiel für die Repo-Verwaltung
+
+1. Navigieren Sie zu Ihrem Hub-Web rootroot@hub`:/root# cd /var/www`
+
+2. Fügen Sie das Theme-Repo hinzu und geben Sie ihm einen nameroot@hub`:/var/www# util/add_theme_repo https://github.com/DeadSuperHero/redmatrix-themes.git DeadSuperHero` \ No newline at end of file
diff --git a/doc/de/adminmanual/troubleshooting.md b/doc/de/adminmanual/troubleshooting.md
new file mode 100644
index 000000000..384202b65
--- /dev/null
+++ b/doc/de/adminmanual/troubleshooting.md
@@ -0,0 +1,51 @@
+### Fehlersuche
+
+#### Logdateien
+
+Die Systemprotokolldatei ist eine äußerst nützliche Ressource, um Fehler aufzuspüren. Dies kann auf der Konfigurationsseite admin/log aktiviert werden. Eine Loglevel-Einstellung von `LOGGER_DEBUG` wird für stabile Produktionsstandorte bevorzugt. Die meisten Probleme bei der Kommunikation oder der Speicherung werden hier aufgelistet. Eine Einstellung von LOGGER_DATA bietet *viel* mehr Details, kann aber Ihre Festplatte füllen. In jedem Fall empfehlen wir die Verwendung von logrotate auf Ihrem Betriebssystem, um die Protokolle zu überprüfen und ältere Einträge zu löschen.
+
+Am Ende der Datei .htconfig.php befinden sich mehrere (auskommentierte) Zeilen, die die PHP-Fehlerprotokollierung aktivieren. Dies meldet Probleme mit der Codesyntax und der Ausführung des Codes und ist die erste Stelle, an der Sie nach Problemen suchen sollten, die zu einem „weißen Bildschirm“ oder einer leeren Seite führen. Dies ist in der Regel das Ergebnis von Code-/Syntaxproblemen. Datenbankfehler werden in der Systemprotokolldatei gemeldet, aber wir haben es als nützlich empfunden, in Ihrem Hauptverzeichnis eine Datei namens dbfail.out anzulegen, in der *nur* datenbankbezogene Probleme gesammelt werden. Wenn die Datei existiert und beschreibbar ist, werden Datenbankfehler sowohl in dieser Datei als auch in der Systemprotokolldatei protokolliert.
+
+Im Falle von „500“-Fehlern werden die Probleme oft in den Protokollen Ihres Webservers protokolliert, oft in /var/log/apache2/error.log oder ähnlichem. Konsultieren Sie die Dokumentation Ihres Betriebssystems.
+
+Es gibt drei verschiedene Protokollierungsmöglichkeiten.
+
+**Die erste ist das Datenbankfehlerprotokoll**. Dieses wird nur verwendet, wenn Sie eine Datei mit dem Namen `dbfail.out` im Stammverzeichnis Ihrer Website anlegen und sie für den Webserver schreibbar machen. Wenn Datenbankabfragen fehlgeschlagen sind, werden sie hier gemeldet. Sie weisen im Allgemeinen auf Tippfehler in unseren Abfragen hin, treten aber auch auf, wenn der Datenbankserver die Verbindung unterbricht oder Tabellen beschädigt werden. In seltenen Fällen werden hier Race Conditions angezeigt, wenn zwei Prozesse versuchen, einen xchan- oder Cache-Eintrag mit derselben ID zu erstellen. Alle anderen Fehler (insbesondere anhaltende Fehler) sollten untersucht werden.
+
+**Der zweite Bereich ist das PHP-Fehlerprotokoll**. Dieses wird vom Sprachprozessor erstellt und meldet nur Probleme in der Sprachumgebung. Auch hier kann es sich um Syntax- oder Programmierfehler handeln, die jedoch in der Regel fatal sind und zu einem „weißen Bildschirm des Todes“ führen, d. h. PHP wird beendet. Sie sollten sich diese Datei ansehen, wenn etwas schief läuft, das nicht zu einem weißen Bildschirm führt, aber es ist nicht ungewöhnlich, dass diese Datei tagelang leer ist.
+
+Am Ende der mitgelieferten Datei `.htconfig.php` befinden sich einige Zeilen, die, wenn sie nicht auskommentiert werden, ein PHP-Fehlerprotokoll aktivieren (*äußerst* nützlich, um die Ursache von Fehlern beim weißen Bildschirm zu finden). Dies ist standardmäßig nicht der Fall, da es Probleme mit dem Eigentum an der Logdatei und den Schreibrechten geben könnte und die Logdatei standardmäßig nicht rotiert.
+
+**Die dritte Datei ist das „Anwendungsprotokoll“.** Dieses wird von Hubzilla verwendet, um zu berichten, was im Programm vor sich geht und meldet normalerweise alle Schwierigkeiten oder unerwarteten Daten, die wir erhalten haben. Gelegentlich werden auch „Heartbeat“-Statusmeldungen ausgegeben, um anzuzeigen, dass wir einen bestimmten Punkt in einem Skript erreicht haben. **Dies** ist die wichtigste Protokolldatei für uns, da wir sie selbst nur zu dem Zweck erstellen, den Status von Hintergrundaufgaben und alles, was seltsam oder fehl am Platz erscheint, zu melden. Es muss nicht unbedingt etwas Schlimmes sein, aber vielleicht einfach nur unerwartet. Wenn Sie eine Aufgabe ausführen und ein Problem auftritt, lassen Sie uns wissen, was in dieser Datei steht, wenn das Problem auftritt. (Bitte schicken Sie mir keine 100M-Dumps, das würde mich nur verärgern). Nur ein paar relevante Zeilen, damit ich ein paar hunderttausend Codezeilen ausschließen und mich darauf konzentrieren kann, wo das Problem auftritt.
+
+Dies sind Ihre Website-Protokolle, nicht meine. Wir melden ernsthafte Probleme auf jeder Protokollebene. Für die meisten Websites empfehle ich die Log-Ebene `DEBUG` - sie liefert ein paar zusätzliche Informationen und erzeugt keine riesigen Logdateien. Wenn ein Problem auftritt, das sich allen Versuchen entzieht, es zu verfolgen, sollten Sie für einen kurzen Zeitraum die Protokollebene `DATA` verwenden, um alle Details der Strukturen zu erfassen, mit denen wir es zu diesem Zeitpunkt zu tun hatten. Diese Protokollebene verbraucht sehr viel Speicherplatz und wird daher nur für kurze Zeiträume oder für Test-Sites von Entwicklern empfohlen.
+
+Ich empfehle die Konfiguration von logrotate sowohl für das php-Protokoll als auch für das Anwendungsprotokoll. Normalerweise werfe ich alle ein bis zwei Wochen einen Blick auf dbfail.out, behebe alle gemeldeten Probleme und beginne dann mit einer neuen Datei. Das Gleiche gilt für die PHP-Protokolldatei. Ich schaue darin ab und zu nach, ob es etwas gibt, das behoben werden muss.
+
+Wenn etwas schief läuft und es sich nicht um einen schwerwiegenden Fehler handelt, schaue ich mir die Anwendungsprotokolldatei an. Oft werde ich
+
+```
+tail -f logfile.out
+```
+
+während ich einen Vorgang wiederhole, bei dem es Probleme gibt. Oft füge ich zusätzliche Logging-Anweisungen in den Code ein, wenn es keinen Hinweis darauf gibt, was schief läuft. Selbst so etwas Einfaches wie „got here“ oder der Ausdruck des Wertes einer Variable, die verdächtig sein könnte. Auch das können Sie tun - ich möchte Sie sogar dazu ermutigen, dies zu tun. Sobald Sie gefunden haben, was Sie suchen müssen, können Sie
+
+```
+git checkout file.php
+```
+
+um sofort alle zusätzlichen Protokollierungsdaten, die Sie hinzugefügt haben, zu löschen. Verwenden Sie die Informationen aus diesem Protokoll und alle Details, die Sie aus Ihrer Untersuchung des Problems liefern können, um Ihren Fehlerbericht einzureichen - es sei denn, Ihre Analyse weist auf die Quelle des Problems hin. In diesem Fall beheben Sie es einfach.
+
+##### Rotierende Protokolldateien
+
+1. Aktiviere das **logrot** Addon im offiziellen [hubzilla-addons](https://framagit.org/hubzilla/addons) repo
+2. Legen Sie in Ihrem Web-Root ein Verzeichnis namens `log` mit Schreibrechten für den Webserver an
+3. Gehen Sie zu den **logrot-Administrationseinstellungen** und geben Sie diesen Ordnernamen sowie die maximale Größe und Anzahl der aufbewahrten Protokolldateien ein.
+
+#### Probleme melden
+
+Wenn Sie Probleme melden, versuchen Sie bitte, so viele Details wie nötig anzugeben, damit die Entwickler das Problem reproduzieren können, und geben Sie den vollständigen Text aller Fehlermeldungen an.
+
+Wir ermutigen Sie, diese Protokolle zusammen mit dem Quellcode, den Sie besitzen, so gut wie möglich zu nutzen, um Probleme zu beheben und ihre Ursache zu finden. Die Community kann oft helfen, aber nur Sie haben Zugang zu den Logdateien Ihrer Website, und es wird als Sicherheitsrisiko angesehen, sie weiterzugeben.
+
+Wenn ein Code-Problem aufgedeckt wurde, melden Sie es bitte im Bugtracker des Projekts (https://framagit.org/hubzilla/core/issues). Geben Sie auch hier so viele Details wie möglich an, damit wir nicht immer wieder Fragen zu Ihrer Konfiguration stellen oder das Problem duplizieren müssen, damit wir das Problem direkt angehen und herausfinden können, was zu tun ist. Sie können auch gerne Ihre eigenen Lösungen anbieten und Patches einreichen. Wir ermutigen Sie sogar dazu, da wir alle Freiwillige sind und nur wenig Zeit zur Verfügung haben. Je mehr Leute mithelfen, desto leichter wird die Arbeit für alle. Es ist in Ordnung, wenn Ihre Lösung nicht perfekt ist. Jedes bisschen hilft, und vielleicht können wir es ja noch verbessern. \ No newline at end of file
diff --git a/doc/de/admins.bb b/doc/de/admins.bb
deleted file mode 100644
index d278c04ac..000000000
--- a/doc/de/admins.bb
+++ /dev/null
@@ -1,10 +0,0 @@
-[h2]Dokumentation für Hub-Administratoren[/h2]
-
-[zrl=[baseurl]/help/install]Installation[/zrl]
-[zrl=[baseurl]/help/red2pi]$Projectname auf einem Raspberry Pi installieren[/zrl]
-[zrl=[baseurl]/help/troubleshooting]Troubleshooting-Tipps[/zrl]
-[zrl=[baseurl]/help/hidden_configs]Versteckte Konfigurations-Optionen[/zrl]
-[zrl=[baseurl]/help/faq_admins]FAQ für Admins[/zrl]
-[zrl=[baseurl]/help/service_classes]Serviceklassen[/zrl]
-[zrl=[baseurl]/help/directories]Arbeit mit Verzeichnissen und ihre Konfiguration[/zrl]
-[zrl=[baseurl]/help/theme_management]Theme-Management[/zrl]
diff --git a/doc/de/basicconcepts.md b/doc/de/basicconcepts.md
new file mode 100644
index 000000000..65a845f73
--- /dev/null
+++ b/doc/de/basicconcepts.md
@@ -0,0 +1,67 @@
+## Grundlegende Konzepte von Hubzilla
+
+
+
+### Kanäle (im Sinne von Identitäten im Gegensatz zu einer App)
+
+Als Nutzer (d. h. als Privatperson oder als Verein) können Sie eine oder mehrere Web-**Identitäten** erstellen. Die Webpräsenz einer Identität wird innerhalb von Hubzilla gebündelt: Ein Besucher der Web-Identität sieht Inhalte, die mit dieser Identität in Verbindung stehen, an einem Ort, der über das Hauptmenü *Apps* für diese Identität verfügbar ist.
+
+Intern, innerhalb der Software, wird eine Identität als „Kanal“ bezeichnet. Für einen Besucher bedeutet das Wort „Kanal“ jedoch **eine** bestimmte App, die zu dieser Identität gehört, nämlich die **Pinnwand**, auf der die (möglicherweise) föderierten Beiträge der Identität in einer zeitachsenbasierten Timeline angezeigt werden.
+
+Im Folgenden verstehen wir Kanäle als Identitäten: Als angemeldeter Benutzer können Sie zwischen Ihren Kanälen wechseln, um die Inhalte für jeden Kanal zu bearbeiten, d. h. Beiträge zu veröffentlichen oder ein oder mehrere Kanalprofile, Websites, Wiki-Seiten und mehr zu erstellen. Pro Kanal können Sie auch Dateien in einer Cloud verwalten, Fotos mit Tags versehen und benennen und die Fotos in einer Webgalerie anzeigen. Ereignisse des Kanals können in einem Kalender angezeigt werden.
+
+
+
+### Dezentrales Netzwerk: Zugriff über Grenzen hinweg (Zot/Nomad) vs. Bereitstellung über Grenzen hinweg (Zot/Nomad, ActivityPub und Diaspora)
+
+Wenn dies erlaubt ist, verbinden sich Identitäten auf Hubzilla über Server- und Verwaltungsgrenzen hinweg über das Kommunikationsprotokoll Zot/Nomad (und, sofern von Ihrem Server bereitgestellt, auch über die Protokolle ActivityPub und Diaspora).
+
+Mit Ausnahme von Beiträgen/Nachrichten bleiben alle veröffentlichten Inhalte lokal auf Ihrem Server. Sie können Inhalte zwar öffentlich veröffentlichen, aber auch lokale Inhalte nur mit bestimmten Verbindungen teilen. Letzteres ist nur mit Verbindungen über Zot/Nomad (also mit Identitäten auf Hubzilla und Streams) möglich.
+
+
+
+### Zugriffskontrolle
+
+Ihre lokalen Inhalte können von den Zot/Nomad-basierten Verbindungen „besucht“ werden, denen Sie die entsprechende Berechtigung erteilt haben. Indem Sie Ihre Verbindungen Verbindungslisten (sogenannten „Datenschutzgruppen“) zuordnen, können Sie allen Mitgliedern dieser Liste Zugriff auf bestimmte Inhalte gewähren.
+
+Sie können Beiträge/Nachrichten (auch über die Protokolle ActivityPub oder Disaspora) an eine Verbindung, an eine Datenschutzgruppe oder an die Öffentlichkeit senden, indem Sie eine Zugriffskontrollliste verwenden.
+
+Es ist möglich, eine Dauer für das Ablaufen eines Beitrags/einer Nachricht festzulegen. Auf diese Weise können Sie den Zugriff zeitlich begrenzen.
+
+
+
+### Fernauthentifizierung
+
+„Kennst du mich? – Ja, ich kenne dich, willkommen!“
+Der Zugriff auf nicht öffentliche Inhalte auf einem Remote-Server sollte nur möglich sein, wenn du dich gegenüber diesem Server authentifizieren kannst. Fediverse-Software, die nur das ActivityPub- oder das Diaspora-Protokoll verwendet, kann jedoch nur Konten vom lokalen Server authentifizieren.
+Das Zot-Protokoll hingegen verfügt über einen integrierten Mechanismus namens MagicAuth, der es einem Server ermöglicht, Identitäten, die auf einem anderen (entfernten) Server registriert sind, Zugriff auf Inhalte und Aktionen zu gewähren oder zu verweigern.
+
+
+
+### Nomadische Identität
+
+Das Zot/Nomad-Protokoll ermöglicht es, Ihre Kanäle von dem Hub zu trennen, auf dem Sie sie erstellt haben. Sie können sie auf einen anderen Hub übertragen oder klonen. In diesem Fall existieren die Identität und die Daten des Kanals gleichzeitig an mehreren Orten. Dies sorgt für Ausfallsicherheit der Kanäle, falls ein Hub abgeschaltet wird oder nicht mehr verfügbar ist.
+
+
+
+### Modulares Ökosystem durch Apps
+
+ Hubzilla stellt für jede Funktion eine separate **App** zur Verfügung, d. h. die Cloud, Fotos, Galerie, Chat, Wiki, Kalender, Kontakte oder die Verbindungs-App. Ein Serveradministrator entscheidet, welche Apps für die Benutzer dieses Servers verfügbar sein sollen. Ein Benutzer kann dann die verfügbaren Apps installieren oder deinstallieren.
+
+
+
+### Zusammenarbeit
+
+Die folgenden gemeinsamen privaten Bereiche ermöglichen die Arbeit im Team mit Hubzilla:
+
+- Als Benutzer erlauben Sie ausgewählten Verbindungen, Ihre Webseiten und Wiki-Seiten zu lesen und zu bearbeiten.
+- Erlauben Sie ihnen, Ihre Dateien, Ihren Kalender und Ihre Kontakte über die Weboberfläche (die Cloud-, Kalender- und Kontakt-Apps) oder WebDAV, CalDAV und CardDAV zu lesen und zu bearbeiten.
+- Verwenden Sie Konversations-Threads (Beiträge), die nur für Ihre Kollaborationsgruppe sichtbar sind, indem Sie die Zugriffskontrollliste entsprechend einstellen.
+
+
+
+### Teilen vs. Boosten
+
+Mit Hubzilla können Sie Beiträge anderer Fediverse-Nutzer (erneut) teilen. Das (erneute) Teilen in Hubzilla ist so, als würden Sie Ihren Freunden Jennie und Omar erzählen, was Giaco gesagt hat. Und dann sagen sie: „Cool, das gefällt mir“.
+
+Hubzilla ermöglicht auch das Boosten. Etwas zu boosten ist so, als würdest du deinen Freunden Jennie und Omar erzählen, was Giaco gesagt hat, während Giaco und alle seine Freunde und seine Familie (die du nicht kennst) zuhören. Giaco und alle seine Freunde und seine Familie können jetzt mit dir sprechen. \ No newline at end of file
diff --git a/doc/de/bugs.bb b/doc/de/bugs.bb
new file mode 100644
index 000000000..57efd10a6
--- /dev/null
+++ b/doc/de/bugs.bb
@@ -0,0 +1,34 @@
+[h2]Fehler, Probleme und Dinge, die nachts für Unruhe sorgen...[/h2]
+[h3]Etwas ist schiefgelaufen! Wer ist für die Behebung zuständig?[/h3]
+
+[b]Hubzilla Community Server[/b]
+
+Hubzilla Community Server ist eine Open-Source-Software, die von „der Community“ – im Wesentlichen unbezahlten Freiwilligen – gepflegt wird. Niemand ist für die Behebung von Fehlern zuständig. Wir arbeiten gemeinsam daran, die Software und das Netzwerk reibungslos und fehlerfrei zu betreiben. Sie sind Mitglied dieser Community, daher benötigen wir auch Ihre Hilfe, um qualitativ hochwertige Software anbieten zu können. Es gibt keine mythischen „Entwickler“, die auf magische Weise alles reparieren. Es liegt an uns allen, mit anzupacken und zu helfen.
+
+Als Erstes sollten Sie sich an Ihren Hub-Administrator wenden – die Person, die Ihre Website betreibt und verwaltet. Er hat als Einziger Zugriff auf die interne Software, die Datenbank und die [b]Logdateien[/b] und muss daher in die Behebung Ihres Problems einbezogen werden. Andere Personen „im Netz“ können Ihnen dabei nicht wirklich helfen. Der Hub-Administrator muss als Erstes die Logdateien überprüfen und/oder versuchen, das Problem zu reproduzieren. Seien Sie daher so hilfsbereit und höflich wie möglich, um ihm bei der Suche nach dem Problem zu helfen.
+
+
+Um Ihren Hub-Administrator zu finden (falls Sie ihn nicht kennen), schauen Sie bitte auf [url=[baseurl]/siteinfo]dieser Seite[/url]. Wenn er auf dieser Seite keine Kontaktinformationen oder ein „Impressum“ angegeben hat, finden Sie diese unter [url=[baseurl]/siteinfo.json]dieser Zusammenfassung der Website-Informationen[/url] unter der Überschrift „admin:“.
+
+Es wird dringend empfohlen, dass Fehlerberichte von Hub-Administratoren eingereicht werden, damit diese die relevanten Logdateien und Datenbankinformationen zum Problem hinzufügen und Workarounds und Folgetests durchführen können. Ohne diese Zusammenarbeit ist es möglicherweise nicht möglich, das Problem zu beheben.
+
+[h3]Ich bin Hub-Administrator; was soll ich tun?[/h3]
+
+Die Softwareanweisungen, die diesen Webdienst bereitstellen, sind Open Source und können von Ihnen eingesehen werden. Wir empfehlen allen, diese zu lesen, um sich über die Funktionsweise zu informieren und sich davon zu überzeugen, dass wir Ihre persönlichen Daten nicht missbräuchlich oder fahrlässig verwenden. Wenn eine Fehlermeldung gemeldet wurde, kann man häufig in den Quelldateien nach dieser Fehlermeldung suchen und herausfinden, wodurch sie ausgelöst wurde. Mit diesen Informationen und den Logdateien der Website lässt sich möglicherweise die Abfolge der Ereignisse ermitteln, die zu dem Fehler geführt haben. Möglicherweise sind auch andere Websites beteiligt, und das Problem liegt gar nicht auf Ihrer Website, sondern an anderer Stelle im Netzwerk. Versuchen Sie, die am Problem beteiligten Kommunikationsendpunkte (Hubs oder Websites) zu ermitteln, und wenden Sie sich an den Administrator dieser Website(s). Geben Sie bitte den Zeitpunkt an, zu dem der Fehler aufgetreten ist, damit er in den Protokollen gefunden werden kann. Arbeiten Sie mit den anderen Administratoren zusammen, um die Ursache des Problems zu finden. Logdateien sind Ihr Freund. Wenn in der Software etwas Unerwartetes passiert, wird dies fast immer protokolliert.
+
+[h3]Der weiße Bildschirm des Todes[/h3]
+
+Wenn Sie bei einer Aktion einen leeren weißen Bildschirm erhalten, handelt es sich fast immer um einen Code- oder Syntaxfehler. In der Datei .htconfig.php der Website finden Sie Anweisungen, mit denen der Website-Administrator die Syntaxprotokollierung aktivieren kann. Wir empfehlen allen Websites, diese Funktion zu verwenden. Wiederholen Sie mit aktivierter Syntaxprotokollierung die Sequenz, die zu dem Fehler geführt hat, und die fehlerhafte Codezeile sollte protokolliert werden. Hoffentlich können Sie das Problem mit diesen Informationen beheben. Wenn ja, reichen Sie die Korrektur bitte „upstream“ ein, damit wir sie mit den anderen Projektmitgliedern und anderen Communities teilen können. Dies ist ein wesentlicher Vorteil der Verwendung von Open-Source-Software – wir teilen miteinander und alle profitieren davon.
+
+[h3]Ich bin ratlos. Ich kann nicht herausfinden, was falsch ist.[/h3]
+
+An dieser Stelle könnte es sich lohnen, das Problem in einem der Online-Foren zu diskutieren. Es gibt möglicherweise mehrere davon, und einige sind vielleicht besser für Ihre Sprache geeignet. Derzeit ist der Kanal „Hubzilla Support Forum“ (adminsforum@hubzilla.org) das empfohlene Forum für die Diskussion von Fehlern.
+
+Wenn Community-Mitglieder mit einer Ausbildung/Erfahrung im Bereich Softwareentwicklung Ihnen nicht sofort helfen können, haben Sie bitte Verständnis dafür, dass sie ehrenamtlich tätig sind und möglicherweise viele andere Aufgaben und Verpflichtungen haben. An diesem Punkt müssen Sie einen Fehlerbericht erstellen. Dazu benötigen Sie ein Konto auf framagit.org. Registrieren Sie sich also und besuchen Sie dann https://framagit.org/hubzilla/core/issues . Erstellen Sie hier ein Ticket und geben Sie alle Informationen an, die Sie online angegeben haben. Lassen Sie nichts aus.
+
+Dann warten Sie. Wenn es sich um ein wichtiges Problem handelt, wird es möglicherweise schnell behoben. Aber niemand ist für die Behebung von Fehlern zuständig. Wenn das Problem weiterhin besteht, nehmen Sie sich bitte etwas mehr Zeit, um es zu untersuchen. Fragen Sie nach allem, was Sie im Zusammenhang mit dem Verhalten nicht verstehen. Sie werden mehr über die Funktionsweise der Software erfahren und möglicherweise herausfinden, warum sie derzeit nicht funktioniert. Letztendlich wird jemand aus der Community das Problem beheben, und Sie sind ein Mitglied dieser Community. So funktioniert der Open-Source-Prozess.
+
+
+
+Andere Personen, die an der Behebung des Problems arbeiten, benötigen möglicherweise weitere Informationen. Bereiten Sie sich daher gut vor und dokumentieren Sie, was passiert ist und was Sie bereits versucht haben. Sagen Sie nicht einfach: „Ich habe xyz gemacht und es hat nicht funktioniert.“ Das sagt uns nichts. Teilen Sie uns genau mit, welche Schritte Sie unternommen haben, welches Ergebnis Sie erwartet haben und was genau passiert ist. Welche Seite/URL haben Sie aufgerufen oder welches Formular haben Sie ausgefüllt? Wenn Fehlermeldungen angezeigt wurden, sagen Sie nicht „Es wurde eine Fehlermeldung angezeigt“. Teilen Sie uns den genauen Wortlaut der Meldung mit. Teilen Sie uns außerdem mit, welchen Hub Sie verwenden, welche Softwareversion Sie ausführen und alle weiteren Details, die für die Konfiguration Ihrer Website relevant sein könnten. Wir verstehen, dass Sie möglicherweise einige Informationen und Ihre Verbindungen geheim halten möchten. Wenn Sie jedoch nicht bereit sind, die Informationen weiterzugeben, die andere Personen benötigen, um das Problem zu reproduzieren/beheben, kann es möglicherweise nicht behoben werden.
+
diff --git a/doc/de/channels.bb b/doc/de/channels.bb
deleted file mode 100644
index 0030208c2..000000000
--- a/doc/de/channels.bb
+++ /dev/null
@@ -1,26 +0,0 @@
-[size=large][b]Kanäle[/b][/size]
-
-Kanäle sind Sammlungen von Inhalten, die an einem Ort gespeichert werden. Ein Kanal kann alles mögliche repräsentieren. Zum Beispiel Dich, eine Website, ein Forum oder ein Fotoalbum. Normalerweise wird Dein erster Kanal Dich selbst repräsentieren.
-
-Die wichtigsten Funktionen für einen Kanal, der einen selbst repräsentiert, sind:
-
-[ul][*]Sichere und private, spamfreie Kommunikation
-[*]Identifikation und automatisches Einloggen im gesamten $Projectname-Netzwerk
-[*]Datenschutzeinstellungen und Zugriffsberechtigungen, die im gesamten Netzwerk gültig sind
-[*]Verzeichnisdienste (ähnlich einem Telefonbuch)[/ul]
-
-Kurz gesagt, ein Kanal der Dich repräsentiert ist sozusagen „Ich im Internet“.
-
-Du musst Deinen ersten Kanal erstellen, während Du Dich anmeldest. Du kannst auch weitere Kanäle erstellen und zwischen ihnen wechseln, indem Du auf „Kanal-Manager“ im Menü unter Deinem Profilbild klickst.
-
-Du wirst nach einem Kanalnamen und einem kurzen Spitznamen gefragt. Für einen Kanal, der Dich repräsentiert, ist es eine gute Idee, hier Deinen Realnamen anzugeben, damit Deine Freunde Dich finden und sich mit Dir verbinden können. Der Spitzname wird genutzt, um Deinen „Webbie“ zu erstellen. Das ist so etwas wie ein Username und sieht aus wie eine E-Mail-Adresse, zum Beispiel spitzname@hubzilla-hub.de. Überlege ein bisschen, was Du als Spitzname nutzen willst. Stell Dir vor, Du wirst nach Deinem Webbie gefragt und musst Deinem Bekannten dann buchstabieren, dass Dein Webbie „llamas.sind-cool_274@example.com“ ist. „llamassindcool@example.com“ wäre da viel einfacher gewesen.
-
-Nachdem Du Deinen Kanal erstellt hast, wirst Du zu den Einstellungen weitergeleitet. Hier kannst Du Deinen Kanal einrichten und die Standard-Berechtigungen setzen.
-
-Nachdem Du auch das getan hast, kannst Du Deinen Kanal verwenden. Unter der Addresse https://example.com/channel/spitzname [observer=1]( [observer.url] )[/observer] findest Du Deinen Kanal. Hier werden Deine letzten Aktivitäten gezeigt, die neuesten oben. Wenn Du etwas in die Textbox schreibst, in der „Teilen“ steht, wird der neue Eintrag ganz oben in Deinem Kanal auftauchen. Du findest hier auch Links zu den anderen Kommunikationsbereichen Deines Kanals. Der „Über“-Reiter enthält Dein Profil, der „Fotos“-Reiter Deine Fotoalben, und der Kalender enthält Termine und Veranstaltungen, die Du und Deine Kontakte geteilt haben.
-
-Die „Grid“-Seite enthält alle neuen Beiträge aus dem gesamten $Projectname-Netzwerk, wieder die neuesten oben. Was genau zu sehen ist ist abhängig von den Zugriffsrechten. Falls die Zugriffsrechte Deines Kanals so eingestellt sind, dass jeder Beiträge in Deinen Stream stellen kann, wirst du auch Beiträge von Dir völlig unbekannten Personen hier sehen. Am anderen Ende der Skala kannst Du die Berechtigungen aber auch so einstellen, dass du nur die Beiträge deiner Freunde oder gar nur Deine eigenen siehst.
-
-Wie zu Anfang erwähnt sind viele Arten von Kanälen möglich, diese unterscheiden sich hauptsächlich durch die Berechtigungen. Das Anlegen dieser Kanäle unterscheidet sich dagegen nicht. Beispiel: Um einen Kanal zum Austausch von Dokumenten zu erstellen, wirst du vermutlich die Berechtigung „Kann in meinen öffentlichen Dateiordner schreiben“ freizügiger einstellen. Für weitere Informationen sieh bitte in der Hilfe unter Zugriffsrechte nach.
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/de/credits.md b/doc/de/credits.md
new file mode 100644
index 000000000..3386d2529
--- /dev/null
+++ b/doc/de/credits.md
@@ -0,0 +1,91 @@
+## Credits
+
+Vielen Dank an alle, die im Laufe der Jahre zu diesem Projekt und seinen Vorgängern beigetragen haben.
+Es ist möglich, dass wir Ihren Namen nicht erwähnt haben, aber das ist nicht beabsichtigt. Wir danken auch der Gemeinschaft und
+ihren Mitgliedern, die uns wertvolle Beiträge geliefert haben und ohne die diese ganze Arbeit sinnlos wäre.
+
+Es lohnt sich auch, die Beiträge und Problemlösungen zu würdigen, die sich aus
+Diskussionen zwischen Mitgliedern und Entwicklern anderer, in gewisser Weise verwandter und konkurrierender Projekte;
+auch wenn wir gelegentlich Meinungsverschiedenheiten hatten.
+
+- Mike Macgirvin
+- Mario Vavti
+- Scott M. Stolz
+- Chris Burger
+- Emanuel Han
+- Fabio Comuni
+- Simon L'nu
+- marijus
+- Tobias Diekershoff
+- fabrixxm
+- tommy tomson
+- Simon
+- zottel
+- Christian Vogeley
+- jeroenpraat
+- Michael Vogel
+- erik
+- Zach Prezkuta
+- Paolo T
+- Michael Meer
+- Michael
+- Abinoam P. Marques Jr
+- Tobias Hößl
+- Alexander Kampmann
+- Olaf Conradi
+- Paolo Tacconi
+- tobiasd
+- Devlon Duthie
+- Zvi ben Yaakov (a.k.a rdc)
+- Alexandre Hannud Abdo
+- Olivier Migeot
+- Chris Case
+- Klaus Weidenbach
+- Michael Johnston
+- olivierm
+- Vasudev Kamath
+- pixelroot
+- Max Weller
+- duthied
+- Martin Schmitt
+- Sebastian Egbers
+- Erkan Yilmaz
+- sasiflo
+- Stefan Parviainen
+- Haakon Meland Eriksen
+- Oliver Hartmann (23n)
+- Erik Lundin
+- habeascodice
+- sirius
+- Charles
+- Tony Baldwin
+- Hauke Zuehl
+- Keith Fernie
+- Anne Walk
+- toclimb
+- Daniel Frank
+- Matthew Exon
+- Michal Supler
+- Tobias Luther
+- U-SOUND\mike
+- mrjive
+- nostupidzone
+- tonnerkiller
+- Antoine G
+- Christian Drechsler
+- Ludovic Grossard
+- RedmatrixCanada
+- Stanislav Lechev [0xAF]
+- aweiher
+- bufalo1973
+- dsp1986
+- felixgilles
+- ike
+- maase2
+- mycocham
+- ndurchx
+- pafcu
+- Simó Albert i Beltran
+- Manuel Reva
+- Manuel Jiménez Friaza
+- Gustav Wall aka "neue medienordnung plus" \ No newline at end of file
diff --git a/doc/en/database/db_abook.bb b/doc/de/database/db_abook.bb
index a346480d7..a346480d7 100644
--- a/doc/en/database/db_abook.bb
+++ b/doc/de/database/db_abook.bb
diff --git a/doc/en/database/db_account.bb b/doc/de/database/db_account.bb
index 35d7a9eb3..35d7a9eb3 100644
--- a/doc/en/database/db_account.bb
+++ b/doc/de/database/db_account.bb
diff --git a/doc/en/database/db_addon.bb b/doc/de/database/db_addon.bb
index bccd295f5..bccd295f5 100644
--- a/doc/en/database/db_addon.bb
+++ b/doc/de/database/db_addon.bb
diff --git a/doc/en/database/db_app.bb b/doc/de/database/db_app.bb
index 09df473ee..09df473ee 100644
--- a/doc/en/database/db_app.bb
+++ b/doc/de/database/db_app.bb
diff --git a/doc/en/database/db_attach.bb b/doc/de/database/db_attach.bb
index 5098401de..5098401de 100644
--- a/doc/en/database/db_attach.bb
+++ b/doc/de/database/db_attach.bb
diff --git a/doc/en/database/db_auth_codes.bb b/doc/de/database/db_auth_codes.bb
index c60f064a4..c60f064a4 100644
--- a/doc/en/database/db_auth_codes.bb
+++ b/doc/de/database/db_auth_codes.bb
diff --git a/doc/en/database/db_cache.bb b/doc/de/database/db_cache.bb
index 02c292f20..02c292f20 100644
--- a/doc/en/database/db_cache.bb
+++ b/doc/de/database/db_cache.bb
diff --git a/doc/en/database/db_channel.bb b/doc/de/database/db_channel.bb
index 518ff0978..518ff0978 100644
--- a/doc/en/database/db_channel.bb
+++ b/doc/de/database/db_channel.bb
diff --git a/doc/en/database/db_chat.bb b/doc/de/database/db_chat.bb
index 1aac2bd15..1aac2bd15 100644
--- a/doc/en/database/db_chat.bb
+++ b/doc/de/database/db_chat.bb
diff --git a/doc/en/database/db_chatpresence.bb b/doc/de/database/db_chatpresence.bb
index 0a7f666c9..0a7f666c9 100644
--- a/doc/en/database/db_chatpresence.bb
+++ b/doc/de/database/db_chatpresence.bb
diff --git a/doc/en/database/db_chatroom.bb b/doc/de/database/db_chatroom.bb
index 1d316288d..1d316288d 100644
--- a/doc/en/database/db_chatroom.bb
+++ b/doc/de/database/db_chatroom.bb
diff --git a/doc/en/database/db_clients.bb b/doc/de/database/db_clients.bb
index 0c66a4fc2..0c66a4fc2 100644
--- a/doc/en/database/db_clients.bb
+++ b/doc/de/database/db_clients.bb
diff --git a/doc/en/database/db_config.bb b/doc/de/database/db_config.bb
index f32d3c259..f32d3c259 100644
--- a/doc/en/database/db_config.bb
+++ b/doc/de/database/db_config.bb
diff --git a/doc/en/database/db_conv.bb b/doc/de/database/db_conv.bb
index 5adfa8c80..5adfa8c80 100644
--- a/doc/en/database/db_conv.bb
+++ b/doc/de/database/db_conv.bb
diff --git a/doc/en/database/db_event.bb b/doc/de/database/db_event.bb
index ad3c15789..ad3c15789 100644
--- a/doc/en/database/db_event.bb
+++ b/doc/de/database/db_event.bb
diff --git a/doc/en/database/db_fcontact.bb b/doc/de/database/db_fcontact.bb
index 9bd8c20fe..9bd8c20fe 100644
--- a/doc/en/database/db_fcontact.bb
+++ b/doc/de/database/db_fcontact.bb
diff --git a/doc/en/database/db_ffinder.bb b/doc/de/database/db_ffinder.bb
index c20158d56..c20158d56 100644
--- a/doc/en/database/db_ffinder.bb
+++ b/doc/de/database/db_ffinder.bb
diff --git a/doc/en/database/db_fserver.bb b/doc/de/database/db_fserver.bb
index 4c4b0b530..4c4b0b530 100644
--- a/doc/en/database/db_fserver.bb
+++ b/doc/de/database/db_fserver.bb
diff --git a/doc/en/database/db_fsuggest.bb b/doc/de/database/db_fsuggest.bb
index 9da1f2f6d..9da1f2f6d 100644
--- a/doc/en/database/db_fsuggest.bb
+++ b/doc/de/database/db_fsuggest.bb
diff --git a/doc/en/database/db_hook.bb b/doc/de/database/db_hook.bb
index 233062f98..233062f98 100644
--- a/doc/en/database/db_hook.bb
+++ b/doc/de/database/db_hook.bb
diff --git a/doc/en/database/db_hubloc.bb b/doc/de/database/db_hubloc.bb
index e4ab7159d..e4ab7159d 100644
--- a/doc/en/database/db_hubloc.bb
+++ b/doc/de/database/db_hubloc.bb
diff --git a/doc/en/database/db_issue.bb b/doc/de/database/db_issue.bb
index 0a6f2912b..0a6f2912b 100644
--- a/doc/en/database/db_issue.bb
+++ b/doc/de/database/db_issue.bb
diff --git a/doc/en/database/db_item.bb b/doc/de/database/db_item.bb
index 6383e13f8..6383e13f8 100644
--- a/doc/en/database/db_item.bb
+++ b/doc/de/database/db_item.bb
diff --git a/doc/en/database/db_item_id.bb b/doc/de/database/db_item_id.bb
index ba4cca247..ba4cca247 100644
--- a/doc/en/database/db_item_id.bb
+++ b/doc/de/database/db_item_id.bb
diff --git a/doc/en/database/db_likes.bb b/doc/de/database/db_likes.bb
index 118c9a87e..118c9a87e 100644
--- a/doc/en/database/db_likes.bb
+++ b/doc/de/database/db_likes.bb
diff --git a/doc/en/database/db_mail.bb b/doc/de/database/db_mail.bb
index 0628584ae..0628584ae 100644
--- a/doc/en/database/db_mail.bb
+++ b/doc/de/database/db_mail.bb
diff --git a/doc/en/database/db_menu.bb b/doc/de/database/db_menu.bb
index 5b478115d..5b478115d 100644
--- a/doc/en/database/db_menu.bb
+++ b/doc/de/database/db_menu.bb
diff --git a/doc/en/database/db_menu_item.bb b/doc/de/database/db_menu_item.bb
index b14aac5e4..b14aac5e4 100644
--- a/doc/en/database/db_menu_item.bb
+++ b/doc/de/database/db_menu_item.bb
diff --git a/doc/en/database/db_notify.bb b/doc/de/database/db_notify.bb
index 4787266cd..4787266cd 100644
--- a/doc/en/database/db_notify.bb
+++ b/doc/de/database/db_notify.bb
diff --git a/doc/en/database/db_obj.bb b/doc/de/database/db_obj.bb
index cc5e75598..cc5e75598 100644
--- a/doc/en/database/db_obj.bb
+++ b/doc/de/database/db_obj.bb
diff --git a/doc/en/database/db_outq.bb b/doc/de/database/db_outq.bb
index 970f99de5..970f99de5 100644
--- a/doc/en/database/db_outq.bb
+++ b/doc/de/database/db_outq.bb
diff --git a/doc/en/database/db_pconfig.bb b/doc/de/database/db_pconfig.bb
index 2ac36e61a..2ac36e61a 100644
--- a/doc/en/database/db_pconfig.bb
+++ b/doc/de/database/db_pconfig.bb
diff --git a/doc/en/database/db_pgrp.bb b/doc/de/database/db_pgrp.bb
index 73265b90e..73265b90e 100644
--- a/doc/en/database/db_pgrp.bb
+++ b/doc/de/database/db_pgrp.bb
diff --git a/doc/en/database/db_pgrp_member.bb b/doc/de/database/db_pgrp_member.bb
index b9ab8171d..b9ab8171d 100644
--- a/doc/en/database/db_pgrp_member.bb
+++ b/doc/de/database/db_pgrp_member.bb
diff --git a/doc/en/database/db_photo.bb b/doc/de/database/db_photo.bb
index 91840ec1e..91840ec1e 100644
--- a/doc/en/database/db_photo.bb
+++ b/doc/de/database/db_photo.bb
diff --git a/doc/en/database/db_poll.bb b/doc/de/database/db_poll.bb
index 57d808b71..57d808b71 100644
--- a/doc/en/database/db_poll.bb
+++ b/doc/de/database/db_poll.bb
diff --git a/doc/en/database/db_poll_elm.bb b/doc/de/database/db_poll_elm.bb
index fd649d5a6..fd649d5a6 100644
--- a/doc/en/database/db_poll_elm.bb
+++ b/doc/de/database/db_poll_elm.bb
diff --git a/doc/en/database/db_profdef.bb b/doc/de/database/db_profdef.bb
index a0904fd79..a0904fd79 100644
--- a/doc/en/database/db_profdef.bb
+++ b/doc/de/database/db_profdef.bb
diff --git a/doc/en/database/db_profext.bb b/doc/de/database/db_profext.bb
index ada9dce2a..ada9dce2a 100644
--- a/doc/en/database/db_profext.bb
+++ b/doc/de/database/db_profext.bb
diff --git a/doc/en/database/db_profile.bb b/doc/de/database/db_profile.bb
index 717fae585..717fae585 100644
--- a/doc/en/database/db_profile.bb
+++ b/doc/de/database/db_profile.bb
diff --git a/doc/en/database/db_profile_check.bb b/doc/de/database/db_profile_check.bb
index 3be64c5da..3be64c5da 100644
--- a/doc/en/database/db_profile_check.bb
+++ b/doc/de/database/db_profile_check.bb
diff --git a/doc/en/database/db_register.bb b/doc/de/database/db_register.bb
index 50672b5e1..50672b5e1 100644
--- a/doc/en/database/db_register.bb
+++ b/doc/de/database/db_register.bb
diff --git a/doc/en/database/db_session.bb b/doc/de/database/db_session.bb
index d7ff0482d..d7ff0482d 100644
--- a/doc/en/database/db_session.bb
+++ b/doc/de/database/db_session.bb
diff --git a/doc/en/database/db_shares.bb b/doc/de/database/db_shares.bb
index be5255c03..be5255c03 100644
--- a/doc/en/database/db_shares.bb
+++ b/doc/de/database/db_shares.bb
diff --git a/doc/en/database/db_sign.bb b/doc/de/database/db_sign.bb
index e80ea7ef3..e80ea7ef3 100644
--- a/doc/en/database/db_sign.bb
+++ b/doc/de/database/db_sign.bb
diff --git a/doc/en/database/db_site.bb b/doc/de/database/db_site.bb
index 8dea4dae6..8dea4dae6 100644
--- a/doc/en/database/db_site.bb
+++ b/doc/de/database/db_site.bb
diff --git a/doc/en/database/db_source.bb b/doc/de/database/db_source.bb
index 92850a82e..92850a82e 100644
--- a/doc/en/database/db_source.bb
+++ b/doc/de/database/db_source.bb
diff --git a/doc/en/database/db_spam.bb b/doc/de/database/db_spam.bb
index b75e1edd3..b75e1edd3 100644
--- a/doc/en/database/db_spam.bb
+++ b/doc/de/database/db_spam.bb
diff --git a/doc/en/database/db_sys_perms.bb b/doc/de/database/db_sys_perms.bb
index 04416a26b..04416a26b 100644
--- a/doc/en/database/db_sys_perms.bb
+++ b/doc/de/database/db_sys_perms.bb
diff --git a/doc/en/database/db_term.bb b/doc/de/database/db_term.bb
index bd155fe21..bd155fe21 100644
--- a/doc/en/database/db_term.bb
+++ b/doc/de/database/db_term.bb
diff --git a/doc/en/database/db_tokens.bb b/doc/de/database/db_tokens.bb
index 35da2458c..35da2458c 100644
--- a/doc/en/database/db_tokens.bb
+++ b/doc/de/database/db_tokens.bb
diff --git a/doc/en/database/db_updates.bb b/doc/de/database/db_updates.bb
index f2e25d84c..f2e25d84c 100644
--- a/doc/en/database/db_updates.bb
+++ b/doc/de/database/db_updates.bb
diff --git a/doc/en/database/db_verify.bb b/doc/de/database/db_verify.bb
index 9d01181c5..9d01181c5 100644
--- a/doc/en/database/db_verify.bb
+++ b/doc/de/database/db_verify.bb
diff --git a/doc/en/database/db_vote.bb b/doc/de/database/db_vote.bb
index 0b9a423eb..0b9a423eb 100644
--- a/doc/en/database/db_vote.bb
+++ b/doc/de/database/db_vote.bb
diff --git a/doc/en/database/db_xchan.bb b/doc/de/database/db_xchan.bb
index 8932969c5..8932969c5 100644
--- a/doc/en/database/db_xchan.bb
+++ b/doc/de/database/db_xchan.bb
diff --git a/doc/en/database/db_xchat.bb b/doc/de/database/db_xchat.bb
index 0897408d1..0897408d1 100644
--- a/doc/en/database/db_xchat.bb
+++ b/doc/de/database/db_xchat.bb
diff --git a/doc/en/database/db_xconfig.bb b/doc/de/database/db_xconfig.bb
index 111d1ce3a..111d1ce3a 100644
--- a/doc/en/database/db_xconfig.bb
+++ b/doc/de/database/db_xconfig.bb
diff --git a/doc/en/database/db_xign.bb b/doc/de/database/db_xign.bb
index 63c6569de..63c6569de 100644
--- a/doc/en/database/db_xign.bb
+++ b/doc/de/database/db_xign.bb
diff --git a/doc/en/database/db_xlink.bb b/doc/de/database/db_xlink.bb
index 528f8da19..528f8da19 100644
--- a/doc/en/database/db_xlink.bb
+++ b/doc/de/database/db_xlink.bb
diff --git a/doc/en/database/db_xprof.bb b/doc/de/database/db_xprof.bb
index bed79e9ca..bed79e9ca 100644
--- a/doc/en/database/db_xprof.bb
+++ b/doc/de/database/db_xprof.bb
diff --git a/doc/en/database/db_xtag.bb b/doc/de/database/db_xtag.bb
index 1e6fb9961..1e6fb9961 100644
--- a/doc/en/database/db_xtag.bb
+++ b/doc/de/database/db_xtag.bb
diff --git a/doc/de/develop.bb b/doc/de/develop.bb
deleted file mode 100644
index 30e2954c6..000000000
--- a/doc/de/develop.bb
+++ /dev/null
@@ -1,30 +0,0 @@
-[h2]Dokumentation für Entwickler[/h2]
-
-[h3]Technische Dokumentation[/h3]
-[zrl=[baseurl]/help/Zot---A-High-Level-Overview]Zot – ein grober Überblick[/zrl]
-[zrl=[baseurl]/help/zot]Eine Einführung ins Zot-Protokoll[/zrl]
-[zrl=[baseurl]/help/zot_structures]Zot-Strukturen[/zrl]
-[zrl=[baseurl]/help/comanche]Seitenbeschreibung in Comanche[/zrl]
-[zrl=[baseurl]/help/Creating-Templates]Vorlagen erstellen mit Comanche[/zrl]
-[zrl=[baseurl]/help/Widgets]Widgets[/zrl]
-[zrl=[baseurl]/help/plugins]Plugins[/zrl]
-[zrl=[baseurl]/help/doco]Selbst Dokumentation beisteuern[/zrl]
-[zrl=[baseurl]/help/DerivedTheme1]Einen Theme basierend auf einem anderen erstellen[/zrl]
-[zrl=[baseurl]/help/schema_development]Schemata[/zrl]
-[zrl=[baseurl]/help/Translations]Übersetzungen[/zrl]
-[zrl=[baseurl]/help/developers]Entwickler[/zrl]
-[zrl=[baseurl]/help/intro_for_developers]Einführung für Entwickler[/zrl]
-[zrl=[baseurl]/help/database]Datenbank-Schema[/zrl]
-[zrl=[baseurl]/help/api_functions]API-Funktionen[/zrl]
-[zrl=[baseurl]/help/api_posting]Mit der API einen Beitrag erstellen[/zrl]
-[zrl=[baseurl]/help/developer_function_primer]Übersicht der wichtigsten $Projectname-Funktionen[/zrl]
-[zrl=[baseurl]/doc/html/]Code-Referenz (mit doxygen generiert - setzt Cookies)[/zrl]
-[zrl=[baseurl]/help/to_do_doco]To-Do-Liste für das Projekt $Projectname-Dokumentation[/zrl]
-[zrl=[baseurl]/help/to_do_code]To-Do-Liste für Entwickler[/zrl]
-[zrl=[baseurl]/help/roadmap]Roadmap[/zrl]
-[zrl=[baseurl]/help/git_for_non_developers]Git für Nicht-Entwickler[/zrl]
-[zrl=[baseurl]/help/dev_beginner]Schritt-für-Schritt-Einführung für neue Entwickler[/zrl]
-
-[h3]Externe Ressourcen[/h3]
-[url=https://zothub.com/channel/one]Entwickler-Kanal[/url]
-[url=https://federated.social/channel/postgres]Postgres-spezifischer Admin-Support-Kanal[/url]
diff --git a/doc/de/developer/API.md b/doc/de/developer/API.md
new file mode 100644
index 000000000..6966024df
--- /dev/null
+++ b/doc/de/developer/API.md
@@ -0,0 +1,787 @@
+### Zot API
+
+Many existing social applications and tools can interface directly using the Twitter/StatusNet API, which is available using the 'twitter_api' addon.
+
+This document describes the native API; which allows direct programmatic access to several internal data structures and libraries extending beyond the basic social interface.
+
+The API endpoints detailed below are relative to `api/z/1.0`, meaning that if an API is listed as `channel/stream` the full API URL is `https://hub.hubzilla.hu/api/z/1.0/channel/stream`.
+
+
+
+### channel/export/basic
+
+Export basic channel data
+
+Options:
+ \- sections
+ comma-separated list of data types to export
+
+ \- posts
+ if true, return default sections plus 3 months of posts
+
+ If no sections are requested, the following sections are returned:
+ channel, connections, config, apps, chatrooms, events, webpages, mail, wikis
+
+ Files and large collections of posts may run into memory limits; these must generally be
+ requested separately.
+
+
+
+### channel/stream
+
+Fetch channel conversation items
+
+
+
+### network/stream
+
+Fetch network conversation items
+
+
+
+
+
+### files
+
+List file storage (attach DB)
+
+GET /api/z/1.0/files
+
+
+Options:
+
+ \- filehash
+ return only entries matching hash (exactly)
+
+ \- filename
+ return only entries matching filename (substring)
+
+ \- filetype
+ return only entries matching filetype/mimetype (substring)
+
+ \- start
+ start at record (default 0)
+
+ \- records
+ number of records to return or 0 for unlimited
+
+
+
+Example:
+
+```
+curl -u mychannel:mypassword https://xyz.macgirvin.com/api/z/1.0/files -d filetype=multipart/mixed
+```
+
+
+Returns:
+
+```
+ {
+
+ "success": true,
+ "results": [
+ {
+ "id": "1",
+ "aid": "1",
+ "uid": "2",
+ "hash": "44ee8b2a1a7f36dea07b93b7747a2383a1bc0fdd08339e8928bfcbe45f65d939",
+ "filename": "Profile Photos",
+ "filetype": "multipart/mixed",
+ "filesize": "0",
+ "revision": "0",
+ "folder": "",
+ "os_storage": "1",
+ "is_dir": "1",
+ "is_photo": "0",
+ "flags": "0",
+ "created": "2016-01-02 21:51:17",
+ "edited": "2016-01-02 21:51:17",
+ "allow_cid": "",
+ "allow_gid": "",
+ "deny_cid": "",
+ "deny_gid": ""
+ },
+ {
+ "id": "12",
+ "aid": "1",
+ "uid": "2",
+ "hash": "71883f1fc64af33889229cbc79c5a056deeec5fc277d765f182f19073e1b2998",
+ "filename": "Cover Photos",
+ "filetype": "multipart/mixed",
+ "filesize": "0",
+ "revision": "0",
+ "folder": "",
+ "os_storage": "1",
+ "is_dir": "1",
+ "is_photo": "0",
+ "flags": "0",
+ "created": "2016-01-15 00:24:33",
+ "edited": "2016-01-15 00:24:33",
+ "allow_cid": "",
+ "allow_gid": "",
+ "deny_cid": "",
+ "deny_gid": ""
+ },
+ {
+ "id": "16",
+ "aid": "1",
+ "uid": "2",
+ "hash": "f48f7ec3278499d1dd86b72c3207beaaf4717b07df5cc9b373f14d7aad2e1bcd",
+ "filename": "2016-01",
+ "filetype": "multipart/mixed",
+ "filesize": "0",
+ "revision": "0",
+ "folder": "",
+ "os_storage": "1",
+ "is_dir": "1",
+ "is_photo": "0",
+ "flags": "0",
+ "created": "2016-01-22 03:24:55",
+ "edited": "2016-01-22 03:26:57",
+ "allow_cid": "",
+ "allow_gid": "",
+ "deny_cid": "",
+ "deny_gid": ""
+ }
+ ]
+ }
+```
+
+### filemeta
+
+Export file metadata for any uploaded file
+
+
+
+### filedata
+
+Provides the ability to download a file from cloud storage in chunks
+
+GET /api/z/1.0/filedata
+
+
+Required:
+
+ \- file_id
+ attach.hash of desired file ('begins with' match)
+
+
+Optional:
+
+ \- start
+ starting byte of returned data in file (counting from 0)
+
+ \- length
+ length (prior to base64 encoding) of chunk to download
+
+
+Returns:
+
+ attach (DB) structure with base64 encoded 'content' comprised of the desired chunk
+
+
+
+Example:
+
+ `https://xyz.macgirvin.com/api/z/1.0/filedata?f=&file_id=9f5217770fd&start=0&length=48`
+
+Returns:
+
+```
+ {
+
+ "attach": {
+ "id": "107",
+ "aid": "1",
+ "uid": "2",
+ "hash": "9f5217770fd55d563bd77f84d534d8e119a187514bbd391714626cd9c0e60207",
+ "creator": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
+ "filename": "pcxtopbm.c",
+ "filetype": "application/octet-stream",
+ "filesize": "3934",
+ "revision": "0",
+ "folder": "",
+ "flags": "0",
+ "is_dir": "0",
+ "is_photo": "0",
+ "os_storage": "1",
+ "os_path": "",
+ "display_path": "",
+ "content": "LyogcGN4dG9wYm0uYyAtIGNvbnZlcnQgUEMgcGFpbnRicnVzaCAoLnBjeCkgZmls",
+ "created": "2016-07-24 23:13:01",
+ "edited": "2016-07-24 23:13:01",
+ "allow_cid": "",
+ "allow_gid": "",
+ "deny_cid": "",
+ "deny_gid": "",
+ "start": 0,
+ "length": 48
+ }
+
+ }
+```
+
+### file/export
+
+### file
+
+### albums
+
+Description: list photo albums
+
+GET /api/z/1.0/albums
+
+
+Output:
+
+ text - textual name
+
+ total - number of photos in this album
+
+ url - web URL
+
+ urlencode - textual name, urlencoded
+
+ bin2hex - textual name using bin2hex (which is used in the web URL link)
+
+
+Example:
+
+
+
+```
+ {
+
+ "success": true,
+ "albums": [
+ {
+ "text": "/",
+ "total": "2",
+ "url": "https://xyz.macgirvin.com/photos/hubzilla/album/",
+ "urlencode": "",
+ "bin2hex": ""
+ },
+ {
+ "text": "2016-01",
+ "total": "6",
+ "url": "https://xyz.macgirvin.com/photos/hubzilla/album/323031362d3031",
+ "urlencode": "2016-01",
+ "bin2hex": "323031362d3031"
+ },
+ {
+ "text": "2016-02",
+ "total": "7",
+ "url": "https://xyz.macgirvin.com/photos/hubzilla/album/323031362d3032",
+ "urlencode": "2016-02",
+ "bin2hex": "323031362d3032"
+ },
+ {
+ "text": "Cover Photos",
+ "total": "5",
+ "url": "https://xyz.macgirvin.com/photos/hubzilla/album/436f7665722050686f746f73",
+ "urlencode": "Cover+Photos",
+ "bin2hex": "436f7665722050686f746f73"
+ },
+ {
+ "text": "Profile Photos",
+ "total": "26",
+ "url": "https://xyz.macgirvin.com/photos/hubzilla/album/50726f66696c652050686f746f73",
+ "urlencode": "Profile+Photos",
+ "bin2hex": "50726f66696c652050686f746f73"
+ }
+ ]
+
+ }
+```
+
+### photos
+
+list photo metadata
+
+
+
+### photo
+
+### group
+
+```
+GET /api/z/1.0/group
+```
+
+Description: list privacy groups
+
+Returns: DB tables of all privacy groups.
+
+To use with API group_members, provide either 'group_id' from the id element returned in this call, or 'group_name' from the gname returned in this call.
+
+
+
+```
+ [
+
+ {
+ "id": "1",
+ "hash": "966c946394f3e2627bbb8a55026b5725e582407098415c02f85232de3f3fde76Friends",
+ "uid": "2",
+ "visible": "0",
+ "deleted": "0",
+ "gname": "Friends"
+ },
+ {
+ "id": "2",
+ "hash": "852ebc17f8c3ed4866f2162e384ded0f9b9d1048f93822c0c84196745f6eec66Family",
+ "uid": "2",
+ "visible": "1",
+ "deleted": "0",
+ "gname": "Family"
+ },
+ {
+ "id": "3",
+ "hash": "cc3cb5a7f9818effd7c7c80a58b09a189b62efa698a74319117babe33ee30ab9Co-workers",
+ "uid": "2",
+ "visible": "0",
+ "deleted": "0",
+ "gname": "Co-workers"
+ }
+ ]
+```
+
+### group_members
+
+```
+GET /api/z/1.0/group_members
+```
+
+Required:
+
+group_id or group_name
+
+
+Returns:
+
+group_member+abook+xchan (DB join) for each member of the privacy group
+
+
+
+```
+ [
+
+ {
+ "id": "1",
+ "uid": "2",
+ "gid": "1",
+ "xchan": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
+ "abook_id": "2",
+ "abook_account": "1",
+ "abook_channel": "2",
+ "abook_xchan": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
+ "abook_my_perms": "218555",
+ "abook_their_perms": "0",
+ "abook_closeness": "0",
+ "abook_created": "2016-01-02 21:16:26",
+ "abook_updated": "2016-01-02 21:16:26",
+ "abook_connected": "0000-00-00 00:00:00",
+ "abook_dob": "0000-00-00 00:00:00",
+ "abook_flags": "0",
+ "abook_blocked": "0",
+ "abook_ignored": "0",
+ "abook_hidden": "0",
+ "abook_archived": "0",
+ "abook_pending": "0",
+ "abook_unconnected": "0",
+ "abook_self": "1",
+ "abook_feed": "0",
+ "abook_profile": "",
+ "abook_incl": "",
+ "abook_excl": "",
+ "abook_instance": "",
+ "xchan_hash": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
+ "xchan_guid": "lql-1VnxtiO4-WF0h72wLX1Fu8szzHDOXgQaTbELwXW77k8AKFfh-hYr70vqMrc3SSvWN-Flrc5HFhRTWB7ICw",
+ "xchan_guid_sig": "PafvEL0VpKfxATxlCqDjfOeSIMdmpr3iU7X-Sysa1h5LzDpjSXsjO37tYZL-accb1M5itLlfnW5epkTa5I4flsW21zSY1A2jCuBQUTLLGV7rNyyBy7lgqJUFvAMRx0TfXzP9lcaPqlM9T1tA6jfWOsOmkdzwofGeXBnsjGfjsO2xdGYe6vwjOU0DSavukvzDMnOayB9DekpvDnaNBTxeGLM45Skzr7ZEMcNF7TeXMbnvpfLaALYEKeQs9bGH-UgAG8fBWgzVAzeBfx_XSR1rdixjyiZGP0kq0h35SlmMPcEjliodOBFwMXqpXFB7Ibp4F6o6te2p2ErViJccQVG8VNKB6SbKNXY6bhP5zVcVsJ-vR-p4xXoYJJvzTN7yTDsGAXHOLF4ZrXbo5yi5gFAlIrTLAF2EdWQwxSGyLRWKxG8PrDkzEzX6cJJ0VRcLh5z6OI5QqQNdeghPZbshMFMJSc_ApCPi9_hI4ZfctCIOi3T6bdgTNKryLm5fhy_eqjwLAZTGP-aUBgLZpb1mf2UojBn6Ey9cCyq-0T2RWyk-FcIcbV4qJ-p_8oODqw13Qs5FYkjLr1bGBq82SuolkYrXEwQClxnrfKa4KYc2_eHAXPL01iS9zVnI1ySOCNJshB97Odpooc4wk7Nb2Fo-Q6THU9zuu0uK_-JbK7IIl6go2qA",
+ "xchan_pubkey": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA18JB76lyP4zzL/y7BCej\neJnfZIWZNtM3MZvI1zEVMWmmwOS+u/yH8oPwyaDk4Y/tnj8GzMPj1lCGVRcd8EJa\nNrCMd50HODA5EsJtxpsOzRcILYjOcTtIAG1K4LtKqELi9ICAaFp0fNfa+Jf0eCek\nvPusx2/ORhy+o23hFoSMhL86o2gmaiRnmnA3Vz4ZMG92ieJEDMXt9IA1EkIqS4y5\nBPZfVPLD1pv8iivj+dtN1XjwplgjUbtxmU0/Ej808nHppscRIqx/XJ0XZU90oNGw\n/wYoK2EzJlPbRsAkwNqoFrAYlr5HPpn4BJ2ebFYQgWBUraD7HwS5atsQEaxGfO21\nlUP0+lDg9t3CXvudDj0UG1jiEKbVIGA+4aG0GN2DSC5AyRq/GRxqyay5W2vQbAZH\nyvxPGrZFO24I65g3pjhpjEsLqZ4ilTLQoLMs0drCIcRm5RxMUo4s/LMg16lT4cEk\n1qRtk2X0Sb1AMQQ2uRXiVtWz77QHMONEYkf6OW4SHbwcv5umvlv69NYEGfCcbgq0\nAV7U4/BWztUz/SWj4r194CG43I9I8dmaEx9CFA/XMePIAXQUuABfe1QMOR6IxLpq\nTHG1peZgHQKeGz4aSGrhQkZNNoOVNaZoIfcvopxcHDTZLigseEIaPPha4WFYoKPi\nUPbZ5o8gTLc750uzrnb2jwcCAwEAAQ==\n-----END PUBLIC KEY-----\n",
+ "xchan_photo_mimetype": "image/png",
+ "xchan_photo_l": "https://xyz.macgirvin.com/photo/profile/l/2",
+ "xchan_photo_m": "https://xyz.macgirvin.com/photo/profile/m/2",
+ "xchan_photo_s": "https://xyz.macgirvin.com/photo/profile/s/2",
+ "xchan_addr": "teller@xyz.macgirvin.com",
+ "xchan_url": "https://xyz.macgirvin.com/channel/teller",
+ "xchan_connurl": "https://xyz.macgirvin.com/poco/teller",
+ "xchan_follow": "https://xyz.macgirvin.com/follow?f=&url=%s",
+ "xchan_connpage": "",
+ "xchan_name": "Teller",
+ "xchan_network": "zot",
+ "xchan_instance_url": "",
+ "xchan_flags": "0",
+ "xchan_photo_date": "2016-10-19 01:26:50",
+ "xchan_name_date": "2016-01-02 21:16:26",
+ "xchan_hidden": "0",
+ "xchan_orphan": "0",
+ "xchan_censored": "0",
+ "xchan_selfcensored": "0",
+ "xchan_system": "0",
+ "xchan_pubforum": "0",
+ "xchan_deleted": "0"
+ },
+ {
+ "id": "12",
+ "uid": "2",
+ "gid": "1",
+ "xchan": "xuSMUYxw1djBB97qXsbrBN1nzJH_gFwQL6pS4zIy8fuusOfBxNlMiVb4h_q5tOEvpE7tYf1EsryjNciMuPIj5w",
+ "abook_id": "24",
+ "abook_account": "1",
+ "abook_channel": "2",
+ "abook_xchan": "xuSMUYxw1djBB97qXsbrBN1nzJH_gFwQL6pS4zIy8fuusOfBxNlMiVb4h_q5tOEvpE7tYf1EsryjNciMuPIj5w",
+ "abook_my_perms": "218555",
+ "abook_their_perms": "218555",
+ "abook_closeness": "80",
+ "abook_created": "2016-01-27 00:48:43",
+ "abook_updated": "2016-12-04 17:16:58",
+ "abook_connected": "2016-12-04 17:16:58",
+ "abook_dob": "0001-01-01 00:00:00",
+ "abook_flags": "0",
+ "abook_blocked": "0",
+ "abook_ignored": "0",
+ "abook_hidden": "0",
+ "abook_archived": "0",
+ "abook_pending": "0",
+ "abook_unconnected": "0",
+ "abook_self": "0",
+ "abook_feed": "0",
+ "abook_profile": "debb5236efb1626cfbad33ccb49892801e5f844aa04bf81f580cfa7d13204819",
+ "abook_incl": "",
+ "abook_excl": "",
+ "abook_instance": "",
+ "xchan_hash": "xuSMUYxw1djBB97qXsbrBN1nzJH_gFwQL6pS4zIy8fuusOfBxNlMiVb4h_q5tOEvpE7tYf1EsryjNciMuPIj5w",
+ "xchan_guid": "d5EMLlt1tHHZ0dANoA7B5Wq9UgXoWcFS9-gXOkL_AAejcPApoQRyxfHTuu8DoTbUaO-bYmX5HPuWuK9PHyqNmA",
+ "xchan_guid_sig": "CVWEMRPtzI1YcHfnnWHTuv3H964OAmSElgUfxMoX6RdQdxNpqb_POirpVuyP8s3W17mVCfO5V9IAjkg5iKcqCk6YcvOD_egmMy-AnM9TC1kKndQHw55CunD82Q8K_xBNSXkSROizcNkKh9DVLjJPFjW1AqtI4njkZ3EMgrWqnbFRM1qPToUoCY9zM3tEMHoAD9YX1zP90wl40LzfN-dtcNWpSBbiz9owou62uzLbN7mrCwKOMlXLjwwGswRnxIsEnb3O-FXOs8hs0mArKe9snq1-BKeD16LyzxgwlpVLElzIJZGEZGtMdIJgeRzKuBvPjsOIpQ1yAkuOpFJ3nGCM-IPOIIjAmyVl5zD3xPVcxxpZlJRn5fG1Y-gnqTgsrEQCA7M6XPWQdrdHU4akZfyUyFJDhv3uM-jon9VzrYTBw68R0WA-1Z8WafEHA4qh5OWAj85lUarwhr7iTiEckH51ypPCPs6VbT6Pw7yMaxfjFOcipashQagx0tfOlDhE5dQANOXKASFtH1J9-CZY2MQdLPQ6u54d5whuHKMGaJ0V68pnmZ2rOn7g344Ah2WCJrm17jj60QsRMorqRFj7GMdPIA1XB8Wrk88MuYOe3Dhyuu6ZWKI7YTWJS690ZVkKUqAiNHqj0W86DtaiPUc_mmGR0fHl4Gksnko3WmCFv9q2X2E",
+ "xchan_pubkey": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoj2xCJktBA8Ww7Hp+ZNL\nrNuQpo8UB/bfvRkIy+yua3xpF1TuXcnAH61kyRz8vXgOu/l2CyxQbIoaGslCV5Sy\n8JKeNXe+IilUdSSEjMIwCPfSPsYnMHsSnHWmPmclvJwEtQUKOZmW5mMuVBvXy7D2\njomFwc69AYphdyys6eQ7Dcn6+FRBiQbyMprZ5lxyVW+O4DuXVNa3ej2ebx0gCJZ4\ntTIlBoKwEey91dY+FyKVFjdwfNczpmL7LgmZXqcVx+MG3mYgibwdVMiXVj5X06cs\nV9hJ5Xi+Aklsv/UWJtjw9FVt7y9TLptnhh4Ra6T/MDmnBBIAkOR7P/X8cRv078MT\nl0IMsP0RJcDEtTLtwHFVtDs6p52KDFqclKWbqmxmxqV3OTPVYtArRGIzgnJi/5ur\nHRr5G6Cif7QY3UowsIOf78Qvy28LwSbdymgBAWwPPKIviXWxGO+9kMWdmPSUQrWy\nK0+7YA9P9fBUFfn9Hc+p8SJQmQ6OAqLwrDGiPSOlGaNrbEqwqLGgIpXwK+lEFcFJ\n3SPOjJRWdR2whlMxvpwX+39+H7dWN3vSa3Al4/Sq7qW8yW2rYwf+eGyp4Z0lRR+8\nJxFMCwZkSw5g14YdlikAPojv5V1c6KuA5ieg8G1hwyONV7A4JHPyEdPt0W0TZi6C\nCOVkPaC3xGrguETZpJfVpwUCAwEAAQ==\n-----END PUBLIC KEY-----\n",
+ "xchan_photo_mimetype": "image/png",
+ "xchan_photo_l": "https://xyz.macgirvin.com/photo/9da63aa910ea14e1501ee1a749d181a6-4",
+ "xchan_photo_m": "https://xyz.macgirvin.com/photo/9da63aa910ea14e1501ee1a749d181a6-5",
+ "xchan_photo_s": "https://xyz.macgirvin.com/photo/9da63aa910ea14e1501ee1a749d181a6-6",
+ "xchan_addr": "cloner@xyz.macgirvin.com",
+ "xchan_url": "http://abc.macgirvin.com/channel/cloner",
+ "xchan_connurl": "http://abc.macgirvin.com/poco/cloner",
+ "xchan_follow": "https://xyz.macgirvin.com/follow?f=&url=%s",
+ "xchan_connpage": "",
+ "xchan_name": "Karen",
+ "xchan_network": "zot",
+ "xchan_instance_url": "",
+ "xchan_flags": "0",
+ "xchan_photo_date": "2016-03-31 19:59:20",
+ "xchan_name_date": "2016-01-26 23:23:42",
+ "xchan_hidden": "0",
+ "xchan_orphan": "0",
+ "xchan_censored": "0",
+ "xchan_selfcensored": "0",
+ "xchan_system": "0",
+ "xchan_pubforum": "0",
+ "xchan_deleted": "0"
+ }
+
+ ]
+```
+
+### xchan
+
+An xchan is a global location independent channel and is the primary record for a network
+identity. It may refer to channels on other websites, networks, or services.
+
+```
+GET /api/z/1.0/xchan
+```
+
+Required: one of [ address, hash, guid ] as GET parameters
+
+Returns a portable xchan structure
+
+Example: `https://xyz.macgirvin.com/api/z/1.0/xchan?f=&address=mike@macgirvin.com`
+
+Returns:
+
+```
+ {
+ "hash": "jr54M_y2l5NgHX5wBvP0KqWcAHuW23p1ld-6Vn63_pGTZklrI36LF8vUHMSKJMD8xzzkz7s2xxCx4-BOLNPaVA",
+ "guid": "sebQ-IC4rmFn9d9iu17m4BXO-kHuNutWo2ySjeV2SIW1LzksUkss12xVo3m3fykYxN5HMcc7gUZVYv26asx-Pg",
+ "guid_sig": "Llenlbl4zHo6-g4sa63MlQmTP5dRCrsPmXHHFmoCHG63BLq5CUZJRLS1vRrrr_MNxr7zob_Ykt_m5xPKe5H0_i4pDj-UdP8dPZqH2fqhhx00kuYL4YUMJ8gRr5eO17vsZQ3XxTcyKewtgeW0j7ytwMp6-hFVUx_Cq08MrXas429ZrjzaEwgTfxGnbgeQYQ0R5EXpHpEmoERnZx77VaEahftmdjAUx9R4YKAp13pGYadJOX5xnLfqofHQD8DyRHWeMJ4G1OfWPSOlXfRayrV_jhnFlZjMU7vOdQwHoCMoR5TFsRsHuzd-qepbvo3pzvQZRWnTNu6oPucgbf94p13QbalYRpBXKOxdTXJrGdESNhGvhtaZnpT9c1QVqC46jdfP0LOX2xrVdbvvG2JMWFv7XJUVjLSk_yjzY6or2VD4V6ztYcjpCi9d_WoNHruoxro_br1YO3KatySxJs-LQ7SOkQI60FpysfbphNyvYMkotwUFI59G08IGKTMu3-GPnV1wp7NOQD1yzJbGGEGSEEysmEP0SO9vnN45kp3MiqbffBGc1r4_YM4e7DPmqOGM94qksOcLOJk1HNESw2dQYWxWQTBXPfOJT6jW9_crGLMEOsZ3Jcss0XS9KzBUA2p_9osvvhUKuKXbNztqH0oZIWlg37FEVsDs_hUwUJpv2Ar09k4",
+ "pubkey": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7QCwvuEIwCHjhjbpz3Oc\ntyei/Pz9nDksNbsc44Cm8jxYGMXsTPFXDZYCcCB5rcAhPPdZSlzaPkv4vPVcMIrw\n5cdX0tvbwa3rNTng6uFE7qkt15D3YCTkwF0Y9FVZiZ2Ko+G23QeBt9wqb9dlDN1d\nuPmu9BLYXIT/JXoBwf0vjIPFM9WBi5W/EHGaiuqw7lt0qI7zDGw77yO5yehKE4cu\n7dt3SakrXphL70LGiZh2XGoLg9Gmpz98t+gvPAUEotAJxIUqnoiTA8jlxoiQjeRK\nHlJkwMOGmRNPS33awPos0kcSxAywuBbh2X3aSqUMjcbE4cGJ++/13zoa6RUZRObC\nZnaLYJxqYBh13/N8SfH7d005hecDxWnoYXeYuuMeT3a2hV0J84ztkJX5OoxIwk7S\nWmvBq4+m66usn6LNL+p5IAcs93KbvOxxrjtQrzohBXc6+elfLVSQ1Rr9g5xbgpub\npSc+hvzbB6p0tleDRzwAy9X16NI4DYiTj4nkmVjigNo9v2VPnAle5zSam86eiYLO\nt2u9YRqysMLPKevNdj3CIvst+BaGGQONlQalRdIcq8Lin+BhuX+1TBgqyav4XD9K\nd+JHMb1aBk/rFLI9/f2S3BJ1XqpbjXz7AbYlaCwKiJ836+HS8PmLKxwVOnpLMbfH\nPYM8k83Lip4bEKIyAuf02qkCAwEAAQ==\n-----END PUBLIC KEY-----\n",
+ "photo_mimetype": "image/jpeg",
+ "photo_l": "https://xyz.macgirvin.com/photo/350b74555c04429148f2e12775f6c403-4",
+ "photo_m": "https://xyz.macgirvin.com/photo/350b74555c04429148f2e12775f6c403-5",
+ "photo_s": "https://xyz.macgirvin.com/photo/350b74555c04429148f2e12775f6c403-6",
+ "address": "mike@macgirvin.com",
+ "url": "https://macgirvin.com/channel/mike",
+ "connurl": "https://macgirvin.com/poco/mike",
+ "follow": "https://macgirvin.com/follow?f=&url=%s",
+ "connpage": "https://macgirvin.com/connect/mike",
+ "name": "Mike Macgirvin",
+ "network": "zot",
+ "instance_url": "",
+ "flags": "0",
+ "photo_date": "2012-12-06 05:06:11",
+ "name_date": "2012-12-06 04:59:13",
+ "hidden": "1",
+ "orphan": "0",
+ "censored": "0",
+ "selfcensored": "0",
+ "system": "0",
+ "pubforum": "0",
+ "deleted": "0"
+ }
+```
+
+### item/update
+
+Create or update an item (post, activity, webpage, etc.)
+
+Usage: `POST /api/z/1.0/item/update`
+
+Description: item/update posts an item (typically a conversation item or post, but can be any item) using form input.
+
+
+Required:
+
+\- body
+
+ text/bbcode contents by default.
+
+
+Optional:
+
+\- $_FILES['media']
+
+ uploaded media file to include with post
+
+\- title
+
+ title of post/item
+
+\- contact_allow
+
+ array of xchan.xchan_hash allowed to view this item
+
+\- group_allow
+
+ array of group.hash allowed to view this item
+
+\- contact_deny
+
+ array of xchan.xchan_hash not allowed to view this item
+
+\- group_deny
+
+ array of group.hash not allowed to view this item
+
+\- coord
+
+ geographic coordinates
+
+\- location
+
+ freefrom location
+
+\- expire
+
+ datetime this post will expire or be removed
+
+\- mimetype
+
+ mimetype if not text/bbcode
+
+\- parent
+
+ item.id of parent to this post (makes it a comment)
+
+\- parent_mid
+
+ alternate form of parent using message_id
+
+\- remote_xchan
+
+ xchan.xchan_hash of this message author if not the channel owner
+
+\- consensus
+
+ boolean set to true if this is a consensus or voting item (default false)
+
+\- nocomment
+
+ boolean set to true if comments are to be disabled (default false)
+
+\- origin
+
+ do not use this without reading the code
+
+\- namespace
+
+ persistent identity for a remote network or service
+
+\- remote_id
+
+ message_id of this resource on a remote network or service
+
+\- message_id
+
+ message_id of this item (leave unset to generate one)
+
+\- created
+
+ datetime of message creation
+
+\- post_id
+
+ existing item.id if this is an edit operation
+
+\- app
+
+ application or network name to display with item
+
+\- category
+
+ comma separated categories for this item
+
+\- webpage
+
+ item.page_type if not 0
+
+\- pagetitle
+
+ for webpage and design elements, the 'page name'
+
+\- layout_mid
+
+ item.mid of layout for this design element
+
+\- plink
+
+ permalink for this item if different than the default
+
+\- verb
+
+ activitystream verb for this item/activity
+
+\- obj_type
+
+ activitystream object type for this item/activity
+
+
+
+Example:
+
+
+
+```
+curl -u mychannel:mypassword https://xyz.macgirvin.com/api/z/1.0/item/update -d body="hello world"
+```
+
+
+Returns:
+
+
+
+```
+ {
+
+ "success": true,
+ "item_id": "2245",
+ "item": {
+ "id": "2245",
+ "mid": "14135cdecf6b8e3891224e4391748722114da6668eebbcb56fe4667b60b88249@xyz.macgirvin.com",
+ "aid": "1",
+ "uid": "2",
+ "parent": "2245",
+ "parent_mid": "14135cdecf6b8e3891224e4391748722114da6668eebbcb56fe4667b60b88249@xyz.macgirvin.com",
+ "thr_parent": "14135cdecf6b8e3891224e4391748722114da6668eebbcb56fe4667b60b88249@xyz.macgirvin.com",
+ "created": "2016-12-03 20:00:12",
+ "edited": "2016-12-03 20:00:12",
+ "expires": "0001-01-01 00:00:00",
+ "commented": "2016-12-03 20:00:12",
+ "received": "2016-12-03 20:00:12",
+ "changed": "2016-12-03 20:00:12",
+ "comments_closed": "0001-01-01 00:00:00",
+ "owner_xchan": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
+ "author_xchan": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
+ "source_xchan": "",
+ "mimetype": "text/bbcode",
+ "title": "",
+ "body": "hello world",
+ "html": "",
+ "app": "",
+ "lang": "",
+ "revision": "0",
+ "verb": "http://activitystrea.ms/schema/1.0/post",
+ "obj_type": "http://activitystrea.ms/schema/1.0/note",
+ "obj": "",
+ "tgt_type": "",
+ "target": "",
+ "layout_mid": "",
+ "postopts": "",
+ "route": "",
+ "llink": "https://xyz.macgirvin.com/display/14135cdecf6b8e3891224e4391748722114da6668eebbcb56fe4667b60b88249@xyz.macgirvin.com",
+ "plink": "https://xyz.macgirvin.com/channel/mychannel/?f=&mid=14135cdecf6b8e3891224e4391748722114da6668eebbcb56fe4667b60b88249@xyz.macgirvin.com",
+ "resource_id": "",
+ "resource_type": "",
+ "attach": "",
+ "sig": "sa4TOQNfHtV13HDZ1tuQGWNBpZp-nWhT2GMrZEmelXxa_IvEepD2SEsCTWOBqM8OKPJLfNy8_i-ORXjrOIIgAa_aT8cw5vka7Q0C8L9eEb_LegwQ_BtH0CXO5uT30e_8uowkwzh6kmlVg1ntD8QqrGgD5jTET_fMQOIw4gQUBh40GDG9RB4QnPp_MKsgemGrADnRk2vHO7-bR32yQ0JI-8G-eyeqGaaJmIwkHoi0vXsfjZtU7ijSLuKEBWboNjKEDU89-vQ1c5Kh1r0pmjiDk-a5JzZTYShpuhVA-vQgEcADA7wkf4lJZCYNwu3FRwHTvhSMdF0nmyv3aPFglQDky38-SAXZyQSvd7qlABHGCVVDmYrYaiq7Dh4rRENbAUf-UJFHPCVB7NRg34R8HIqmOKq1Su99bIWaoI2zuAQEVma9wLqMoFsluFhxX58KeVtlCZlro7tZ6z619-dthS_fwt0cL_2dZ3QwjG1P36Q4Y4KrCTpntn9ot5osh-HjVQ01h1I9yNCj6XPgYJ8Im3KT_G4hmMDFM7H9RUrYLl2o9XYyiS2nRrf4aJHa0UweBlAY4zcQG34bw2AMGCY53mwsSArf4Hs3rKu5GrGphuwYX0lHa7XEKMglwBWPWHI49q7-oNWr7aWwn1FnfaMfl4cQppCMtKESMNRKm_nb9Dsh5e0",
+ "diaspora_meta": "",
+ "location": "",
+ "coord": "",
+ "public_policy": "",
+ "comment_policy": "contacts",
+ "allow_cid": "",
+ "allow_gid": "",
+ "deny_cid": "",
+ "deny_gid": "",
+ "item_restrict": "0",
+ "item_flags": "0",
+ "item_private": "0",
+ "item_origin": "1",
+ "item_unseen": "0",
+ "item_starred": "0",
+ "item_uplink": "0",
+ "item_consensus": "0",
+ "item_wall": "1",
+ "item_thread_top": "1",
+ "item_notshown": "0",
+ "item_nsfw": "0",
+ "item_relay": "0",
+ "item_mentionsme": "0",
+ "item_nocomment": "0",
+ "item_obscured": "0",
+ "item_verified": "1",
+ "item_retained": "0",
+ "item_rss": "0",
+ "item_deleted": "0",
+ "item_type": "0",
+ "item_hidden": "0",
+ "item_unpublished": "0",
+ "item_delayed": "0",
+ "item_pending_remove": "0",
+ "item_blocked": "0"
+ }
+
+ }
+```
+
+### item/full
+
+Get all data associated with an item
+
+
+
+### abook
+
+Connections
+
+
+
+### abconfig
+
+Connection metadata (such as permissions)
+
+
+
+### perm_allowed
+
+Check a permission for a given xchan \ No newline at end of file
diff --git a/doc/de/developer/Plugins.md b/doc/de/developer/Plugins.md
new file mode 100644
index 000000000..e6d76b2d5
--- /dev/null
+++ b/doc/de/developer/Plugins.md
@@ -0,0 +1,251 @@
+### Erstellen von Plugins/Addons für Hubzilla
+
+Sie möchten also, dass Hubzilla etwas tut, was es noch nicht tut. Da gibt es viele Möglichkeiten. Aber lass uns lernen, wie man ein Plugin oder Addon schreibt.
+In deinem Hubzilla-Ordner/Verzeichnis wirst du wahrscheinlich ein Unterverzeichnis namens 'addon' finden. Wenn Sie noch keins haben, erstellen Sie es einfach.
+
+```
+mkdir addon
+```
+
+Überlegen Sie sich dann einen Namen für Ihr Addon. Wahrscheinlich haben Sie zumindest eine vage Vorstellung davon, was es tun soll. Für unser Beispiel werde ich ein Plugin mit dem Namen „randplace“ erstellen, das einen zufälligen Speicherort für jeden Ihrer Beiträge angibt. Der Name Ihres Plugins wird verwendet, um die Funktionen zu finden, auf die wir zugreifen müssen, und ist Teil des Funktionsnamens, also verwenden Sie zur Sicherheit nur einfache Textzeichen.
+Sobald Sie einen Namen gewählt haben, erstellen Sie ein Verzeichnis unterhalb von 'addon', um Ihre Arbeitsdatei(en) zu speichern.
+
+```
+mkdir addon/randplace
+```
+
+Erstellen Sie nun Ihre Plugin-Datei. Sie muss den gleichen Namen haben und ist ein PHP-Skript. Erstellen Sie also mit Ihrem Lieblingseditor die Datei
+
+```
+addon/randplace/randplace.php
+```
+
+Die allererste Zeile dieser Datei muss lauten
+
+```
+<?php
+```
+
+Dann werden wir einen Kommentarblock erstellen, der das Plugin beschreibt. Hierfür gibt es ein spezielles Format. Wir verwenden /* ... */ Kommentar-Stil und einige markierte Zeilen, die aus
+
+
+
+ /**
+ *
+ * Name: Random Place (here you can use better descriptions than you could in the filename)
+ * Description: Sample Hubzilla plugin, Sets a random place when posting.
+ * Version: 1.0
+ * Author: Mike Macgirvin <mike@zothub.com>
+ *
+ */
+
+Diese Tags sind für den Website-Administrator sichtbar, wenn er Plugins über die Verwaltungskonsole installiert oder verwaltet. Es kann mehr als einen Autor geben. Fügen Sie einfach eine weitere Zeile hinzu, die mit „Autor:“ beginnt.
+Ein typisches Plugin hat mindestens die folgenden Funktionen:
+
+- pluginname_load()
+- pluginname_unload()
+
+In unserem Fall werden wir sie randplace_load() und randplace_unload() nennen, da dies der Name unseres Plugins ist. Diese Funktionen werden immer dann aufgerufen, wenn wir das Plugin entweder initialisieren oder von der aktuellen Webseite entfernen wollen. Auch wenn Ihr Plugin Dinge wie die Änderung des Datenbankschemas erfordert, bevor es zum ersten Mal ausgeführt werden kann, würden Sie diese Anweisungen wahrscheinlich in den Funktionen namens
+
+- pluginname_install()
+- pluginname_uninstall()
+
+Als nächstes werden wir über **Hooks** sprechen. Hooks sind Stellen im Hubzilla-Code, an denen wir Plugins erlauben, etwas zu tun. Es gibt eine [Menge davon](help/hooks), und jedes hat einen Namen. Normalerweise verwenden wir die Funktion pluginname_load(), um eine „Handler-Funktion“ für alle Hooks zu registrieren, an denen Sie interessiert sind. Wenn dann einer dieser Hooks ausgelöst wird, wird Ihr Code aufgerufen.
+Wir registrieren Hook-Handler mit der Funktion 'register_hook()'. Sie benötigt 3 Argumente. Das erste ist der Hook, den wir abfangen wollen, das zweite ist der Dateiname der Datei, in der sich unsere Handler-Funktion befindet (relativ zur Basis Ihrer Hubzilla-Installation), und das dritte ist der Funktionsname Ihrer Handler-Funktion. Lassen Sie uns also jetzt unsere randplace_load()-Funktion erstellen.
+
+
+ function randplace_load() {
+ register_hook('post_local', 'addon/randplace/randplace.php', 'randplace_post_hook');
+
+ register_hook('feature_settings', 'addon/randplace/randplace.php', 'randplace_settings');
+ register_hook('feature_settings_post', 'addon/randplace/randplace.php', 'randplace_settings_post');
+
+ }
+
+Wir werden also drei Ereignisse abfangen: „post_local“, das ausgelöst wird, wenn ein Beitrag im lokalen System erstellt wird, „feature_settings“, um einige Einstellungen für unser Plugin festzulegen, und „feature_settings_post“, um diese Einstellungen zu speichern.
+
+Als nächstes erstellen wir eine unload-Funktion. Das ist ganz einfach, denn sie hebt nur die Registrierung unserer Hooks auf. Sie nimmt genau die gleichen Argumente entgegen.
+
+ function randplace_unload() {
+ unregister_hook('post_local', 'addon/randplace/randplace.php', 'randplace_post_hook');
+
+ unregister_hook('feature_settings', 'addon/randplace/randplace.php', 'randplace_settings');
+ unregister_hook('feature_settings_post', 'addon/randplace/randplace.php', 'randplace_settings_post');
+
+ }
+
+Hooks werden mit zwei Argumenten aufgerufen. Das erste ist immer $a, das unsere globale App-Struktur ist und eine riesige Menge an Informationen über den Zustand der Web-Anfrage enthält, die wir gerade verarbeiten, sowie darüber, wer der Betrachter ist, und was unser Login-Status ist, und der aktuelle Inhalt der Webseite, die wir wahrscheinlich aufbauen.
+
+Das zweite Argument ist spezifisch für den Hook, den Sie aufrufen. Es enthält Informationen, die für diese bestimmte Stelle im Programm relevant sind, und ermöglicht es Ihnen oft, sie anzusehen und sogar zu ändern. Um sie zu ändern, müssen Sie dem Variablennamen ein '&' hinzufügen, damit sie als Referenz an Ihre Funktion übergeben wird. Andernfalls wird eine Kopie erstellt, und alle Änderungen, die Sie vornehmen, gehen verloren, wenn der Hook-Prozess zurückkehrt. Normalerweise (aber nicht immer) ist das zweite Argument ein benanntes Array von Datenstrukturen. Bitte lesen Sie die „Hook-Referenz“ (zu diesem Zeitpunkt noch nicht geschrieben) für Details zu jedem spezifischen Hook. Gelegentlich müssen Sie sich den Programmcode ansehen, um genau zu sehen, wie ein bestimmter Hook aufgerufen wird und wie die Ergebnisse verarbeitet werden.
+
+Fügen wir nun etwas Code hinzu, um unseren post_local-Hook-Handler zu implementieren.
+
+ function randplace_post_hook($a, &$item) {
+
+ /**
+ *
+ * An item was posted on the local system.
+ * We are going to look for specific items:
+ * - A status post by a profile owner
+ * - The profile owner must have allowed our plugin
+ *
+ */
+
+ logger('randplace invoked');
+
+ if(! local_channel()) /* non-zero if this is a logged in user of this system */
+ return;
+
+ if(local_channel() != $item['uid']) /* Does this person own the post? */
+ return;
+
+ if(($item['parent']) || (! is_item_normal($item))) {
+ /* If the item has a parent, or isn't "normal", this is a comment or something else, not a status post. */
+ return;
+ }
+
+ /* Retrieve our personal config setting */
+
+ $active = get_pconfig(local_channel(), 'randplace', 'enable');
+
+ if(! $active)
+ return;
+ /**
+ *
+ * OK, we're allowed to do our stuff.
+ * Here's what we are going to do:
+ * load the list of timezone names, and use that to generate a list of world cities.
+ * Then we'll pick one of those at random and put it in the "location" field for the post.
+ *
+ */
+
+ $cities = array();
+ $zones = timezone_identifiers_list();
+ foreach($zones as $zone) {
+ if((strpos($zone,'/')) && (! stristr($zone,'US/')) && (! stristr($zone,'Etc/')))
+ $cities[] = str_replace('_', ' ',substr($zone,strpos($zone,'/') + 1));
+ }
+
+ if(! count($cities))
+ return;
+ $city = array_rand($cities,1);
+ $item['location'] = $cities[$city];
+
+ return;
+ }
+
+
+Fügen wir nun unsere Funktionen zum Erstellen und Speichern von Einstellungswerten hinzu.
+
+ /**
+ *
+ * Callback from the settings post function.
+ * $post contains the global $_POST array.
+ * We will make sure we've got a valid user account
+ * and that only our own submit button was clicked
+ * and if so set our configuration setting for this person.
+ *
+ */
+
+ function randplace_settings_post($a,$post) {
+ if(! local_channel())
+ return;
+ if($_POST['randplace-submit'])
+ set_pconfig(local_channel(),'randplace','enable',intval($_POST['randplace']));
+ }
+
+ /**
+ *
+ * Called from the Feature Setting form.
+ * The second argument is a string in this case, the HTML content region of the page.
+ * Add our own settings info to the string.
+ *
+ * For uniformity of settings pages, we use the following convention
+ * <div class="settings-block">
+ * <h3>title</h3>
+ * .... settings html - many elements will be floated...
+ * <div class="clear"></div> <!-- generic class which clears all floats -->
+ * <input type="submit" name="pluginnname-submit" class="settings-submit" ..... />
+ * </div>
+ */
+
+ function randplace_settings(&$a,&$s) {
+
+ if(! local_channel())
+ return;
+
+ /* Add our stylesheet to the page so we can make our settings look nice */
+
+ head_add_css('/addon/randplace/randplace.css');
+
+ /* Get the current state of our config variable */
+
+ $enabled = get_pconfig(local_channel(),'randplace','enable');
+
+ $checked = (($enabled) ? ' checked="checked" ' : '');
+
+ /* Add some HTML to the existing form */
+
+ $s .= '<div class="settings-block">';
+ $s .= '<h3>' . t('Randplace Settings') . '</h3>';
+ $s .= '<div id="randplace-enable-wrapper">';
+ $s .= '<label id="randplace-enable-label" for="randplace-checkbox">' . t('Enable Randplace Plugin') . '</label>';
+ $s .= '<input id="randplace-checkbox" type="checkbox" name="randplace" value="1" ' . $checked . '/>';
+ $s .= '</div><div class="clear"></div>';
+
+ /* provide a submit button */
+
+ $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="randplace-submit" class="settings-submit" value="' . t('Submit') . '" /></div></div>';
+
+ }
+
+#### *Erweiterte Plugins*
+
+Manchmal möchten Ihre Plugins eine Reihe neuer Funktionen bereitstellen, die gar nicht oder nur umständlich über Hooks bereitgestellt werden können. In diesem Fall kann Ihr Plugin auch als „Modul“ fungieren. Ein Modul ist in unserem Fall ein strukturierter Webpage-Handler, der auf eine bestimmte URL reagiert. Dann wird alles, was auf diese URL zugreift, vollständig von Ihrem Plugin behandelt.
+Der Schlüssel dazu ist die Erstellung einer einfachen Funktion namens pluginname_module(), die nichts tut.
+
+```
+function randplace_module() { return; }
+```
+
+Sobald diese Funktion existiert, wird die URL `https://yoursite/randplace` auf Ihr Plugin als Modul zugreifen. Dann können Sie Funktionen definieren, die an verschiedenen Stellen aufgerufen werden, um eine Webseite aufzubauen, genau wie die Module im mod/-Verzeichnis. Die typischen Funktionen und die Reihenfolge, in der sie aufgerufen werden, sind
+
+ modulename_init($a) // (e.g. randplace_init($a);) called first - if you wish to emit json or xml,
+ // you should do it here, followed by killme() which will avoid the default action of building a webpage
+ modulename_aside($a) // Often used to create sidebar content
+ modulename_post($a) // Called whenever the page is accessed via the "post" method
+ modulename_content($a) // called to generate the central page content. This function should return a string
+ // consisting of the central page content.
+
+Ihre Modulfunktionen haben Zugriff auf den URL-Pfad, als ob sie eigenständige Programme im Unix-Betriebssystem wären. Wenn Sie zum Beispiel die Seite
+
+```
+https://yoursite/randplace/something/somewhere/whatever
+```
+
+besuchen, erstellen wir eine argc/argv-Liste zur Verwendung durch Ihre Modulfunktionen
+
+ $x = argc(); $x will be 4, the number of path arguments after the sitename
+
+ for($x = 0; $x < argc(); $x ++)
+ echo $x . ' ' . argv($x);
+
+
+ 0 randplace
+ 1 something
+ 2 somewhere
+ 3 whatever
+
+#### *Portierung von Friendica-Plugins*
+
+Hubzilla verwendet eine ähnliche Plugin-Architektur wie das Friendica-Projekt. Die Authentifizierungs-, Identitäts- und Rechtesysteme sind jedoch völlig unterschiedlich. Viele Friendica-Plugins können relativ einfach portiert werden, indem man ein paar Funktionen umbenennt - und dann sicherstellt, dass das Berechtigungsmodell befolgt wird. Die Funktionen, die umbenannt werden müssen, sind:
+
+- Friendica's pluginname_install() wird zu pluginname_load()
+- Friendicas pluginname_uninstall() wird zu pluginname_unload()
+
+Hubzilla hat _install und _uninstall Funktionen, aber diese werden unterschiedlich benutzt.
+
+- Friendicas „plugin_settings“-Haken wird „feature_settings“ genannt
+- Friendicas „plugin_settings_post“-Haken wird „feature_settings_post“ genannt
+
+Wenn Sie diese ändern, wird Ihr Plugin oft funktionieren, aber bitte überprüfen Sie Ihren gesamten Berechtigungs- und Identitätscode, da die Konzepte dahinter in Hubzilla völlig anders sind. Viele strukturierte Datennamen (insbesondere DB-Schema-Spalten) sind auch ganz anders. \ No newline at end of file
diff --git a/doc/de/developer/attribution.md b/doc/de/developer/attribution.md
new file mode 100644
index 000000000..555abfa92
--- /dev/null
+++ b/doc/de/developer/attribution.md
@@ -0,0 +1,3 @@
+#### Namensnennung
+
+Dieser Verhaltenskodex ist eine Anpassung des [Contributor Covenant](http://contributor-covenant.org/), Version 1.4, verfügbar unter [http://contributor-covenant.org/version/1/4.](http://contributor-covenant.org/version/1/4/) \ No newline at end of file
diff --git a/doc/de/developer/code_of_conduct.md b/doc/de/developer/code_of_conduct.md
new file mode 100644
index 000000000..c197f0965
--- /dev/null
+++ b/doc/de/developer/code_of_conduct.md
@@ -0,0 +1,8 @@
+### Verhaltenskodex für Mitwirkende
+
+#include doc/de/developer/our_pledge.md;
+#include doc/de/developer/our_standards.md;
+#include doc/de/developer/our_responsibilities.md;
+#include doc/de/developer/scope.md;
+#include doc/de/developer/enforcement.md;
+#include doc/de/developer/attribution.md;
diff --git a/doc/de/developer/coding_style.md b/doc/de/developer/coding_style.md
new file mode 100644
index 000000000..fab3d7157
--- /dev/null
+++ b/doc/de/developer/coding_style.md
@@ -0,0 +1,12 @@
+### Codierungsstil
+
+Im Interesse der Konsistenz verwenden wir den folgenden Code-Stil. Wir akzeptieren auch Patches, die andere Stile verwenden, aber bitte versuchen Sie, wenn möglich, einen einheitlichen Code-Stil zu verwenden. Wir werden nicht über die Vorzüge dieses Stils streiten oder diskutieren, und es ist irrelevant, was Projekt „xyz“ verwendet. Dies ist nicht Projekt „xyz“. Dies ist eine Grundlinie, um zu versuchen, den Code jetzt und in Zukunft lesbar zu halten.
+
+- Alle Kommentare sollten in Englisch sein.
+- Wir verwenden Doxygen, um Dokumentation zu erstellen. Dies wurde nicht konsequent angewandt, aber es wird dringend empfohlen, es zu lernen und zu benutzen.
+- Die Einrückung erfolgt hauptsächlich durch Tabulatoren mit einer Tabulatorbreite von 4.
+- String-Verkettungen und Operatoren sollten durch Leerzeichen getrennt werden. z.B. `$foo = $bar . 'abc';` anstelle von `$foo=$bar.‘abc';`
+- Im Allgemeinen verwenden wir einfache Anführungszeichen für String-Variablen und doppelte Anführungszeichen für SQL-Anweisungen. „Hier Dokumente“ sollten vermieden werden. Manchmal ist die Verwendung von Strings in doppelten Anführungszeichen mit Variablenersetzung das effizienteste Mittel zur Erstellung des Strings. In den meisten Fällen sollten Sie einfache Anführungszeichen verwenden.
+- Verwenden Sie Leerzeichen großzügig, um die Lesbarkeit zu verbessern. Bei der Erstellung von Arrays mit vielen Elementen wird oft ein Schlüssel/Wert-Paar pro Zeile gesetzt, das entsprechend von der übergeordneten Zeile eingerückt wird. Das Aneinanderreihen der Zuweisungsoperatoren erfordert etwas mehr Arbeit, erhöht aber auch die Lesbarkeit.
+- Im Allgemeinen werden öffnende geschweifte Klammern in dieselbe Zeile gesetzt wie das, was die Klammer öffnet. Sie sind das letzte Zeichen in der Zeile. Schließende Klammern stehen in einer eigenen Zeile.
+- Einige Funktionen nehmen Argumente im argc/argv-Stil entgegen, wie main() in C oder Funktions-Args in Bash oder Perl. Urls werden innerhalb eines Moduls aufgeteilt. z.B. bei `http://example.com/module/arg1/arg2` ist $this->argc 3 (integer) und $this->argv enthält: [0] => 'module', [1] => 'arg1', [2] => 'arg2'. Es wird immer nur ein Argument angegeben. Wenn eine nackte Domain-URL angegeben wird, wird $this->argv[0] auf „home“ gesetzt. \ No newline at end of file
diff --git a/doc/de/developer/dev-function-overview.md b/doc/de/developer/dev-function-overview.md
new file mode 100644
index 000000000..36b27b449
--- /dev/null
+++ b/doc/de/developer/dev-function-overview.md
@@ -0,0 +1,43 @@
+### Hubzilla-Entwicklung - einige nützliche Grundfunktionen
+
+- `get_account_id()`
+
+Gibt die numerische account_id zurück, wenn authentifiziert oder 0. Es ist möglich, authentifiziert und nicht mit einem Channel verbunden zu sein.
+
+- `local_channel()`
+
+Gibt die numerische channel_id zurück, wenn sie authentifiziert und mit einem Channel verbunden ist, oder 0. Wird im Code manchmal als $uid bezeichnet.
+
+- `remote_channel()`
+
+Gibt einen authentifizierten String-Hash des Red Global Identifier zurück, wenn er über Remote-Auth authentifiziert wurde, oder einen leeren String.
+
+- `App::get_observer()`
+
+gibt eine xchan-Struktur zurück, die den aktuellen Betrachter repräsentiert, wenn er authentifiziert ist (lokal oder remote).
+
+- `get_config($family,$key), get_pconfig($uid,$family,$key)`
+
+Gibt die Konfigurationseinstellung für $family und $key zurück oder false, wenn sie nicht gesetzt ist.
+Veraltet: Verwenden Sie stattdessen `Zotlabs\Lib\Config::Get`.
+
+- `set_config($family,$key,$value), set_pconfig($uid,$family,$key,$value)`
+
+Setzt den Wert der Konfigurationseinstellung für $family und $key auf $value. Gibt $value zurück. Die config-Versionen arbeiten mit systemweiten Einstellungen. Die pconfig-Versionen erhalten/setzen die Werte für eine bestimmte Integer uid (channel_id).
+Veraltet: Verwenden Sie stattdessen `Zotlabs\Lib\Config::Set`.
+
+- `dbesc()`
+
+Escape-Zeichenfolgen, die in DB-Abfragen verwendet werden. Diese Funktion gibt die escapte Zeichenkette zurück. Ganzzahlige DB-Parameter sollten alle mit intval() in Ganzzahlen umgewandelt werden
+
+- `q($sql,$var1...)`
+
+Führt eine DB-Abfrage mit der SQL-Anweisung $sql durch. Die printf-artigen Argumente %s und %d werden durch variable Argumente ersetzt, die jeweils entsprechend dbesc() oder intval() sein sollten. SELECT-Abfragen geben ein Array von Ergebnissen oder false zurück, wenn ein SQL- oder DB-Fehler vorliegt. Andere Abfragen geben true zurück, wenn der Befehl erfolgreich war, oder false, wenn er nicht erfolgreich war.
+
+- `t($Zeichenfolge)`
+
+Gibt die übersetzte Variante von $string für die aktuelle Sprache zurück oder $string (Standardsprache 'en'), wenn die Sprache nicht bekannt ist oder eine übersetzte Version des Strings nicht existiert.
+
+- `x($var), $x($array,$key)`
+
+Kurzer Test, um zu sehen, ob die Variable $var gesetzt und nicht leer ist. Die Tests variieren je nach Typ. Gibt false zurück, wenn $var oder $key nicht gesetzt ist. Wenn die Variable gesetzt ist, wird 1 zurückgegeben, wenn sie einen Wert ungleich Null hat, andernfalls wird 0 zurückgegeben. -- z.B. x('') oder x(0) gibt 0 zurück; \ No newline at end of file
diff --git a/doc/de/developer/dev_beginner.md b/doc/de/developer/dev_beginner.md
new file mode 100644
index 000000000..91f19a109
--- /dev/null
+++ b/doc/de/developer/dev_beginner.md
@@ -0,0 +1,460 @@
+### Sie möchten Code beisteuern?
+
+**...und wissen nicht, wie Sie anfangen sollen...**
+
+- Hubzilla debuggen (php auf dem Webserver),
+- Code für das Projekt beisteuern,
+- optional - das alles von einer virtuellen Maschine aus zu tun
+
+Diese Anleitung wurde für Debian (Wheezy) als virtuelle Maschine auf Lubuntu (Ubuntu 14.0) als Host getestet.
+
+#### Installation einer virtuellen Maschine (KVM)
+
+[Hier](https://wiki.debian.org/KVM) die Installationsanleitung für Linux Debian. Die Zusammenfassung:
+
+1. KVM installieren
+
+```
+# apt-get install qemu-kvm libvirt-bin
+```
+
+1. füge dich der Gruppe libvirt hinzu
+
+```
+# adduser <deinBenutzer> libvirt
+```
+
+1. Installieren Sie die Benutzeroberfläche zur Verwaltung der virtuellen Maschinen
+
+```
+# apt-get install virt-manager
+```
+
+1. Laden Sie ein Betriebssystem herunter, das in der virtuellen Maschine laufen soll ([mini.iso](http://ftp.nl.debian.org/debian/dists/wheezy/main/installer-amd64/current/images/netboot/mini.iso))
+2. Starten Sie den Virt-Manager
+
+- Erstellen Sie eine neue virtuelle Maschine (klicken Sie auf das Symbol)
+- Wählen Sie Ihr Iso-Image (soeben heruntergeladen) als Installationsquelle
+- optional: konfigurieren Sie die neue vm: ram, cpu's,...
+- Virtuelle Maschine starten > Ergebnis: Linux Debian startet in einem neuen Fenster.
+
+1. (optional) Vermeiden Sie Netzwerkfehler nach dem Neustart des Host-Betriebssystems
+
+```
+# virsh net-start default
+# virsh net-autostart standard
+```
+
+#### Apache-Webserver installieren
+
+Öffnen Sie ein Terminal und machen Sie sich zum root
+
+```
+su -l
+```
+
+Erstellen Sie die Standardgruppe für den Apache-Webserver
+
+```
+groupadd www-data
+```
+
+könnte bereits existieren
+
+```
+usermod -a -G www-data www-data
+```
+
+Prüfen Sie, ob das System wirklich auf dem neuesten Stand ist
+
+```
+apt-get update
+apt-get upgrade
+```
+
+Optionaler Neustart der Dienste nach der Installation
+
+```
+reboot
+```
+
+Wenn Sie neu gestartet haben, machen Sie sich zum root
+
+```
+su -l
+```
+
+Installieren Sie Apache:
+
+```
+apt-get install apache2 apache2-doc apache2-utils
+```
+
+Öffnen Sie den Webbrowser auf dem PC und überprüfen Sie [localhost](localhost) Sollte Ihnen eine Seite wie „It works“ anzeigen
+(Quelle http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#)
+
+#### PHP, MySQL und phpMyAdmin installieren
+
+##### PHP, MySQL
+
+```
+su -l
+apt-get install libapache2-mod-php8.2 php8.2 php-pear php8.2-xcache php8.2-curl php8.2-mcrypt php8.2-xdebug
+apt-get install php8.2-mysql
+apt-get install mysql-server mysql-client
+```
+
+das mysql-Passwort eingeben und notieren
+Optional, da es bereits während der phpmyadmin-Einrichtung aktiviert wurde
+
+```
+phpenmod mcrypt
+```
+
+##### phpMyAdmin
+
+phpMyAdmin installieren
+
+```
+apt-get install phpmyadmin
+```
+
+Konfigurieren von phpmyadmin
+
+- Wählen Sie apache2 aus (Tipp: benutzen Sie die Tabulator-Taste zum Auswählen)
+- Datenbank für phpmyadmin mit dbconfig-common? konfigurieren: Wählen Sie Ja
+
+(Quelle [http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#](http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#))
+
+##### Aktivieren Sie Rewrite
+
+Bei der Standardinstallation von Apache2 ist mod_rewrite bereits installiert. Um zu prüfen, ob dies der Fall ist, überprüfen Sie die Existenz von `/etc/apache2/mods-available/rewrite.load`
+
+```
+nano /etc/apache2/mods-available/rewrite.load
+```
+
+(Sie sollten den Inhalt finden: `LoadModule rewrite_module` `/usr/lib/apache2/modules/mod_rewrite.so`) Um mod_rewrite zu aktivieren und zu laden, führen Sie die restlichen Schritte aus. Erstellen Sie einen symbolischen Link in `/etc/apache2/mods-enabled`
+
+```
+cd /var/www
+a2enmod rewrite
+```
+
+Öffnen Sie dann die folgende Datei und ersetzen Sie jedes Vorkommen von „`AllowOverride None`“ durch „`AllowOverride all`“.
+
+```
+nano /etc/apache2/apache2.conf
+```
+
+oder
+
+```
+gedit /etc/apache2/sites-enabled/000-default
+```
+
+Starten Sie schließlich Apache2 neu.
+
+```
+service apache2 restart
+```
+
+##### Testen Sie die Installation
+
+```
+cd /var/www
+```
+
+Erstellen Sie eine php-Datei, um die php-Installation zu testen
+
+```
+nano phpinfo.php
+```
+
+In die Datei einfügen:
+
+```
+<?php
+ phpinfo();
+?>
+```
+
+(STRG+0, ENTER, STRG+X speichern)
+Webbrowser auf dem PC öffnen und `http://localhost/phpinfo.php` ausprobieren (Seite zeigt Infos zu php)
+phpMyAdmin mit der MySQL-Datenbank verbinden
+
+```
+nano /etc/apache2/apache2.conf
+```
+
+- CTRL+V... bis zum Ende der Datei
+- Am Ende der Datei einfügen: (CTRL+0, ENTER, CTRL+X speichern)
+
+```
+Einfügen von /etc/phpmyadmin/apache.conf
+```
+
+Apache neu starten
+
+```
+/etc/init.d/apache2 neu starten
+apt-get update
+apt-get upgrade
+reboot
+```
+
+phpMyAdmin
+Webbrowser auf dem PC öffnen und `http://localhost/phpmyadmin` ausprobieren
+(Quelle [http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#](http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#))
+
+##### Erstelle eine leere Datenbank... die später von Hubzilla verwendet wird
+
+öffne den Webbrowser auf dem PC und probiere `http://localhost/phpmyadmin`
+Erstelle eine leere Datenbank, zum Beispiel mit dem Namen „red“. Erstelle einen Datenbankbenutzer, z.B. „red“. Erteile dem Benutzer „red“ alle Rechte für die Datenbank „red“.
+Notieren Sie sich die Zugangsdaten (Hostname, Benutzername, Passwort, Datenbankname).
+
+##### Forken Sie das Projekt auf github
+
+Bitte folgen Sie den Anweisungen in der offiziellen [Dokumentation](http://git-scm.com/book/en/v2/GitHub-Contributing-to-a-Project) von git. Es ist eine gute Idee, das gesamte Handbuch zu lesen! Git unterscheidet sich in vielerlei Hinsicht von anderen Versionskontrollsystemen.
+Jetzt sollten Sie
+
+- ein Konto bei github.com erstellen
+- fork `https://framagit.org/hubzilla/core`
+- fork `https://framagit.org/hubzilla/addons`
+
+Wenn Sie GIT nicht von der Kommandozeile aus benutzen wollen - es gibt ein nützliches Eclipse-Plugin namens „Eclipse Mylyn to GitHub connector“.
+
+#### Installieren Sie Hubzilla und seine Addons
+
+##### Git auf Ihrem Computer / vm
+
+Sie sollten ein Konto auf GitHub erstellt und die Projekte geforkt haben, bevor Sie fortfahren.
+Löschen Sie das Verzeichnis www
+
+```
+rm -R www/
+```
+
+Installieren Sie git (und optional git-gui, eine Client-Gui)
+
+```
+apt-get install git git-gui
+```
+
+##### Hubzilla und Addons herunterladen
+
+Laden Sie das Hauptprojekt hubzilla und hubzilla-addons herunter
+
+```
+git clone https://github.com/yourname/hubzilla www
+cd www/
+git clone https://github.com/yourname/hubzilla-addons addon
+```
+
+Erstelle diesen zusätzlichen Ordner
+
+```
+mkdir -p „store/[data]/smarty3“
+```
+
+Erstellen Sie .htconfig.php und machen Sie sie für den Webserver schreibbar
+
+```
+touch .htconfig.php
+chmod ou+w .htconfig.php
+```
+
+Machen Sie den Benutzer www-data (Webserver) zum Eigentümer aller Projektdateien
+
+```
+cd ..
+chown -R www-data:www-data www/
+```
+
+Fügen Sie sich selbst („surfer“ in diesem Beispiel) zur Gruppe www-data hinzu. Warum das? Wenn Sie später Dateien in eclipse oder einem anderen Editor bearbeiten wollen.
+Dann machen Sie alle Dateien für die Gruppe www-data, in der Sie jetzt Mitglied sind, schreibbar.
+
+```
+cd www/
+usermod -G www-data surfer
+chmod -R g+w www/
+```
+
+Starten Sie den Computer (oder vm) neu
+Wenn Sie immer noch nicht in der Lage sind, die Projektdateien zu ändern, können Sie die Mitglieder der Gruppe www-data überprüfen mit
+
+```
+cat /etc/group
+```
+
+##### Melden Sie sich als Admin an
+
+Öffnen Sie `http://localhost` und starten Sie die Matrix
+Bevor Sie einen ersten Benutzer registrieren, schalten Sie die Registrierungsmails aus. Öffnen Sie `/var/www/.htconfig.php` und stellen Sie sicher, dass in dieser Zeile „0“ gesetzt ist
+
+```
+App::$config['system']['verify_email'] = 0;
+```
+
+Sie sollten in der Lage sein, die Datei als „Sie selbst“ zu ändern (anstelle von root oder www-data).
+
+##### Cron und der Poller
+
+Wichtig! Lassen Sie den Poller laufen, um die jüngsten „öffentlichen“ Beiträge Ihrer Freunde zu erfassen. Richten Sie einen Cron-Job oder eine geplante Aufgabe ein, um den Poller alle 5-10 Minuten laufen zu lassen, um die jüngsten „öffentlichen“ Beiträge Ihrer Freunde zu erfassen
+
+```
+crontab -e
+```
+
+Fügen Sie hinzu.
+
+```
+*/10 * * * * cd /var/www/; /usr/bin/php include/poller.php
+```
+
+Wenn du den Pfad zu PHP nicht kennst, gib ein
+
+```
+which php
+```
+
+#### Debuggen Sie den Server über eclipse
+
+##### Überprüfen Sie die Konfiguration von xdebug
+
+Sie sollten xdebug bereits in den vorhergehenden Schritten installiert haben
+
+```
+apt-get install php-xdebug
+```
+
+Xdebug konfigurieren
+Öffnen Sie Ihr Terminal und geben Sie als root (su -l)
+
+```
+gedit /etc/php/mods-available/xdebug.ini
+```
+
+Wenn die Datei leer ist, versuchen Sie es an dieser Stelle
+
+```
+gedit /etc/php/conf.d/xdebug.ini
+```
+
+Dieser Befehl sollte den Texteditor gedit mit der Xdebug-Konfigurationsdatei öffnen Am Ende des Dateiinhalts fügen Sie den folgenden Text ein
+
+```
+xdebug.remote_enable=on
+xdebug.remote_handler=dbgp
+xdebug.remote_host=localhost
+xdebug.remote_port=9000
+```
+
+Speichern Sie die Änderungen und schließen Sie den Editor. Geben Sie in Ihr Terminal ein, um den Webserver neu zu starten.
+
+```
+service apache2 neustart
+```
+
+##### Eclipse installieren und Debugging starten
+
+Installieren Sie Eclipse. Starten Sie Eclipse mit dem Standard-Arbeitsbereich (oder wie Sie möchten)
+Installieren Sie das PHP-Plugin Menü > Hilfe > Neue Software installieren... Installieren Sie „PHP-Entwicklungswerkzeuge ...“
+Optional - Installieren Sie das GitHub Connector Plugin Menü > Hilfe > Neue Software installieren... Installieren Sie „Eclipse Mylyn zu GitHub Connector“.
+Konfigurieren Sie das PHP-Plugin Menü > Fenster > Präferenzen...
+
+> Allgemein > Webbrowser > Ändern Sie auf „Externen Webbrowser verwenden“ PHP > Debug > Debug-Einstellungen > PHP Debugger > Ändern Sie auf „XDebug“.
+
+Erstellen Sie ein neues PHP-Projekt Menü > Datei > Neues Projekt > Wählen Sie PHP > „PHP-Projekt“.
+
+> Wählen Sie „Projekt an bestehendem Ort erstellen“ und „/var/www“.
+
+Debugging starten Öffnen Sie index.php und „Debuggen als...“. Wählen Sie als Start-URL: „`http://localhost/`“
+Erwartet:
+
+- Der Webbrowser startet
+- Der Debugger wird bei der ersten php-Zeile anhalten
+
+#### Stellen Sie Ihre Änderungen über github zur Verfügung
+
+##### Vorbereitungen
+
+Es gibt eine entsprechende Seite in dieser Dokumentation: [zrl=[baseurl]/help/git_for_non_developers]Git for Non-Developers[/zrl]. Wie bereits erwähnt, ist es empfehlenswert, die offizielle Dokumentation [GitHub-Contributing-to-a-Project](http://git-scm.com/book/en/v2/GitHub-Contributing-to-a-Project) von Git zu lesen.
+Eclipse hat ein nützliches Plugin für GIT: „Eclipse Mylyn to GitHub connector“.
+Stellen Sie sicher, dass Sie Ihre Daten eingestellt haben
+
+```
+git config --global user.name „Ihr Name“
+git config --global user.email „your@mail.com“
+```
+
+##### Dein erster Beitrag
+
+Erstelle einen beschreibenden Themenzweig
+
+```
+git checkout -b dev_beginning
+```
+
+Stellen Sie sicher, dass Ihr lokales Repository mit dem Hauptprojekt auf dem neuesten Stand ist. Fügen Sie das ursprüngliche Repository als Remote-Repository mit dem Namen „upstream“ hinzu, falls noch nicht geschehen
+
+```
+git remote add upstream https://framagit.org/hubzilla/core/
+```
+
+Holen Sie sich die neueste Arbeit von dieser Remote
+
+```
+git fetch upstream
+git merge upstream/master
+```
+
+Tipp: Sie können die Zweige auflisten
+
+```
+git branch -v
+```
+
+Nehmen Sie Ihre Änderungen vor. In diesem Beispiel ist es eine neue Doc-Datei.
+Prüfen Sie Ihre Änderungen
+
+```
+git status
+```
+
+Fügen Sie die neue Datei hinzu (stage)
+
+```
+git add doc/dev_beginner.bb
+```
+
+Übertragen Sie die Änderungen in Ihren lokalen Zweig. Dadurch wird ein Editor geöffnet, um eine Nachricht zu übermitteln.
+
+```
+git commit -a
+```
+
+Pushen Sie online zurück in den gleichen Themenzweig
+
+```
+git push
+```
+
+Nun können Sie zu Ihrem (Online-)Konto bei github gehen und den Pull Request erstellen.
+
+##### Folgende Beiträge
+
+Für den Fall, dass die Hauptentwickler wollen, dass du etwas änderst. Holen Sie sich die neueste Arbeit aus dem entfernten Upstream/Master, um sicher zu sein, dass Sie die neuesten Änderungen haben.
+
+```
+git fetch upstream
+git merge upstream/master
+```
+
+Nehmen Sie Ihre Änderungen vor, testen Sie sie, committen Sie (in das lokale Repository), pushen Sie (in das Online-Repository)
+
+```
+git status
+git commit -a -m „Änderung des Zweigs hinzugefügt“
+git push
+``` \ No newline at end of file
diff --git a/doc/de/developer/developers_guide.md b/doc/de/developer/developers_guide.md
new file mode 100644
index 000000000..898cb17c7
--- /dev/null
+++ b/doc/de/developer/developers_guide.md
@@ -0,0 +1,27 @@
+## Entwicklerhandbuch
+
+Informationen für Hubzilla-Entwickler
+
+#include doc/de/developer/who_is_a_hubzilla_developer.md;
+#include doc/de/developer/dev_beginner.md;
+#include doc/de/developer/versions.md;
+#include doc/de/developer/git_repository.md;
+#include doc/de/developer/git_for_non_developers.md;
+#include doc/de/developer/tools_workflows.md;
+#include doc/de/developer/doco.md;
+#include doc/de/developer/translations.md;
+#include doc/de/developer/licensing.md;
+#include doc/de/developer/coding_style.md;
+#include doc/de/developer/file_system_layour.md;
+#include doc/de/developer/dev-functions-overview.md;
+#include doc/de/developer/Plugins.md;
+#include doc/de/developer/testing.md;
+#include doc/de/developer/federate.md;
+#include doc/de/developer/code_of_conduct.md;
+#include doc/de/developer/nomad_protocol.md;
+#include doc/de/developer/technical_introductions.md;
+#include doc/de/developer/magic_auth.md;
+#include doc/de/developer/nomad_structures.md;
+#include doc/de/developer/API.md;
+#include doc/de/developer/hooks.md;
+#include doc/de/developer/unorganized.md;
diff --git a/doc/de/developer/doco.md b/doc/de/developer/doco.md
new file mode 100644
index 000000000..c62db2703
--- /dev/null
+++ b/doc/de/developer/doco.md
@@ -0,0 +1,29 @@
+### Dokumentation erstellen
+
+Um eine Dokumentation zu erstellen, müssen Sie nur ein paar Wörter in eine geschickte Reihenfolge bringen und ihre Existenz einem Entwickler bekannt geben. Sie können dies buchstäblich überall tun, solange ein Entwickler es sehen kann. Sobald Sie es bekannt gemacht haben, wird es jemand für Sie einchecken. Sie sollten versuchen, proprietäre Formate oder Orte, die eine Authentifizierung mit anderen Methoden als Nomad erfordern, zu vermeiden, um einem Entwickler den Zugang zu erleichtern, aber selbst das ist keine strikte Voraussetzung.
+Wenn Sie direkt beitragen wollen, ist das auch in Ordnung. Um direkt beizutragen, sollte die Dokumentation in einem der folgenden Formate vorliegen:
+
+- Markdown
+- BBCode
+- HTML
+- Einfacher Text
+- Andere Formate sind ebenfalls erlaubt, aber die Unterstützung für das Format muss zuerst zu mod/help.php hinzugefügt werden.
+
+Wenn Sie eine reine Textdatei bearbeiten, halten Sie bitte die Spaltenbreite auf 80. Dies liegt daran, dass reiner Text in Fällen verwendet wird, in denen wir keine funktionierende Installation haben - zum Beispiel die Installationsdokumentation - und es sollte einfach sein, diese mit einem CLI-Texteditor zu lesen.
+Der Vorteil von Markdown ist, dass es für Menschen lesbar ist.
+
+Der Vorteil von BBCode ist, dass es identitätsbewusst ist.
+
+Wenn Sie also BBCode verwenden, versuchen Sie, das Beste daraus zu machen:
+
+- Verwenden Sie gegebenenfalls ZRL-Links, um sicherzustellen, dass ein Link zu einer anderen Website die Authentifizierung beibehält und die identitätsbasierte Dokumentation funktioniert
+- Verwenden Sie baseurl- oder observer.baseurl-Tags, wo es angebracht ist, anstelle von example.com für authentifizierte Betrachter.
+- Unterstützen Sie nicht-authentifizierte Benutzer mit observer=0-Tags. Aufgrund historischer Versäumnisse tun wir dies derzeit nicht. Dies muss überall hinzugefügt werden.
+
+**Übersetzungen**
+Um die Dokumentation zu übersetzen oder in anderen Sprachen als Englisch bereitzustellen:
+
+- Erstellen Sie ein Verzeichnis in doc/ mit Ihrem zweistelligen Ländercode, falls es noch nicht existiert (z.B. doc/de/ für Deutsch oder doc/fr/ für Französisch)
+- Erstellen Sie ein Dokument mit demselben Dateinamen wie die englische Version, aber mit dem Inhalt in Ihrer eigenen Sprache. So können wir auf die englische Version zurückgreifen, wenn die Übersetzung für eine bestimmte Seite nicht vorhanden ist.
+
+Um eine Dokumentation zu erstellen, für die es keine gleichwertige Datei auf Englisch gibt, können Sie eine neue Datei mit einem Namen Ihrer Wahl erstellen - allerdings müssen Sie auch eine lokalisierte Version der Indexseite (main.bb auf Englisch) bereitstellen, damit sie über das Menü zugänglich ist. \ No newline at end of file
diff --git a/doc/de/developer/enforcement.md b/doc/de/developer/enforcement.md
new file mode 100644
index 000000000..0878fed3f
--- /dev/null
+++ b/doc/de/developer/enforcement.md
@@ -0,0 +1,5 @@
+#### Durchsetzung
+
+Fälle von beleidigendem, belästigendem oder anderweitig inakzeptablem Verhalten können beim Projektteam unter project@hubzilla.org gemeldet werden. Alle Beschwerden werden geprüft und untersucht und führen zu einer Reaktion, die als notwendig und den Umständen angemessen erachtet wird. Das Projektteam ist zur Vertraulichkeit gegenüber der Person, die einen Vorfall meldet, verpflichtet. Weitere Einzelheiten zu spezifischen Durchsetzungsrichtlinien können separat veröffentlicht werden.
+
+Projektbetreuer, die den Verhaltenskodex nicht in gutem Glauben befolgen oder durchsetzen, können mit vorübergehenden oder dauerhaften Konsequenzen rechnen, die von anderen Mitgliedern der Projektleitung festgelegt werden. \ No newline at end of file
diff --git a/doc/de/developer/federate.md b/doc/de/developer/federate.md
new file mode 100644
index 000000000..6b07dca15
--- /dev/null
+++ b/doc/de/developer/federate.md
@@ -0,0 +1,56 @@
+### Erstellen von Protokoll- Föderationsdiensten
+
+Es gibt drei Hauptkomponenten beim Schreiben von Föderations-Plugins. Diese sind:
+
+1. Kanalerkennung und Verbindungsaufbau
+2. Senden von Beiträgen/Nachrichten
+3. Empfangen von Beiträgen/Nachrichten
+
+Darüber hinaus müssen Verbundtreiber Folgendes bewältigen
+
+4. Unterschiede in den Datenschutzrichtlinien (und Inhaltsformaten)
+
+#### Herstellen von Verbindungen
+
+Die Kernanwendung bietet eine Kanalerkennung in der folgenden Reihenfolge:
+
+1. Nomad-Kanal-Erkennung
+2. Webfinger (channel@hub) Lookup 2.1 RFC7033 Webfinger 2.2 XRD basierter Webfinger (alter Stil)
+3. URL-Ermittlung (derzeit nur zur Ermittlung von RSS-Feeds verwendet)
+4. Wenn alle diese Methoden fehlschlagen, ist das Netzwerk „unbekannt“ und wir sind nicht in der Lage, mit dem Kanal zu kommunizieren oder eine Verbindung zu ihm herzustellen. Ein „xchan“-Datensatz *kann* dennoch erstellt werden, **wenn** genügend Informationen bekannt sind, um einen eindeutigen Kanal zu identifizieren.
+
+Jeder der Lookup-Dienste kann an ein Plugin gebunden und erweitert werden. Wenn ein Kanal entdeckt wird, erstellen wir einen „xchan“-Datensatz, der eine plattformneutrale Darstellung der Kanalidentität ist. Wenn wir Informationen an den Kanal senden müssen, ist in der Regel auch ein „hubloc“-Datensatz (Standort des Hubs) erforderlich. Ein 'follow'-Plugin-Hook wird bereitgestellt, um Webfinger und diese Erkennungssequenz vollständig zu umgehen.
+
+Der letzte Schritt, um dies alles zusammenzufügen, ist die Erstellung eines „abook“-Datensatzes, der einen xchan in einer Beziehung zu einem lokalen Kanal mit einem bestimmten Satz von Berechtigungen anlegt.
+Bei Netzwerken, die keine nomadische Identität unterstützen, muss Ihr Plugin auch „abook_instance“ setzen, eine durch Kommata getrennte Liste von lokalen URLs, mit denen der entfernte Kanal verbunden ist. Wenn Ihr Mitglied zum Beispiel mit meinem Channel-Klon unter [https://example.com](https://example.com/) verbunden war, würde die abook_instance für diese Verbindung['https://example.com](https://example.com/)' lauten. Wenn Sie sich auch mit meinem Klon unter [https://abc.example.com](https://abc.example.com/) verbunden haben, würde die Zeichenfolge in[„https://example.com,https://abc.example.com](https://example.com,https//abc.example.com)“ geändert werden. Dies ermöglicht es den lokalen Kanälen zu unterscheiden, mit welcher Instanz ein bestimmter entfernter Kanal verbunden ist und vermeidet Zustellungsfehler an diese Kanäle von anderen Kloninstanzen.
+
+Ein Föderations-Plugin für ein Webfinger-basiertes System muss nur die Webfinger- oder XRD-Ergebnisse untersuchen und einen Protokoll-Stack identifizieren, der für die Verbindung oder Kommunikation verwendet werden kann. Dann erstellt es einen xchan-Datensatz mit dem angegebenen 'xchan_network'-Typ und einen hubloc-Datensatz mit demselben 'hubloc_network' mit den angegebenen Informationen. Derzeit muss das Plugin den gesamten Datensatz erstellen, aber in Zukunft wird dies erweitert, so dass das Plugin nur einen Netzwerknamen identifizieren muss und der Datensatz mit allen anderen bekannten Werten gefüllt wird.
+
+Ein xchan-Datensatz ist immer erforderlich, um eine Verbindung herzustellen. Um eine Verbindung herzustellen, erstellen Sie einen abook-Eintrag mit den gewünschten Berechtigungen.
+Zusätzliche Informationen, die Ihr Plugin für die Kommunikation benötigt, können in der xconfig-Tabelle und/oder abconfig-Tabelle gespeichert werden, wenn es in den xchan- oder hubloc-Tabellen keine passende Tabellenspalte gibt.
+
+Wenn eine Verbindung hergestellt wird, rufen wir normalerweise den Notifier (include/notifier.php) auf, um eine Nachricht an den entfernten Kanal zu senden. Dies ist an den Hook 'permissions_create' gebunden. Ihr Plugin muss dies verarbeiten, um eine „Follow“- oder „Make Friends“-Nachricht an das andere Netzwerk zu senden.
+
+Anmerkungen: Die erste Stufe des Nomad lookup wird durch einen webfinger lookup ersetzt. Diese Arbeit ist in Arbeit. Ursprünglich war ein separater Lookup erforderlich, da Webfinger keine Nicht-SSL-Verbindungen zulässt. Um diese Einschränkung zu umgehen, werden wir Nomad-Lookups, die nicht auf SSL basieren (in der Regel Test- und Entwicklungsseiten), über den „alten“ XRD-basierten Webfinger anbieten.
+
+Die Kernanwendung wird versuchen, xchan-Einträge für Projekte zu erstellen, die als Mitglieder des „offenen Webs“ identifiziert wurden; derzeit Hubzilla, Friendica, Diaspora, GNU-Social und Pump.io. Dies geschieht, damit Kommentare zwischen den Projektseiten weitergegeben werden können und das Netzwerk korrekt identifiziert werden kann. Ein Föderations-Plugin ist erforderlich, um eine vollständige Verbindung mit anderen Netzwerken herzustellen, aber Kommentare können auch an Seiten weitergeleitet werden, auf denen ein solches Plugin nicht installiert ist, damit es keine unerklärlichen Löcher in den Unterhaltungen gibt.
+
+Die Kernanwendung muss auch die Möglichkeit bieten, Diaspora-Kommentare zu signieren, da diese ein spezielles Signierformat erfordern und vom Verfasser des Kommentars signiert werden müssen, unabhängig davon, ob dieser Kanal mit Diaspora föderiert ist. Der Eigentümer der Konversation kann mit Diaspora föderieren, so dass die Kommentare signiert werden müssen. Dies ist bedauerlich, aber notwendig.
+
+#### Versenden von Nachrichten
+
+Jedes Mal, wenn eine Nachricht gesendet wird (mit der einzigen Ausnahme der Verzeichniskommunikation), rufen wir den Notifier (include/notifier.php) auf und übergeben ihm die Art der Nachricht und im Allgemeinen einen Identifikator, um die Informationen, die gesendet werden, aus der Datenbank abzurufen (Artikel oder Konversationsdinge, private E-Mails, Aktualisierungen von Berechtigungen usw.).
+Der Notifier verfügt über mehrere Hooks, die von Plugins auf unterschiedliche Weise verwendet werden können, je nachdem, wie ihre Zustellschleife funktioniert. Für verschiedene Nachrichtentypen und komplexe Zustellungssituationen müssen Sie möglicherweise mehrere Hooks verwenden. Der „permissions_create“-Hook wurde bereits im ersten Abschnitt erwähnt. Es gibt auch eine „permissions_update“-Nachricht, wenn sich die Berechtigungen geändert haben und das andere Ende der Verbindung darüber informiert werden muss. Nur wenige Dienste werden dies bereitstellen oder handhaben (da ihre Berechtigungen statisch sind), aber es wird z. B. auch verwendet, um Nachrichten zur Aktualisierung von Profilen und Profilfotos zu senden, und Sie möchten dies vielleicht handhaben.
+Der nächste Plugin-Hook ist 'notifier_process'. Ihm wird ein Array übergeben, das den kompletten Status des Notifizierers enthält und einmal pro Notifiziereraufruf aufgerufen wird. Er enthält die vollständige Liste der Empfänger (mit jeweils gesetztem xchan_network).
+
+Es gibt auch 'notifier_hub', dem wie 'notifier_process' der komplette Status des Antragstellers übergeben wird, aber der Unterschied besteht darin, dass er für jede einzelne Hub- oder eindeutige URL-Zustellung aufgerufen wird und auf den Typ hubloc_network abgestimmt werden kann. Die Hub-Zustellung ist wesentlich effizienter als die Empfängerzustellung, eignet sich aber möglicherweise nicht für alle Protokollstacks.
+
+Ihr Plugin muss den Nachrichtenstatus und die Empfänger verstehen und das gesendete Element in das gewünschte Format übersetzen. Sie müssen auch den Datenschutz prüfen und die Kommunikation an alle außer den vorgesehenen Empfängern blockieren - *wenn* es sich um eine private Kommunikation handelt. Das Plugin wird an dieser Stelle die Nachricht oft in die Warteschlange stellen und die Warteschlangen-ID an den Melder zurückgeben.
+
+Es gibt bereits Warteschlangen-Handler für einfache gepostete Daten. Wenn Sie einen Warteschlangeneintrag vom Typ 'post' erstellen, öffnen wir eine HTTP-POST-Anfrage und senden die bereitgestellten Daten und bestätigen den Erfolg oder Misserfolg. Sie können andere Kommunikationsformen erstellen, indem Sie einen anderen outq_driver-Typ angeben und die Verarbeitung von Warteschlangenanfragen selbst übernehmen. Wenn Sie verschiedene Fehlerzustände oder etwas anderes als Erfolg/Misserfolg bestätigen möchten, ist eine Zustellungsmeldung verfügbar. Für erweiterte Zustellungsberichte ist ebenfalls ein eigener Warteschlangentyp erforderlich. Der Basistyp „post“ behandelt nur Erfolg (erfolgreiche Kommunikation mit der Gegenstelle) und Misserfolg.
+
+
+
+#### Empfang von Nachrichten
+
+Für den Empfang von Nachrichten aus dem entfernten Netz ist wahrscheinlich ein „Empfangs“-Endpunkt oder -Modul erforderlich, der bzw. das für Ihr Netzwerk-Kommunikationsprotokoll vorgesehen ist. Dabei handelt es sich um eine URL-Route, die Ihr Plugin möglicherweise mit dem Hook 'module_loaded' registrieren muss. Ihr Modul übernimmt dann die Verantwortung für den Import aller Daten, die an diesem Endpunkt ankommen, und die Übersetzung in das für dieses Projekt erforderliche Format sowie die Speicherung der resultierenden Daten. Die von uns verwendete Grundstruktur ist ein erweiterbares Activitystream-Element, jedoch mit leicht abweichenden Feldnamen und mehreren optionalen Feldern. Sie kann leicht auf einen Activitystream abgebildet werden. Zusätzliche Daten können in der Tabelle „iconfig“ gespeichert werden. item_store() und item_store_update() werden im Allgemeinen verwendet, um die Daten zu speichern und entsprechende Benachrichtigungen zu senden. Ähnliche Möglichkeiten gibt es für private Post und Profilinformationen. \ No newline at end of file
diff --git a/doc/de/developer/file_system_layout.md b/doc/de/developer/file_system_layout.md
new file mode 100644
index 000000000..ae62791ee
--- /dev/null
+++ b/doc/de/developer/file_system_layout.md
@@ -0,0 +1,20 @@
+### Dateisystem-Layout
+
+| Directory | Description |
+| ------------------------- | ------------------------------------------------------------ |
+| addon | optionale Addons/Plugins |
+| boot.php | Jeder Prozess verwendet dies, um die Anwendungsstruktur zu booten |
+| doc | Hilfedateien |
+| images | erforderliche Bilder |
+| include | Das „Modell“ in MVC - (Back-End-Funktionen), enthält auch PHP „Executables“ für die Hintergrundverarbeitung |
+| index.php | Der Front-End-Controller für den Webzugang |
+| install | Installations- und Upgrade-Dateien und DB-Schema |
+| library | Module von Drittanbietern (müssen lizenzkompatibel sein) |
+| mod | Steuerungsmodule basierend auf URL-Pfadnamen (z.B. http://sitename/foo lädt mod/foo.php) |
+| mod/site/ | Site-spezifische Mod-Overrides, die von Git ausgeschlossen sind |
+| util | Übersetzungstools, Hauptdatenbank für englische Zeichenketten und andere verschiedene Dienstprogramme |
+| version.inc | enthält die aktuelle Version (die automatisch über cron für das Haupt-Repository aktualisiert und über git verteilt wird) |
+| view | Themen- und Sprachdateien |
+| view/(css,js,img,php,tpl) | Standard-Theme-Dateien |
+| view/(en,it,es ...) | Sprachstrings und Ressourcen |
+| view/theme/ | Einzelne benannte Themen, die (css,js,img,php,tpl) Overrides enthalten | \ No newline at end of file
diff --git a/doc/de/developer/git_for_non_developers.md b/doc/de/developer/git_for_non_developers.md
new file mode 100644
index 000000000..3f053b891
--- /dev/null
+++ b/doc/de/developer/git_for_non_developers.md
@@ -0,0 +1,72 @@
+### Git für Nicht-Entwickler
+
+Sie kümmern sich um eine Übersetzung oder tragen zu einem Thema bei, und jedes Mal, wenn Sie eine Pull-Anfrage stellen, müssen Sie mit einem der Entwickler sprechen, bevor Ihre Änderungen eingefügt werden können?
+Wahrscheinlich haben Sie noch keine Kurzanleitung gefunden, die erklärt, wie Sie die Dinge auf Ihrer Seite synchronisieren können. Es ist wirklich sehr einfach.
+Nachdem Sie einen Fork des Repo erstellt haben (klicken Sie einfach auf „fork“ bei github), müssen Sie Ihre eigene Kopie klonen.
+Für das Beispiel nehmen wir an, dass du an einem Thema namens redexample arbeitest (das nicht existiert).
+
+```
+git clone https://github.com/username/red.git
+```
+
+Wechseln Sie anschließend in das Verzeichnis und fügen Sie einen Upstream hinzu.
+
+```
+cd rot
+git remote add upstream https://framagit.org/hubzilla/core/
+```
+
+Von nun an können Sie Änderungen im Upstream mit dem Befehl
+
+```
+git fetch upstream
+```
+
+Bevor Ihre Änderungen automatisch zusammengeführt werden können, müssen Sie oft Änderungen im Upstream zusammenführen.
+
+```
+git merge upstream/master
+```
+
+Sie sollten Upstream-Änderungen immer zusammenführen, bevor Sie sie veröffentlichen, und *Sie müssen* Upstream-Änderungen mit allen Pull-Requests zusammenführen, damit sie automatisch zusammengeführt werden können.
+In 99% der Fälle wird das alles gut gehen. Das einzige Mal, dass es nicht klappt, ist, wenn jemand anderes die gleichen Dateien wie Sie bearbeitet hat - und oft nur, wenn sie die gleichen Zeilen der gleichen Dateien bearbeitet haben. In diesem Fall wäre es ein guter Zeitpunkt, um Hilfe zu bitten, bis Sie den Umgang mit Ihren eigenen Konflikten beim Zusammenführen in den Griff bekommen haben.
+Dann müssen Sie nur noch Ihre Änderungen hinzufügen
+
+```
+git add view/theme/redexample/
+```
+
+Dadurch werden alle Dateien in view/theme/redexample und alle Unterverzeichnisse hinzugefügt. Wenn Ihre speziellen Dateien über den gesamten Code verteilt sind, sollten Sie eine nach der anderen hinzufügen. Versuchen Sie nicht, git add -a auszuführen, da dies alles hinzufügt, einschließlich temporärer Dateien (wir fangen diese meist, aber nicht immer, mit .gitignore ab) und lokaler Änderungen, die Sie haben, aber nicht übertragen wollten.
+Wenn Sie alle Dateien, die Sie geändert haben, hinzugefügt haben, müssen Sie sie übertragen.
+
+```
+git commit
+```
+
+Daraufhin öffnet sich ein Editor, in dem Sie die vorgenommenen Änderungen beschreiben können. Speichern Sie diese Datei und beenden Sie den Editor.
+Zum Schluss pushen Sie die Änderungen auf Ihr eigenes Git
+
+```
+git push
+```
+
+Und das war's, Ihr Projektarchiv ist auf dem neuesten Stand!
+Alles, was Sie jetzt noch tun müssen, ist die Pull-Anfrage zu erstellen. Es gibt zwei Möglichkeiten, dies zu tun.
+Der einfache Weg, wenn Sie Github benutzen, ist, einfach auf den grünen Button oben in Ihrer eigenen Kopie des Repositorys zu klicken, eine Beschreibung der Änderungen einzugeben und auf „create pull request“ zu klicken. Das Haupt-Repository, Themes und Addons haben alle ihren Hauptzweig bei Github, so dass diese Methode meistens verwendet werden kann.
+Die meisten Leute können hier aufhören.
+Einige Projekte in der erweiterten RedMatrix-Ökosphäre haben keine Github-Präsenz, um für diese eine Pull-Anfrage zu stellen, ist es etwas anders - Sie müssen Ihre Pull-Anfrage manuell erstellen. Glücklicherweise ist das nicht viel schwieriger.
+
+```
+git request-pull -p <start> <url>
+```
+
+Start ist der Name eines Commits, mit dem begonnen werden soll. Dieser muss stromaufwärts existieren. Normalerweise wollen Sie nur master.
+URL ist die URL *Ihres* Repos.
+Man kann auch `<end>` angeben. Die Voreinstellung ist HEAD.
+Beispiel:
+
+```
+git request-pull master https://example.com/project
+```
+
+Und senden Sie die Ausgabe einfach an den Projektbetreuer.
diff --git a/doc/de/developer/git_repository.md b/doc/de/developer/git_repository.md
new file mode 100644
index 000000000..653e2f401
--- /dev/null
+++ b/doc/de/developer/git_repository.md
@@ -0,0 +1,6 @@
+### Git-Repository-Zweige
+
+Es gibt zwei offizielle Zweige des Hubzilla-Git-Projektarchivs.
+
+- Die stabile Version wird im **Master-Zweig** gepflegt. Die letzte Übertragung in diesem Zweig gilt als geeignet für Produktions-Hubs.
+- Die experimentelle Entwicklung findet im **dev-Zweig** statt, der in den **master-Zweig** überführt wird, sobald er als getestet und stabil genug erachtet wird. \ No newline at end of file
diff --git a/doc/de/developer/hooks.md b/doc/de/developer/hooks.md
new file mode 100644
index 000000000..16b8672cf
--- /dev/null
+++ b/doc/de/developer/hooks.md
@@ -0,0 +1,698 @@
+### Hooks
+
+Hooks ermöglichen es Plugins/Addons, sich an vielen Stellen in den Code „einzuhaken“ und das Verhalten zu ändern oder anderweitig unabhängige Aktionen durchzuführen, wenn eine Aktivität stattfindet oder auf bestimmte Datenstrukturen zugegriffen wird. Es gibt viele Hooks, die es Ihnen ermöglichen, sich an fast jeder Stelle in die Software einzuklinken und etwas anderes zu tun als das, was standardmäßig vorgesehen ist. Diesen Hooks werden zwei Variablen übergeben. Die erste ist die App-Struktur, die Details über den gesamten Zustand der Seitenanforderung enthält, während wir die resultierende Seite aufbauen. Die zweite ist eindeutig für den spezifischen Hook, der aufgerufen wird, und liefert spezifische Details darüber, was in der Software zum Zeitpunkt des Aufrufs des Hooks passiert.
+
+[Erstellter Index aller Hooks und der Dateien, die sie aufrufen](/help/de/hooks)
+
+[module_mod_aftercontent](/help/de/hook/module_mod_aftercontent)
+Allgemeiner Hook für jedes Modul, ausgeführt nach mod_content(). Ersetzen Sie „module“ durch den Namen des Moduls, z. B. „photos_mod_aftercontent“.
+
+[module_mod_content](/help/de/hook/module_mod_content)
+Allgemeiner Hook für ein beliebiges Modul, wird vor mod_content() ausgeführt. Ersetzen Sie 'module' durch den Modulnamen, z. B. 'photos_mod_content'.
+
+[module_mod_init](/help/de/hook/module_mod_init)
+Allgemeiner Hook für ein beliebiges Modul, wird vor mod_init() ausgeführt. Ersetzen Sie 'module' durch den Modulnamen, z. B. 'photos_mod_init'.
+
+[module_mod_post](/help/de/hook/module_mod_post)
+Allgemeiner Hook für ein beliebiges Modul, der vor mod_post() ausgeführt wird. Ersetzen Sie 'module' durch den Namen des Moduls, z. B. 'photos_mod_post'.
+
+[about_hook](/help/de/hook/about_hook)
+Aufgerufen von der Seite siteinfo
+
+[accept_follow](/help/de/hook/accept_follow)
+Wird aufgerufen, wenn eine Verbindung akzeptiert wird (Freundschaftsanfrage)
+
+[account_downgrade](/help/de/hook/account_downgrade)
+Wird aufgerufen, wenn ein Konto abgelaufen ist, was auf eine mögliche Herabstufung auf die Serviceklasse „basic“ hinweist
+
+[Konto_Einstellungen](/help/de/hook/account_settings)
+Wird bei der Erstellung des Formulars für die Kontoeinstellungen aufgerufen
+
+[account_settings_post](/help/de/hook/account_settings_post)
+Wird bei der Buchung aus dem Kontoeinstellungsformular aufgerufen
+
+[tätigkeit_filter](/help/de/hook/activity_filter)
+Wird bei der Erstellung der Liste der Filter für die Netzwerkseite aufgerufen
+
+[activity_mapper](/help/de/hook/activity_filter)
+Wird bei der Bestimmung der Vorgangsart für die Übertragung aufgerufen.
+
+[activity_decode_mapper](/help/de/hook/activity_filter)
+Wird aufgerufen, wenn die Vorgangsart für die Übertragung bestimmt wird.
+
+[activity_obj_mapper](/help/de/hook/activity_filter)
+Wird aufgerufen, wenn der Objekttyp für die Übertragung bestimmt wird.
+
+[activity_obj_decode_mapper](/help/de/hook/activity_filter)
+Wird bei der Bestimmung des Objekttyps für die Übertragung aufgerufen.
+
+[activity_order](/help/de/hook/activity_order)
+Wird bei der Generierung der Liste der Bestelloptionen für die Netzseite aufgerufen
+
+[addon_app_installed_filter](/help/de/hook/addon_app_installed_filter)
+Wird aufgerufen, wenn festgestellt wird, ob eine addon_app installiert ist
+
+[activity_received](/help/de/hook/activity_received)
+Wird aufgerufen, wenn eine Aktivität (Beitrag, Kommentar, Like, etc.) von einer Nomad-Quelle empfangen wurde
+
+[admin_aside](/help/de/hook/admin_aside)
+Wird aufgerufen, wenn das Seitenleisten-Widget der Verwaltungsseite erzeugt wird
+
+[affinity_labels](/help/de/hook/affinity_labels)
+Wird verwendet, um alternative Beschriftungen für den Affinitätsslider zu generieren.
+
+[api_perm_is_allowed](/help/de/hook/api_perm_is_allowed)
+Wird aufgerufen, wenn perm_is_allowed() von einem API-Aufruf ausgeführt wird.
+
+[app_destroy](/help/de/hook/app_destroy)
+Wird aufgerufen, wenn eine App gelöscht wird.
+
+[app_installed_filter](/help/de/hook/app_installed_filter)
+Wird aufgerufen, wenn festgestellt wird, ob eine App installiert ist
+
+[app_menu](/help/de/hook/app_menu)
+Wird bei der Erstellung des app_menu Dropdowns aufgerufen (kann veraltet sein)
+
+[attach_delete](/help/de/hook/attach_delete)
+Wird aufgerufen, wenn Anhänge aus der Tabelle attach gelöscht werden
+
+[atom_author](/help/de/hook/atom_author)
+Wird aufgerufen, wenn ein Autor- oder Eigentümer-Element für einen Atom ActivityStream-Feed erzeugt wird
+
+[atom_entry](/help/de/hook/atom_entry)
+Wird bei der Erzeugung jedes Eintrags eines Atom ActivityStreams Feeds aufgerufen
+
+[atom_feed](/help/de/hook/atom_feed)
+Wird bei der Generierung eines Atom ActivityStreams Feeds aufgerufen
+
+[atom_feed_end](/help/de/hook/atom_feed_end)
+Wird aufgerufen, wenn die Erzeugung eines Atom ActivityStreams Feeds abgeschlossen ist
+
+[attach_upload_file](/help/de/hook/attach_upload_file)
+Wird beim Hochladen einer Datei aufgerufen
+
+[authentifizieren](/help/de/hook/authenticate)
+Kann alternative Authentifizierungsmechanismen bereitstellen
+
+[author_is_pmable](/help/de/hook/author_is_pmable)
+Wird aus dem Aktionsmenü des Threads aufgerufen, um festzustellen, ob wir dem Verfasser des Beitrags eine private E-Mail schicken können
+
+[bb2diaspora](/help/de/hook/bb2diaspora)
+Wird bei der Umwandlung von bbcode in Markdown aufgerufen
+
+[bbcode](/help/de/hook/bbcode)
+Wird am Ende der Konvertierung von bbcode in HTML aufgerufen
+
+[bbcode_filter](/help/de/hook/bbcode_filter)
+Wird zu Beginn der Umwandlung von bbcode in HTML aufgerufen
+
+[bb_translate_video](/help/de/hook/bb_translate_video)
+Wird aufgerufen, wenn eingebettete Dienste aus bbcode-Videoelementen extrahiert werden (wird selten verwendet)
+
+[build_pagehead](/help/de/hook/build_pagehead)
+Wird bei der Erstellung des HTML-Seitenkopfes aufgerufen
+
+[can_comment_on_post](/help/de/hook/can_comment_on_post)
+Wird aufgerufen, wenn entschieden wird, ob ein Kommentarfeld für einen Beitrag angezeigt werden soll oder nicht
+
+[change_channel](/help/de/hook/change_channel)
+Wird aufgerufen, wenn man sich bei einem Channel anmeldet (entweder während des Logins oder danach über den Channelmanager)
+
+[channel_remove](/help/de/hook/channel_remove)
+Wird aufgerufen, wenn ein Channel entfernt wird
+
+[channel_links](/help/de/hook/channel_links)
+Wird bei der Generierung des Link: HTTP-Header für einen Kanal
+
+[channel_settings](/help/de/hook/channel_settings)
+Wird aufgerufen, wenn die Seite mit den Channel-Einstellungen angezeigt wird
+
+[chat_message](/help/de/hook/chat_message)
+Wird aufgerufen, um eine Chat-Nachricht zu erstellen.
+
+[chat_post](/help/de/hook/chat_post)
+Wird aufgerufen, wenn eine Chat-Nachricht gepostet wurde.
+
+[check_account_email](/help/de/hook/check_account_email)
+Überprüft die bei einer Kontoregistrierung angegebene E-Mail
+
+[check_account_invite](/help/de/hook/check_account_invite)
+Validierung eines Einladungscodes bei der Verwendung von Website-Einladungen
+
+[check_account_password](/help/de/hook/check_account_password)
+Dient der Kontrolle von Kontopasswörtern (Mindestlänge, Einbeziehung von Zeichensätzen usw.)
+
+[check_channelallowed](/help/de/hook/check_channelallowed)
+Wird verwendet, um die Sperrlisten für schwarze und weiße Kanäle außer Kraft zu setzen oder zu umgehen.
+
+[check_siteallowed](/help/de/hook/check_siteallowed)
+Wird verwendet, um die schwarzen/weißen Sperrlisten für Websites außer Kraft zu setzen oder zu umgehen.
+
+[collect_public_recipients](/help/de/hook/collect_public_recipients)
+Wird verwendet, um eine Liste von Empfängern zu erstellen, an die eine öffentliche Nachricht gesendet werden soll.
+
+[comment_buttons](/help/de/hook/comment_buttons)
+Wird aufgerufen, wenn die Bearbeitungsschaltflächen für Kommentare angezeigt werden.
+
+[comments_are_now_closed](/help/de/hook/comments_are_now_closed)
+Wird aufgerufen, wenn entschieden wird, ob ein Kommentarfeld für einen Beitrag angezeigt werden soll oder nicht
+
+[connect_premium](/help/de/hook/connect_premium)
+Wird aufgerufen, wenn eine Verbindung zu einem Premium-Kanal hergestellt wird
+
+[connection_remove](/help/de/hook/connection_remove)
+Wird aufgerufen, wenn eine Verbindung gelöscht/entfernt wird
+
+[connector_settings](/help/de/hook/connector_settings)
+Wird aufgerufen, wenn die Seite mit den Features/Addon-Einstellungen aufgerufen wird
+
+[construct_page](/help/de/hook/construct_page)
+Allgemeiner Hook zur Bereitstellung von Inhalten für bestimmte Seitenbereiche. Wird aufgerufen, wenn die Comanche-Seite erstellt wird.
+
+[kontakt_block_ende](/help/de/hook/contact_block_end)
+Wird bei der Erstellung des „Connections“-Widgets in der Seitenleiste aufgerufen
+
+[kontakt_edit](/help/de/hook/contact_edit)
+Wird bei der Bearbeitung einer Verbindung über connedit aufgerufen
+
+[kontakt_edit_post](/help/de/hook/contact_edit_post)
+Wird aufgerufen, wenn ein Beitrag an connedit gesendet wird
+
+[Kontakt_Auswahl_Optionen](/help/de/hook/contact_select_options)
+Veraltet/unbenutzt
+
+[content_security_policy](/help/de/hook/content_security_policy)
+Wird vor der Ausgabe des Content-Security-Policy-Headers aufgerufen
+
+[conversation_start](/help/de/hook/conversation_start)
+Wird zu Beginn des Renderns einer Konversation (Nachricht oder Nachrichtensammlung oder Stream) aufgerufen
+
+[cover_photo_content_end](/help/de/hook/cover_photo_content_end)
+Wird aufgerufen, nachdem ein Titelbild hochgeladen wurde
+
+[create_identity](/help/de/hook/create_identity)
+Wird bei der Erstellung eines Channels aufgerufen
+
+[cron](/help/de/hook/cron)
+Wird aufgerufen, wenn eine geplante Aufgabe (Poller) ausgeführt wird
+
+[cron_daily](/help/de/hook/cron_daily)
+Wird aufgerufen, wenn täglich geplante Aufgaben ausgeführt werden
+
+[cron_weekly](/help/de/hook/cron_weekly)
+Wird aufgerufen, wenn wöchentlich geplante Aufgaben ausgeführt werden
+
+[crypto_methods](/help/de/hook/crypto_methods)
+Wird aufgerufen, wenn eine Liste von Kryptoalgorithmen in der lokal bevorzugten Reihenfolge erstellt wird
+
+[daemon_addon](/help/de/hook/daemon_addon)
+Wird aufgerufen, wenn der erweiterbare Hintergrund-Daemon aufgerufen wird
+
+[daemon_master_release](/help/de/hook/daemon_master_release)
+Wird zu Beginn der Verarbeitung von \Zotlabs\Daemon\Master::Release() aufgerufen
+
+[directory_item](/help/de/hook/directory_item)
+Wird beim Erzeugen einer Verzeichnisliste für die Anzeige aufgerufen
+
+[discover_channel_webfinger](/help/de/hook/discover_channel_webfinger)
+Wird aufgerufen, wenn ein Webfinger-Lookup durchgeführt wird
+
+[display_item](/help/de/hook/display_item)
+Wird für jedes Element aufgerufen, das in einem Gesprächsfaden angezeigt wird
+
+[display_settings](/help/de/hook/display_settings)
+Wird vom Einstellungsmodul aufgerufen, wenn der Abschnitt 'Anzeigeeinstellungen' angezeigt wird
+
+[display_settings_post](/help/de/hook/display_settings_post)
+Wird aufgerufen, wenn ein Beitrag aus dem Formular „Einstellungen anzeigen“ des Einstellungsmoduls angezeigt wird
+
+[donate_contributors](/help/de/hook/donate_contributors)
+Wird vom 'donate'-Addon aufgerufen, wenn eine Liste von Spendenempfängern erstellt wird
+
+[donate_plugin](/help/de/hook/donate_plugin)
+wird vom 'donate'-Addon aufgerufen
+
+[donate_sponsoren](/help/de/hook/donate_sponsors)
+aufgerufen durch das 'donate'-Addon
+
+[dreport_ist_storable](/help/de/hook/dreport_is_storable)
+wird vor dem Speichern eines Dreport-Datensatzes aufgerufen, um festzustellen, ob er gespeichert werden soll
+
+[dreport_process](/help/de/hook/dreport_process)
+wird für jeden gültigen Lieferbericht aufgerufen
+
+[dropdown_extras](/help/de/hook/dropdown_extras)
+Hinzufügen zusätzlicher Elemente zum Dropdown-Menü, wenn Element/Threads angezeigt werden.
+
+[drop_item](/help/de/hook/drop_item)
+wird aufgerufen, wenn ein 'item' entfernt wird
+
+[encode_object](/help/de/hook/encode_object)
+wird aufgerufen, wenn ein Objekt für die Übertragung kodiert wird.
+
+[enotify](/help/de/hook/enotify)
+wird vor jeder Benachrichtigung aufgerufen
+
+[enotify_mail](/help/de/hook/enotify_mail)
+wird aufgerufen, wenn eine Benachrichtigungs-E-Mail gesendet wird
+
+[enotify_store](/help/de/hook/enotify_store)
+wird beim Speichern eines Benachrichtigungsdatensatzes aufgerufen
+
+[enotify_store_end](/help/de/hook/enotify_store_end)
+wird aufgerufen, nachdem ein Benachrichtigungsdatensatz gespeichert wurde
+
+[event_created](/help/de/hook/event_created)
+wird aufgerufen, wenn ein Ereignisdatensatz erstellt wird
+
+[event_store_event](/help/de/hook/event_store_event)
+wird aufgerufen, wenn ein Ereignisdatensatz erstellt oder aktualisiert wird
+
+[event_updated](/help/de/hook/event_updated)
+wird aufgerufen, wenn ein Ereignisdatensatz geändert wird
+
+[externals_url_select](/help/de/hook/externals_url_select)
+wird aufgerufen, wenn eine Liste mit zufälligen Websites erstellt wird, von denen öffentliche Beiträge abgerufen werden sollen
+
+[feature_enabled](/help/de/hook/feature_enabled)
+wird aufgerufen, wenn 'feature_enabled()' verwendet wird
+
+[merkmal_settings](/help/de/hook/feature_settings)
+wird von der Einstellungsseite aufgerufen, wenn man 'addon/feature settings' besucht
+
+[feature_settings_post](/help/de/hook/feature_settings_post)
+wird von der Einstellungsseite aufgerufen, wenn von 'addon/feature settings' aus gepostet wird
+
+[fetch_and_store](/help/de/hook/fetch_and_store)
+wird aufgerufen, um das Filtern von 'entschlüsselten' Elementen vor der Speicherung zu ermöglichen.
+
+[file_thumbnail](/help/de/hook/file_thumbnail)
+wird aufgerufen, wenn Miniaturbilder für die Wolkenseite im Modus „Kacheln anzeigen“ erzeugt werden
+
+[folgen](/help/de/hook/follow)
+wird aufgerufen, wenn eine Follow-Operation stattfindet
+
+[follow_from_feed](/help/de/hook/follow_from_feed)
+wird aufgerufen, wenn eine Follow-Operation in einem RSS-Feed stattfindet
+
+[follow_allow](/help/de/hook/follow_allow)
+wird aufgerufen, bevor die Ergebnisse einer Follow-Operation gespeichert werden
+
+[gender_selector](/help/de/hook/gender_selector)
+wird bei der Erstellung der Dropdown-Liste „Geschlecht“ aufgerufen (erweitertes Profil)
+
+[gender_selector_min](/help/de/hook/gender_selector_min)
+wird bei der Erstellung der Dropdown-Liste „Geschlecht“ aufgerufen (normales Profil)
+
+[generate_map](/help/de/hook/generate_map)
+wird aufgerufen, um den HTML-Code für die Anzeige eines Orts auf der Karte nach Koordinaten zu erzeugen
+
+[generate_named_map](/help/de/hook/generate_named_map)
+wird aufgerufen, um die HTML-Datei für die Anzeige eines Kartenorts anhand eines Textes zu erzeugen
+
+[get_all_api_perms](/help/de/hook/get_all_api_perms)
+Wird aufgerufen, wenn die Berechtigungen für API-Verwendungen abgerufen werden
+
+[get_all_perms](/help/de/hook/get_all_perms)
+wird aufgerufen, wenn get_all_perms() verwendet wird
+
+[get_best_language](/help/de/hook/get_best_language)
+wird aufgerufen, wenn die bevorzugte Sprache für die Seite ausgewählt wird
+
+[get_default_export_sections](/help/de/hook/get_default_export_sections)
+Wird aufgerufen, um die Standardliste der zu exportierenden Funktionsdatengruppen in identity_basic_export() zu erhalten
+
+[get_features](/help/de/hook/get_features)
+Wird aufgerufen, wenn get_features() aufgerufen wird
+
+[get_photo](/help/de/hook/get_photo)
+Wird aufgerufen, wenn Fotoinhalte (außer Profilfotos) in mod_photo abgerufen werden
+
+[get_profile_photo](/help/de/hook/get_profile_photo)
+Wird aufgerufen, wenn der Inhalt des lokalen Profilfotos in mod_photo abgerufen wird
+
+[get_role_perms](/help/de/hook/get_role_perms)
+Wird aufgerufen, wenn get_role_perms() aufgerufen wird, um Berechtigungen für benannte Berechtigungsrollen zu erhalten
+
+[global_permissions](/help/de/hook/global_permissions)
+Wird aufgerufen, wenn die globale Berechtigungsliste erstellt wird
+
+[home_content](/help/de/hook/home_content)
+Wird von mod_home aufgerufen, um den Inhalt der Home-Seite zu ersetzen
+
+[home_init](/help/de/hook/home_init)
+Wird von der Funktion home_init() der Homepage aufgerufen
+
+[hostxrd](/help/de/hook/hostxrd)
+Wird bei der Erzeugung von .well-known/hosts-meta für „old webfinger“ aufgerufen (wird vom Diaspora-Protokoll verwendet)
+
+[html2bb_video](/help/de/hook/html2bb_video)
+Wird aufgerufen, wenn die html2bbcode-Übersetzung verwendet wird, um eingebettete Medien zu behandeln
+
+[html2bbcode](/help/de/hook/html2bbcode)
+Wird bei der Verwendung der html2bbcode-Übersetzung aufgerufen
+
+[identität_basic_export](/help/de/hook/identity_basic_export)
+Wird aufgerufen, wenn die Basisinformationen eines Channels zur Sicherung oder Übertragung exportiert werden.
+
+[import_autor_xchan](/help/de/hook/import_author_xchan)
+Wird aufgerufen, wenn ein Autor eines Beitrags mit xchan_hash gesucht wird, um sicherzustellen, dass er einen xchan-Eintrag auf unserer Website hat
+
+[import_channel](/help/de/hook/import_channel)
+Wird aufgerufen, wenn ein Kanal aus einer Datei oder einer API-Quelle importiert wird
+
+[import_directory_profile](/help/de/hook/import_directory_profile)
+Wird aufgerufen, wenn die Lieferung einer Profilstruktur aus einer externen Quelle verarbeitet wird (normalerweise für die Speicherung in Verzeichnissen)
+
+[import_xchan](/help/de/hook/import_xchan)
+Wird bei der Verarbeitung des Ergebnisses von zot_finger() aufgerufen, um das Ergebnis zu speichern
+
+[item_photo_menu](/help/de/hook/item_photo_menu)
+Wird aufgerufen, wenn die Liste der Aktionen erzeugt wird, die mit einem angezeigten Konversationselement verbunden sind
+
+[item_store](/help/de/hook/item_store)
+Wird aufgerufen, wenn item_store() einen Datensatz vom Typ item speichert
+
+[item_stored](/help/de/hook/item_stored)
+Wird aufgerufen, nachdem item_store() einen Datensatz des Typs item in der Datenbank gespeichert hat.
+
+[item_custom](/help/de/hook/item_custom)
+Wird aufgerufen, bevor item_store() einen Datensatz des Typs item speichert (damit Addons ITEM_TYPE_CUSTOM-Elemente verarbeiten können).
+
+[item_store_update](/help/de/hook/item_store_update)
+Wird aufgerufen, wenn item_store_update() aufgerufen wird, um einen gespeicherten Eintrag zu aktualisieren.
+
+[item_stored_update](/help/de/hook/item_stored_update)
+Wird aufgerufen, nachdem item_store_update() ein gespeichertes Element aktualisiert hat.
+
+[item_translate](/help/de/hook/item_translate)
+Wird von item_store und item_store_update aufgerufen, nachdem die Sprache des Beitrags automatisch erkannt wurde.
+
+[jot_networks](/help/de/hook/jot_networks)
+Wird aufgerufen, um die Liste der zusätzlichen Post-Plugins zu generieren, die aus dem ACL-Formular aktiviert werden sollen
+
+[jot_tool](/help/de/hook/jot_tool)
+Veraltet und möglicherweise überflüssig. Ermöglicht das Hinzufügen von Aktionsschaltflächen zum Beitragseditor.
+
+[jot_tpl_filter](/help/de/hook/jot_tpl_filter)
+Wird aufgerufen, um Vorlagenvariablen vor der Ersetzung in jot.tpl zu filtern.
+
+[jot_header_tpl_filter](/help/de/hook/jot_header_tpl_filter)
+Wird aufgerufen, um Vorlagenvariablen vor der Ersetzung in jot_header.tpl zu filtern.
+
+[legal_webbie](/help/de/hook/legal_webbie)
+Wird aufgerufen, um eine Kanaladresse zu validieren
+
+[legal_webbie_text](/help/de/hook/legal_webbie_text)
+Bietet eine Erklärung der Text-/Zeichenbeschränkungen für legal_webbie()
+
+[load_pdl](/help/de/hook/load_pdl)
+Wird aufgerufen, wenn wir eine PDL-Datei oder eine Beschreibung laden
+
+[local_dir_update](/help/de/hook/local_dir_update)
+Wird aufgerufen, wenn eine Verzeichnisaktualisierung von einem Channel auf dem Verzeichnisserver verarbeitet wird
+
+[location_move](/help/de/hook/location_move)
+Wird aufgerufen, wenn einem UNO-Channel ein neuer Standort mitgeteilt wurde (was auf eine Verschiebung und nicht auf einen Klon hinweist)
+
+[protokolliert](/help/de/hook/logged_in)
+Wird aufgerufen, wenn die Authentifizierung auf irgendeine Weise erfolgreich war
+
+[Logger](/help/de/hook/logger)
+Wird aufgerufen, wenn ein Eintrag in die Logdatei der Anwendung gemacht wird
+
+[logging_out](/help/de/hook/logging_out)
+Wird bei der Abmeldung aufgerufen
+
+[login_hook](/help/de/hook/login_hook)
+Wird bei der Generierung des Anmeldeformulars aufgerufen
+
+[magic_auth](/help/de/hook/magic_auth)
+Wird bei der Verarbeitung einer magic-auth-Sequenz aufgerufen
+
+[markdown_to_bb](/help/de/hook/markdown_to_bb)
+Wird bei der Verarbeitung der Markdown-Konvertierung aufgerufen
+
+[match_webfinger_location](/help/de/hook/match_webfinger_location)
+Wird bei der Verarbeitung von Webfinger-Anfragen aufgerufen
+
+[magic_auth_openid_success](/help/de/hook/magic_auth_openid_success)
+Wird aufgerufen, wenn ein magic-auth aufgrund von openid-Anmeldedaten erfolgreich war
+
+[magic_auth_success](/help/de/hook/magic_auth_success)
+Wird aufgerufen, wenn ein magic-auth erfolgreich war
+
+[main_slider](/help/de/hook/main_slider)
+Wird bei der Generierung des Affinitätswerkzeugs aufgerufen
+
+[marital_selector](/help/de/hook/marital_selector)
+Wird aufgerufen, wenn die Auswahlliste für das Dropdown-Menü des Profils „Familienstand“ erstellt wird (erweitertes Profil)
+
+[marital_selector_min](/help/de/hook/marital_selector_min)
+Wird bei der Erstellung der Auswahlliste für das Dropdown-Profil „Familienstand“ aufgerufen (normales Profil)
+
+[module_loaded](/help/de/hook/module_loaded)
+Wird aufgerufen, wenn ein Modul erfolgreich für eine URL-Anfrage auf dem Server lokalisiert wurde.
+
+[mood_verbs](/help/de/hook/mood_verbs)
+Wird bei der Erstellung der Liste der Stimmungen aufgerufen
+
+[nav](/help/de/hook/nav)
+Wird bei der Erstellung der Navigationsleiste aufgerufen
+
+[network_content_init](/help/de/hook/network_content_init)
+Wird beim Laden des Inhalts für die Netzwerkseite aufgerufen
+
+[netzwerk_ping](/help/de/hook/network_ping)
+Wird bei einer Ping-Anfrage aufgerufen
+
+[netzwerk_zu_name](/help/de/hook/network_to_name)
+Veraltet
+
+[notifier_end](/help/de/hook/notifier_end)
+Wird aufgerufen, wenn eine Zustellschleife abgeschlossen ist
+
+[notifier_hub](/help/de/hook/notifier_hub)
+Wird aufgerufen, wenn ein Hub zugestellt wurde
+
+[notifier_normal](/help/de/hook/notifier_normal)
+Wird aufgerufen, wenn der Notifizierer für eine 'normale' Zustellung aufgerufen wird
+
+[notifier_process](/help/de/hook/notifier_process)
+Wird aufgerufen, wenn der Notifizierende eine Nachricht/Ereignis verarbeitet
+
+[obj_verbs](/help/de/hook/obj_verbs)
+Wird bei der Erstellung der Liste der für das Profil „Dinge“ verfügbaren Verben aufgerufen.
+
+[oembed_action](/help/de/hook/oembed_action)
+Wird aufgerufen, wenn entschieden wird, ob eine Oembed-Url gefiltert, blockiert oder genehmigt werden soll
+
+[oembed_probe](/help/de/hook/oembed_probe)
+Wird aufgerufen, wenn eine Suche nach Oembed-Inhalten durchgeführt wird.
+
+[other_encapsulate](/help/de/hook/other_encapsulate)
+Wird aufgerufen, wenn Inhalte verschlüsselt werden, für die der Algorithmus unbekannt ist (siehe auch crypto_methods)
+
+[other_unencapsulate](/help/de/hook/other_unencapsulate)
+Wird aufgerufen, wenn Inhalte entschlüsselt werden, deren Algorithmus unbekannt ist (siehe auch crypto_methods)
+
+[page_content_top](/help/de/hook/page_content_top)
+Wird aufgerufen, wenn wir eine Webseite generieren (vor dem Aufruf der Modul-Content-Funktion)
+
+[page_end](/help/de/hook/page_end)
+Wird aufgerufen, nachdem wir den Seiteninhalt generiert haben
+
+[page_header](/help/de/hook/page_header)
+Wird bei der Generierung der Navigationsleiste aufgerufen
+
+[page_meta](/help/de/hook/page_header)
+Wird bei der Generierung der Metadaten im Seitenkopf aufgerufen.
+
+[parse_atom](/help/de/hook/parse_atom)
+Wird aufgerufen, wenn ein Atom/RSS-Feed-Element geparst wird.
+
+[parse_link](/help/de/hook/parse_link)
+Wird aufgerufen, wenn eine URL abgefragt wird, um daraus einen Beitrag zu generieren
+
+[pdl_selector](/help/de/hook/pdl_selector)
+Wird bei der Erstellung einer Layoutauswahl in einem Formular aufgerufen
+
+[perm_is_allowed](/help/de/hook/perm_is_allowed)
+Wird während perm_is_allowed() aufgerufen, um festzustellen, ob eine Berechtigung für diesen Kanal und Beobachter erlaubt ist
+
+[permissions_create](/help/de/hook/permissions_create)
+Wird aufgerufen, wenn ein Bucheintrag (Verbindung) erstellt wird
+
+[permissions_update](/help/de/hook/permissions_update)
+Wird aufgerufen, wenn eine Berechtigungsaktualisierung übertragen wird
+
+[permit_hook](/help/de/hook/permit_hook)
+Wird aufgerufen, bevor ein registrierter Hook tatsächlich ausgeführt wird, um festzustellen, ob er erlaubt oder blockiert werden soll
+
+[personal_xrd](/help/de/hook/personal_xrd)
+Wird bei der Generierung der persönlichen XRD für „old webfinger“ (Diaspora) aufgerufen
+
+[photo_post_end](/help/de/hook/photo_post_end)
+Wird nach dem Hochladen eines Fotos aufgerufen
+
+[photo_upload_begin](/help/de/hook/photo_upload_begin)
+Wird aufgerufen, wenn versucht wird, ein Foto hochzuladen
+
+[photo_upload_end](/help/de/hook/photo_upload_end)
+Wird aufgerufen, wenn ein Foto-Upload verarbeitet wurde
+
+[photo_upload_file](/help/de/hook/photo_upload_file)
+Wird aufgerufen, um alternative Dateinamen für einen Upload zu generieren
+
+[photo_upload_form](/help/de/hook/photo_upload_form)
+Wird aufgerufen, wenn ein Foto-Upload-Formular generiert wird
+
+[photo_view_filter](/help/de/hook/photo_view_filter)
+Wird aufgerufen, bevor die Daten an die photo_view-Vorlage übergeben werden
+
+[poke_verbs](/help/de/hook/poke_verbs)
+Wird bei der Erstellung der Liste der Aktionen für das Modul „poke“ aufgerufen
+
+[post_local](/help/de/hook/post_local)
+Wird aufgerufen, wenn ein Artikel auf diesem Rechner über mod/item.php eingestellt wurde (auch über API)
+
+[post_local_end](/help/de/hook/post_local_end)
+Wird aufgerufen, wenn ein lokaler Postvorgang abgeschlossen ist
+
+[post_local_start](/help/de/hook/post_local_start)
+Wird aufgerufen, wenn ein lokaler Postvorgang beginnt
+
+[post_mail](/help/de/hook/post_mail)
+Wird aufgerufen, wenn eine Mail-Nachricht verfasst wurde
+
+[post_mail_end](/help/de/hook/post_mail_end)
+Wird aufgerufen, wenn eine Mail-Nachricht zugestellt wurde
+
+[post_remote](/help/de/hook/post_remote)
+Wird aufgerufen, wenn eine Aktivität von einem anderen Standort eintrifft
+
+[post_remote_end](/help/de/hook/post_remote_end)
+Wird nach der Verarbeitung einer Remote-Post aufgerufen
+
+[post_remote_update](/help/de/hook/post_remote_update)
+Wird bei der Verarbeitung eines entfernten Beitrags aufgerufen, der eine Bearbeitung oder Aktualisierung beinhaltet
+
+[post_remote_update_end](/help/de/hook/post_remote_update_end)
+Wird nach der Verarbeitung eines entfernten Beitrags aufgerufen, der eine Bearbeitung oder Aktualisierung beinhaltete
+
+[prepare_body](/help/de/hook/prepare_body)
+Wird aufgerufen, wenn der HTML-Code für ein angezeigtes Konversationsobjekt generiert wird.
+
+[prepare_body_final](/help/de/hook/prepare_body_final)
+Wird nach der Generierung des HTML für ein angezeigtes Konversationselement aufgerufen
+
+[prepare_body_init](/help/de/hook/prepare_body_init)
+Wird vor der Generierung des HTML für ein angezeigtes Konversationselement aufgerufen
+
+[privacygroup_extras](/help/de/hook/privacygroup_extras)
+Wird vor der Generierung des HTML für die Bearbeitungsoptionen der Privacy Group aufgerufen
+
+[privacygroup_extras_delete](/help/de/hook/privacygroup_extras_delete)
+Wird aufgerufen, nachdem die Privatsphärengruppe gelöscht wurde.
+
+[privacygroup_extras_post](/help/de/hook/privacygroup_extras_post)
+Wird aufgerufen, wenn das Formular zur Bearbeitung der Privatsphärengruppe abgeschickt wird.
+
+[proc_run](/help/de/hook/proc_run)
+Wird beim Aufruf von PHP-Unterprozessen aufgerufen
+
+[process_channel_sync_delivery](/help/de/hook/process_channel_sync_delivery)
+Wird aufgerufen, wenn ein 'Sync-Paket' mit Struktur- und Tabellenaktualisierungen von einem Channel-Clone angenommen wird.
+
+[profile_advanced](/help/de/hook/profile_advanced)
+Wird bei der Generierung einer erweiterten Profilseite aufgerufen
+
+[profil_edit](/help/de/hook/profile_edit)
+Wird bei der Bearbeitung eines Profils aufgerufen
+
+[profil_foto_inhalt_ende](/help/de/hook/profile_photo_content_end)
+Wird bei der Änderung eines Profilfotos aufgerufen
+
+[profil_post](/help/de/hook/profile_post)
+Wird beim Posten eines bearbeiteten Profils aufgerufen
+
+[profile_sidebar](/help/de/hook/profile_sidebar)
+Wird aufgerufen, wenn die „Kanal-Seitenleiste“ oder das Miniprofil erstellt wird
+
+[profile_sidebar_enter](/help/de/hook/profile_sidebar_enter)
+Wird vor der Erstellung der 'Channel Sidebar' oder des Miniprofils aufgerufen
+
+[queue_deliver](/help/de/hook/queue_deliver)
+Wird aufgerufen, wenn eine Nachricht in der Warteschlange zugestellt wird
+
+[register_account](/help/de/hook/register_account)
+Wird aufgerufen, wenn ein Konto erstellt worden ist
+
+[render_location](/help/de/hook/render_location)
+Wird aufgerufen, um eine ineraktive Inline-Map zu erzeugen
+
+[replace_macros](/help/de/hook/replace_macros)
+Wird vor dem Aufrufen des Vorlagenprozessors aufgerufen
+
+[reverse_magic_auth](/help/de/hook/reverse_magic_auth)
+Wird vor dem Aufruf von reverse magic auth aufgerufen, um Sie auf Ihre eigene Website zu schicken, damit Sie sich auf dieser Website authentifizieren können
+
+[Einstellungen_Konto](/help/de/hook/settings_account)
+Wird bei der Erstellung des Formulars für die Kontoeinstellungen aufgerufen
+
+[einstellungen_form](/help/de/hook/settings_form)
+Wird bei der Erstellung des Formulars für die Channel-Einstellungen aufgerufen
+
+[einstellungen_post](/help/de/hook/settings_post)
+Wird bei der Buchung aus dem Formular für die Kanaleinstellungen aufgerufen
+
+[sexpref_selector](/help/de/hook/sexpref_selector)
+Wird aufgerufen, wenn ein Dropdown-Menü für sexuelle Präferenzen erstellt wird (erweitertes Profil)
+
+[sexpref_selector_min](/help/de/hook/sexpref_selector_min)
+Wird aufgerufen, wenn eine Auswahlliste der sexuellen Präferenzen erstellt wird (normales Profil)
+
+[smilie](/help/de/hook/smilie)
+Wird beim Übersetzen von Emoticons aufgerufen
+
+[status_editor](/help/de/hook/status_editor)
+Wird bei der Erstellung des status_editor aufgerufen.
+
+[stream_item](/help/de/hook/stream_item)
+Wird für jedes Element aufgerufen, das über conversation() zur Anzeige gerendert wird
+
+[system_app_installed_filter](/help/de/hook/system_app_installed_filter)
+Wird aufgerufen, wenn festgestellt wird, ob eine System-App installiert ist.
+
+[tagged](/help/de/hook/tagged)
+Wird aufgerufen, wenn eine Lieferung verarbeitet wird, die dazu führt, dass Sie getaggt werden
+
+[thumbnail](/help/de/hook/thumbnail)
+Wird beim Erzeugen von Miniaturbildern für die Kachelansicht des Cloud-Speichers aufgerufen
+
+[update_unseen](/help/de/hook/update_unseen)
+Wird vor dem automatischen Markieren von im Browser geladenen Sendungen, die gesehen wurden, aufgerufen
+
+[validate_channelname](/help/de/hook/validate_channelname)
+Wird verwendet, um die von einem Kanal verwendeten Namen zu validieren
+
+[webfinger](/help/de/hook/webfinger)
+Wird beim Besuch des Dienstes webfinger (RFC7033) aufgerufen
+
+[well_known](/help/de/hook/well_known)
+Wird beim Zugriff auf die speziellen '.well-known'-Site-Adressen aufgerufen
+
+[wiki_preprocess](/help/de/hook/wiki_preprocess)
+Wird aufgerufen, bevor Markdown-/Bbcode-Prozessoren für Wikiseiten ausgeführt werden
+
+[zot_best_algorithm](/help/de/hook/zot_best_algorithm)
+Wird bei der Aushandlung von Verschlüsselungsalgorithmen mit entfernten Sites aufgerufen
+
+[zid](/help/de/hook/zid)
+Wird aufgerufen, wenn die zid des Beobachters zu einer URL hinzugefügt wird
+
+[zid_init](/help/de/hook/zid_init)
+Wird bei der Authentifizierung eines Besuchers aufgerufen, der zid verwendet hat
+
+[zot_finger](/help/de/hook/zot_finger)
+Wird aufgerufen, wenn ein Nomad-Infopaket angefordert wurde (dies ist unser Webfinger-Erkennungsmechanismus)
diff --git a/doc/de/developer/licensing.md b/doc/de/developer/licensing.md
new file mode 100644
index 000000000..b7ff16748
--- /dev/null
+++ b/doc/de/developer/licensing.md
@@ -0,0 +1,3 @@
+### Lizenzvergabe
+
+Der gesamte zum Projekt beigetragene Code unterliegt der MIT-Lizenz, sofern nicht anders angegeben. Wir akzeptieren Code von Drittanbietern, der unter MIT, BSD und LGPL fällt, aber Copyleft-Lizenzen (GPL und AGPL) sind nur in Addons erlaubt. Es muss möglich sein, den GPL-(Copyleft-)Code vollständig aus dem Hauptprojekt zu entfernen, ohne etwas zu zerstören. \ No newline at end of file
diff --git a/doc/de/developer/magic_auth.md b/doc/de/developer/magic_auth.md
new file mode 100644
index 000000000..0625fb983
--- /dev/null
+++ b/doc/de/developer/magic_auth.md
@@ -0,0 +1,49 @@
+### Magic Auth
+
+Der so genannte „magic auth“ erfolgt durch einen speziellen Austausch. Auf dem entfernten Computer wird eine Umleitung zum Nomad-Endpunkt mit speziellen GET-Parametern vorgenommen.
+
+Endpunkt: [https://example.com/post/name](https://example.com/post/name?f=&zid=pepecyb@hub.hubzilla.hu)
+
+wobei 'name' die linke Seite des Kanal-Webbies ist, zum Beispiel 'mike', wenn der Webbie 'mike@zothub.com' ist.
+
+Zusätzlich werden vier Parameter übergeben:
+
+- auth => die Webbie der Person, die den Zugang beantragt
+- dest => die gewünschte Ziel-URL (urlencodiert)
+- sec => eine zufällige Zeichenkette, die auch lokal gespeichert wird, um sie während der Verifizierungsphase zu verwenden.
+- version => die Zot-Revision
+
+Wenn dieses Paket empfangen wird, wird eine Nomad-Nachricht an die auth-Identität gesendet:
+
+```
+ {
+ "type":"auth_check",
+ "sender":{
+ "guid":"kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA",
+ "guid_sig":"PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK-R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q",
+ "url":"http:\/\/podunk.edu",
+ "url_sig":"T8Bp7j5DHHhQDCFcAHXfuhUfGk2P3inPbImwaXXF1xJd3TGgluoXyyKDx6WDm07x0hqbupoAoZB1qBP3_WfvWiJVAK4N1FD77EOYttUEHZ7L43xy5PCpojJQmkppGbPJc2jnTIc_F1vvGvw5fv8gBWZvPqTdb6LWF6FLrzwesZpi7j2rsioZ3wyUkqb5TDZaNNeWQrIEYXrEnWkRI_qTSOzx0dRTsGO6SpU1fPWuOOYMZG8Nh18nay0kLpxReuHCiCdxjXRVvk5k9rkcMbDBJcBovhiSioPKv_yJxcZVBATw3z3TTE95kGi4wxCEenxwhSpvouwa5b0hT7NS4Ay70QaxoKiLb3ZjhZaUUn4igCyZM0h6fllR5I6J_sAQxiMYD0v5ouIlb0u8YVMni93j3zlqMWdDUZ4WgTI7NNbo8ug9NQDHd92TPmSE1TytPTgya3tsFMzwyq0LZ0b-g-zSXWIES__jKQ7vAtIs9EwlPxqJXEDDniZ2AJ6biXRYgE2Kd6W_nmI7w31igwQTms3ecXe5ENI3ckEPUAq__llNnND7mxp5ZrdXzd5HHU9slXwDShYcW3yDeQLEwAVomTGSFpBrCX8W77n9hF3JClkWaeS4QcZ3xUtsSS81yLrp__ifFfQqx9_Be89WVyIOoF4oydr08EkZ8zwlAsbZLG7eLXY"
+ },
+ "recipients":{
+ {
+ "guid":"ZHSqb3yGar3TYV_o9S-JkD-6o_V4DhUcxtv0VeyX8Kj_ENHPI_M3SyAUucU835-mIayGMmTpqJz3ujPkcavVhA",
+ "guid_sig":"JsAAXigNghTkkbq8beGMJjj9LBKZn28hZ-pHSsoQuwYWvBJ2lSnfc4r9l--WgO6sucH-SR6RiBo78eWn1cZrh_cRMu3x3LLx4y-tjixg-oOOgtZakkBg4vmOhkKPkci0mFtzvUrpY4YHySqsWTuPwRx_vOlIYIGEY5bRXpnkNCoC8J4EJnRucDrgSARJvA8QQeQQL0H4mWEdGL7wlsZp_2VTC6nEMQ48Piu6Czu5ThvLggGPDbr7PEMUD2cZ0jE4SeaC040REYASq8IdXIEDMm6btSlGPuskNh3cD0AGzH2dMciFtWSjmMVuxBU59U1I6gHwcxYEV6BubWt_jQSfmA3EBiPhKLyu02cBMMiOvYIdJ3xmpGoMY1Cn__vhHnx_vEofFOIErb6nRzbD-pY49C28AOdBA5ffzLW3ss99d0A-6GxZmjsyYhgJu4tFUAa7JUl84tMbq28Tib0HW6qYo6QWw8K1HffxcTpHtwSL5Ifx0PAoGMJsGDZDD1y_r9a4vH5pjqmGrjL3RXJJUy-m4eLV5r7xMWXsxjqu3D8r04_dcw4hwwexpMT1Nwf8CTB0TKb8ElgeOpDFjYVgrqMYWP0XdhatcFtAJI7gsS-JtzsIwON9Kij66-VAkqy_z1IXI0ziyqV1yapSVYoUV1vMScRZ_nMqwiB5rEDx-XLfzko"
+ }
+ }
+ "callback":"\/post",
+ "version":1,
+ "secret":"1eaa6613699be6ebb2adcefa5379c61a3678aa0df89025470fac871431b70467",
+ "secret_sig":"eKV968b1sDkOVdSMi0tLRtOhQw4otA8yFKaVg6cA4I46_zlAQGbFptS-ODiZlSAqR7RiiZQv4E2uXCKar52dHo0vvNKbpOp_ezWYcwKRu1shvAlYytsflH5acnDWL-FKOOgz5zqLLZ6cKXFMoR1VJGG_Od-DKjSwajyV9uVzTry58Hz_w0W2pjxwQ-Xv11rab5R2O4kKSW77YzPG2R5E6Q7HN38FrLtyWD_ai3K9wlsFOpwdYC064dk66X7BZtcIbKtM6zKwMywcfJzvS5_0U5yc5GGbIY_lY6SViSfx9shOKyxkEKHfS29Ynk9ATYGnwO-jnlMqkJC7t149H-sI9hYWMkLuCzaeLP56k2B2TmtnYvE_vHNQjzVhTwuHCIRVr-p6nplQn_P3SkOpYqPi3k_tnnOLa2d3Wtga8ClEY90oLWFJC3j2UkBf_VEOBNcg-t5XO3T-j9O4Sbk96k1Qoalc-QlznbGx4bOVsGkRBBMiH4YUqiiWB_OkFHtdqv7dqGeC-u-B4u9IxzYst46vvmyA3O-Q4APSZ1RY8ITUH0jLTbh6EAV7Oki8pIbOg0t56p-8RlanOZqmFvR-grVSc7Ak1ZcD8NACmvidUpa1B7WEvRcOeffx9lype0bt5XenDnMyx6szevwxZIiM8qGM2lsSk4fu8HI9cW0mLywzZT0"
+ }
+```
+
+auth_check-Nachrichten MÜSSEN verschlüsselt werden. Diese Nachricht wird an die Herkunftsseite gesendet, die prüft, ob das „secret“ mit dem „sec“ übereinstimmt, das sie ursprünglich übermittelt hat. Sie prüft auch secret_sig, das mit dem privaten Schlüssel des Zielkanals signiert und mit base64url kodiert ist. Wenn alles in Ordnung ist, wird ein json-Paket zurückgegeben:
+
+```
+ {
+ "success":1,
+ "confirm":"q0Ysovd1uQRsur2xG9Tg6bC23ynzw0191SkVd7CJcYoaePy6e_v0vnmPg2xBUtIaHpx_aSuhgAkd3aVjPeaVBmts6aakT6a_yAEy7l2rBydntu2tvrHhoVqRNOmw0Q1tI6hwobk1BgK9Pm0lwOeAo8Q98BqIJxf47yO9pATa0wktOg6a7LMogC2zkkhwOV5oEqjJfeHeo27TiHr1e2WaphfCusjmk27V_FAYTzw05HvW4SPCx55EeeTJYIwDfQwjLfP4aKV-I8HQCINt-2yxJvzH7Izy9AW-7rYU0Il_gW5hrhIS5MTM12GBXLVs2Ij1CCLXIs4cO0x6e8KEIKwIjf7iAu60JPmnb_fx4QgBlF2HLw9vXMwZokor8yktESoGl1nvf5VV5GHWSIKAur3KPS2Tb0ekNh-tIk9u-xob4d9eIf6tge_d3aq1LcAtrDBDLk8AD0bho5zrVuTmZ9k-lBVPr_DRHSV_dlpu088j3ThaBsuV1olHK3vLFRhYCDIO0CqqK5IuhqtRNnRaqhlNN6fQUHpXk2SwHiJ2W36RCYMTnno6ezFk_tN-RA2ly-FomNZoC5FPA9gFwoJR7ZmVFDmUeK3bW-zYTA5vu15lpBPnt7Up_5rZKkr0WQVbhWJmylqOuwuNWbn3SrMQ8rYFZ23Tv300cOfKVgRBaePWQb4"
+ }
+```
+
+"confirm“ ist in diesem Fall die base64url-kodierte RSA-Signatur der Verkettung von ‚secret‘ mit dem base64url-kodierten Whirlpool-Hash der Quellguid und guid_sig; signiert mit dem privaten Schlüssel des Quellkanals. Dies verhindert, dass ein Manin-the-Middle ein betrügerisches Erfolgspaket einfügt. Nach Empfang und erfolgreicher Überprüfung dieses Pakets wird die Zielseite zur ursprünglichen Ziel-URL umgeleitet und zeigt eine erfolgreiche Fernanmeldung an. \ No newline at end of file
diff --git a/doc/de/developer/nomad_protocol.md b/doc/de/developer/nomad_protocol.md
new file mode 100644
index 000000000..67252eddf
--- /dev/null
+++ b/doc/de/developer/nomad_protocol.md
@@ -0,0 +1,47 @@
+### Das Nomad Protokoll
+
+#### Was ist Nomad?
+
+Nomad ist das revolutionäre Protokoll, das Hubzilla antreibt und die **Kommunikation**, das **Identitätsmanagement** und die **Zugangskontrolle** in einem vollständig **dezentralisierten** Netzwerk unabhängiger Websites, oft als „das Grid“ bezeichnet, ermöglicht. Die daraus resultierende Plattform ist ein robustes System, das den Datenschutz und die Sicherheit unterstützt und gleichzeitig die Art von umfangreichen Webdiensten ermöglicht, die normalerweise nur in zentralisierten, proprietären Lösungen zu finden sind.
+
+Betrachten Sie dieses typische Szenario:
+
+Jaquelina möchte Fotos von ihrem Blog unter **jaquelina.org** mit Roberto teilen, aber mit niemandem sonst. Roberto unterhält seinen eigenen Familien-Hub unter **roberto.net** auf einem völlig unabhängigen Server. Nomad erlaubt Jaquelina, ihre Fotos mit einer *Zugriffskontrollliste (ACL)* zu veröffentlichen, die nur Roberto einschließt. Das bedeutet, dass Roberto die Fotos zwar sehen kann, wenn er ihren Blog besucht, sein Bruder Marco jedoch nicht, und auch kein anderes Familienmitglied, das ein Konto auf **roberto.net** hat.
+
+Der Clou an diesem Szenario ist die Tatsache, dass Roberto sich nie auf Jaquelinas Website angemeldet hat. Stattdessen musste er sich nur einmal mit seinem Passwort auf seiner *eigenen* Website **roberto.net** anmelden. Wenn Roberto **jaquelina.org** besucht, wird er von ihrem Hub nahtlos authentifiziert, indem sein Server im Hintergrund abgefragt wird.
+
+Es ist nicht ungewöhnlich, dass Server technische Probleme haben oder aus verschiedenen Gründen unzugänglich werden. Nomad bietet Robustheit für Robertos Online-Aktivitäten, indem es ihm erlaubt, *Klone* seiner Online-Identität oder seines *Kanals* auf mehreren unabhängigen Hubs zu haben. Stellen Sie sich vor, dass Robertos Server aus irgendeinem Grund zusammenbricht und er sich dort nicht mehr einloggen kann. Er meldet sich einfach bei einem seiner Klone auf **gadfly.com** an, einer Website, die von seinem Freund Peter betrieben wird. Sobald er sich bei **gadfly.com** authentifiziert hat, kann Roberto Jaquelinas Blog wie zuvor ansehen, ohne dass Jaquelina zusätzlichen Zugang gewähren muss!
+
+#### Kommunikation
+
+Kommunikation und soziale Netzwerke sind ein wesentlicher Bestandteil des Grids. Jeder Kanal (und jeder Dienst, der von diesem Kanal bereitgestellt wird) kann die funktionsreiche soziale Kommunikation auf globaler Ebene in vollem Umfang nutzen. Diese Kommunikation kann öffentlich oder privat sein - und private Kommunikation umfasst nicht nur einen vollständig verschlüsselten Transport, sondern auch eine verschlüsselte Speicherung zum Schutz vor versehentlichem Schnüffeln und Offenlegung durch unseriöse Systemadministratoren und Internetdienstanbieter.
+
+Nomad unterstützt eine breite Palette von Hintergrunddiensten im Grid, von Freundschaftsvorschlägen bis hin zu Verzeichnisdiensten. Neue Inhalte und Datenaktualisierungen werden im Hintergrund zwischen den Hubs im gesamten Grid gemäß den Zugriffskontrolllisten und den von den *Sender- und* Empfängerkanälen festgelegten Berechtigungen weitergegeben. Die Daten werden auch zwischen einer beliebigen Anzahl von Kanalklonen synchronisiert, so dass Hub-Mitglieder auch dann auf Daten zugreifen und nahtlos weiter zusammenarbeiten können, wenn ihr primärer Hub nicht erreichbar oder offline ist.
+
+#### Identität
+
+Die Identitätsschicht von Nomad ist einzigartig. Sie bietet ein **unsichtbares Single Sign-On** für alle Standorte im Grid.
+
+Sie bietet auch eine **nomadische Identität**, so dass Ihre Kommunikation mit Freunden, Familie oder anderen Personen, mit denen Sie kommunizieren, nicht durch den Verlust Ihres primären Kommunikationsknotens beeinträchtigt wird - weder vorübergehend noch dauerhaft.
+
+Die wichtigen Teile Ihrer Identität und Ihrer Beziehungen können auf einem USB-Stick oder Ihrem Laptop gesichert werden und jederzeit an einem beliebigen Knoten im Netz erscheinen - mit all Ihren Freunden und Vorlieben.
+
+Entscheidend ist, dass diese nomadischen Instanzen synchron gehalten werden, so dass jede Instanz übernehmen kann, wenn eine andere gefährdet oder beschädigt ist. Dies schützt Sie nicht nur vor größeren Systemausfällen, sondern auch vor vorübergehender Überlastung der Website und staatlicher Manipulation oder Zensur.
+
+Wir sind der Meinung, dass die nomadische Identität, die einmalige Anmeldung und die Dezentralisierung von Hubzilla ein hohes Maß an **Widerstandsfähigkeit** und **Beständigkeit** in die Internetkommunikation einbringen, die angesichts der weltweiten Tendenzen zur Zentralisierung von Unternehmen sowie der massenhaften und wahllosen staatlichen Überwachung und Zensur dringend benötigt werden.
+
+Beim Durchsuchen des Netzes, beim Anzeigen von Kanälen und deren einzigartigen Inhalten, werden Sie nahtlos authentifiziert, sogar über völlig unterschiedliche Server-Hubs hinweg. Sie müssen keine Passwörter eingeben. Sie müssen nichts eintippen. Sie werden auf jeder neuen Seite, die Sie besuchen, einfach mit Ihrem Namen begrüßt.
+
+Wie funktioniert das bei Nomad? Wir nennen es **„magic-auth“**, weil Hubzilla die Details der Komplexität von Single-Sign-On-Logins und nomadischen Identitäten vor dem Surfen im Netz verbirgt. Dies ist eines der Designziele von Hubzilla: die Privatsphäre und die Freiheit im Internet zu erhöhen und gleichzeitig die Komplexität und die Langeweile zu reduzieren, die durch die Notwendigkeit entstehen, für jeden Besuch im Internet neue Passwörter und Anmeldenamen einzugeben. Sie melden sich nur einmal auf Ihrem Home-Hub (oder einem von Ihnen gewählten nomadischen Backup-Hub) an. Dadurch können Sie auf alle authentifizierten Dienste zugreifen, die überall im Netz angeboten werden - wie Einkaufen, Blogs, Foren und Zugang zu privaten Informationen. Ihr Passwort wird nicht auf tausend verschiedenen Websites gespeichert, sondern auf Servern, die Sie kontrollieren oder denen Sie vertrauen.
+
+Sie können nicht zum Schweigen gebracht werden. Sie können nicht aus dem Netz entfernt werden, es sei denn, Sie selbst entscheiden sich dafür, es zu verlassen.
+
+#### Zugangskontrolle
+
+Die Identitätsschicht von Nomad ermöglicht es Ihnen, fein abgestufte Berechtigungen für jeden Inhalt zu vergeben, den Sie veröffentlichen möchten - und diese Berechtigungen erstrecken sich über das gesamte Grid. Das ist so, als hätte man eine riesige Website, die aus einer Armee kleiner individueller Websites besteht - und bei der jeder Kanal im Grid seine Datenschutz- und Freigabepräferenzen für alle von ihm erstellten Webressourcen vollständig kontrollieren kann.
+
+Derzeit unterstützt Hubzilla die Zugriffskontrolle für viele Datentypen, darunter Diskussionsbeiträge und -kommentare, Fotoalben, Veranstaltungen, Cloud-Dateien, Webseiten, Wikis und mehr. Jedes Objekt und die Art und Weise, wie und mit wem es geteilt wird, unterliegt vollständig Ihrer Kontrolle.
+
+Diese Art der Kontrolle ist bei großen Unternehmensanbietern trivial, da sie die Benutzerdatenbank besitzen. Im Grid brauchen Sie keine riesige Benutzerdatenbank auf Ihrem Rechner - denn das Grid **ist** Ihre Benutzerdatenbank. Sie verfügt über eine im Grunde unendliche Kapazität (begrenzt durch die Gesamtzahl der Hubs, die im Internet online sind) und ist auf Hunderte, möglicherweise sogar Millionen von Computern verteilt.
+
+Der Zugang kann für jede Ressource, jeden Kanal oder jede Gruppe von Kanälen gewährt oder verweigert werden - überall im Grid. Andere können auf Ihre Inhalte zugreifen, wenn Sie es ihnen erlauben, und sie müssen nicht einmal ein Konto in Ihrem Hub haben. \ No newline at end of file
diff --git a/doc/de/developer/nomad_structures.md b/doc/de/developer/nomad_structures.md
new file mode 100644
index 000000000..c3f77c8b7
--- /dev/null
+++ b/doc/de/developer/nomad_structures.md
@@ -0,0 +1,141 @@
+### Nomad-Strukturen
+
+#### Nomad-Signaturen
+
+Alle signierten Daten in Nomad werden durch eine RSA-Signaturoperation mit dem privaten Schlüssel des Initiators erzeugt. Das binäre Ergebnis wird dann für den Transport mit base64url kodiert.
+
+#### Nomad-Verschlüsselung
+
+Die Verschlüsselung erfolgt derzeit mit AES256CTR. Weitere Algorithmen KÖNNEN unterstützt werden. Ein 32-Oktett-Schlüssel und ein 16-Oktett-Initialisierungsvektor werden nach dem Zufallsprinzip erzeugt. Die gewünschten Daten werden dann mit diesen generierten Zeichenketten verschlüsselt und das Ergebnis base64url-kodiert. Dann wird ein Array erstellt:
+
+- data
+
+ Die base64url-kodierten verschlüsselten Daten
+
+- alg
+
+ Der gewählte Algorithmus, in diesem Fall die Zeichenkette „aes256ctr“.
+
+- key
+
+ Der zufällig erzeugte Schlüssel, RSA-verschlüsselt mit dem öffentlichen Schlüssel des Empfängers, und das Ergebnis base64url-kodiert
+
+- iv
+
+ Der zufällig erzeugte Initialisierungsvektor, RSA-verschlüsselt mit dem öffentlichen Schlüssel des Empfängers, und das Ergebnis base64url-kodiert
+
+#### Grundlegendes Nomad-Paket
+
+Wird verwendet, um einen Dialog mit einer anderen Nomad-Site einzuleiten. Dieses Paket KANN verschlüsselt sein. Das Vorhandensein eines Array-Elements 'iv' zeigt an, dass eine Verschlüsselung vorgenommen wurde. Beim Senden eines 'auth_check'-Pakets MUSS dieses Paket verschlüsselt werden, wobei der öffentliche Schlüssel der Zielsite (der Site-Schlüssel, im Gegensatz zu einem Absenderschlüssel) verwendet wird.
+
+```
+ {
+ "type":"notify",
+ "sender":{
+ "guid":"kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA",
+ "guid_sig":"PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK-R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q",
+ "url":"http:\/\/podunk.edu",
+ "url_sig":"T8Bp7j5DHHhQDCFcAHXfuhUfGk2P3inPbImwaXXF1xJd3TGgluoXyyKDx6WDm07x0hqbupoAoZB1qBP3_WfvWiJVAK4N1FD77EOYttUEHZ7L43xy5PCpojJQmkppGbPJc2jnTIc_F1vvGvw5fv8gBWZvPqTdb6LWF6FLrzwesZpi7j2rsioZ3wyUkqb5TDZaNNeWQrIEYXrEnWkRI_qTSOzx0dRTsGO6SpU1fPWuOOYMZG8Nh18nay0kLpxReuHCiCdxjXRVvk5k9rkcMbDBJcBovhiSioPKv_yJxcZVBATw3z3TTE95kGi4wxCEenxwhSpvouwa5b0hT7NS4Ay70QaxoKiLb3ZjhZaUUn4igCyZM0h6fllR5I6J_sAQxiMYD0v5ouIlb0u8YVMni93j3zlqMWdDUZ4WgTI7NNbo8ug9NQDHd92TPmSE1TytPTgya3tsFMzwyq0LZ0b-g-zSXWIES__jKQ7vAtIs9EwlPxqJXEDDniZ2AJ6biXRYgE2Kd6W_nmI7w31igwQTms3ecXe5ENI3ckEPUAq__llNnND7mxp5ZrdXzd5HHU9slXwDShYcW3yDeQLEwAVomTGSFpBrCX8W77n9hF3JClkWaeS4QcZ3xUtsSS81yLrp__ifFfQqx9_Be89WVyIOoF4oydr08EkZ8zwlAsbZLG7eLXY"
+ "sitekey":"-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxTeIwXZWrw/S+Ju6gewh
+LgkKnNNe2uCUqCqMZoYgJar3T5sHCDhvXc4dDCbDkxVIaA/+V1mURtBV60a3IGjn
+OAO0W0XGGLe2ED7G5o9U8T9mVGq8Mauv0v1oQ5wIR1gEAhBavkQ2OUGuF/YKn2nj
+HlKsv9HzUAHpcDMUe3Uklc2RhQbMcnJxEgkyjCkDyrTtCZzISkTAocHvpCG1KSog
+njUZdiz9UWxvM4rCFkCJvQU4RwRZJb7GA9ul+9JrF7NvUQTx8csRP2weBk1E9yyj
+wbe187E0eVj9RXX2Mx3mYhgrTdodxLOVMSXZLg1/SMpeFFl7QBhuM0SiOPg8a7Et
+e2iNA/RD4WiUFqCDfafasRa1TOozOm7LA+08lkAh5PeQPJsJbrX0wVVft++Y+5/z
+BvcUOP73vcbz7j5hJ7HLsbQtye/UUCfODBFybuDqRM84Aet8rjZohX7vukXdMD4I
+2HuB7pjR4uIfyYr0J63ANkvbsn8LR+RnirmHrK5H/OgxxjXcfYbGEQgFxvxhF6lA
+FpMu6Do4dx3CIb6pRmZ8bjSImXexJ0BSo9n3gtrz0XYLecsYFlQ9+QQjm83qxyLb
+M23in0xqMVsyQvzjNkpImrO/QdbEFRIIMee83IHq+adbyjQR49Z2hNEIZhkLPc3U
+2cJJ2HkzkOoF2K37qwIzk68CAwEAAQ==
+-----END PUBLIC KEY-----
+"
+ },
+ "recipients":{
+ {
+ "guid":"lql-1VnxtiO4-WF0h72wLX1Fu8szzHDOXgQaTbELwXW77k8AKFfh-hYr70vqMrc3SSvWN-Flrc5HFhRTWB7ICw",
+ "guid_sig":"PafvEL0VpKfxATxlCqDjfOeSIMdmpr3iU7X-Sysa1h5LzDpjSXsjO37tYZL-accb1M5itLlfnW5epkTa5I4flsW21zSY1A2jCuBQUTLLGV7rNyyBy7lgqJUFvAMRx0TfXzP9lcaPqlM9T1tA6jfWOsOmkdzwofGeXBnsjGfjsO2xdGYe6vwjOU0DSavukvzDMnOayB9DekpvDnaNBTxeGLM45Skzr7ZEMcNF7TeXMbnvpfLaALYEKeQs9bGH-UgAG8fBWgzVAzeBfx_XSR1rdixjyiZGP0kq0h35SlmMPcEjliodOBFwMXqpXFB7Ibp4F6o6te2p2ErViJccQVG8VNKB6SbKNXY6bhP5zVcVsJ-vR-p4xXoYJJvzTN7yTDsGAXHOLF4ZrXbo5yi5gFAlIrTLAF2EdWQwxSGyLRWKxG8PrDkzEzX6cJJ0VRcLh5z6OI5QqQNdeghPZbshMFMJSc_ApCPi9_hI4ZfctCIOi3T6bdgTNKryLm5fhy_eqjwLAZTGP-aUBgLZpb1mf2UojBn6Ey9cCyq-0T2RWyk-FcIcbV4qJ-p_8oODqw13Qs5FYkjLr1bGBq82SuolkYrXEwQClxnrfKa4KYc2_eHAXPL01iS9zVnI1ySOCNJshB97Odpooc4wk7Nb2Fo-Q6THU9zuu0uK_-JbK7IIl6go2qA"
+ },
+ },
+ "callback":"\/post",
+ "version":"1.2",
+ "encryption":{
+ "aes256ctr"
+ },
+ "secret":"1eaa6613699be6ebb2adcefa5379c61a3678aa0df89025470fac871431b70467",
+ "secret_sig":"0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q"
+ }
+```
+
+type
+
+Der Nachrichtentyp: **notify, purge, refresh, force_refresh, auth_check, ping** oder **pickup**. Der Inhalt der Pakete variiert je nach Nachrichtentyp. Hier wird das **notify-Paket** beschrieben.
+
+
+
+callback
+
+Eine Zeichenkette, die an die URL angehängt wird und den Nomad-Kommunikationsendpunkt auf diesem System identifiziert. In der Regel ist dies die Zeichenfolge „/post“.
+
+
+
+version
+
+Die Kennung des Nomad-Protokolls, damit künftige Protokollrevisionen nebeneinander bestehen können.
+
+
+
+encryption
+
+Array der unterstützten Verschlüsselungsalgorithmen, geordnet nach abnehmender Präferenz. Wenn keine kompatiblen Verschlüsselungsmethoden angegeben sind, MÜSSEN Anwendungen „aes256cbc“ verwenden.
+
+
+
+secret
+
+Eine 64-stellige Zeichenkette, die von der sendenden Seite zufällig generiert wird.
+
+
+
+secret_sig
+
+Die RSA-Signatur des Geheimnisses, signiert mit dem privaten Schlüssel des Absenders.
+
+
+
+sender
+
+Ein Array aus vier Komponenten, die eine tragbare Identität liefern. Wir können die angegebene URL kontaktieren und ein Nomad-Infopaket herunterladen, um den öffentlichen Schlüssel des Absenders zu erhalten, und diesen zur Überprüfung der Absender-Guid und der Signaturen der Entsende-URL verwenden.
+
+- guid
+
+ In der Regel eine 64 Zeichen lange base64url-kodierte Zeichenfolge. Sie wird erzeugt, wenn eine Identität erstellt wird, und es wird versucht, dass sie eindeutig ist; dies ist jedoch nicht erforderlich.
+
+- guid_sig
+
+ Die RSA-Signatur der guid, signiert mit dem privaten Schlüssel des Absenders und base64url-kodiert.
+
+- url
+
+ Die Basis-URL des Ortes, von dem dieser Beitrag stammt.
+
+- url_sig
+
+ Die RSA-Signatur der url, signiert mit dem privaten Schlüssel des Absenders und base64url kodiert.
+
+- sitekey
+
+ Der öffentliche Schlüssel der in der Url angegebenen Website
+
+recipients
+
+Wird nur für private Nachrichten verwendet. Ein Array von Umschlag-Empfängern. Jeder Empfänger wird durch ein Array aus guid und guid_sig dargestellt. Wenn Empfänger angegeben sind, wird das gesamte Paket auch mit einem ausgehandelten kryptografischen Algorithmus oder 'aes256cbc' gekapselt, wenn keiner ausgehandelt werden konnte.
+
+- guid
+
+ Die guid eines privaten Empfängers.
+
+- guid_sig
+
+ Die RSA-Signatur der guid, signiert mit dem privaten Schlüssel des Empfängers und base64url-kodiert \ No newline at end of file
diff --git a/doc/de/developer/our_pledge.md b/doc/de/developer/our_pledge.md
new file mode 100644
index 000000000..0a8c17349
--- /dev/null
+++ b/doc/de/developer/our_pledge.md
@@ -0,0 +1,3 @@
+#### Unser Versprechen
+
+Im Interesse der Förderung eines offenen und einladenden Umfelds verpflichten wir uns als Mitwirkende und Betreuer, die Teilnahme an unserem Projekt und unserer Gemeinschaft zu einer belästigungsfreien Erfahrung für jeden zu machen, unabhängig von Alter, Körpergröße, Behinderung, ethnischer Zugehörigkeit, Geschlechtsidentität und -ausdruck, Erfahrungsniveau, Nationalität, persönlichem Aussehen, Ethnie, Religion oder sexueller Identität und Orientierung. \ No newline at end of file
diff --git a/doc/de/developer/our_responsibilities.md b/doc/de/developer/our_responsibilities.md
new file mode 100644
index 000000000..32acdbf07
--- /dev/null
+++ b/doc/de/developer/our_responsibilities.md
@@ -0,0 +1,3 @@
+#### Unsere Verantwortlichkeiten
+
+Die Projektbetreuer sind dafür verantwortlich, die Standards für akzeptables Verhalten zu klären, und es wird von ihnen erwartet, dass sie angemessene und faire Korrekturmaßnahmen ergreifen, wenn ein inakzeptables Verhalten festgestellt wird. Die Projektbetreuer haben das Recht und die Verantwortung, Kommentare, Commits, Code, Wiki-Edits, Issues und andere Beiträge, die nicht mit diesem Verhaltenskodex übereinstimmen, zu entfernen, zu bearbeiten oder abzulehnen, oder Beiträge, die sie als unangemessen, bedrohlich, beleidigend oder schädlich erachten, vorübergehend oder dauerhaft zu verbieten. \ No newline at end of file
diff --git a/doc/de/developer/our_standards.md b/doc/de/developer/our_standards.md
new file mode 100644
index 000000000..f745d29d4
--- /dev/null
+++ b/doc/de/developer/our_standards.md
@@ -0,0 +1,24 @@
+#### Unsere Standards
+
+Beispiele für Verhaltensweisen, die zur Schaffung eines positiven Umfelds beitragen
+umfassen:
+
+- Eine einladende und integrative Sprache verwenden
+
+- Respekt vor unterschiedlichen Standpunkten und Erfahrungen
+
+- Konstruktive Kritik anständig annehmen
+
+- Sich auf das konzentrieren, was für die Gemeinschaft am besten ist
+
+- Einfühlungsvermögen gegenüber anderen Mitgliedern der Gemeinschaft zeigen
+
+
+
+Beispiele für inakzeptables Verhalten von Teilnehmern sind:
+
+- Die Verwendung sexualisierter Sprache oder Bilder und unerwünschte sexuelle Aufmerksamkeit oder Annäherungsversuche
+- Trolling, beleidigende/abwertende Kommentare und persönliche oder politische Angriffe
+- Öffentliche oder private Belästigung
+- Veröffentlichung privater Informationen anderer, wie z. B. einer physischen oder elektronischen Adresse, ohne ausdrückliche Erlaubnis
+- sonstiges Verhalten, das in einem beruflichen Umfeld als unangemessen angesehen werden könnte \ No newline at end of file
diff --git a/doc/de/developer/scope.md b/doc/de/developer/scope.md
new file mode 100644
index 000000000..03c71d1dd
--- /dev/null
+++ b/doc/de/developer/scope.md
@@ -0,0 +1,3 @@
+#### Geltungsbereich
+
+Dieser Verhaltenskodex gilt sowohl innerhalb der Projektbereiche als auch im öffentlichen Umfeld, wenn eine Person das Projekt oder seine Gemeinschaft vertritt. Beispiele für das Repräsentieren eines Projekts oder einer Gemeinschaft sind die Verwendung einer offiziellen Projekt-E-Mail-Adresse, das Posten über ein offizielles Social-Media-Konto oder das Handeln als ernannter Vertreter bei einer Online- oder Offline-Veranstaltung. Die Repräsentation eines Projekts kann von den Projektbetreuern weiter definiert und präzisiert werden. \ No newline at end of file
diff --git a/doc/de/developer/technical_introduction.md b/doc/de/developer/technical_introduction.md
new file mode 100644
index 000000000..320544ce0
--- /dev/null
+++ b/doc/de/developer/technical_introduction.md
@@ -0,0 +1,309 @@
+### Technische Einführung
+
+Nomad ist ein JSON-basiertes Web-Framework zur Implementierung sicherer dezentraler Kommunikation und Dienste. Um diese Funktionalität bereitzustellen, erstellt Nomad eine dezentralisierte, weltweit eindeutige Kennung für jeden Knotenpunkt im Netz. Dieser globale Identifikator ist nicht untrennbar mit dem DNS verbunden, wodurch die erforderliche Mobilität gewährleistet wird. Viele der bestehenden dezentralen Kommunikationsrahmen bieten zwar den Kommunikationsaspekt, aber keine Fernzugriffskontrolle und Authentifizierung. Außerdem basieren die meisten dieser Systeme auf dem „Webfinger“, der die Identität immer noch an Domänennamen bindet und keine nomadische Identität unterstützen kann.
+
+Die Hauptprobleme, die Nomad angeht, sind
+
+- vollständig dezentralisierte Kommunikation
+- Unabhängigkeit von DNS-basierter Identität
+- Knotenmobilität
+- nahtlose Fernauthentifizierung
+- hohe Leistung
+
+Wir werden uns auf DNS-basierte user@host-Adressen als „benutzerfreundlichen“ Mechanismus stützen, um anderen mitzuteilen, wo man sich befindet, nämlich auf einem benannten Host mit einem bestimmten Benutzernamen, und die Kommunikation mit DNS-Entitäten wird über TCP und das Web abgewickelt.
+
+Das zugrundeliegende Protokoll bietet jedoch eine Abstraktionsschicht darüber, so dass ein Kommunikationsknoten (z. B. „Identität“) zu einem anderen DNS-Standort wechseln kann und sich (nach bestem Wissen und Gewissen) von Standortwechseln erholt und/oder bereits bestehende Kommunikationsbeziehungen aufrechterhält. Ein Nebeneffekt dieser Anforderung ist die Möglichkeit, von alternativen/mehreren DNS-Standorten und Dienstanbietern aus zu kommunizieren und dennoch eine einzige Online-Identität beizubehalten.
+
+Wir nennen dieses Overlay-Netz das „Grid“. Die an dieses Netz angeschlossenen Server werden als „Hubs“ bezeichnet und können eine beliebige Anzahl von individuellen Identitäten unterstützen.
+
+Eine Identität muss nicht unbedingt mit einer Person übereinstimmen. Sie ist lediglich etwas, das die Fähigkeit erfordert, innerhalb des Netzes zu kommunizieren.
+
+Die Fähigkeit zur Wiederherstellung wird durch Kommunikation mit dem ursprünglichen Standort erreicht, wenn eine neue oder Ersatzidentität erstellt wird, oder als Rückgriff auf eine gespeicherte Datei, die die Identität und ihre Kontakte beschreibt, für den Fall, dass der alte Standort nicht mehr antwortet.
+
+Zumindest kurzfristig hat die Mobilität bestehender Inhalte nicht die höchste Priorität. Dies kann zu einem späteren Zeitpunkt geschehen oder auch nicht. Die wichtigsten Dinge, die wir behalten wollen, sind Ihre Identität und Ihre Freunde.
+
+Adressen, die von mehreren Personen gemeinsam genutzt werden, sind user@host und beschreiben die **aktuellen** lokalen Zugangsdaten für eine bestimmte Identität. Es handelt sich um DNS-basierte Adressen, die als Seed verwendet werden, um eine bestimmte Identität innerhalb des Netzes zu lokalisieren. Die Maschinenkommunikation bindet diese Adresse an eine weltweit eindeutige ID. Eine einzelne, weltweit eindeutige ID kann an eine beliebige Anzahl von DNS-Standorten angehängt oder gebunden werden. Sobald eine Identität einem DNS-Standort zugeordnet oder an diesen gebunden ist, besteht die Kommunikation nur noch aus der Kenntnis der weltweit eindeutigen Adresse und des derzeit verwendeten DNS (url) (um die aktuelle Kommunikation zurückzurufen und zu überprüfen/abzuschließen). Diese Konzepte werden in Zukunft noch genauer spezifiziert.
+
+Damit eine Identität über Standorte hinweg bestehen bleibt, muss man in der Lage sein, Folgendes bereitzustellen oder wiederherzustellen
+
+
+
+- die weltweit eindeutige ID für diese Identität
+- den dieser Identität zugewiesenen privaten Schlüssel
+- (falls der ursprüngliche Server nicht mehr existiert) ein Adressbuch mit Kontakten für diese Identität.
+
+Diese Informationen können vom ursprünglichen Server über eine API exportiert und/oder auf eine Festplatte oder einen USB-Stick heruntergeladen werden.
+
+Wir können auch versuchen, die Identität mit noch weniger Informationen wiederherzustellen, aber dies ist anfällig für Account-Hijacking und erfordert, dass Ihre Kontakte die Änderung bestätigen.
+
+Um eine hochleistungsfähige Kommunikation zu ermöglichen, ist das Datenübertragungsformat für alle Aspekte von Nomad JSON. XML-Kommunikation erfordert viel zu viel Overhead.
+
+Die bidirektionale Verschlüsselung basiert auf RSA-Schlüsseln mit 4096 Bit, die im DER/ASN.1-Format unter Verwendung der PKCS#8-Kodierungsvariante ausgedrückt werden, mit AES-Verschlüsselung von Elementen mit variabler Länge oder großen Elementen. Die genauen Verschlüsselungsalgorithmen sind zwischen den Standorten verhandelbar.
+
+Einige Aspekte der bekannten „Föderationsprotokolle“ (Webfinger, Salmon, Activitystreams, Portablecontacts usw.) können in Nomad verwendet werden, aber wir sind nicht an sie gebunden und werden auch nicht an sie gebunden sein. Das Hubzilla-Projekt versucht einige ziemlich neuartige Entwicklungen im Bereich der dezentralen Kommunikation, und wenn es notwendig sein sollte, von solchen „Standardprotokollen“ abzuweichen, werden wir dies ohne Fragen oder Zögern tun.
+
+Um eine weltweit eindeutige ID zu erstellen, werden wir sie auf einem Whirlpool-Hash der Identitäts-URL des Ursprungsknotens und einer Pseudo-Zufallszahl basieren, was uns eine 256-Bit-ID mit einer extrem geringen Kollisionswahrscheinlichkeit liefern sollte (256 Bits entsprechen ungefähr 115 Quattuorviginitillionen oder 1,16 X 10^77 eindeutigen Zahlen). Diese wird in der Kommunikation als base64url-kodierte Zeichenfolge dargestellt. Wir werden uns jedoch nicht auf Wahrscheinlichkeiten verlassen, und die ID muss auch mit einem öffentlichen Schlüssel verbunden sein, wobei die Kryptographie mit öffentlichem Schlüssel verwendet wird, um eine Identitätsgarantie zu bieten, die nicht kopiert oder irgendwie im Whirlpool-Hash-Raum kollidiert wurde.
+
+Durch die Verwendung der DNS als Grundlage für die ID wird ein weltweit eindeutiger Seed bereitgestellt, was nicht der Fall wäre, wenn die ID vollständig auf einer Pseudo-Zufallszahlengenerierung basieren würde.
+
+Wir bezeichnen die kodierte, weltweit eindeutige uid-Zeichenkette als zot_uid
+
+Da es mehr als einen DNS-Standort geben kann, der mit einer bestimmten zot_uid-Identität verknüpft/verbunden ist, sollten die Zustellungsprozesse an alle diese Standorte liefern, da wir nicht mit Sicherheit wissen, auf welche Hub-Instanz zu einem bestimmten Zeitpunkt zugegriffen werden kann. Wir werden jedoch einen DNS-Standort als „primär“ bezeichnen, der der bevorzugte Standort für die Anzeige von Webprofilinformationen sein wird.
+
+Wir müssen auch die Möglichkeit bieten, den primären Standort auf einen neuen Standort zu ändern. Ein Nachschlagen von Informationen über den aktuellen primären Standort kann einen „Weiterleitungszeiger“ liefern, der uns anweist, unsere Einträge zu aktualisieren und alles an den neuen Standort zu verschieben. Es besteht auch die Möglichkeit, dass der Hauptstandort nicht mehr existiert und nicht mehr antwortet. In diesem Fall kann ein Ort, der bereits dieser zot_uid zugeordnet ist, die Kontrolle übernehmen und sich selbst als primär deklarieren. In beiden Fällen wird die primäre Bezeichnung automatisch bestätigt und verschoben. Es kann auch ein Antrag von einem primären Standort gestellt werden, der die Entfernung eines anderen Standorts verlangt.
+
+Um eine zot_uid einem zweiten (oder tertiären) Standort zuzuordnen, ist ein sicherer Austausch erforderlich, bei dem überprüft wird, ob der neue Standort im Besitz des privaten Schlüssels für diese zot_uid ist. Die Sicherheit des privaten Schlüssels ist daher von grundlegender Bedeutung, um ein Hijacking von Identitäten zu verhindern.
+
+Die Kommunikation erfordert dann
+
+- zot_uid (Zeichenfolge)
+- uid_sig
+- callback (aktueller Standort Nomad-Endpunkt url)
+- callback_sig
+- spec (int)
+
+wird mit jeder Mitteilung übergeben. Bei spec handelt es sich um eine Revisionsnummer der geltenden Nomad-Spezifikation, damit die Kommunikation mit Hubs, die ältere und möglicherweise inkompatible Protokollspezifikationen verwenden, aufrechterhalten werden kann. Die Kommunikation wird mit Hilfe eines gespeicherten öffentlichen Schlüssels verifiziert, der beim ersten Aufbau der Verbindung zu dieser zot_uid kopiert wurde.
+
+Der Widerruf und Ersatz des Schlüssels muss vom primären Hub aus erfolgen. Die genaue Form hierfür wird noch ausgearbeitet, wird aber wahrscheinlich eine Benachrichtigung an alle anderen gebundenen Hubs sein, um „nach Hause zu telefonieren“ (zum primären Hub) und eine Kopie des neuen Schlüssels anzufordern. Diese Mitteilung sollte mit einem Standort- oder Hub-Schlüssel verifiziert werden, da der ursprüngliche Identitätsschlüssel kompromittiert worden sein könnte und nicht vertrauenswürdig ist.
+
+Um Verwirrung zu vermeiden, sollte es für jeden Hub genau eine kanonische Url geben, damit diese indiziert und eindeutig referenziert werden kann.
+
+Um Mehrdeutigkeit des Schemas zu vermeiden, wird dringend empfohlen, dass alle Adressen https mit einem „browser valid“-Zertifikat und einer einzigen gültigen Host-Komponente (entweder www.domain.ext oder domain.ext, aber nicht beides) sein sollten, die in der gesamten Kommunikation verwendet wird. Es können mehrere URLs lokal angegeben werden, aber es sollte nur eine einzige URL für die gesamte Nomad-Kommunikation innerhalb des Grids verwendet werden.
+
+Testinstallationen, die nicht mit dem öffentlichen Netz verbunden sind, können Nicht-SSL verwenden, aber der gesamte Datenverkehr, der über öffentliche Netze fließt, sollte vor Session-Hijacking geschützt sein, vorzugsweise mit einem „browsererkannten“ Zertifikat.
+
+Wenn möglich, empfiehlt das Nomadt die Verwendung von „Batching“, um den Netzwerkverkehr zwischen zwei Hubs zu minimieren. Dies bedeutet, dass Standort A„ mehrere Nachrichten an Standort B“ in einer einzigen Transaktion senden kann und auch die Zustellung identischer Nachrichten an mehrere Empfänger im selben Hub konsolidiert werden kann.
+
+Die Nachrichten selbst können während der Übertragung verschlüsselt werden oder auch nicht, je nach der privaten Natur der Nachrichten. SSL (dringend empfohlen) bietet eine bedingungslose Verschlüsselung des Datenstroms, allerdings macht es wenig Sinn, öffentliche Mitteilungen zu verschlüsseln, die als uneingeschränkt sichtbar eingestuft wurden. Die Verschlüsselung der Datenspeicherung und die sogenannte „Ende-zu-Ende-Verschlüsselung“ liegen außerhalb des Anwendungsbereichs des Nomad. Es wird davon ausgegangen, dass die Betreiber von Knotenpunkten angemessene Sicherheitsvorkehrungen treffen, um die Sicherheit ihrer Datenspeicher zu gewährleisten, und diese sind Funktionen der Anwendungs- und Standortintegrität im Gegensatz zur Protokollintegrität.
+
+
+
+#### Nachrichten
+
+In Anbetracht der zuvor aufgeführten Einschränkungen sollte eine Nomad-Mitteilung daher ein json-Array aus einzelnen Nachrichten sein. Diese können innerhalb der gleichen Übertragung gemischt und kombiniert werden.
+
+Jede Nachricht erfordert dann:
+
+- Typ
+- (optional) Empfängerliste
+
+Das Fehlen einer Empfängerliste würde auf eine unverschlüsselte öffentliche Nachricht oder eine Nachricht auf Standortebene hinweisen. Die Empfängerliste würde ein Array von zot_uid mit einem individuellen Entschlüsselungsschlüssel und einer gemeinsamen iv enthalten. Der Entschlüsselungsschlüssel ist mit dem öffentlichen Schlüssel der Empfängeridentität verschlüsselt. Die iv wird mit dem privaten Schlüssel des Absenders verschlüsselt.
+
+Alle Nachrichten sollten vom Absender digital signiert werden.
+
+Der Typ kann einer der folgenden sein (diese Liste ist erweiterbar):
+
+- Post (oder Aktivität)
+- Mail
+- Identität
+- authentifizieren
+
+Identitätsnachrichten haben keine Empfänger und benachrichtigen den sozialen Graphen des Systems über eine Identitätsaktualisierung, bei der es sich um eine neue oder gelöschte Identität, einen neuen oder gelöschten Standort oder eine Änderung des primären Hubs handeln kann. Die Signatur für diese Nachrichten verwendet Systemschlüssel im Gegensatz zu identitätsspezifischen Schlüsseln.
+
+Posts umfassen viele verschiedene Arten von Aktivitäten, wie z. B. Top-Level-Posts, Likes/Dislikes, Kommentare, Tagging-Aktivitäten usw. Diese Typen werden in der Nachrichtenstruktur angegeben.
+
+Authentifizierungsnachrichten führen zu einer gegenseitigen Authentifizierung und einer Browserumleitung zu geschützten Ressourcen auf dem entfernten Hub, wie z. B. die Möglichkeit, Nachrichten von Wand zu Wand zu posten und private Fotoalben und Ereignisse einzusehen, usw.
+
+
+
+#### Erkundung
+
+Eine bekannte URL wird verwendet, um einen Hub auf Nomad-Fähigkeiten und Identitätsabfragen zu untersuchen, einschließlich der Ermittlung von öffentlichen Schlüsseln, Profilstandorten, Profilfotos und dem primären Hub-Standort.
+
+Der Speicherort für diesen Dienst ist /.well-known/zot-info - und muss im Stammverzeichnis der gewählten Domain verfügbar sein.
+
+Um eine Abfrage durchzuführen, wird eine POST-Anfrage mit den folgenden Parametern an den Erkennungsort gestellt:
+
+Erforderlich:
+
+Adresse => eine Adresse auf dem Zielsystem wie „john“
+
+Optional:
+
+target => die Nomad „guid“ des Beobachters für die Auswertung der Berechtigungen
+
+target_sig => eine RSA-Signatur (base64url-kodiert) der guid
+
+key => Der öffentliche Schlüssel, der zur Verifizierung der Signatur benötigt wird
+
+token => eine vom anfragenden Dienst gewählte Zeichenfolge (möglicherweise zufällig). Falls vorhanden, wird ein Eintrag im entdeckten Paket mit der Bezeichnung „signed_token“ bereitgestellt, der aus der base64url_kodierten RSA-Signatur der Verkettung der Zeichenkette „token.“ und dem bereitgestellten Token unter Verwendung des privaten Schlüssels des entdeckten Kanals besteht. Dies kann mit dem bereitgestellten „key“-Eintrag verifiziert werden und bietet die Sicherheit, dass der Server im Besitz des privaten Schlüssels für die ermittelte Identität ist. Nach dem 01.01.2017 ist es **erforderlich,** dass ein Server ein signed_token bereitstellt *, wenn* ein Token in der Anfrage angegeben wurde.
+
+Wenn kein Ziel angegeben wird, sind die zurückgegebenen Berechtigungen generische Berechtigungen
+für unbekannte oder unauthentifizierte Beobachter
+
+Beispiel eines Erkennungspakets für '`mike@zothub.com`'
+
+```
+ {
+
+ "success": true,
+ "signed_token": "KBJrKTq1qrctNuxF3GwVh3GAGRqmgkirlXANPcJZAeWlvSt_9TMV097slR4AYnYCBEushbVqHEJ9Rb5wHTa0HzMbfRo8cRdl2yAirvvv5d98dtwHddQgX1jB0xEypXtmIYMdPGDLvhI1RNdIBhHkkrRcNreRzoy4xD--HM6m1W0-A8PJJJ9BcNxmGPcBtLzW08wzoP9trJ3M7DQ6Gkk6j7iwVsyApw1ZBaDvabGTdc_SFV-Iegtqw3rjzT_xXWsfzMlKBy-019MYn_KS-gu23YzjvGu5tS_zDfkQb8DMUlPLz5yyxM0yOMlUDtG2qQgIJAU2O0X6T5xDdJ6mtolNyhepg845PvFDEqBQGMIH1nc47CNumeudDi8IWymEALhjG_U8KAK7JVlQTJj2EKUb0au1g6fpiBFab5mmxCMtZEX3Jreyak5GOcFFz-WpxuXJD9TdSoIvaBfBFOoJnXkg2zE4RHXeQzZ2FotmrbBG5dm8B-_6byYGoHBc08ZsWze1K96JIeRnLpBaj6ifUDcVHxZMPcGHHT27dvU2PNbgLiBjlAsxhYqkhN5qOHN8XBcg2KRjcMBaI3V0YMxlzXz5MztmZq3fcB1p-ccIoIyMPMzSj3yMB7J9CEU2LYPSTHMdPkIeDE6GaCkQKviaQQJQde346tK_YjA2k7_SOBmvPYE",
+ "guid": "sebQ-IC4rmFn9d9iu17m4BXO-kHuNutWo2ySjeV2SIW1LzksUkss12xVo3m3fykYxN5HMcc7gUZVYv26asx-Pg",
+ "guid_sig": "Llenlbl4zHo6-g4sa63MlQmTP5dRCrsPmXHHFmoCHG63BLq5CUZJRLS1vRrrr_MNxr7zob_Ykt_m5xPKe5H0_i4pDj-UdP8dPZqH2fqhhx00kuYL4YUMJ8gRr5eO17vsZQ3XxTcyKewtgeW0j7ytwMp6-hFVUx_Cq08MrXas429ZrjzaEwgTfxGnbgeQYQ0R5EXpHpEmoERnZx77VaEahftmdjAUx9R4YKAp13pGYadJOX5xnLfqofHQD8DyRHWeMJ4G1OfWPSOlXfRayrV_jhnFlZjMU7vOdQwHoCMoR5TFsRsHuzd-qepbvo3pzvQZRWnTNu6oPucgbf94p13QbalYRpBXKOxdTXJrGdESNhGvhtaZnpT9c1QVqC46jdfP0LOX2xrVdbvvG2JMWFv7XJUVjLSk_yjzY6or2VD4V6ztYcjpCi9d_WoNHruoxro_br1YO3KatySxJs-LQ7SOkQI60FpysfbphNyvYMkotwUFI59G08IGKTMu3-GPnV1wp7NOQD1yzJbGGEGSEEysmEP0SO9vnN45kp3MiqbffBGc1r4_YM4e7DPmqOGM94qksOcLOJk1HNESw2dQYWxWQTBXPfOJT6jW9_crGLMEOsZ3Jcss0XS9KzBUA2p_9osvvhUKuKXbNztqH0oZIWlg37FEVsDs_hUwUJpv2Ar09k4",
+ "key": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7QCwvuEIwCHjhjbpz3Oc\ntyei/Pz9nDksNbsc44Cm8jxYGMXsTPFXDZYCcCB5rcAhPPdZSlzaPkv4vPVcMIrw\n5cdX0tvbwa3rNTng6uFE7qkt15D3YCTkwF0Y9FVZiZ2Ko+G23QeBt9wqb9dlDN1d\nuPmu9BLYXIT/JXoBwf0vjIPFM9WBi5W/EHGaiuqw7lt0qI7zDGw77yO5yehKE4cu\n7dt3SakrXphL70LGiZh2XGoLg9Gmpz98t+gvPAUEotAJxIUqnoiTA8jlxoiQjeRK\nHlJkwMOGmRNPS33awPos0kcSxAywuBbh2X3aSqUMjcbE4cGJ++/13zoa6RUZRObC\nZnaLYJxqYBh13/N8SfH7d005hecDxWnoYXeYuuMeT3a2hV0J84ztkJX5OoxIwk7S\nWmvBq4+m66usn6LNL+p5IAcs93KbvOxxrjtQrzohBXc6+elfLVSQ1Rr9g5xbgpub\npSc+hvzbB6p0tleDRzwAy9X16NI4DYiTj4nkmVjigNo9v2VPnAle5zSam86eiYLO\nt2u9YRqysMLPKevNdj3CIvst+BaGGQONlQalRdIcq8Lin+BhuX+1TBgqyav4XD9K\nd+JHMb1aBk/rFLI9/f2S3BJ1XqpbjXz7AbYlaCwKiJ836+HS8PmLKxwVOnpLMbfH\nPYM8k83Lip4bEKIyAuf02qkCAwEAAQ==\n-----END PUBLIC KEY-----\n",
+ "name": "Mike Macgirvin",
+ "name_updated": "2012-12-06 04:59:13",
+ "address": "mike@zothub.com",
+ "photo_mimetype": "image/jpeg",
+ "photo": "https://zothub.com/photo/profile/l/1",
+ "photo_updated": "2012-12-06 05:06:11",
+ "url": "https://zothub.com/channel/mike",
+ "connections_url": "https://zothub.com/poco/mike",
+ "target": "",
+ "target_sig": "",
+ "searchable": false,
+ "permissions": {
+ "view_stream": true,
+ "view_profile": true,
+ "view_photos": true,
+ "view_contacts": true,
+ "view_storage": true,
+ "view_pages": true,
+ "send_stream": false,
+ "post_wall": false,
+ "post_comments": false,
+ "post_mail": false,
+ "post_photos": false,
+ "tag_deliver": false,
+ "chat": false,
+ "write_storage": false,
+ "write_pages": false,
+ "delegate": false
+ },
+ "profile": {
+ "description": "Freedom Fighter",
+ "birthday": "0000-05-14",
+ "next_birthday": "2013-05-14 00:00:00",
+ "gender": "Male",
+ "marital": "It's complicated",
+ "sexual": "Females",
+ "locale": "",
+ "region": "",
+ "postcode": "",
+ "country": "Australia"
+ },
+ "locations": [
+ {
+ "host": "zothub.com",
+ "address": "mike@zothub.com",
+ "primary": true,
+ "url": "https://zothub.com",
+ "url_sig": "eqkB_9Z8nduBYyyhaSQPPDN1AhSm5I4R0yfcFxPeFpuu17SYk7jKD7QzvmsyahM5Kq7vDW6VE8nx8kdFYpcNaurqw0_IKI2SWg15pGrhkZfrCnM-g6A6qbCv_gKCYqXvwpSMO8SMIO2mjQItbBrramSbWClUd2yO0ZAceq3Z_zhirCK1gNm6mGRJaDOCuuTQNb6D66TF80G8kGLklv0o8gBfxQTE12Gd0ThpUb5g6_1L3eDHcsArW_RWM2XnNPi_atGNyl9bS_eLI2TYq0fuxkEdcjjYx9Ka0-Ws-lXMGpTnynQNCaSFqy-Fe1aYF7X1JJVJIO01LX6cCs-kfSoz29ywnntj1I8ueYldLB6bUtu4t7eeo__4t2CUWd2PCZkY3PKcoOrrnm3TJP5_yVFV_VpjkcBCRj3skjoCwISPcGYrXDufJxfp6bayGKwgaCO6QoLPtqqjPGLFm-fbn8sVv3fYUDGilaR3sFNxdo9mQ3utxM291XE2Pd0jGgeUtpxZSRzBuhYeOybu9DPusID320QbgNcbEbEImO8DuGIxuVRartzEXQF4WSYRdraZzbOqCzmU0O55P836JAfrWjgxTQkXlYCic-DBk-iE75JeT72smCtZ4AOtoFWCjZAABCw42J7JELY9APixZXWriKtjy6JI0G9d3fs6r7SrXr1JMy0",
+ "callback": "https://zothub.com/post",
+ "sitekey": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1IWXwd/BZuevq8jzNFoR\n3VkenduQH2RpR3Wy9n4+ZDpbrUKGJddUGm/zUeWEdKMVkgyllVA/xHdB7jdyKs1X\nuIet9mIdnzvhdLO/JFD5hgbNG2wpSBIUY6aSNeCFTzszqXmuSXMW5U0Ef5pCbzEA\nnhoCoGL1KAgPqyxnGKUlj7q2aDwC9IRNtAqNyFQL67oT91vOQxuMThjlDhbR/29Q\ncYR4i1RzyahgEPCnHCPkT2GbRrkAPjNZAdlnk9UesgP16o8QB3tE2j50TVrbVc/d\nYRbzC56QMPP9UgUsapNeSJBHji75Ip/E5Eg/kfJC/HEQgyCqjCGfb7XeUaeQ7lLO\nqc7CGuMP+Jqr/cE54/aSHg8boTwxkMp11Ykb+ng17fl57MHTM2RJ99qZ1KBkXezR\nuH1lyvjzeJPxEFr9rkUqc4GH74/AgfbgaFvQc8TS7ovEa5I/7Pg04m7vLSEYc6UF\nYJYxXKrzmZT2TDoKeJzaBBx5MFLhW19l68h9dQ8hJXIpTP0hJrpI+Sr6VUAEfFQC\ndIDRiFcgjz6j7T/x8anqh63/hpsyf2PMYph1+4/fxtSCWJdvf+9jCRM8F1IDfluX\n87gm+88KBNaklYpchmGIohbjivJyru41CsSLe0uinQFvA741W00w6JrcrOAX+hkS\nRQuK1dDVwGKoIY85KtTUiMcCAwEAAQ==\n-----END PUBLIC KEY-----\n"
+ }
+ ],
+ "site": {
+ "url": "https://zothub.com",
+ "directory_mode": "primary",
+ "directory_url": "https://zothub.com/dirsearch"
+ }
+
+ }
+```
+
+Discovery gibt ein JSON-Array mit den folgenden Komponenten zurück:
+
+'success' => (true oder false) Operation war erfolgreich, wenn true. Andernfalls kann eine optionale 'message' vorhanden sein, die die Fehlerursache angibt.
+
+'signed_token' => Wenn ein Token-Parameter in der Anfrage angegeben wurde, wird ihm der Text 'token.' vorangestellt und dann mit dem privaten Schlüssel des Kanals RSA-signiert und base64url-kodiert und als 'signed_token' zurückgegeben.
+
+guid' => die guid der Adresse auf dem Zielsystem
+
+guid_sig“ => die mit base64url kodierte RSA-Signatur der guid, signiert mit dem mit dieser guid verbundenen privaten Schlüssel.
+
+'key' => der öffentliche Schlüssel, der mit dieser guid verknüpft ist
+
+name“ => Name des Kanals
+
+name_updated' => Zeitstempel im MySQL-Stil '2012-01-01 00:00:00', als der Name zuletzt geändert wurde (UTC)
+
+address' => „webbie“ oder user@host-Adresse, die mit diesem Kanal verbunden ist
+
+photo' => URL eines Profilfotos für diesen Kanal (idealerweise 175x175)
+
+photo_mimetype“ => Inhaltstyp des Profilfotos
+
+photo_updated' => Zeitstempel im MySQL-Stil, wann das Foto zuletzt aktualisiert wurde (UTC)
+
+'url' => Standort der Kanal-Homepage
+
+'connections_url' => Speicherort der URL für tragbare Kontakte (erweitert für Nomad) für diesen Kanal
+
+target' => wenn ein Berechtigungsziel angegeben wurde, wird es gespiegelt
+
+'target_sig' => wenn ein Berechtigungsziel angegeben wurde, wird die Signatur gespiegelt.
+
+'searchable' => (true oder false) true bedeutet, dass dieser Eintrag in einem Verzeichnis gesucht werden kann
+
+
+
+##### Berechtigungen
+
+'permissions' => erweiterbares Array von Berechtigungen, die für dieses Ziel geeignet sind, Werte sind true oder false
+
+Berechtigungen können beinhalten:
+
+- view_stream
+- view_profile
+- view_photos
+- view_contacts
+- view_storage
+- view_pages
+- send_stream
+- post_wall
+- post_comments
+- post_mail
+- post_photos
+- tag_deliver
+- chat
+- write_storage
+- write_pages
+- delegate
+
+##### Profil
+
+'Profil' => Array mit wichtigen Profilfeldern
+
+- description
+- birthday YYYY-MM-DD , all fields are optional, any field (such as year) may be zero
+- next_birthday => MySQL datetime string representing the next upcoming birthday, converted from the channel's default timezone to UTC.
+- gender (free form)
+- marital (marital status)
+- sexual (preference)
+- locale (city)
+- region (state)
+- postcode
+- country
+
+##### Standorte
+
+'locations' => Array der registrierten Standorte (DNS-Standorte), von denen aus dieser Kanal sichtbar sein kann oder gebucht werden kann
+
+Jeder Standort ist ein Array aus
+
+'host' => DNS-Hostname, z.B. example.com
+
+'address' => die Webbie- oder user@host-Kennung, die mit diesem Standort verbunden ist
+
+'primary' => (true oder false), ob dies der primäre Ort für diesen Kanal ist, an dem Dateien und Webseiten im Allgemeinen zu finden sind
+
+url“ => Url des Stammverzeichnisses dieses DNS-Speicherorts, z. B. `https://example.com`
+
+url_sig' => base64url kodierte RSA-Signatur der URL, signiert mit dem privaten Schlüssel des Kanals
+
+'callback' => Nomad-Kommunikationsendpunkt auf dieser Site, normalerweise `https://example.com/post]`
+
+sitekey' => öffentlicher Schlüssel dieser Seite/Host
+
+
+
+##### Standort
+
+'site' => Array, das die Verzeichnisrolle der Site angibt, die auf diese Anfrage antwortet
+
+url' => url dieser Site z.B. `https://example.com`
+
+directory_mode' => eines von 'primary', 'secondary', 'normal' oder 'standalone'
+
+directory_url' => wenn es sich um einen Verzeichnisserver oder einen Einzelplatz handelt, die URL für das Verzeichnissuchmodul \ No newline at end of file
diff --git a/doc/de/developer/testing.md b/doc/de/developer/testing.md
new file mode 100644
index 000000000..bbe15050f
--- /dev/null
+++ b/doc/de/developer/testing.md
@@ -0,0 +1,131 @@
+### Testen
+
+Hubzilla verwendet [PHPUnit] für automatisierte Tests, oft auch *Unit-Tests* oder *Integrationstests* genannt.
+Die Tests werden als PHP-Klassen geschrieben und befinden sich im Unterverzeichnis `tests/unit` des Hubzilla-Hauptrepositorys. In diesem Leitfaden wird erklärt, wie man die Tests ausführt, wie die Tests strukturiert sind und wie Sie Ihre eigenen Tests schreiben können.
+
+#### Ausführen der Testsuite
+
+##### Installieren der Abhängigkeiten
+
+Um die Tests ausführen zu können, müssen Sie die Entwickler-Abhängigkeiten installieren, die in der `composer.json-Datei` aufgeführt sind. Stellen Sie sicher, dass Sie [composer installiert] auf Ihrem Entwicklungssystem haben, und führen Sie dann aus:
+
+```
+% composer install
+```
+
+Dies wird phpunit und einige andere Abhängigkeiten installieren. Es wird auch einige Dateien im Unterverzeichnis `vendor/composer` aktualisieren. Dies mag ein wenig laut erscheinen, aber es ist wichtig, dass diese Änderungen *nicht* in das Repository übertragen werden!
+
+**Warnung!** Übertragen Sie die Änderungen an den Dateien im Verzeichnis `vendor/composer` nicht in Ihr Projektarchiv!
+
+##### Einrichten der Testdatenbank
+
+Wir haben ein Skript (`tests/create_test_db.sh`) beigefügt, das Ihnen hilft, die Testdatenbank einzurichten. Sie können es wie folgt ausführen:
+
+```
+% HZ_TEST_DB_TYPE=mysql ./tests/create_test_db.sh
+```
+
+Wenn Sie stattdessen PostgreSQL verwenden, erstellen Sie die Testdatenbank wie folgt:
+
+```
+% HZ_TEST_DB_TYPE=postgres ./tests/create_test_db.sh
+```
+
+Das Skript macht einige Annahmen über Ihre Einrichtung, aber alles ist über Umgebungsvariablen konfigurierbar. Falls Sie Anpassungen benötigen, finden Sie die Details im Quelltext des Skripts.
+
+##### Ausführen der Tests
+
+Sobald Sie die Entwicklerabhängigkeiten installiert und die Testdatenbank eingerichtet haben, können Sie die Tests wie folgt ausführen:
+
+```
+% ./vendor/bin/phpunit -c tests/phpunit.xml
+```
+
+Auch hier macht die Konfiguration in `tests/phpunit.xml` standardmäßig einige Annahmen über Ihre Einrichtung, die mit Hilfe von Umgebungsvariablen außer Kraft gesetzt werden können. Siehe `tests/phpunit.xml` für die Details.
+
+Sie können auch einen bestimmten Test oder eine bestimmte Gruppe von Tests ausführen, indem Sie das Argument `--filter` in PHPUnit verwenden:
+
+```
+% ./vendor/bin/phpunit -c tests/phpunit.xml --filter Autoname
+```
+
+Führt jeden Test mit der Bezeichnung „Autoname“ aus.
+
+##### Erzeugen von Abdeckungsberichten
+
+Um Abdeckungsberichte zu generieren, benötigen Sie einen Treiber, der in der Lage ist, die Abdeckungsinformationen zu generieren, die PHPUnit sammelt. Wir empfehlen [Xdebug], aber sehen Sie sich die PHPUnit [Seite zur Codeabdeckungsanalyse](https://docs.phpunit.de/en/9.6/code-coverage-analysis.html) für Alternativen und Details an.
+
+Wenn Xdebug richtig eingestellt ist, können Sie den Abdeckungsbericht wie folgt erzeugen:
+
+```
+% XDEBUG_MODE=coverage ./vendor/bin/phpunit -c ./tests/phpunit.xml
+```
+
+Dies erzeugt eine Reihe von HTML-Dateien in den Verzeichnissen des Unterverzeichnisses `tests/results/coverage/`.
+
+Öffnen Sie die Datei „index.php“ in Ihrem Webbrowser, um die Statistiken zu sehen.
+
+##### Fehlersuche
+
+Wenn Xdebug installiert ist, können Sie auch Step-Debugging und eine Reihe anderer Dinge tun, um die Tests zu debuggen und Informationen über ihre Ausführung zu erhalten. Lesen Sie auf den [Xdebug]-Seiten und in Ihrem bevorzugten Editor nach, wie Sie dies einrichten können.
+
+#### Teststruktur und Organisation
+
+Die Tests befinden sich im Unterverzeichnis `tests/unit` und in weiteren Unterverzeichnissen, die mehr oder weniger das Verzeichnislayout des Core Code Repository widerspiegeln.
+
+Tests werden als PHP-Klassen geschrieben, die die UnitTestCase-Klasse erweitern (zu finden in `tests/unit/UnitTestcase.php`). Der Dateiname und die darin enthaltene Testklasse sollten gleich lauten und *müssen* mit `Test.php` enden.
+
+Beispiele sind:
+
+- `tests/unit/includes/AccountTest.php`
+- `tests/unit/Lib/ActivityTest.php`
+
+Die Testklassen enthalten eine oder mehrere Testfunktionen, diese müssen öffentlich sein und mit dem Präfix `test` versehen werden.
+
+Hier ist ein Beispiel:
+
+```
+use Zotlabs\Tests\Unit\UnitTestCase;
+
+class AccountTest extends UnitTestCase {
+ public function test_get_account_by_id_returns_existing_account() {
+ $Konto = get_account_by_id(42);
+ $this->assertNotFalse($account);
+ $this->assertEquals(
+ $this->fixtures['account'][0]['account_email'],
+ $account['account_email']
+ );
+ }
+```
+
+Beachten Sie, dass wir versuchen, den Namen der Testfunktion so aussagekräftig wie möglich zu gestalten.
+Die Klasse kann auch eine beliebige Anzahl anderer Funktionen enthalten, um die Dinge ordentlich und übersichtlich zu halten. Diese können je nach Bedarf des Testcodes privat, geschützt oder öffentlich sein.
+Die Ergebnisse und Artefakte der Testläufe werden im Verzeichnis `tests/results/` abgelegt. Dazu gehören in der Regel das Testprotokoll, der Code Coverage Report usw.
+
+#### Hubzilla-spezifische Funktionen
+
+##### Testdatenbankzugriff:
+
+Idealerweise sollte es in der Lage sein, jeden Teil des Codes isoliert zu testen, wobei alle Abhängigkeiten durch Stubs, Mocks oder Fakes ersetzt werden sollten.
+
+Dies ist bei einer Legacy-Codebasis wie Hubzilla, die nicht im Hinblick auf Testbarkeit geschrieben wurde, nicht möglich. Aus diesem Grund verwenden wir eine separate Testdatenbank und stellen sie dem zu testenden Code mit denselben Mechanismen zur Verfügung, die auch Hubzilla normalerweise verwendet.
+Das bedeutet, dass jeder Code, der Datenbankabfragen ausführt, sich normal verhält, wenn er von einem Test aufgerufen wird.
+
+Damit dies funktioniert, stellen wir vor der Ausführung jeder Testfunktion eine Verbindung zur Datenbank her. Wir laden auch einige anfängliche Daten in die Datenbank, um sicherzustellen, dass die Dinge wie erwartet funktionieren und dass wir einen bekannten Zustand der Datenbank haben, wenn der Test läuft.
+Nach Beendigung des Tests wird die Testdatenbank gelöscht, so dass bei der nächsten Testdurchführung derselbe konsistente Zustand vorliegt.
+
+All dies wird von der Basisklasse `UnitTestCase` gehandhabt.
+
+##### Datenbank-Fixtures:
+
+Wir benötigen einige vorhersehbare Inhalte in der Datenbank, um Dinge wie die Protokollierung und andere Inhalte, die für die Tests im Allgemeinen nützlich sind, einzurichten. Dies sind Datenbank-Fixtures, die für jeden Testlauf in die Datenbank geladen werden.
+
+Die Datenbankfixtures befinden sich im Verzeichnis `tests/unit/include/dba/_files` und bestehen aus YAML-Dateien, wobei jede Datei den Inhalt einer Datenbanktabelle mit dem gleichen Namen wie die Datei selbst darstellt.
+
+Obwohl Datenbank-Fixtures sehr nützlich sind, versuchen wir, die Anzahl der Fixtures so gering wie möglich zu halten. In der Regel ist es besser, alle für einen bestimmten Test benötigten Inhalte im Test selbst hinzuzufügen.
+
+##### Fakes:
+
+Fakes sind Klassen, die wir an den zu testenden Code übergeben können und die genauso aussehen und sich genauso verhalten wie die Originalklasse (für den zu testenden Code). Sie sind nützlich, wenn wir Objekte an den zu testenden Code übergeben können, da wir sie vom Testcode aus vollständig kontrollieren können.
+
+Fakes befinden sich im Verzeichnis `tests/fakes`. \ No newline at end of file
diff --git a/doc/de/developer/tools_workflows.md b/doc/de/developer/tools_workflows.md
new file mode 100644
index 000000000..406d293a9
--- /dev/null
+++ b/doc/de/developer/tools_workflows.md
@@ -0,0 +1,5 @@
+### Tools und Arbeitsabläufe für Entwickler
+
+#### Hub-Snapshots
+
+Die Seite mit den [Hub-Snapshots](/hlp/de/adminmanual/hub_snapshot_tools.md) enthält Anweisungen und Skripte für die Erstellung vollständiger Snapshots eines Hubs, um den Wechsel zwischen konsistenten und vollständig bekannten Zuständen zu unterstützen. Dies ist nützlich, um Situationen zu vermeiden, in denen der Inhalt oder das Datenbankschema mit dem Code inkompatibel sein könnte. \ No newline at end of file
diff --git a/doc/de/developer/translations.md b/doc/de/developer/translations.md
new file mode 100644
index 000000000..db6730c6c
--- /dev/null
+++ b/doc/de/developer/translations.md
@@ -0,0 +1,57 @@
+### Übersetzungen
+
+Unsere Übersetzungen werden über Transifex verwaltet. Wenn Sie bei der Übersetzung von Hubzilla in eine andere Sprache helfen möchten, melden Sie sich auf transifex.com an, besuchen Sie [Transifex](https://www.transifex.com/Friendica/hubzilla/?f=&zid=pepecyb@hub.hubzilla.hu) und beantragen Sie die Mitgliedschaft in einem der bestehenden Sprachteams oder erstellen Sie ein neues. Benachrichtigen Sie einen der Hauptentwickler, wenn Sie ein Übersetzungsupdate haben, das zusammengeführt werden muss, oder fragen Sie, ob Sie es selbst zusammenführen können, wenn Sie mit Git und PHP vertraut sind. Wir haben eine Zeichenkettendatei namens 'messages.po', die gettext-kompatibel ist, und eine Handvoll E-Mail-Vorlagen, aus denen wir automatisch die Sprachdateien der Anwendung generieren.
+
+#### Der Übersetzungsprozess
+
+Die in der Benutzeroberfläche von Hubzilla verwendeten Strings werden bei [Transifex](https://www.transifex.com/Friendica/hubzilla/?f=&zid=pepecyb@hub.hubzilla.hu) übersetzt und dann
+in das Git-Repository auf github aufgenommen. Wenn Sie bei der Übersetzung helfen möchten
+einer Sprache helfen wollen, sei es bei der Korrektur von Begriffen oder bei der Übersetzung von Hubzilla in eine derzeit nicht unterstützte Sprache übersetzen möchten, registrieren Sie bitte ein Konto auf transifex.com und kontaktieren Sie dort das Übersetzungsteam.
+
+Hubzilla zu übersetzen ist ganz einfach. Verwenden Sie einfach das Online-Tool auf transifex. Wenn Sie
+Wenn Sie sich nicht mit Git & Co. herumschlagen wollen, ist das kein Problem, wir überprüfen den Status der Übersetzungen regelmäßig und importieren sie in den Quellbaum auf github, damit andere sie nutzen können.
+
+Wir nehmen nicht jede Übersetzung von transifex in den Quellbaum auf, um eine eine verstreute und gestörte Gesamterfahrung zu vermeiden. Als ungefähre Schätzung haben wir eine Untergrenze von 50% übersetzter Strings, bevor wir die Sprache einbeziehen. Diese Grenze richtet sich nur nach der Menge der übersetzten Zeichenfolgen, wobei wir davon ausgehen dass die wichtigsten Zeichenfolgen für die Benutzeroberfläche zuerst von einem Übersetzungsteam übersetzt Team übersetzt werden. Wenn Sie der Meinung sind, dass Ihre Übersetzung vor dieser Grenze brauchbar ist, kontaktieren Sie uns bitte und wir werden die Arbeit Ihres Teams wahrscheinlich in den Quellbaum aufnehmen.
+
+Wenn Sie Ihre Arbeit selbst in den Quellbaum aufnehmen möchten, können Sie dies gerne tun und kontaktieren Sie uns bei allen Fragen, die sich ergeben. Der Prozess ist einfach und Hubzilla wird mit allen notwendigen Werkzeugen geliefert.
+
+Der Speicherort der übersetzten Dateien im Quellbaum ist
+
+`/view/LNG-CODE/`
+
+wobei LNG-CODE der Code der verwendeten Sprache ist, z. B. de für Deutsch oder fr für Französisch.
+Für die E-Mail-Vorlagen (die *.tpl-Dateien) legen Sie sie einfach in das Verzeichnis und Sie sind fertig. Die übersetzten Strings kommen als „hmessages.po“-Datei von Transifex, die in die PHP-Datei übersetzt werden muss, die Hubzilla verwendet. Dazu Sie die Datei in das oben genannte Verzeichnis und verwenden Sie das Dienstprogramm „po2php“ aus dem aus dem Verzeichnis util Ihrer Hubzilla-Installation.
+
+Angenommen, Sie möchten die deutsche Lokalisierung konvertieren, die sich in view/de/hmessages.po steht, würden Sie wie folgt vorgehen.
+
+1. Navigieren Sie an der Eingabeaufforderung in das Basisverzeichnis Ihrer
+
+Hubzilla-Installation
+
+2. Führen Sie das po2php-Skript aus, das die Übersetzung
+
+in die Datei hstrings.php einfügt, die von Hubzilla verwendet wird.
+
+`$> php util/po2php.php view/de/hmessages.po`
+
+Die Ausgabe des Skripts wird in der Datei view/de/hstrings.php platziert, wo froemdoca es erwartet, so dass Sie Ihre Übersetzung sofort testen können.
+
+3. Besuchen Sie Ihre Hubzilla-Seite und überprüfen Sie, ob sie in der Sprache ist, die Sie gerade übersetzt haben. Wenn nicht, versuchen Sie, den Fehler zu finden, wahrscheinlich gibt PHP Ihnen einen Hinweis im Log/Warnings.über den Fehler.
+
+Zur Fehlersuche können Sie auch versuchen, die Datei mit PHP „auszuführen“. Dies sollte keine Ausgabe geben, wenn die Datei in Ordnung ist, aber vielleicht einen Hinweis für die Fehler in der Datei zu finden.
+
+`$> php view/de/hstrings.php`
+
+4. Übertragen Sie die beiden Dateien mit einer aussagekräftigen Commit-Nachricht in Ihr Git Repository, pushen Sie es zu Ihrem Fork des Hubzilla Repository auf github und stellen einen Pull Request für diesen Commit.
+
+#### Dienstprogramme
+
+Zusätzlich zum po2php-Skript gibt es einige weitere Hilfsprogramme für die Übersetzung im Verzeichnis „util“ des Hubzilla-Quellbaums. Wenn Sie nur Hubzilla in eine andere Sprache übersetzen wollen, werden Sie wahrscheinlich keines dieser Werkzeuge aber es gibt Ihnen eine Vorstellung davon, wie der Übersetzungsprozess von Hubzilla funktioniert.
+
+Weitere Informationen finden Sie in der Datei utils/README.
+
+#### Bekannte Probleme
+
+- Hubzilla verwendet die Spracheinstellung des Browsers des Besuchers, um die Sprache für die Benutzeroberfläche. Meistens funktioniert dies, aber es gibt einige bekannte Macken.
+
+- Die frühen Übersetzungen basieren auf den Friendica-Übersetzungen, wenn Sie einige grobe Übersetzungen finden, lassen Sie es uns bitte wissen oder korrigieren Sie sie bei Transifex.
diff --git a/doc/de/developer/unorganized.md b/doc/de/developer/unorganized.md
new file mode 100644
index 000000000..fd668f49e
--- /dev/null
+++ b/doc/de/developer/unorganized.md
@@ -0,0 +1,56 @@
+### Noch zu organisierende Informationen
+
+**Hier ist, wie Sie sich uns anschließen können.**
+Richten Sie sich zunächst ein funktionierendes Git-Paket auf dem System ein, auf dem Sie entwickeln werden.
+
+Erstellen Sie Ihr eigenes Github-Konto.
+
+Sie können das Red-Repository von https://framagit.org/hubzilla/core.git forken/klonen.
+Folgen Sie den Anweisungen hier: [http://help.github.com/fork-a-repo/](http://help.github.com/fork-a-repo/), um Ihren eigenen Tracking-Fork auf github zu erstellen und zu verwenden.
+
+Gehen Sie dann auf Ihre Github-Seite und erstellen Sie einen „Pull request“, wenn Sie bereit sind, uns mitzuteilen, dass Ihre Arbeit zusammengeführt werden soll.
+
+**Wichtig**
+
+Bitte ziehen Sie alle Änderungen aus dem Projekt-Repository ein und fügen Sie sie mit Ihrer Arbeit zusammen **, bevor** Sie einen Pull-Request erstellen. Wir behalten uns das Recht vor, jeden Patch abzulehnen, der zu einer großen Anzahl von Merge-Konflikten führt. Dies gilt insbesondere für Sprachübersetzungen, bei denen wir möglicherweise nicht in der Lage sind, die feinen Unterschiede zwischen den konfligierenden Versionen zu verstehen.
+
+**Testen** Sie außerdem **Ihre Änderungen**. Gehen Sie nicht davon aus, dass eine einfache Korrektur nicht etwas anderes kaputt macht. Bitten Sie nach Möglichkeit einen erfahrenen Red-Entwickler, den Code zu überprüfen.
+
+**Wie man Hubzilla thematisiert**
+Dies ist eine kurze Dokumentation über das, was ich beim Versuch, das Aussehen von Hubzilla zu ändern, herausgefunden habe.
+
+Zuerst müssen Sie ein neues Thema erstellen. Dieses befindet sich in /view/theme, und ich habe mich für „redbasic“ entschieden, da es im Moment das einzig verfügbare ist. Nehmen wir an, ich nenne es .
+Oh, und vergessen Sie nicht, die Funktion _init in /php/theme.php in _init() statt redbasic_init() umzubenennen.
+
+Wenn Sie nun Javascript- oder CSS-Dateien hinzufügen müssen, fügen Sie sie zu /js oder /css hinzu und „registrieren“ sie dann in `_init()` durch `head_add_js('file.js')` und `head_add_css('file.css')`.
+Jetzt werden Sie wahrscheinlich eine Vorlage ändern wollen. Diese sind in /view/tpl ODER view//tpl zu finden. Alles, was Sie tun müssen, ist, das, was Sie ändern wollen, in das tpl-Verzeichnis Ihres Themes zu kopieren.
+
+Wir sind ziemlich entspannt, wenn es um Entwickler geht. Wir haben nicht viele Regeln. Einige von uns sind überlastet, und wenn Sie helfen wollen, lassen wir Sie gerne helfen. Wenn Sie sich jedoch an ein paar Richtlinien halten, wird der Prozess reibungsloser und die Zusammenarbeit einfacher. Wir haben Entwickler aus der ganzen Welt mit unterschiedlichen Fähigkeiten, unterschiedlichen kulturellen Hintergründen und unterschiedlicher Geduld. Unsere oberste Regel ist es, andere zu respektieren. Manchmal ist das schwierig, und manchmal haben wir sehr unterschiedliche Ansichten darüber, wie die Dinge funktionieren sollten, aber wenn sich jeder bemüht, werden wir gut miteinander auskommen.
+
+**Hier ist, wie Sie sich uns anschließen können.**
+
+Besorgen Sie sich zunächst ein funktionierendes Git-Paket auf dem System, auf dem Sie die Entwicklung durchführen werden.
+
+Erstellen Sie Ihr eigenes Github-Konto.
+
+Du kannst das Hubzilla-Repository von https://framagit.org/hubzilla/core.git forken/klonen.
+Folgen Sie den Anweisungen hier: [http://help.github.com/fork-a-repo/](http://help.github.com/fork-a-repo/), um Ihren eigenen Tracking-Fork auf github zu erstellen und zu verwenden.
+
+Gehen Sie dann auf Ihre Github-Seite und erstellen Sie eine „Pull-Anfrage“, wenn Sie bereit sind, uns mitzuteilen, dass Ihre Arbeit zusammengeführt werden soll.
+
+**Wichtig**
+
+Bitte ziehen Sie alle Änderungen aus dem Projekt-Repository ein und fügen Sie sie mit Ihrer Arbeit zusammen **, bevor** Sie einen Pull-Request erstellen. Wir behalten uns das Recht vor, jeden Patch abzulehnen, der zu einer großen Anzahl von Merge-Konflikten führt. Dies gilt insbesondere für Sprachübersetzungen, bei denen wir möglicherweise nicht in der Lage sind, die feinen Unterschiede zwischen den konfligierenden Versionen zu verstehen.
+
+**Testen** Sie außerdem **Ihre Änderungen**. Gehen Sie nicht davon aus, dass eine einfache Korrektur nicht etwas anderes kaputt macht. Bitten Sie nach Möglichkeit einen erfahrenen Red-Entwickler, den Code zu überprüfen.
+
+Weitere Dokumentation finden Sie auf den Github-Wikiseiten unter: https://github.com/friendica/red/wiki
+
+**Konsensbildung**
+
+Codeänderungen, die einen offensichtlichen Fehler beheben, sind ziemlich einfach. Wenn Sie zum Beispiel auf „Speichern“ klicken und das, was Sie zu speichern versuchen, nicht gespeichert wird, ist es ziemlich offensichtlich, was das beabsichtigte Verhalten sein sollte. Bei der Entwicklung von Funktionsanfragen kann es vorkommen, dass eine große Anzahl von Community-Mitgliedern davon betroffen ist und dass andere Mitglieder der Community nicht mit der Notwendigkeit der Funktion oder der von Ihnen vorgeschlagenen Implementierung einverstanden sind. Es kann sein, dass sie etwas nicht als Fehler oder als wünschenswerte Funktion ansehen.
+
+Wir ermutigen zur Konsensbildung innerhalb der Gemeinschaft, wenn es um eine Funktion geht, die als kontrovers angesehen werden könnte oder wenn es keine einstimmige Entscheidung gibt, dass die vorgeschlagene Funktion der richtige Weg ist, um die Aufgabe zu erfüllen. Die erste Anlaufstelle für Ihre Ideen ist [Channel One](https://zothub.com/channel/one)]. Andere haben vielleicht Anregungen oder können Sie auf Aspekte Ihres Konzepts hinweisen, die in unserem Umfeld problematisch sein könnten. Vielleicht stoßen Sie aber auch auf Widerstand gegen Ihren Plan. Das bedeutet nicht, dass Sie aufhören und/oder die Funktion ignorieren sollten. Hören Sie sich die Bedenken der anderen an und versuchen Sie, alle Probleme bei der Umsetzung zu lösen.
+
+Es gibt Stellen, an denen Widerstände nicht ausgeräumt werden können. In diesen Fällen sollten Sie in Erwägung ziehen, Ihre Funktion **optional** zu machen oder ein nicht standardmäßiges Verhalten vorzusehen, das ausdrücklich aktiviert werden muss. Diese Technik kann oft verwendet werden, wenn eine Funktion eine bedeutende, aber nicht einstimmige Unterstützung hat. Diejenigen, die die Funktion wünschen, können sie einschalten, und diejenigen, die sie nicht wollen, können sie ausgeschaltet lassen.
+Wenn eine Funktion andere Netzwerke oder Websites nutzt oder nur von einer kleinen Minderheit der Gemeinschaft als wünschenswert angesehen wird, sollten Sie in Erwägung ziehen, die Funktion über ein Addon oder Plugin verfügbar zu machen. Auch hier gilt, dass diejenigen, die die Funktion nicht wünschen, sie auch nicht installieren müssen. Plugins sind relativ einfach zu erstellen und „Haken“ können leicht hinzugefügt oder geändert werden, wenn die aktuellen Haken nicht das tun, was nötig ist, damit Ihr Plugin funktioniert. \ No newline at end of file
diff --git a/doc/de/developer/versions.md b/doc/de/developer/versions.md
new file mode 100644
index 000000000..d3301e2f9
--- /dev/null
+++ b/doc/de/developer/versions.md
@@ -0,0 +1,23 @@
+### Versionen und Releases
+
+Hubzilla verwendet derzeit eine Standard-Versionsnummerierungssequenz von $x.$y(.$z), zum Beispiel '1.12' oder '1.12.1'. Die erste Ziffer ist die Hauptversionsnummer. Hauptversionen werden „ungefähr“ einmal pro Jahr veröffentlicht, oft im Dezember.
+
+Die zweite Ziffer ist die Nummer der Nebenversion. Wenn diese Zahl ungerade ist, handelt es sich um eine Entwicklungsversion. Ist die Zahl gerade, handelt es sich um eine freigegebene Version. Nebenversionen werden in der Regel einmal pro Monat freigegeben (von der Entwicklungs- in die Hauptversion verschoben), wenn die Entwicklung „stabil“ ist, aber das wird wahrscheinlich zunehmen. Zukünftig werden kleinere Versionen zwischen einem und drei Monaten veröffentlicht; das entspricht einem stabilen Codepunkt und wenn es einen allgemeinen Konsens in der Gemeinschaft gibt, dass die aktuelle Codebasis stabil genug ist, um eine Veröffentlichung in Betracht zu ziehen.
+
+Die letzte Ziffer ist eine Schnittstellen- oder Patch-Kennung.
+
+Der Freigabeprozess beinhaltet die Änderung der Versionsnummer (per Definition ist die Minor-Versionsnummer ungerade, und die Minor-Nummer wird erhöht). Einmal im Jahr wird bei einer Hauptversion die Hauptversionsnummer erhöht und die Nebenversionsnummer auf 0 zurückgesetzt.
+
+Der Versionskandidat wird in einen neuen Zweig verschoben und die Tests beginnen bzw. werden für einen Zeitraum von 1-2 Wochen fortgesetzt, oder bis alle wichtigen Probleme behoben sind. Dieser Zweig wird in der Regel mit RC (Release Candidate) bezeichnet; beispielsweise steht 1.8RC für die bevorstehende Veröffentlichung der Version 1.8. Zu diesem Zeitpunkt wird die Versionsnummer des Entwicklungszweigs auf die nächsthöhere ungerade Zahl erhöht. (Zum Beispiel 1.9). Neue Entwicklungen können dann im Entwicklungszweig stattfinden.
+
+Fehlerkorrekturen sollten immer auf „dev“ angewendet werden und von dort aus (in der Regel mit git cherry-pick) auf den RC-Zweig und, falls erforderlich, auf den Master- oder offiziellen Release-Zweig übertragen werden.
+
+Zum Zeitpunkt der Erstellung eines Release Candidate wird die Sprachstring-Datei eingefroren, bis eine neue Version veröffentlicht wird. Die Übersetzungsarbeit kann fortgesetzt werden, aber alle Übersetzungen sollten an „dev“ übermittelt und in RC zusammengeführt werden.
+
+Sobald die RC-Tests abgeschlossen sind, wird RC mit 'master' zusammengeführt und der RC-Versionsbezeichner entfernt; dies führt zu einem letzten Checkin, um die Versionsnummer zu ändern. Die CHANGELOG-Datei sollte ebenfalls zu diesem Zeitpunkt oder kurz davor aktualisiert werden. Wenn es während dieser letzten Zusammenführung Konflikte gibt, wird die Zusammenführung abgebrochen und „git merge -s ours“ angewendet. Dies führt dazu, dass der Master-Zweig durch den Inhalt des RC-Zweigs ersetzt wird. Konflikte entstehen oft durch String-Updates, die nach der letzten Veröffentlichung an master vorgenommen wurden und nicht einfach ohne manuelle Bearbeitung gelöst werden können. Da es sich hier um eine Veröffentlichung von getesteten Code handelt, wird von einer manuellen Bearbeitung abgeraten und stattdessen sollte die Strategie des Replacement Merge verwendet werden. Es wird davon ausgegangen, dass RC nun den neuesten, gut getesteten Code enthält.
+
+Sobald die Veröffentlichung live ist und in den Master-Zweig überführt wurde, kann der RC-Zweig entfernt werden.
+
+Nach der Veröffentlichung können Korrekturen an Master vorgenommen werden. Wenn möglich, sollten diese in dev vorgenommen werden und „git cherry-pick“ zum Zusammenführen verwendet werden; dadurch bleiben die Commit-Informationen erhalten und Konflikte beim Zusammenführen im nächsten Zyklus werden vermieden. Nur selten gilt ein Patch nur für den Master-Zweig. Falls nötig, kann das gemacht werden. Wenn die Änderung schwerwiegend ist, sollte die Versionsnummer der Schnittstelle erhöht werden. Dies liegt im Ermessen der Gemeinschaft. In jedem Fall sollte ein „git pull“ des Master-Zweigs immer zur neuesten Version führen, auf die alle Patches nach der Veröffentlichung angewendet werden.
+
+Die Schnittstellennummer (das $z in $x.$y.$z) sollte in dev immer dann erhöht werden, wenn eine Änderung vorgenommen wird, die die Schnittstellen oder die API in inkompatibler Weise verändert, so dass alle externen Pakete (insbesondere Addons und API-Clients), die sich auf das aktuelle Verhalten verlassen, ihre eigenen Schnittstellen an dem Punkt, an dem es sich geändert hat, entdecken und entsprechend ändern können. \ No newline at end of file
diff --git a/doc/de/developer/who_is_a_hubzilla_developer.md b/doc/de/developer/who_is_a_hubzilla_developer.md
new file mode 100644
index 000000000..23ffcf6bd
--- /dev/null
+++ b/doc/de/developer/who_is_a_hubzilla_developer.md
@@ -0,0 +1,14 @@
+### Wer ist ein Hubzilla-Entwickler? Sollte ich das lesen?
+
+Jeder, der dazu beiträgt, Hubzilla besser zu machen, ist ein Entwickler. Es gibt viele verschiedene und wichtige Möglichkeiten, wie Sie zu dieser erstaunlichen Technologie beitragen können, *auch wenn Sie nicht wissen, wie man Code schreibt*. Die Software selbst ist nur ein Teil des Hubzilla-Projekts. Sie können beitragen durch
+
+- den Text in Ihre Sprache übersetzen, damit Menschen auf der ganzen Welt die Möglichkeit haben, Hubzilla zu nutzen
+- für Hubzilla werben und das Bewusstsein für die Plattform durch Blogbeiträge, Artikel und Mundpropaganda verbreiten
+- Erstellung von Kunstwerken und Grafiken für Projektressourcen wie Icons und Marketingmaterial
+- Unterstützung der Projektinfrastruktur wie der Projektwebsite und der Demoserver
+
+*Softwareentwickler* sind natürlich willkommen; es gibt so viele großartige Ideen, die umgesetzt werden können, und nicht genug Leute, um sie alle zu verwirklichen! Die Hubzilla-Codebasis ist ein fortschrittliches und ausgereiftes System, aber die Plattform ist immer noch sehr flexibel und offen für neue Ideen.
+
+Wir sind ziemlich entspannt, wenn es um Entwickler geht. Bei uns gibt es nicht viele Regeln. Einige von uns sind überlastet, und wenn Sie helfen wollen, lassen wir Sie gerne helfen. Wenn Sie sich jedoch an ein paar Richtlinien halten, wird der Prozess reibungsloser und die Zusammenarbeit einfacher. Von allen Entwicklern wird erwartet, dass sie sich an unseren [Verhaltenskodex](/help/de/developer/code_of_conduct.md) halten. Wir haben Entwickler aus der ganzen Welt mit unterschiedlichen Fähigkeiten, unterschiedlichen kulturellen Hintergründen und unterschiedlich viel Geduld. Unsere oberste Regel ist es, andere zu respektieren. Manchmal ist das schwierig, und manchmal haben wir sehr unterschiedliche Ansichten darüber, wie die Dinge funktionieren sollten, aber wenn sich jeder bemüht, werden wir gut miteinander auskommen.
+
+Dieses Dokument wird Ihnen dabei helfen, Hubzilla kennenzulernen und mitzugestalten. \ No newline at end of file
diff --git a/doc/de/developer/zot_protocol.md b/doc/de/developer/zot_protocol.md
new file mode 100644
index 000000000..67252eddf
--- /dev/null
+++ b/doc/de/developer/zot_protocol.md
@@ -0,0 +1,47 @@
+### Das Nomad Protokoll
+
+#### Was ist Nomad?
+
+Nomad ist das revolutionäre Protokoll, das Hubzilla antreibt und die **Kommunikation**, das **Identitätsmanagement** und die **Zugangskontrolle** in einem vollständig **dezentralisierten** Netzwerk unabhängiger Websites, oft als „das Grid“ bezeichnet, ermöglicht. Die daraus resultierende Plattform ist ein robustes System, das den Datenschutz und die Sicherheit unterstützt und gleichzeitig die Art von umfangreichen Webdiensten ermöglicht, die normalerweise nur in zentralisierten, proprietären Lösungen zu finden sind.
+
+Betrachten Sie dieses typische Szenario:
+
+Jaquelina möchte Fotos von ihrem Blog unter **jaquelina.org** mit Roberto teilen, aber mit niemandem sonst. Roberto unterhält seinen eigenen Familien-Hub unter **roberto.net** auf einem völlig unabhängigen Server. Nomad erlaubt Jaquelina, ihre Fotos mit einer *Zugriffskontrollliste (ACL)* zu veröffentlichen, die nur Roberto einschließt. Das bedeutet, dass Roberto die Fotos zwar sehen kann, wenn er ihren Blog besucht, sein Bruder Marco jedoch nicht, und auch kein anderes Familienmitglied, das ein Konto auf **roberto.net** hat.
+
+Der Clou an diesem Szenario ist die Tatsache, dass Roberto sich nie auf Jaquelinas Website angemeldet hat. Stattdessen musste er sich nur einmal mit seinem Passwort auf seiner *eigenen* Website **roberto.net** anmelden. Wenn Roberto **jaquelina.org** besucht, wird er von ihrem Hub nahtlos authentifiziert, indem sein Server im Hintergrund abgefragt wird.
+
+Es ist nicht ungewöhnlich, dass Server technische Probleme haben oder aus verschiedenen Gründen unzugänglich werden. Nomad bietet Robustheit für Robertos Online-Aktivitäten, indem es ihm erlaubt, *Klone* seiner Online-Identität oder seines *Kanals* auf mehreren unabhängigen Hubs zu haben. Stellen Sie sich vor, dass Robertos Server aus irgendeinem Grund zusammenbricht und er sich dort nicht mehr einloggen kann. Er meldet sich einfach bei einem seiner Klone auf **gadfly.com** an, einer Website, die von seinem Freund Peter betrieben wird. Sobald er sich bei **gadfly.com** authentifiziert hat, kann Roberto Jaquelinas Blog wie zuvor ansehen, ohne dass Jaquelina zusätzlichen Zugang gewähren muss!
+
+#### Kommunikation
+
+Kommunikation und soziale Netzwerke sind ein wesentlicher Bestandteil des Grids. Jeder Kanal (und jeder Dienst, der von diesem Kanal bereitgestellt wird) kann die funktionsreiche soziale Kommunikation auf globaler Ebene in vollem Umfang nutzen. Diese Kommunikation kann öffentlich oder privat sein - und private Kommunikation umfasst nicht nur einen vollständig verschlüsselten Transport, sondern auch eine verschlüsselte Speicherung zum Schutz vor versehentlichem Schnüffeln und Offenlegung durch unseriöse Systemadministratoren und Internetdienstanbieter.
+
+Nomad unterstützt eine breite Palette von Hintergrunddiensten im Grid, von Freundschaftsvorschlägen bis hin zu Verzeichnisdiensten. Neue Inhalte und Datenaktualisierungen werden im Hintergrund zwischen den Hubs im gesamten Grid gemäß den Zugriffskontrolllisten und den von den *Sender- und* Empfängerkanälen festgelegten Berechtigungen weitergegeben. Die Daten werden auch zwischen einer beliebigen Anzahl von Kanalklonen synchronisiert, so dass Hub-Mitglieder auch dann auf Daten zugreifen und nahtlos weiter zusammenarbeiten können, wenn ihr primärer Hub nicht erreichbar oder offline ist.
+
+#### Identität
+
+Die Identitätsschicht von Nomad ist einzigartig. Sie bietet ein **unsichtbares Single Sign-On** für alle Standorte im Grid.
+
+Sie bietet auch eine **nomadische Identität**, so dass Ihre Kommunikation mit Freunden, Familie oder anderen Personen, mit denen Sie kommunizieren, nicht durch den Verlust Ihres primären Kommunikationsknotens beeinträchtigt wird - weder vorübergehend noch dauerhaft.
+
+Die wichtigen Teile Ihrer Identität und Ihrer Beziehungen können auf einem USB-Stick oder Ihrem Laptop gesichert werden und jederzeit an einem beliebigen Knoten im Netz erscheinen - mit all Ihren Freunden und Vorlieben.
+
+Entscheidend ist, dass diese nomadischen Instanzen synchron gehalten werden, so dass jede Instanz übernehmen kann, wenn eine andere gefährdet oder beschädigt ist. Dies schützt Sie nicht nur vor größeren Systemausfällen, sondern auch vor vorübergehender Überlastung der Website und staatlicher Manipulation oder Zensur.
+
+Wir sind der Meinung, dass die nomadische Identität, die einmalige Anmeldung und die Dezentralisierung von Hubzilla ein hohes Maß an **Widerstandsfähigkeit** und **Beständigkeit** in die Internetkommunikation einbringen, die angesichts der weltweiten Tendenzen zur Zentralisierung von Unternehmen sowie der massenhaften und wahllosen staatlichen Überwachung und Zensur dringend benötigt werden.
+
+Beim Durchsuchen des Netzes, beim Anzeigen von Kanälen und deren einzigartigen Inhalten, werden Sie nahtlos authentifiziert, sogar über völlig unterschiedliche Server-Hubs hinweg. Sie müssen keine Passwörter eingeben. Sie müssen nichts eintippen. Sie werden auf jeder neuen Seite, die Sie besuchen, einfach mit Ihrem Namen begrüßt.
+
+Wie funktioniert das bei Nomad? Wir nennen es **„magic-auth“**, weil Hubzilla die Details der Komplexität von Single-Sign-On-Logins und nomadischen Identitäten vor dem Surfen im Netz verbirgt. Dies ist eines der Designziele von Hubzilla: die Privatsphäre und die Freiheit im Internet zu erhöhen und gleichzeitig die Komplexität und die Langeweile zu reduzieren, die durch die Notwendigkeit entstehen, für jeden Besuch im Internet neue Passwörter und Anmeldenamen einzugeben. Sie melden sich nur einmal auf Ihrem Home-Hub (oder einem von Ihnen gewählten nomadischen Backup-Hub) an. Dadurch können Sie auf alle authentifizierten Dienste zugreifen, die überall im Netz angeboten werden - wie Einkaufen, Blogs, Foren und Zugang zu privaten Informationen. Ihr Passwort wird nicht auf tausend verschiedenen Websites gespeichert, sondern auf Servern, die Sie kontrollieren oder denen Sie vertrauen.
+
+Sie können nicht zum Schweigen gebracht werden. Sie können nicht aus dem Netz entfernt werden, es sei denn, Sie selbst entscheiden sich dafür, es zu verlassen.
+
+#### Zugangskontrolle
+
+Die Identitätsschicht von Nomad ermöglicht es Ihnen, fein abgestufte Berechtigungen für jeden Inhalt zu vergeben, den Sie veröffentlichen möchten - und diese Berechtigungen erstrecken sich über das gesamte Grid. Das ist so, als hätte man eine riesige Website, die aus einer Armee kleiner individueller Websites besteht - und bei der jeder Kanal im Grid seine Datenschutz- und Freigabepräferenzen für alle von ihm erstellten Webressourcen vollständig kontrollieren kann.
+
+Derzeit unterstützt Hubzilla die Zugriffskontrolle für viele Datentypen, darunter Diskussionsbeiträge und -kommentare, Fotoalben, Veranstaltungen, Cloud-Dateien, Webseiten, Wikis und mehr. Jedes Objekt und die Art und Weise, wie und mit wem es geteilt wird, unterliegt vollständig Ihrer Kontrolle.
+
+Diese Art der Kontrolle ist bei großen Unternehmensanbietern trivial, da sie die Benutzerdatenbank besitzen. Im Grid brauchen Sie keine riesige Benutzerdatenbank auf Ihrem Rechner - denn das Grid **ist** Ihre Benutzerdatenbank. Sie verfügt über eine im Grunde unendliche Kapazität (begrenzt durch die Gesamtzahl der Hubs, die im Internet online sind) und ist auf Hunderte, möglicherweise sogar Millionen von Computern verteilt.
+
+Der Zugang kann für jede Ressource, jeden Kanal oder jede Gruppe von Kanälen gewährt oder verweigert werden - überall im Grid. Andere können auf Ihre Inhalte zugreifen, wenn Sie es ihnen erlauben, und sie müssen nicht einmal ein Konto in Ihrem Hub haben. \ No newline at end of file
diff --git a/doc/de/developer/zot_structures.md b/doc/de/developer/zot_structures.md
new file mode 100644
index 000000000..c3f77c8b7
--- /dev/null
+++ b/doc/de/developer/zot_structures.md
@@ -0,0 +1,141 @@
+### Nomad-Strukturen
+
+#### Nomad-Signaturen
+
+Alle signierten Daten in Nomad werden durch eine RSA-Signaturoperation mit dem privaten Schlüssel des Initiators erzeugt. Das binäre Ergebnis wird dann für den Transport mit base64url kodiert.
+
+#### Nomad-Verschlüsselung
+
+Die Verschlüsselung erfolgt derzeit mit AES256CTR. Weitere Algorithmen KÖNNEN unterstützt werden. Ein 32-Oktett-Schlüssel und ein 16-Oktett-Initialisierungsvektor werden nach dem Zufallsprinzip erzeugt. Die gewünschten Daten werden dann mit diesen generierten Zeichenketten verschlüsselt und das Ergebnis base64url-kodiert. Dann wird ein Array erstellt:
+
+- data
+
+ Die base64url-kodierten verschlüsselten Daten
+
+- alg
+
+ Der gewählte Algorithmus, in diesem Fall die Zeichenkette „aes256ctr“.
+
+- key
+
+ Der zufällig erzeugte Schlüssel, RSA-verschlüsselt mit dem öffentlichen Schlüssel des Empfängers, und das Ergebnis base64url-kodiert
+
+- iv
+
+ Der zufällig erzeugte Initialisierungsvektor, RSA-verschlüsselt mit dem öffentlichen Schlüssel des Empfängers, und das Ergebnis base64url-kodiert
+
+#### Grundlegendes Nomad-Paket
+
+Wird verwendet, um einen Dialog mit einer anderen Nomad-Site einzuleiten. Dieses Paket KANN verschlüsselt sein. Das Vorhandensein eines Array-Elements 'iv' zeigt an, dass eine Verschlüsselung vorgenommen wurde. Beim Senden eines 'auth_check'-Pakets MUSS dieses Paket verschlüsselt werden, wobei der öffentliche Schlüssel der Zielsite (der Site-Schlüssel, im Gegensatz zu einem Absenderschlüssel) verwendet wird.
+
+```
+ {
+ "type":"notify",
+ "sender":{
+ "guid":"kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA",
+ "guid_sig":"PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK-R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q",
+ "url":"http:\/\/podunk.edu",
+ "url_sig":"T8Bp7j5DHHhQDCFcAHXfuhUfGk2P3inPbImwaXXF1xJd3TGgluoXyyKDx6WDm07x0hqbupoAoZB1qBP3_WfvWiJVAK4N1FD77EOYttUEHZ7L43xy5PCpojJQmkppGbPJc2jnTIc_F1vvGvw5fv8gBWZvPqTdb6LWF6FLrzwesZpi7j2rsioZ3wyUkqb5TDZaNNeWQrIEYXrEnWkRI_qTSOzx0dRTsGO6SpU1fPWuOOYMZG8Nh18nay0kLpxReuHCiCdxjXRVvk5k9rkcMbDBJcBovhiSioPKv_yJxcZVBATw3z3TTE95kGi4wxCEenxwhSpvouwa5b0hT7NS4Ay70QaxoKiLb3ZjhZaUUn4igCyZM0h6fllR5I6J_sAQxiMYD0v5ouIlb0u8YVMni93j3zlqMWdDUZ4WgTI7NNbo8ug9NQDHd92TPmSE1TytPTgya3tsFMzwyq0LZ0b-g-zSXWIES__jKQ7vAtIs9EwlPxqJXEDDniZ2AJ6biXRYgE2Kd6W_nmI7w31igwQTms3ecXe5ENI3ckEPUAq__llNnND7mxp5ZrdXzd5HHU9slXwDShYcW3yDeQLEwAVomTGSFpBrCX8W77n9hF3JClkWaeS4QcZ3xUtsSS81yLrp__ifFfQqx9_Be89WVyIOoF4oydr08EkZ8zwlAsbZLG7eLXY"
+ "sitekey":"-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxTeIwXZWrw/S+Ju6gewh
+LgkKnNNe2uCUqCqMZoYgJar3T5sHCDhvXc4dDCbDkxVIaA/+V1mURtBV60a3IGjn
+OAO0W0XGGLe2ED7G5o9U8T9mVGq8Mauv0v1oQ5wIR1gEAhBavkQ2OUGuF/YKn2nj
+HlKsv9HzUAHpcDMUe3Uklc2RhQbMcnJxEgkyjCkDyrTtCZzISkTAocHvpCG1KSog
+njUZdiz9UWxvM4rCFkCJvQU4RwRZJb7GA9ul+9JrF7NvUQTx8csRP2weBk1E9yyj
+wbe187E0eVj9RXX2Mx3mYhgrTdodxLOVMSXZLg1/SMpeFFl7QBhuM0SiOPg8a7Et
+e2iNA/RD4WiUFqCDfafasRa1TOozOm7LA+08lkAh5PeQPJsJbrX0wVVft++Y+5/z
+BvcUOP73vcbz7j5hJ7HLsbQtye/UUCfODBFybuDqRM84Aet8rjZohX7vukXdMD4I
+2HuB7pjR4uIfyYr0J63ANkvbsn8LR+RnirmHrK5H/OgxxjXcfYbGEQgFxvxhF6lA
+FpMu6Do4dx3CIb6pRmZ8bjSImXexJ0BSo9n3gtrz0XYLecsYFlQ9+QQjm83qxyLb
+M23in0xqMVsyQvzjNkpImrO/QdbEFRIIMee83IHq+adbyjQR49Z2hNEIZhkLPc3U
+2cJJ2HkzkOoF2K37qwIzk68CAwEAAQ==
+-----END PUBLIC KEY-----
+"
+ },
+ "recipients":{
+ {
+ "guid":"lql-1VnxtiO4-WF0h72wLX1Fu8szzHDOXgQaTbELwXW77k8AKFfh-hYr70vqMrc3SSvWN-Flrc5HFhRTWB7ICw",
+ "guid_sig":"PafvEL0VpKfxATxlCqDjfOeSIMdmpr3iU7X-Sysa1h5LzDpjSXsjO37tYZL-accb1M5itLlfnW5epkTa5I4flsW21zSY1A2jCuBQUTLLGV7rNyyBy7lgqJUFvAMRx0TfXzP9lcaPqlM9T1tA6jfWOsOmkdzwofGeXBnsjGfjsO2xdGYe6vwjOU0DSavukvzDMnOayB9DekpvDnaNBTxeGLM45Skzr7ZEMcNF7TeXMbnvpfLaALYEKeQs9bGH-UgAG8fBWgzVAzeBfx_XSR1rdixjyiZGP0kq0h35SlmMPcEjliodOBFwMXqpXFB7Ibp4F6o6te2p2ErViJccQVG8VNKB6SbKNXY6bhP5zVcVsJ-vR-p4xXoYJJvzTN7yTDsGAXHOLF4ZrXbo5yi5gFAlIrTLAF2EdWQwxSGyLRWKxG8PrDkzEzX6cJJ0VRcLh5z6OI5QqQNdeghPZbshMFMJSc_ApCPi9_hI4ZfctCIOi3T6bdgTNKryLm5fhy_eqjwLAZTGP-aUBgLZpb1mf2UojBn6Ey9cCyq-0T2RWyk-FcIcbV4qJ-p_8oODqw13Qs5FYkjLr1bGBq82SuolkYrXEwQClxnrfKa4KYc2_eHAXPL01iS9zVnI1ySOCNJshB97Odpooc4wk7Nb2Fo-Q6THU9zuu0uK_-JbK7IIl6go2qA"
+ },
+ },
+ "callback":"\/post",
+ "version":"1.2",
+ "encryption":{
+ "aes256ctr"
+ },
+ "secret":"1eaa6613699be6ebb2adcefa5379c61a3678aa0df89025470fac871431b70467",
+ "secret_sig":"0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q"
+ }
+```
+
+type
+
+Der Nachrichtentyp: **notify, purge, refresh, force_refresh, auth_check, ping** oder **pickup**. Der Inhalt der Pakete variiert je nach Nachrichtentyp. Hier wird das **notify-Paket** beschrieben.
+
+
+
+callback
+
+Eine Zeichenkette, die an die URL angehängt wird und den Nomad-Kommunikationsendpunkt auf diesem System identifiziert. In der Regel ist dies die Zeichenfolge „/post“.
+
+
+
+version
+
+Die Kennung des Nomad-Protokolls, damit künftige Protokollrevisionen nebeneinander bestehen können.
+
+
+
+encryption
+
+Array der unterstützten Verschlüsselungsalgorithmen, geordnet nach abnehmender Präferenz. Wenn keine kompatiblen Verschlüsselungsmethoden angegeben sind, MÜSSEN Anwendungen „aes256cbc“ verwenden.
+
+
+
+secret
+
+Eine 64-stellige Zeichenkette, die von der sendenden Seite zufällig generiert wird.
+
+
+
+secret_sig
+
+Die RSA-Signatur des Geheimnisses, signiert mit dem privaten Schlüssel des Absenders.
+
+
+
+sender
+
+Ein Array aus vier Komponenten, die eine tragbare Identität liefern. Wir können die angegebene URL kontaktieren und ein Nomad-Infopaket herunterladen, um den öffentlichen Schlüssel des Absenders zu erhalten, und diesen zur Überprüfung der Absender-Guid und der Signaturen der Entsende-URL verwenden.
+
+- guid
+
+ In der Regel eine 64 Zeichen lange base64url-kodierte Zeichenfolge. Sie wird erzeugt, wenn eine Identität erstellt wird, und es wird versucht, dass sie eindeutig ist; dies ist jedoch nicht erforderlich.
+
+- guid_sig
+
+ Die RSA-Signatur der guid, signiert mit dem privaten Schlüssel des Absenders und base64url-kodiert.
+
+- url
+
+ Die Basis-URL des Ortes, von dem dieser Beitrag stammt.
+
+- url_sig
+
+ Die RSA-Signatur der url, signiert mit dem privaten Schlüssel des Absenders und base64url kodiert.
+
+- sitekey
+
+ Der öffentliche Schlüssel der in der Url angegebenen Website
+
+recipients
+
+Wird nur für private Nachrichten verwendet. Ein Array von Umschlag-Empfängern. Jeder Empfänger wird durch ein Array aus guid und guid_sig dargestellt. Wenn Empfänger angegeben sind, wird das gesamte Paket auch mit einem ausgehandelten kryptografischen Algorithmus oder 'aes256cbc' gekapselt, wenn keiner ausgehandelt werden konnte.
+
+- guid
+
+ Die guid eines privaten Empfängers.
+
+- guid_sig
+
+ Die RSA-Signatur der guid, signiert mit dem privaten Schlüssel des Empfängers und base64url-kodiert \ No newline at end of file
diff --git a/doc/de/features.bb b/doc/de/features.bb
deleted file mode 100644
index febdc65ee..000000000
--- a/doc/de/features.bb
+++ /dev/null
@@ -1,203 +0,0 @@
-[h1][b]$Projectname-Features[/b][/h1]
-
-[h1]$Projectname kurz zusammengefasst[/h1]
-
-tl;dr
-
-$Projectname stellt verteiltes Web-Publishing und soziale Kommunikation mit [b]dezentraler Rechteverwaltung[/b] zur Verfügung.
-
-Aber was genau ist eine dezentrale Rechteverwaltung? Sie gibt mir die Möglichkeit, etwas auf meiner Website (Fotos, Medien, Dateien, Webseiten etc.) mit bestimmten Personen auf anderen Websites zu teilen – aber nicht unbedingt mit [i]allen[/i] auf diesen Websites. Und: Sie brauchen kein Konto auf meiner Website und müssen sich auf meiner Website nicht extra einloggen, um sich die Dinge anzusehen, die ich mit ihnen geteilt habe. Sie haben ein Konto auf ihrer Heimat-Website, und „Magic Authentication“ zwischen den Websites besorgt den Rest. Da das Netzwerk dezentral aufgebaut ist, gibt es auch keinen einzelnen Betreiber des Netzwerks, der an der Rechteverwaltung vorbei alles sehen kann.
-
-$Projectname kombiniert viele Features von tradionellen Blogs, sozialen Netzwerken und Medien, Content-Management-Systemen und persönlichem Cloud-Speicher auf einer einfach zu nutzenden Plattform. Jeder Hub (Web-Server) im Grid kann isoliert operieren oder sich mit anderen Hubs zu einem Super-Netzwerk vereinen. Die Kontrolle über die Privatsphäre hat immer derjenige, der die Inhalte veröffentlicht.
-
-$Projectname ist eine Open-Source Webserver-Applikation, geschrieben ursprünglich für PHP/MySQL. Mit minimaler Erfahrung als Admin ist sie leicht zu installieren. Sie kann auch durch Plugins und Themes und weitere Angebote von Drittanbietern erweitert werden.
-
-[h1][b]$Projectname-Features[/b][/h1]
-
-$Projectname ist ein Allzweck-Web-Publishing- und Kommunikationsnetzwerk mit einigen einzigartigen Features. Es wurde für eine große Bandbreite von Nutzern entwickelt, von Nutzern sozialer Netzwerke über technisch nicht interessierte Blogger bis hin zu PHP-Experten und erfahrenen Systemadministratoren.
-
-Diese Seite listet einige der Kern-Features von $Projectname auf, die in der offiziellen Distribution enthalten sind. Wie immer bei freier Open-Source-Software sind den Möglichkeiten keine Grenzen gesetzt. Beliebige Erweiterungen, Addons, Themes und Konfigurationen sind möglich.
-
-[h2]Entwickelt für Privatsphäre und Freiheit[/h2]
-
-Eines der Design-Ziele von $Projectname ist einfache Kommunikations über das Web, ohne die Privatsphäre zu vernachlässigen, wenn die Nutzer das wünschen. Um dieses Ziel zu erreichen, verfügt $Projectname über einige Features, die beliebige Stufen des Privatsphäre-Schutzes ermöglichen:
-
-[b]Beziehungs-Tool[/b]
-
-Wenn Du in der $Projectname einen Kontakt hinzufügst (und das Beziehungs-Tool aktiviert hast), hast Du die Möglichkeit, einen „Grad der Freundschaft“ zu bestimmen. Bespiel: Wenn Du ein Blog eines Bekannten hinzufügst, könntest Du ihm den Freundschaftsgrad „Bekannte“ (Acquaintances) geben.
-
-Wenn Du aber den privaten Kanal eines Freundes hinzufügst, wäre der Freundschaftsgrad „Freunde“ vermutlich passender.
-
-Wenn Du allen Kontakten solche Freundschaftsgrade zugeordnet hast, kannst Du mit dem Beziehungs-Tool, das (sofern aktiviert) oben auf Deiner Matrix-Seite erscheint, bestimmen, welche Inhalte Du sehen willst. Indem Du die Schieberegler einstellst, legst Du fest, was angezeigt wird – nur Kanäle mit einem Freundschaftsgrad innerhalb des eingestellten Bereichs werden angezeigt
-
-Das Beziehungs-Tool erlaubt blitzschnelles Filtern von großen Mengen Inhalt, gruppiert nach Freundschaftsgrad.
-
-[b]Filter für Verbindungen[/b]
-
-Du kannst ganz genau kontrollieren, was in Deinem Stream erscheint, wenn Du den optionalen „Filter für Verbindungen“ aktivierst. Dann kannst Du beim Bearbeiten einer Verbindung Kriterien festlegen, nach denen entschieden wird, ob einzelne Beiträge dieser Verbindung importiert werden sollen oder nicht (Einschluss oder Ausschluss möglich). Wurde ein Beitrag einmal importiert, wirst Du auch alle Kommentare dazu sehen, egal ob eines der Kriterien auf sie zutrifft oder nicht. Du könntest einzelne Wörter festlegen, die, wenn sie in einem Beitrag vorkommen, dafür sorgen, dass er geblockt oder eben nicht geblockt wird. Auch reguläre Ausdrüce können benutzt werden, genauso wie Hashtags oder sogar die Sprache, in der der Beitrag verfasst wurde.
-
-[b]Zugriffsrechte[/b]
-
-Wenn Du Inhalte mit anderen teilst, hast Du die Option, den Zugriff darauf einzuschränken. Wenn Du auf das Schloss unterhalb des Beitrags-Editors klickst, kannst Du auswählen, wer diesen Beitrag sehen darf, indem Du einfach auf die Namen klickst.
-
-Diese Nachricht kann dann nur vom Absender und den eingestellten Empfängern betrachtet werden. Mit anderen Worten, sie erscheint nicht öffentlich auf einer Pinnwand.
-
-Solche Zugriffsrechte gibt es bei Beiträgen, Fotos, Terminen, Webseiten, Chat-Räumen und Dateien.
-
-[b]Ein Passwort für alle $Projectname-Server (Single Sign-on)[/b]
-
-Zugriffsrechte funktionieren im gesamten Grid mit allen Kanälen. Die meisten Links, die innerhalb von $Projectname verlinken, enthalten Deine Identität (zid), so dass der Zielserver Dich direkt anmelden kann. Du kannst Dich aber auch so auf jedem $Projectname-Server mit Deiner $Projectname-Identität anmelden und erhältst dann Zugriff auf die Inhalte, die für Dich freigegeben sind.
-
-Du loggst Dich nur einmal auf Deinem Heimat-Hub ein. Ab dann funktioniert die Authentifizierung gegenüber anderen $Projectname-Hubs „magisch“ von selbst.
-
-[b]Dateiablage (Cloud) mit WebDAV-Zugriff[/b]
-
-Du kannst in Deinem persönlichen Speicherbereich Dateien hochladen und ihn sogar als Verzeichnis von Deinem lokalen Betriebssystem anzeigen lassen (via WebDAV). Die Dateien können über Zugriffsrechte bestimmten $Projectname-Mitgliedern (und den Mitgliedern mancher anderer Netze) zugänglich gemacht oder auch komplett öffentlich zur Verfügung gestellt werden.
-
-[b]Fotoalben[/b]
-
-Stelle Deine Fotos online in Alben zur Verfügung. Auch hier kann der Zugriff über die Zugriffsrechte eingeschränkt werden.
-
-[b]Terminkalender[/b]
-
-Im eingebauten Terminkalender kannst Du Termine erstellen und verwalten. Auch hier greifen die Zugriffsrechte für andere. Termine können im vcalendar/iCal-Format importiert/exportiert und in Beiträgen mit anderen geteilt werden. Wenn Deine Kontakte ihren Geburtstag in ihr Profil eingetragen haben, werden diese Geburtstage automatisch zu Deinem Kalender hinzugefügt – mit entsprechender Anpassung der Zeitzone, so dass Du nie zu früh oder zu spät gratulierst. Termine werden normalerweise mit Teilnehmerzählern erstellt, so dass Deine Freunde und Verbindungen sofort zu- oder absagen können.
-
-[b]Chat-Räume[/b]
-
-Du kannst Chaträume erstellen und über die Zugriffsrechte nur bestimmten Nutzern öffnen. Die Nachrichten sind sicherer verschlüsselt als es normalerweise bei Jabber/XMPP, IRC und anderen Instant Messengern üblich ist. Über Plugins ist es aber auch möglich, andere IM-Dienste aus der $Projectname heraus zu nutzen.
-
-[b]Erstellen von Webseiten[/b]
-
-In $Projectname gibt es Werkzeuge für „Content Management“, mit denen Du einfache Webseiten erstellen kannst, aber auch komplexe Layouts, Menüs, Blöcke und Widgets. Auch hier greifen die Zugriffsrechte, so dass die entstandenen Seiten nur von denen betrachtet werden können, denen Du das Recht dazu eingeräumt hast.
-
-[b]Apps[/b]
-
-$Projectname-Mitglieder könnnen Apps erstellen und verteilen. Anders als bei anderen Systemen, bei denen man an den System-Anbieter gebunden ist, werden diese Apps komplett vom App-Anbieter kontrolliert, der auf Wunsch seine eigene Zugriffskontrolle und ein Bezahlsystem einbauen kann. Die meisten Apps in der $Projectname sind kostenlos. Sie sind sehr einfach und ohne große Programmierkenntnisse zu erstellen.
-
-[b]Layout[/b]
-
-Das Seiten-Layout basiert auf eine Beschreibungssprache namens Comanche. $Projectname ist selbst in Comanche-Layouts verfasst, die man verändern kann. Dadurch ist eine sehr starke Anpassung an die eigenen Bedürfnisse möglich, wie man sie so in Multi-User-Umgebungen normalerweise nicht findet.
-
-[b]Lesezeichen[/b]
-
-Du kannst Lesezeichen teilen, speichern und verwalten, direkt aus den Unterhaltungen mit anderen heraus.
-
-[b]Verschlüsselung privater Nachrichten[/b]
-
-Private Nachrichten werden verschlüsselt gespeichert. Das bietet keine absolute Sicherheit, erschwert aber einfaches Herumschnüffeln durch den Administrator oder Internet Provider.
-
-Jeder $Projectname-Kanal hat seinen eigenes 4096-bit-RSA-Schlüsselpaar, das erzeugt wird, wenn der Kanal erstellt wird. Damit werden private Nachrichten und Beiträge mit eingeschränktem Empfängerkreis während der Übermittlung zu anderen Hubs geschützt.
-
-Zusätzlich können Nachrichten mit Ende-zu-Ende-Verschlüsselung versehen werden, so dass weder $Projectname-Hub-Administratoren noch ISPs irgendetwas mitlesen können, solange sie nicht über das Passwort verfügen.
-
-Komplett öffentliche Nachrichten werden weder in der Datenbank noch bei der Übertragung verschlüsselt (abgesehen ggfs. von SSL).
-
-Private Nachrichten und Beiträge können gelöscht (zurückgezogen) werden, aber es kann natürlich nicht garantiert werden, dass der Empfänger sie nicht schon gelesen hat.
-
-Alle Beiträge können mit einem „Verfallsdatum“ versehen werden. Zu diesem Zeitpunkt werden sie dann von den Servern der Empfänger gelöscht.
-
-[b]Verbindung zu anderen Diensten[/b]
-
-Neben Plugins, die das „crossposten“ zu diversen anderen Netzwerk erlauben, wird der Import von RSS/Atom-Feeds nativ unterstützt, auch, um mit diesen Inhalten spezielle Kanäle zu erstellen. Außerdem kann über das Diaspora-Protokoll mit Kontakten in den Netzwerken Friendica und Diaspora kommuniziert werden. Diese Unterstützung ist als experimentell eingestuft, da diese Netzwerke nicht die gleichen Möglichkeiten wie $Projectname in Sachen Privatsphäre und Verschlüsselung bieten, so dass Kommunikation mit ihnen zu Privatsphäreproblemen führen könnte.
-
-Weiterhin wird OpenID auf experimenteller Ebene unterstützt und kann bei den Zugriffsrechten genutzt werden, um Inhalte für per OpenID authentifizierte Nutzer freizugeben. An dieser Funktion wird noch gearbeitet. Jeder $Projectname-Hub kann außerdem als OpenID-Provider dienen.
-
-Die Inhalte von Kanälen können als Quellen für andere Kanäle dienen (wenn der Kanalinhaber das erlaubt), so dass Themen-Kanäle mit den Inhalten von zwei oder mehr Kanälen erstellt werden können.
-
-[b]Sammlungen[/b]
-
-„Sammlungen“ sind unsere Implementierung von Privatsphäregruppen, ähnlich den „Kreisen“ bei Google+ und den „Aspekten“ bei Diaspora. Sammlungen können zur Filterung der angezeigten Nachrichten genutzt werden (nur Threads anzeigen, die von einem Mitglied dieser Sammlung gestartet wurden), aber auch zum Setzen von Zugriffsrechten (bevor der Beitrag abgeschickt wird).
-
-[b]Verzeichnisdienste[/b]
-
-Wir stellen einfachen Zugriff auf ein Mitgliederverzeichnis zur Verfügung, samt einer dezentralen Möglichkeit, sich neue Kontakte basierend auf den eigenen vorschlagen zu lassen. Die Verzeichnis-Server sind normale $Projectname-Server, bei denen der Administrator sich entschieden hat, sie auch als Verzeichnis agieren zu lassen. Das benötigt mehr Ressourcen als eine normale $Projectname-Installation, deshalb ist das nicht voreingestellt. Die Verzeichnis-Server synchronisieren sich miteinander, so dass (abgesehen von einer gewissen Verzögerung bis zur nächsten Synchronisation) alle Verzeichnis-Server aktuelle Informationen über das gesamte Netzwerk bereitstellen können.
-
-[b]TLS/SSL[/b]
-
-$Projectname-Server, die TLS/SSL benutzen, verschlüsseln ihre Kommunikation vom Server zum Nutzer mit SSL. Nach den aktuellen Enthüllungen über das Umgehen von Verschlüsselung durch NSA, GHCQ und andere Dienste, sollte man jedoch nicht mehr davon ausgehen, dass diese Verbindungen nicht mitgelesen werden können. Private Kommunikation (nicht komplett öffentliche Beiträge) wird darüberhinaus zusätzlich verschlüsselt, bevor sie von einem Server zum anderen geschickt wird.
-
-[b]Kanal-Einstellungen[/b]
-
-Wenn ein Kanal erstellt wird, muss eine bestimmte Zugriffsrechte-Kategorie (z.B. öffentliches Forum oder privater Kanal für soziales Netzwerken) ausgewählt werden, die dafür sorgt, dass sinnvolle Privatsphäre-Einstellungen für diese Art von Kanal ausgewählt werden.
-
-Wenn Du die Experten-Kategorie wählst, kannst Du detaillierte Zugriffseinstellungen für verschiedenste Aspekte der Kommunikation festlegen. Unter den „Sicherheits- und Privatsphäre-Einstellungen“ kann für jeden Punkt auf der linken Seite eine von 7-8 möglichen Optionen aus dem Menü gewählt werden. Daneben gibt es diverse weitere Einstellmöglichkeiten zum Thema Privatsphäre.
-
-Die Optionen für die einzelnen Punkte (z.B., wer Deine normalen Beiträge sehen kann) sind:
-[ul][*]Niemand außer Du selbst
-[*]Nur die, denen Du es explizit erlaubst
-[*]Angenommene Verbindungen
-[*]Beliebige Verbindungen
-[*]Jeder auf diesem Website
-[*]Alle $Projectname-Nutzer
-[*]Jeder authentifizierte
-[*]Jeder im Internet[/ul]
-
-[b]Private und öffentliche Foren[/b]
-
-Foren sind Kanäle, in denen mehrere Nutzer als Autoren fungieren können; eine Nachricht eines entsprechend berechtigten Nutzers an das Forum wird an alle Foren-Mitglieder verteilt. Es gibt momentan zwei Arten, um auf diese Weise an ein Forum zu posten: 1) Direktes Posten auf der Kanal-Seite des Forums („wall-to-wall post“) oder 2) über @mention-Tags. Jeder kann Foren erstellen, und sie können für beliebige Zwecke genutzt werden. Das Kanal-Verzeichnis ermöglicht es, direkt nach öffentlichen Foren zu suchen. Private Foren können meist nur von den Mitgliedern beschickt und gelesen werden.
-
-[b]Klone[/b]
-
-Konten in der $Projectname werden auch als [i]nomadische Identitäten[/i] bezeichnet. Nomadisch, weil bei anderen Diensten die Identität eines Nutzers an den Server oder die Plattform gebunden ist, auf der er ursprünglich erstellt wurde. Ein Facebook- oder Gmail-Konto ist and diese Dienste gekettet. Er funktioniert nicht ohne Facebook.com bzw. Gmail.com.
-
-Bei $Projectname ist das anders. Sagen wir, Du hast eine $Projectname-Indentität namens tina@$Projectnamehub.com. Die kannst Du auf einen anderen Server klonen, mit dem gleichen oder einem anderen Namen, zum Beispiel lebtEwig@Anderer$ProjectnameHub.info.
-
-Beide Kanäle sind jetzt miteinander synchronisiert, das heißt, dass alle Kontakte und Einstellungen auf dem Klon immer die gleichen sind wie auf dem ursprünglichen Kanal. Es ist egal, ob Du eine Nachricht von dort aus oder vom Klon aus schickst. Alle Nachrichten sind in beiden Klonen vorhanden.
-
-Das ist ein ziemlich revolutionäres Feature, wenn man sich einige Szenarien dazu ansieht:
-
-[ul][*]Was passiert, wenn ein Server, auf dem sich Deine Identität befindet, plötzlich offline ist (sicher haben viele von Euch den Twitter-„Fail Whale“ gesehen und verflucht)? Ohne Klone ist der Nutzer nicht in der Lage zu kommunizieren, bis der Server wieder online ist. Mit Klonen loggst Du Dich einfach bei Deinem geklonten Kanal ein und lebst glücklich bis an Dein Ende.
-[*]Der Administrator Deines $Projectname-Hubs kann es sich nicht länger leisten, seinen für alle kostenlosen Server zu bezahlen. Er gibt bekannt, dass der Server in zwei Wochen vom Netz gehen wird. Zeit genug, um Deine $Projectname-Kanäle auf andere Server zu klonen und somit Verbindungen und Freunde zu behalten.
-[*]Was, wenn Dein Kanal staatlicher Zensur unterliegt? Dein Server-Admin könnte gezwungen werden, Dein Konto und alle damit verbundenen Kanäle und Daten zu löschen. Durch Klone bietet $Projectname Zensur-Resistenz. Wenn Du willst, kannst Du hunderte von Klonen haben, alle mit unterschiedlichen Namen und auf unterschiedlichen Hubs überall im Internet.[/ul]
-
-$Projectname bietet interessante, neue Möglichkeiten in Bezug auf die Privatsphäre. Mehr dazu unter „Tipps und Tricks zur privaten Kommunikation“.
-
-Klone unterliegen einigen Restriktionen. Eine vollständige Erklärung zum Klonen von Identitäten gibt es unter „Klone“.
-
-[b]Mehrere Profile[/b]
-
-Jeder Kanal kann beliebig viele Profile mit unterschiedlichen Informationen definieren. Dann kannst Du einstellen, wer von Deinen Kontakten welches Profil zu sehen bekommt. Das Default-Profil ist für alle anderen zu sehen und kann so auf nur wenige Informationen beschränkt werden, während Freunde und Bekannte mehr zu sehen bekommen.
-
-[b]Kanal-Backups[/b]
-
-In $Projectname gibt es ein einfaches Ein-Klick-Backup, mit dem Du ein komplettes Backup Deiner Kanal-Einstellungen und Verbindungen herunterladen kannst.
-
-Solche Backups sind ein Weg, um Klone zu erstellen, und können genutzt werden, um einen Kanal wiederherzustellen.
-
-[b]Löschen von Konten[/b]
-
-Konten und Kanäle können sofort gelöscht werden, indem Du einfach auf einen Link klickst. Das wars. Alle damit verbundenen Inhalte werden aus dem Grid gelöscht (inklusiver aller Beiträge und sonstiger Inhalte, die von dem gelöschten Konto/Kanal erzeugt wurden). Je nach Anzahl Deiner Verbindungen kann es etwas dauern, bis die Inhalte auch von allen Servern Deiner Kontakte gelöscht werden, aber die Löschung wird so schnell wie sinnvoll möglich durchgeführt.
-
-[h2]Erstellen von Inhalten[/h2]
-
-[b]Beiträge schreiben[/b]
-
-$Projectname unterstützt diverse verschiedene Wege, um Inhalte mit Auszeichnung (z.B. fett, kursiv, farbig etc.) zu erstellen. Voreinstellung ist die $Projectname-Variante von BBCode (wie in vielen Web-Foren) mit einigen Ergänzungen, die nur hier funktionieren. Du kannst auch Markdown benutzen, wenn Dir das leichter fällt. Bis vor kurzem konnte auch ein grafischer Editor eingesetzt werden, der jedoch große Probleme aufwies und deshalb entfernt wurde. Wir suchen gerade nach einer Alternative.
-
-Webseiten können neben BBCode und Markdown auch in HTML und Plain Text erstellt werden.
-
-[b]Inhalte löschen[/b]
-
-Alle Inhalte in $Projectname bleiben unter der Kontrolle des Mitglieds (bzw. Kanals), der sie ursprünglich erstellt hat. Alle Beiträge können jederzeit gelöscht werden, egal, ob sie auf dem Heimat-Server des Nutzers oder auf einem anderen Server erstellt wurden, an dem der Nutzer via Zot (Kommunikations- und Authentifizierungsprotokoll von $Projectname) angemeldet war.
-
-[b]Medien[/b]
-
-Genau wie jedes andere Blog-System, soziale Netzwerk oder Mikro-Blogging-Dienst unterstützt $Projectname das Hochladen von Dateien, das Einbetten von Bildern und Videos und das Verlinken von Seiten.
-
-[b]Vorschau/Editieren[/b]
-
-Vor dem Absenden kann eine Vorschau von Beiträgen betrachtet werden. Außerdem können Beiträge auch nach dem Absenden noch verändert werden.
-
-[b]Umfragen[/b]
-
-Beiträge können als Umfragen gestaltet werden – die Leser können dann mittels entsprechender Buttons zustimmen, ablehnen oder sich enthalten, was ähnlich wie „Likes“ am Beitrag sichtbar wird. Dadurch kannst Du abschätzen, wie gut neue Ideen ankommen, oder informelle Umfragen starten.
-
-[b]$Projectname erweitern[/b]
-
-Die $Projectname kann auf vielerlei Art erweitert werden: Durch Server-Anpassung, persönliche Anpassung, setzen von Optionen, Themes und Addons/Plugins.
-
-[b]API[/b]
-
-Es existiert eine API, die von beliebigen Programmen/Apps und Diensten genutzt werden kann. Sie basiert auf der ursprünglichen Twitter-API (für die es hunderte von Tools und Apps gibt). Sie wird aktuell erweitert, um Zugriff auf Möglichkeiten zu gewähren, die es nur in $Projectname gibt. Authentifikation erfolgt über Login/Passwort oder OAuth. Eine Client-Registrierung für OAuth-Applikationen ist möglich.
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/de/functions.md b/doc/de/functions.md
new file mode 100644
index 000000000..484c978b9
--- /dev/null
+++ b/doc/de/functions.md
@@ -0,0 +1,229 @@
+### Funktionen
+
+Im Folgenden einige der Hauptfunktionen von Hubzilla, die in der offiziellen Version enthalten sind. Hubzilla ist eine hochgradig erweiterbare Plattform, so dass weitere Funktionen und Möglichkeiten durch zusätzliche Themes und Plugins hinzugefügt werden können.
+
+
+
+#### Affinity-Schieberegler
+
+Beim Hinzufügen von Verbindungen in Hubzilla haben Mitglieder die Möglichkeit, der neuen Verbindung „Affinitätsstufen“ zuzuweisen (wie eng Ihre Freundschaft ist). Wenn Sie z. B. jemanden hinzufügen, dessen Blog Sie verfolgen, können Sie seinem Kanal die Affinitätsstufe „Bekannte“ zuweisen.
+
+Wenn Sie hingegen den Kanal eines Freundes hinzufügen, könnten Sie ihm die Affinitätsstufe „Freunde“ zuweisen.
+
+An diesem Punkt passt das Hubzilla *Affinitäts-Schieberegler-Tool*, das in der Regel oben auf Ihrer „Stream“-Seite erscheint, den Inhalt der Seite so an, dass die Kanäle innerhalb des gewünschten Affinitätsbereichs angezeigt werden. Kanäle außerhalb dieses Bereichs werden nicht angezeigt, es sei denn, Sie passen den Schieberegler so an, dass er sie einschließt.
+
+Der Schieberegler für die Affinität ermöglicht die sofortige Filterung großer Mengen von Inhalten, gruppiert nach Grad der Nähe.
+
+
+
+#### Filterung von Verbindungen
+
+Mit dem optionalen „Verbindungsfilter“ haben Sie die Möglichkeit, genau zu steuern, was in Ihrem Stream angezeigt wird. Wenn er aktiviert ist, bietet der Verbindungseditor Eingaben zur Auswahl von Kriterien, die erfüllt werden müssen, um einen bestimmten Beitrag aus einem bestimmten Kanal aufzunehmen oder auszuschließen. Sobald ein Beitrag zugelassen wurde, sind alle Kommentare zu diesem Beitrag erlaubt, unabhängig davon, ob sie den Auswahlkriterien entsprechen. Sie können Wörter auswählen, die, falls vorhanden, den Beitrag blockieren oder sicherstellen, dass er in Ihren Stream aufgenommen wird. Reguläre Ausdrücke können für eine noch feinere Kontrolle verwendet werden, ebenso wie Hashtags oder sogar die erkannte Sprache des Beitrags.
+
+
+
+#### Zugriffskontrolllisten
+
+Bei der Freigabe von Inhalten haben die Mitglieder die Möglichkeit einzuschränken, wer den Inhalt sehen kann. Durch Anklicken des Vorhängeschlosses unter dem Freigabefeld kann man die gewünschten Empfänger des Beitrags auswählen, indem man auf deren Namen klickt.
+
+Nach dem Versenden kann die Nachricht nur vom Absender und den ausgewählten Empfängern eingesehen werden. Mit anderen Worten, die Nachricht wird nicht auf öffentlichen Wänden erscheinen.
+
+Zugangskontrolllisten können auf Inhalte und Beiträge, Fotos, Ereignisse, Webseiten, Chatrooms und Dateien angewendet werden.
+
+
+
+#### Einmalige Anmeldung
+
+Zugriffskontrolllisten funktionieren dank unserer einzigartigen Single-Sign-On-Technologie für alle Kanäle im Grid. Die meisten internen Links bieten ein Identitäts-Token, das auf anderen Hubzilla-Sites verifiziert und zur Kontrolle des Zugriffs auf private Ressourcen verwendet werden kann. Sie melden sich einmal bei Ihrem Home Hub an. Danach ist die Authentifizierung für alle Hubzilla-Ressourcen „magisch“.
+
+
+
+#### WebDAV-fähiger Dateispeicher
+
+Dateien können mit den Dienstprogrammen Ihres Betriebssystems (in den meisten Fällen per Drag & Drop) in Ihren persönlichen Speicherbereich hochgeladen werden. Sie können diese Dateien mit Zugriffskontrolllisten für eine beliebige Kombination von Hubzilla-Mitgliedern (einschließlich einiger Netzwerkmitglieder von Drittanbietern) schützen oder sie öffentlich zugänglich machen.
+
+
+
+#### Fotoalben
+
+Speichern Sie Fotos in Alben. Alle Ihre Fotos können durch Zugriffskontrolllisten geschützt werden.
+
+
+
+#### Veranstaltungskalender
+
+Erstellen und verwalten Sie Ereignisse und Aufgaben, die ebenfalls mit Zugriffskontrolllisten geschützt werden können. Ereignisse können mit dem Industriestandard vcalendar/iCal-Format in andere Software importiert/exportiert und in Posts mit anderen geteilt werden. Geburtstagsereignisse werden automatisch von Ihren Freunden hinzugefügt und in Ihre korrekte Zeitzone umgewandelt, so dass Sie genau wissen, wann der Geburtstag stattfindet - unabhängig davon, wo auf der Welt Sie sich in Bezug auf die Geburtstagsperson befinden. Veranstaltungen werden normalerweise mit Anwesenheitszählern erstellt, so dass Ihre Freunde und Verbindungen sofort zusagen können.
+
+
+
+#### Chaträume
+
+Sie können eine beliebige Anzahl von persönlichen Chatrooms erstellen und den Zugang über Zugangskontrolllisten erlauben. Diese sind in der Regel sicherer als XMPP, IRC und andere Instant Messaging-Transporte, obwohl wir auch die Nutzung dieser anderen Dienste über Plugins erlauben.
+
+
+
+#### Erstellung von Webseiten
+
+Hubzilla verfügt über zahlreiche „Content Management“-Werkzeuge zur Erstellung von Webseiten, einschließlich Layout-Bearbeitung, Menüs, Blöcke, Widgets und Seiten-/Inhaltsbereiche. Alle diese Tools können zugriffsgesteuert werden, so dass die resultierenden Seiten nur für das vorgesehene Publikum zugänglich sind.
+
+
+
+#### Anwendungen
+
+Apps können von Mitgliedern erstellt und verteilt werden. Diese unterscheiden sich von den traditionellen „Vendor Lockin“-Apps, da sie vollständig vom Autor kontrolliert werden - der den Zugriff auf die Zielseiten der App kontrollieren und entsprechend für diesen Zugriff bezahlen kann. Die meisten Apps in Hubzilla sind kostenlos und können auch von Personen ohne Programmierkenntnisse leicht erstellt werden.
+
+
+
+#### Gestaltung
+
+Das Seitenlayout basiert auf einer Beschreibungssprache namens Comanche. Hubzilla selbst ist in Comanche-Layouts geschrieben, die Sie ändern können. Dies ermöglicht ein Maß an individueller Anpassung, das man in so genannten „Multi-User-Umgebungen“ normalerweise nicht findet.
+
+
+
+#### Lesezeichen
+
+Teilen und speichern/verwalten Sie Lesezeichen von Links, die in Unterhaltungen bereitgestellt werden.
+
+
+
+#### Verschlüsselung privater Nachrichten und Datenschutzaspekte
+
+Private Nachrichten werden in einem verschlüsselten Format gespeichert. Dies ist zwar nicht hundertprozentig sicher, verhindert aber in der Regel das gelegentliche Ausspähen durch den Site-Administrator oder ISP.
+
+Jeder Hubzilla-Channel verfügt über einen eigenen Satz privater und zugehöriger öffentlicher RSA 4096-Bit-Schlüssel, die bei der ersten Erstellung des Channels generiert werden. Dies wird verwendet, um private Nachrichten und Beiträge während der Übertragung zu schützen.
+
+Zusätzlich können Nachrichten mit einer „Ende-zu-Ende-Verschlüsselung“ erstellt werden, die von Hubzilla-Betreibern, ISPs oder anderen Personen, die den Passcode nicht kennen, nicht gelesen werden kann.
+
+Öffentliche Nachrichten werden in der Regel weder bei der Übertragung noch bei der Speicherung verschlüsselt.
+
+Private Nachrichten können zurückgezogen (nicht gesendet) werden, obwohl es keine Garantie gibt, dass der Empfänger sie noch nicht gelesen hat.
+
+Beiträge und Nachrichten können mit einem Verfallsdatum versehen werden, nach dessen Ablauf sie auf der Seite des Empfängers gelöscht/entfernt werden.
+
+
+
+#### Dienst-Föderation
+
+Neben zusätzlichen „Cross-Post-Connectoren“ zu einer Vielzahl von alternativen Netzwerken gibt es eine native Unterstützung für den Import von Inhalten aus RSS/Atom-Feeds und deren Verwendung zur Erstellung spezieller Kanäle. Es sind auch Plugins verfügbar, um mit anderen über die Protokolle Diaspora und GNU-Social (OStatus) zu kommunizieren. Diese Netzwerke unterstützen keine nomadische Identität oder domänenübergreifende Zugriffskontrolle; allerdings wird die grundlegende Kommunikation mit/von Diaspora, Friendica, GNU-Social, Mastodon und anderen Anbietern, die diese Protokolle verwenden, unterstützt.
+
+Es gibt auch eine experimentelle Unterstützung für OpenID-Authentifizierung, die in Zugriffskontrolllisten verwendet werden kann. Dies ist ein Projekt, an dem gearbeitet wird. Ihr Hubzilla-Hub kann als OpenID-Provider verwendet werden, um Sie bei externen Diensten, die diese Technologie nutzen, zu authentifizieren.
+
+Channels können die Berechtigung haben, zu „derivativen Channels“ zu werden, bei denen zwei oder mehr bestehende Channels kombiniert werden, um einen neuen thematischen Channel zu erstellen.
+
+
+
+#### Datenschutz-Gruppen
+
+Unsere Implementierung von Datenschutzgruppen ähnelt den „Circles“ von Google und den „Aspekten“ von Diaspora. Dies ermöglicht Ihnen, Ihren eingehenden Stream nach ausgewählten Gruppen zu filtern und die ausgehende Zugriffskontrollliste automatisch auf die Mitglieder dieser Gruppe zu beschränken, wenn Sie etwas posten. Sie können dies jederzeit (vor dem Senden des Beitrags) rückgängig machen.
+
+
+
+#### Verzeichnisdienste
+
+Wir bieten einen einfachen Zugang zu einem Mitgliederverzeichnis und stellen dezentralisierte Tools zur Verfügung, die „Vorschläge“ für Freunde machen können. Die Verzeichnisse sind normale Hubzilla-Sites, die sich entschieden haben, die Rolle des Verzeichnisservers zu übernehmen. Dies erfordert mehr Ressourcen als die meisten typischen Sites und ist daher nicht die Standardeinstellung. Die Verzeichnisse werden synchronisiert und gespiegelt, so dass sie alle aktuelle Informationen über das gesamte Netzwerk enthalten (vorbehaltlich normaler Ausbreitungsverzögerungen).
+
+
+
+#### TLS/SSL
+
+Bei Hubzilla-Hubs, die TLS/SSL verwenden, wird die Kommunikation zwischen Client und Server über TLS/SSL verschlüsselt. In Anbetracht der jüngsten Enthüllungen in den Medien über die weit verbreitete, weltweite Überwachung und die Umgehung der Verschlüsselung durch die NSA und das GCHQ kann man davon ausgehen, dass HTTPS-geschützte Kommunikation auf verschiedene Weise kompromittiert werden kann. Private Kommunikation wird daher auf einer höheren Ebene verschlüsselt, bevor sie nach außen gesendet wird.
+
+
+
+#### Channel-Einstellungen
+
+Bei der Erstellung eines Channels wird eine Rolle ausgewählt, die eine Reihe von vorkonfigurierten Sicherheits- und Datenschutzeinstellungen anwendet. Diese werden nach bewährten Verfahren ausgewählt, um den Datenschutz auf dem gewünschten Niveau zu halten.
+
+Wenn Sie eine „benutzerdefinierte“ Datenschutzrolle wählen, können Sie für jeden Kanal fein abgestufte Berechtigungen für verschiedene Aspekte der Kommunikation festlegen. Unter der Überschrift „Sicherheits- und Datenschutzeinstellungen“ gibt es zum Beispiel für jeden Aspekt auf der linken Seite sechs (6) mögliche Anzeige-/Zugriffsoptionen, die durch Anklicken des Dropdown-Menüs ausgewählt werden können. Es gibt auch eine Reihe anderer Datenschutzeinstellungen, die Sie bearbeiten können.
+
+Die Optionen sind:
+
+\- Niemand außer Ihnen selbst.
+\- Nur diejenigen, die Sie ausdrücklich zulassen.
+\- Jeder, der in Ihrem Adressbuch steht.
+\- Alle Personen auf dieser Website.
+\- Alle Personen in diesem Netzwerk.
+\- Jeder, der authentifiziert ist.
+\- Jeder, der sich im Internet befindet.
+
+
+
+#### Öffentliche und private Foren
+
+Foren sind in der Regel Kanäle, an denen sich mehrere Autoren beteiligen können. Derzeit gibt es zwei Möglichkeiten, Beiträge in Foren zu veröffentlichen: 1) „Wand-zu-Wand“-Beiträge und 2) über @mention-Tags im Forum. Foren können von jedem erstellt und für jeden Zweck verwendet werden. Das Verzeichnis enthält eine Option zur Suche nach öffentlichen Foren. In privaten Foren können nur Beiträge verfasst werden, die oft nur von Mitgliedern gesehen werden.
+
+
+
+#### Klonen von Accounts
+
+Konten in Hubzilla werden als *nomadische Identitäten* bezeichnet, da die Identität eines Mitglieds nicht an den Hub gebunden ist, in dem die Identität ursprünglich erstellt wurde. Wenn Sie zum Beispiel ein Facebook- oder Gmail-Konto erstellen, ist es an diese Dienste gebunden. Sie können nicht ohne Facebook.com oder Gmail.com funktionieren.
+
+Angenommen, Sie haben eine Hubzilla-Identität namens `tina@Hubzillahub.com` erstellt. Sie können sie auf einen anderen Hubzilla-Hub klonen, indem Sie denselben oder einen anderen Namen wählen: `liveForever@SomeHubzillaHub.info`
+
+Beide Kanäle werden nun synchronisiert, d. h. alle Ihre Kontakte und Einstellungen werden auf Ihrem Klon dupliziert. Es spielt keine Rolle, ob Sie einen Beitrag von Ihrem ursprünglichen Hub oder von dem neuen Hub aus senden. Die Beiträge werden auf beiden Konten gespiegelt.
+
+Dies ist eine ziemlich revolutionäre Funktion, wenn wir uns einige Szenarien vor Augen führen:
+
+\- Was passiert, wenn der Hub, in dem sich eine Identität befindet, plötzlich offline geht? Ohne Klonen ist ein Mitglied nicht in der Lage zu kommunizieren, bis der Hub wieder online ist (zweifellos haben viele von Ihnen den „Fail Whale“ von Twitter gesehen und verflucht). Beim Klonen loggen Sie sich einfach in Ihr geklontes Konto ein, und das Leben geht fröhlich weiter.
+
+\- Der Administrator Ihres Hubs kann es sich nicht mehr leisten, für seinen kostenlosen und öffentlichen Hubzilla-Hub zu bezahlen. Er kündigt an, dass der Hub in zwei Wochen abgeschaltet wird. Das gibt Ihnen reichlich Zeit, Ihre Identität(en) zu klonen und Ihre Hubzilla-Beziehungen, Freunde und Inhalte zu bewahren.
+
+\- Was ist, wenn Ihre Identität der staatlichen Zensur unterliegt? Ihr Hub-Anbieter kann gezwungen sein, Ihr Konto sowie alle Identitäten und zugehörigen Daten zu löschen. Mit Klonen bietet Hubzilla **Widerstand gegen Zensur**. Wenn Sie möchten, können Sie Hunderte von Klonen haben, die alle einen anderen Namen tragen und auf vielen verschiedenen Hubs im Internet verstreut sind.
+
+Hubzilla bietet interessante neue Möglichkeiten für die Privatsphäre. Weitere Informationen finden Sie auf der Seite [Tipps zum Schutz der Privatsphäre](/help/de/member/protection_of_privacy.md).
+
+Es gelten einige Vorbehalte. Eine vollständige Erklärung des Klonens von Identitäten finden Sie auf der Seite [Klonen](/help/de/member/clone.md).
+
+
+
+#### Mehrere Profile
+
+Es kann eine beliebige Anzahl von Profilen mit unterschiedlichen Informationen erstellt werden, die für bestimmte Ihrer Verbindungen/Freunde sichtbar gemacht werden können. Ein „Standard“-Profil kann von jedem eingesehen werden und kann begrenzte Informationen enthalten, während mehr Informationen für ausgewählte Gruppen oder Personen zugänglich sind. Das bedeutet, dass das Profil (und der Inhalt der Website), das Ihre biertrinkenden Freunde sehen, anders sein kann als das, was Ihre Kollegen sehen, und auch völlig anders als das, was für die Allgemeinheit sichtbar ist.
+
+
+
+#### Account-Sicherung
+
+Hubzilla bietet ein einfaches Konto-Backup mit nur einem Klick, bei dem Sie ein vollständiges Backup Ihres Profils herunterladen können. Die Backups können dann zum Klonen oder Wiederherstellen eines Profils verwendet werden.
+
+
+
+#### Konto-Löschung
+
+Konten können sofort gelöscht werden, indem Sie auf einen Link klicken. Das war's. Alle zugehörigen Inhalte werden dann aus dem Netz gelöscht (einschließlich der Beiträge und aller anderen Inhalte, die von dem gelöschten Profil erstellt wurden). Je nach der Anzahl der Verbindungen, die Sie haben, kann der Vorgang des Löschens entfernter Inhalte einige Zeit in Anspruch nehmen, aber er wird so schnell wie möglich durchgeführt.
+
+
+
+#### Löschung von Inhalten
+
+Alle in Hubzilla erstellten Inhalte bleiben unter der Kontrolle des Mitglieds (oder Channels), das sie ursprünglich erstellt hat. Ein Mitglied kann zu jeder Zeit eine Nachricht oder eine Reihe von Nachrichten löschen. Der Löschvorgang stellt sicher, dass der Inhalt gelöscht wird, unabhängig davon, ob er auf dem primären (Heimat-)Hub eines Channels oder auf einem anderen Hub gepostet wurde, bei dem der Channel über Nomad (Hubzilla-Kommunikations- und Authentifizierungsprotokoll) remote authentifiziert wurde.
+
+
+
+#### Medien
+
+Ähnlich wie jedes andere moderne Blogging-System, soziale Netzwerk oder ein Micro-Blogging-Dienst unterstützt Hubzilla das Hochladen von Dateien, das Einbetten von Videos und das Verlinken von Webseiten.
+
+
+
+#### Vorschau/Bearbeitung
+
+Beiträge und Kommentare können vor dem Senden in der Vorschau angezeigt und nach dem Senden bearbeitet werden.
+
+
+
+#### Abstimmen/Umfragen
+
+Beiträge können in „Umfrage“-Elemente umgewandelt werden, die es den Lesern ermöglichen, Feedback zu geben, das in Zählern für „Zustimmung“, „Ablehnung“ und „Enthaltung“ zusammengefasst wird. Auf diese Weise können Sie das Interesse für Ideen messen und informelle Umfragen erstellen.
+
+
+
+#### Erweitern von Hubzilla
+
+Hubzilla kann auf verschiedene Weise erweitert werden: durch Anpassung der Website, persönliche Anpassung, Optionseinstellungen, Themen und Addons/Plugins.
+
+
+
+#### API
+
+Eine API ist für die Nutzung durch Dienste von Drittanbietern verfügbar. Ein Plugin bietet auch eine grundlegende Implementierung der Twitter-API (für die es Hunderte von Drittanbieter-Tools gibt). Der Zugriff kann über Login/Passwort oder OAuth erfolgen, und eine Client-Registrierung von OAuth-Anwendungen ist vorgesehen. \ No newline at end of file
diff --git a/doc/de/general.bb b/doc/de/general.bb
deleted file mode 100644
index 2ce5533f1..000000000
--- a/doc/de/general.bb
+++ /dev/null
@@ -1,17 +0,0 @@
-[h2]Informationen über das Projekt und diesen Hub[/h2]
-
-[zrl=[baseurl]/help/Privacy]Informationen zum Datenschutz[/zrl]
-
-[h3]Externe Ressourcen[/h3]
-[zrl=[baseurl]/help/external-resource-links]Links zu externen Ressourcen[/zrl]
-
-[url=https://framagit.org/hubzilla/core/]Haupt-Website[/url]
-[url=https://framagit.org/hubzilla/addons]Addons-Website[/url]
-
-[url=[baseurl]/help/credits]$Projectname Credits[/url]
-
-[h3]Über diesen $Projectname-Hub[/h3]
-[zrl=[baseurl]/help/TermsOfService]Nutzungsbedingungen dieses Hubs[/zrl]
-[zrl=[baseurl]/siteinfo]Informationen zu diesem Hub und der $Projectname-Version[/zrl]
-[zrl=[baseurl]/siteinfo/json]Detaillierte technische Informationen zu diesem Hub im JSON-Format[/zrl]
-
diff --git a/doc/de/glossary.md b/doc/de/glossary.md
new file mode 100644
index 000000000..32b2c4fe8
--- /dev/null
+++ b/doc/de/glossary.md
@@ -0,0 +1,31 @@
+### Glossar
+
+- **Hub**
+
+ Eine Instanz dieser Software, die auf einem Standard-Webserver läuft.
+
+- **Grid**
+
+ Das globale Netzwerk von Hubs, die mit Hilfe des Zot-Protokolls Informationen untereinander austauschen.
+
+- **Kanal**
+
+ Die grundlegende Identität im Grid. Ein Channel kann eine Person, einen Blog oder ein Forum repräsentieren, um nur einige zu nennen. Channels können Verbindungen mit anderen Channels herstellen, um Informationen mit sehr detaillierten Berechtigungen zu teilen.
+
+- **Klonen**
+
+ Kanäle können Klone haben, die mit separaten und ansonsten nicht verbundenen Konten auf unabhängigen Hubs verbunden sind. Die mit einem Channel geteilte Kommunikation wird zwischen den Channel-Klonen synchronisiert, so dass ein Channel Nachrichten senden und empfangen und auf gemeinsame Inhalte von mehreren Hubs zugreifen kann. Dies bietet Ausfallsicherheit bei Netzwerk- und Hardwareausfällen, was für selbst gehostete oder mit begrenzten Ressourcen ausgestattete Webserver ein großes Problem darstellen kann. Das Klonen ermöglicht es Ihnen, einen Channel vollständig von einem Hub zu einem anderen zu verschieben und dabei Ihre Daten und Verbindungen mitzunehmen. Siehe nomadische Identität.
+
+- **nomadische Identität**
+
+ Die Fähigkeit, eine Identität über unabhängige Hubs und Webdomänen hinweg zu authentifizieren und einfach zu migrieren. Die nomadische Identität bietet echte Eigentumsrechte an einer Online-Identität, da die Identitäten der Kanäle, die von einem Konto auf einem Hub kontrolliert werden, nicht an den Hub selbst gebunden sind. Ein Hub ist eher eine Art „Gastgeber“ für Kanäle. Bei Hubzilla haben Sie kein „Konto“ auf einem Server wie bei typischen Websites, sondern Sie besitzen eine Identität, die Sie über das Netz mitnehmen können, indem Sie Klone verwenden.
+
+- **Nomad**
+
+ Das neuartige JSON-basierte Protokoll zur Implementierung sicherer dezentraler Kommunikation und Dienste. Es unterscheidet sich von vielen anderen Kommunikationsprotokollen, indem es die Kommunikation auf einem dezentralen Identitäts- und Authentifizierungsrahmen aufbaut. Die Authentifizierungskomponente ähnelt dem OpenID-Konzept, ist aber von DNS-basierten Identitäten isoliert. Soweit möglich, erfolgt die Fernauthentifizierung still und unsichtbar. Dies bietet einen Mechanismus für eine verteilte Zugangskontrolle im Internet, der unauffällig ist.
+
+ Ursprünglich trug das Protokoll den Namen Zot. Im Jahr 2021 wurde es von Mike Macgirvin in Nomad umbenannt. Inzwischen wird zwischen dem Protokoll und der Implementierung (Software) des Protokolls unterschieden. Die Implementierung wird bei Hubzilla weiterhin Zot genannt (genauer Zot6, weil es die Implementierung des damals noch gleich benannten Protokolls in der Version 6 fortführt).
+
+ Grundsätzlich wird das Protokoll nur noch als Nomad bezeichnet. Wenn der Begriff Zot oder Zot6 (meist in der Form "Nomad/Zot6") verwendet wird, ist, sofern es um das Protokoll geht, das Nomad-Protokoll gemeint. Zot bzw. Zot6 tauchen eigenständig nur noch im Bereich der Softwareentwicklung bei Hubzilla auf, weil die Routinen und die Programmbibliothek, welche Nomad in der Praxis umsetzen, diesen Namen tragen.
+
+ <u>Hinweis:</u> Die Implementierungen von Nomad in Hubzilla und in (streams) sind in Teilen nicht miteinander kompatibel. Das betrifft insbesondere die nomadische Identität. So ist es nicht möglich, einen Hubzilla-Kanal (Nomad v. Zot6) auf einer (streams) Instanz (Nomad v, Zot12) zu klonen, und umgekehrt.
diff --git a/doc/en/hook/about_hook.bb b/doc/de/hook/about_hook.bb
index 22b60d786..22b60d786 100644
--- a/doc/en/hook/about_hook.bb
+++ b/doc/de/hook/about_hook.bb
diff --git a/doc/en/hook/accept_follow.bb b/doc/de/hook/accept_follow.bb
index e8b1ed0c4..e8b1ed0c4 100644
--- a/doc/en/hook/accept_follow.bb
+++ b/doc/de/hook/accept_follow.bb
diff --git a/doc/en/hook/account_downgrade.bb b/doc/de/hook/account_downgrade.bb
index 63bae0a58..63bae0a58 100644
--- a/doc/en/hook/account_downgrade.bb
+++ b/doc/de/hook/account_downgrade.bb
diff --git a/doc/en/hook/account_settings.bb b/doc/de/hook/account_settings.bb
index 91b3a8385..91b3a8385 100644
--- a/doc/en/hook/account_settings.bb
+++ b/doc/de/hook/account_settings.bb
diff --git a/doc/en/hook/account_settings_post.bb b/doc/de/hook/account_settings_post.bb
index bbd7a57a8..bbd7a57a8 100644
--- a/doc/en/hook/account_settings_post.bb
+++ b/doc/de/hook/account_settings_post.bb
diff --git a/doc/en/hook/activity_decode_mapper.bb b/doc/de/hook/activity_decode_mapper.bb
index 43d08a136..43d08a136 100644
--- a/doc/en/hook/activity_decode_mapper.bb
+++ b/doc/de/hook/activity_decode_mapper.bb
diff --git a/doc/en/hook/activity_filter.bb b/doc/de/hook/activity_filter.bb
index 9d0768577..9d0768577 100644
--- a/doc/en/hook/activity_filter.bb
+++ b/doc/de/hook/activity_filter.bb
diff --git a/doc/en/hook/activity_mapper.bb b/doc/de/hook/activity_mapper.bb
index db65fadc4..db65fadc4 100644
--- a/doc/en/hook/activity_mapper.bb
+++ b/doc/de/hook/activity_mapper.bb
diff --git a/doc/en/hook/activity_obj_decode_mapper.bb b/doc/de/hook/activity_obj_decode_mapper.bb
index a96b32eee..a96b32eee 100644
--- a/doc/en/hook/activity_obj_decode_mapper.bb
+++ b/doc/de/hook/activity_obj_decode_mapper.bb
diff --git a/doc/en/hook/activity_obj_mapper.bb b/doc/de/hook/activity_obj_mapper.bb
index 7c14a1b81..7c14a1b81 100644
--- a/doc/en/hook/activity_obj_mapper.bb
+++ b/doc/de/hook/activity_obj_mapper.bb
diff --git a/doc/en/hook/activity_order.bb b/doc/de/hook/activity_order.bb
index 4a4670d03..4a4670d03 100644
--- a/doc/en/hook/activity_order.bb
+++ b/doc/de/hook/activity_order.bb
diff --git a/doc/en/hook/activity_received.bb b/doc/de/hook/activity_received.bb
index 2e9d68bf3..2e9d68bf3 100644
--- a/doc/en/hook/activity_received.bb
+++ b/doc/de/hook/activity_received.bb
diff --git a/doc/en/hook/addon_app_installed_filter.bb b/doc/de/hook/addon_app_installed_filter.bb
index e610b3205..e610b3205 100644
--- a/doc/en/hook/addon_app_installed_filter.bb
+++ b/doc/de/hook/addon_app_installed_filter.bb
diff --git a/doc/en/hook/affinity_labels.bb b/doc/de/hook/affinity_labels.bb
index 7234b7632..7234b7632 100644
--- a/doc/en/hook/affinity_labels.bb
+++ b/doc/de/hook/affinity_labels.bb
diff --git a/doc/en/hook/api_perm_is_allowed.bb b/doc/de/hook/api_perm_is_allowed.bb
index 862cbd653..862cbd653 100644
--- a/doc/en/hook/api_perm_is_allowed.bb
+++ b/doc/de/hook/api_perm_is_allowed.bb
diff --git a/doc/en/hook/app_destroy.bb b/doc/de/hook/app_destroy.bb
index 386d7af16..386d7af16 100644
--- a/doc/en/hook/app_destroy.bb
+++ b/doc/de/hook/app_destroy.bb
diff --git a/doc/en/hook/app_installed_filter.bb b/doc/de/hook/app_installed_filter.bb
index f0d91d6f0..f0d91d6f0 100644
--- a/doc/en/hook/app_installed_filter.bb
+++ b/doc/de/hook/app_installed_filter.bb
diff --git a/doc/en/hook/atom_author.bb b/doc/de/hook/atom_author.bb
index c9d05a593..c9d05a593 100644
--- a/doc/en/hook/atom_author.bb
+++ b/doc/de/hook/atom_author.bb
diff --git a/doc/en/hook/atom_entry.bb b/doc/de/hook/atom_entry.bb
index 0aec89f16..0aec89f16 100644
--- a/doc/en/hook/atom_entry.bb
+++ b/doc/de/hook/atom_entry.bb
diff --git a/doc/en/hook/atom_feed.bb b/doc/de/hook/atom_feed.bb
index 69775ca5e..69775ca5e 100644
--- a/doc/en/hook/atom_feed.bb
+++ b/doc/de/hook/atom_feed.bb
diff --git a/doc/en/hook/atom_feed_end.bb b/doc/de/hook/atom_feed_end.bb
index 4f019fc8f..4f019fc8f 100644
--- a/doc/en/hook/atom_feed_end.bb
+++ b/doc/de/hook/atom_feed_end.bb
diff --git a/doc/en/hook/attach_delete.bb b/doc/de/hook/attach_delete.bb
index 3b63f28d3..3b63f28d3 100644
--- a/doc/en/hook/attach_delete.bb
+++ b/doc/de/hook/attach_delete.bb
diff --git a/doc/en/hook/attach_upload_file.bb b/doc/de/hook/attach_upload_file.bb
index 1f8056caa..1f8056caa 100644
--- a/doc/en/hook/attach_upload_file.bb
+++ b/doc/de/hook/attach_upload_file.bb
diff --git a/doc/en/hook/authenticate.bb b/doc/de/hook/authenticate.bb
index eb8071e73..eb8071e73 100644
--- a/doc/en/hook/authenticate.bb
+++ b/doc/de/hook/authenticate.bb
diff --git a/doc/en/hook/author_is_pmable.bb b/doc/de/hook/author_is_pmable.bb
index 11d1185f3..11d1185f3 100644
--- a/doc/en/hook/author_is_pmable.bb
+++ b/doc/de/hook/author_is_pmable.bb
diff --git a/doc/en/hook/bb2diaspora.bb b/doc/de/hook/bb2diaspora.bb
index c28f1883e..c28f1883e 100644
--- a/doc/en/hook/bb2diaspora.bb
+++ b/doc/de/hook/bb2diaspora.bb
diff --git a/doc/en/hook/bbcode.bb b/doc/de/hook/bbcode.bb
index f6b8711b0..f6b8711b0 100644
--- a/doc/en/hook/bbcode.bb
+++ b/doc/de/hook/bbcode.bb
diff --git a/doc/en/hook/bbcode_filter.bb b/doc/de/hook/bbcode_filter.bb
index efeb2e1b0..efeb2e1b0 100644
--- a/doc/en/hook/bbcode_filter.bb
+++ b/doc/de/hook/bbcode_filter.bb
diff --git a/doc/en/hook/build_pagehead.bb b/doc/de/hook/build_pagehead.bb
index 8fc3486c7..8fc3486c7 100644
--- a/doc/en/hook/build_pagehead.bb
+++ b/doc/de/hook/build_pagehead.bb
diff --git a/doc/en/hook/can_comment_on_post.bb b/doc/de/hook/can_comment_on_post.bb
index 2cfd3b2da..2cfd3b2da 100644
--- a/doc/en/hook/can_comment_on_post.bb
+++ b/doc/de/hook/can_comment_on_post.bb
diff --git a/doc/en/hook/change_channel.bb b/doc/de/hook/change_channel.bb
index 4514b9265..4514b9265 100644
--- a/doc/en/hook/change_channel.bb
+++ b/doc/de/hook/change_channel.bb
diff --git a/doc/en/hook/channel_links.bb b/doc/de/hook/channel_links.bb
index c0243dac6..c0243dac6 100644
--- a/doc/en/hook/channel_links.bb
+++ b/doc/de/hook/channel_links.bb
diff --git a/doc/en/hook/channel_remove.bb b/doc/de/hook/channel_remove.bb
index db9e9dd82..db9e9dd82 100644
--- a/doc/en/hook/channel_remove.bb
+++ b/doc/de/hook/channel_remove.bb
diff --git a/doc/en/hook/chat_message.bb b/doc/de/hook/chat_message.bb
index ccc93bb2c..ccc93bb2c 100644
--- a/doc/en/hook/chat_message.bb
+++ b/doc/de/hook/chat_message.bb
diff --git a/doc/en/hook/chat_post.bb b/doc/de/hook/chat_post.bb
index 7cb3c9fa1..7cb3c9fa1 100644
--- a/doc/en/hook/chat_post.bb
+++ b/doc/de/hook/chat_post.bb
diff --git a/doc/en/hook/check_account_email.bb b/doc/de/hook/check_account_email.bb
index b309706a0..b309706a0 100644
--- a/doc/en/hook/check_account_email.bb
+++ b/doc/de/hook/check_account_email.bb
diff --git a/doc/en/hook/check_account_invite.bb b/doc/de/hook/check_account_invite.bb
index 8d4a40522..8d4a40522 100644
--- a/doc/en/hook/check_account_invite.bb
+++ b/doc/de/hook/check_account_invite.bb
diff --git a/doc/en/hook/check_account_password.bb b/doc/de/hook/check_account_password.bb
index ce5202f48..ce5202f48 100644
--- a/doc/en/hook/check_account_password.bb
+++ b/doc/de/hook/check_account_password.bb
diff --git a/doc/en/hook/check_channelallowed.bb b/doc/de/hook/check_channelallowed.bb
index e7559c92f..e7559c92f 100644
--- a/doc/en/hook/check_channelallowed.bb
+++ b/doc/de/hook/check_channelallowed.bb
diff --git a/doc/en/hook/check_siteallowed.bb b/doc/de/hook/check_siteallowed.bb
index 28134cbd2..28134cbd2 100644
--- a/doc/en/hook/check_siteallowed.bb
+++ b/doc/de/hook/check_siteallowed.bb
diff --git a/doc/en/hook/collect_public_recipients.bb b/doc/de/hook/collect_public_recipients.bb
index de3f4049e..de3f4049e 100644
--- a/doc/en/hook/collect_public_recipients.bb
+++ b/doc/de/hook/collect_public_recipients.bb
diff --git a/doc/en/hook/comments_are_now_closed.bb b/doc/de/hook/comments_are_now_closed.bb
index 4d3baa95a..4d3baa95a 100644
--- a/doc/en/hook/comments_are_now_closed.bb
+++ b/doc/de/hook/comments_are_now_closed.bb
diff --git a/doc/en/hook/connect_premium.bb b/doc/de/hook/connect_premium.bb
index ae3aafc66..ae3aafc66 100644
--- a/doc/en/hook/connect_premium.bb
+++ b/doc/de/hook/connect_premium.bb
diff --git a/doc/en/hook/connection_remove.bb b/doc/de/hook/connection_remove.bb
index bd13ae5f2..bd13ae5f2 100644
--- a/doc/en/hook/connection_remove.bb
+++ b/doc/de/hook/connection_remove.bb
diff --git a/doc/en/hook/connector_settings.bb b/doc/de/hook/connector_settings.bb
index 9b59c49da..9b59c49da 100644
--- a/doc/en/hook/connector_settings.bb
+++ b/doc/de/hook/connector_settings.bb
diff --git a/doc/en/hook/construct_page.bb b/doc/de/hook/construct_page.bb
index 700d9256f..700d9256f 100644
--- a/doc/en/hook/construct_page.bb
+++ b/doc/de/hook/construct_page.bb
diff --git a/doc/en/hook/contact_block_end.bb b/doc/de/hook/contact_block_end.bb
index 30a7d2d76..30a7d2d76 100644
--- a/doc/en/hook/contact_block_end.bb
+++ b/doc/de/hook/contact_block_end.bb
diff --git a/doc/en/hook/contact_edit.bb b/doc/de/hook/contact_edit.bb
index 5fd31fb1d..5fd31fb1d 100644
--- a/doc/en/hook/contact_edit.bb
+++ b/doc/de/hook/contact_edit.bb
diff --git a/doc/en/hook/contact_edit_post.bb b/doc/de/hook/contact_edit_post.bb
index bc736f8b8..bc736f8b8 100644
--- a/doc/en/hook/contact_edit_post.bb
+++ b/doc/de/hook/contact_edit_post.bb
diff --git a/doc/en/hook/contact_select_options.bb b/doc/de/hook/contact_select_options.bb
index 65f9154ff..65f9154ff 100644
--- a/doc/en/hook/contact_select_options.bb
+++ b/doc/de/hook/contact_select_options.bb
diff --git a/doc/en/hook/content_security_policy.bb b/doc/de/hook/content_security_policy.bb
index 96b8095ae..96b8095ae 100644
--- a/doc/en/hook/content_security_policy.bb
+++ b/doc/de/hook/content_security_policy.bb
diff --git a/doc/en/hook/conversation_start.bb b/doc/de/hook/conversation_start.bb
index 7208c8d8f..7208c8d8f 100644
--- a/doc/en/hook/conversation_start.bb
+++ b/doc/de/hook/conversation_start.bb
diff --git a/doc/en/hook/create_identity.bb b/doc/de/hook/create_identity.bb
index 5c1da2d43..5c1da2d43 100644
--- a/doc/en/hook/create_identity.bb
+++ b/doc/de/hook/create_identity.bb
diff --git a/doc/en/hook/cron.bb b/doc/de/hook/cron.bb
index 55120b6c2..55120b6c2 100644
--- a/doc/en/hook/cron.bb
+++ b/doc/de/hook/cron.bb
diff --git a/doc/en/hook/cron_daily.bb b/doc/de/hook/cron_daily.bb
index 802bea5e4..802bea5e4 100644
--- a/doc/en/hook/cron_daily.bb
+++ b/doc/de/hook/cron_daily.bb
diff --git a/doc/en/hook/cron_weekly.bb b/doc/de/hook/cron_weekly.bb
index c01bf9611..c01bf9611 100644
--- a/doc/en/hook/cron_weekly.bb
+++ b/doc/de/hook/cron_weekly.bb
diff --git a/doc/en/hook/crypto_methods.bb b/doc/de/hook/crypto_methods.bb
index 1b16f567d..1b16f567d 100644
--- a/doc/en/hook/crypto_methods.bb
+++ b/doc/de/hook/crypto_methods.bb
diff --git a/doc/en/hook/daemon_addon.bb b/doc/de/hook/daemon_addon.bb
index b60b25748..b60b25748 100644
--- a/doc/en/hook/daemon_addon.bb
+++ b/doc/de/hook/daemon_addon.bb
diff --git a/doc/en/hook/daemon_master_release.bb b/doc/de/hook/daemon_master_release.bb
index a17216d48..a17216d48 100644
--- a/doc/en/hook/daemon_master_release.bb
+++ b/doc/de/hook/daemon_master_release.bb
diff --git a/doc/en/hook/directory_item.bb b/doc/de/hook/directory_item.bb
index cb710e0b4..cb710e0b4 100644
--- a/doc/en/hook/directory_item.bb
+++ b/doc/de/hook/directory_item.bb
diff --git a/doc/en/hook/discover_channel_webfinger.bb b/doc/de/hook/discover_channel_webfinger.bb
index b0eb5f2c4..b0eb5f2c4 100644
--- a/doc/en/hook/discover_channel_webfinger.bb
+++ b/doc/de/hook/discover_channel_webfinger.bb
diff --git a/doc/en/hook/display_item.bb b/doc/de/hook/display_item.bb
index a6bfd621d..a6bfd621d 100644
--- a/doc/en/hook/display_item.bb
+++ b/doc/de/hook/display_item.bb
diff --git a/doc/en/hook/display_settings.bb b/doc/de/hook/display_settings.bb
index 1f1e0b491..1f1e0b491 100644
--- a/doc/en/hook/display_settings.bb
+++ b/doc/de/hook/display_settings.bb
diff --git a/doc/en/hook/display_settings_post.bb b/doc/de/hook/display_settings_post.bb
index d3bb39359..d3bb39359 100644
--- a/doc/en/hook/display_settings_post.bb
+++ b/doc/de/hook/display_settings_post.bb
diff --git a/doc/en/hook/donate_contributors.bb b/doc/de/hook/donate_contributors.bb
index f97c77efa..f97c77efa 100644
--- a/doc/en/hook/donate_contributors.bb
+++ b/doc/de/hook/donate_contributors.bb
diff --git a/doc/en/hook/donate_plugin.bb b/doc/de/hook/donate_plugin.bb
index db4a6f113..db4a6f113 100644
--- a/doc/en/hook/donate_plugin.bb
+++ b/doc/de/hook/donate_plugin.bb
diff --git a/doc/en/hook/donate_sponsors.bb b/doc/de/hook/donate_sponsors.bb
index 3abd46d42..3abd46d42 100644
--- a/doc/en/hook/donate_sponsors.bb
+++ b/doc/de/hook/donate_sponsors.bb
diff --git a/doc/en/hook/dreport_is_storable.bb b/doc/de/hook/dreport_is_storable.bb
index 9ca99b896..9ca99b896 100644
--- a/doc/en/hook/dreport_is_storable.bb
+++ b/doc/de/hook/dreport_is_storable.bb
diff --git a/doc/en/hook/dreport_process.bb b/doc/de/hook/dreport_process.bb
index 3ad331f41..3ad331f41 100644
--- a/doc/en/hook/dreport_process.bb
+++ b/doc/de/hook/dreport_process.bb
diff --git a/doc/en/hook/drop_item.bb b/doc/de/hook/drop_item.bb
index 35bb80f82..35bb80f82 100644
--- a/doc/en/hook/drop_item.bb
+++ b/doc/de/hook/drop_item.bb
diff --git a/doc/en/hook/dropdown_extras.bb b/doc/de/hook/dropdown_extras.bb
index 6d7110a76..6d7110a76 100644
--- a/doc/en/hook/dropdown_extras.bb
+++ b/doc/de/hook/dropdown_extras.bb
diff --git a/doc/en/hook/encode_object.bb b/doc/de/hook/encode_object.bb
index 0c8e86458..0c8e86458 100644
--- a/doc/en/hook/encode_object.bb
+++ b/doc/de/hook/encode_object.bb
diff --git a/doc/en/hook/enotify.bb b/doc/de/hook/enotify.bb
index 703a3ffa0..703a3ffa0 100644
--- a/doc/en/hook/enotify.bb
+++ b/doc/de/hook/enotify.bb
diff --git a/doc/en/hook/enotify_mail.bb b/doc/de/hook/enotify_mail.bb
index adeb8bd30..adeb8bd30 100644
--- a/doc/en/hook/enotify_mail.bb
+++ b/doc/de/hook/enotify_mail.bb
diff --git a/doc/en/hook/enotify_store.bb b/doc/de/hook/enotify_store.bb
index dc44cc320..dc44cc320 100644
--- a/doc/en/hook/enotify_store.bb
+++ b/doc/de/hook/enotify_store.bb
diff --git a/doc/en/hook/event_created.bb b/doc/de/hook/event_created.bb
index 222602e77..222602e77 100644
--- a/doc/en/hook/event_created.bb
+++ b/doc/de/hook/event_created.bb
diff --git a/doc/en/hook/event_store_event.bb b/doc/de/hook/event_store_event.bb
index 7015a8322..7015a8322 100644
--- a/doc/en/hook/event_store_event.bb
+++ b/doc/de/hook/event_store_event.bb
diff --git a/doc/en/hook/event_updated.bb b/doc/de/hook/event_updated.bb
index 69e3c72c1..69e3c72c1 100644
--- a/doc/en/hook/event_updated.bb
+++ b/doc/de/hook/event_updated.bb
diff --git a/doc/en/hook/externals_url_select.bb b/doc/de/hook/externals_url_select.bb
index a542dcb29..a542dcb29 100644
--- a/doc/en/hook/externals_url_select.bb
+++ b/doc/de/hook/externals_url_select.bb
diff --git a/doc/en/hook/feature_enabled.bb b/doc/de/hook/feature_enabled.bb
index 5630cc768..5630cc768 100644
--- a/doc/en/hook/feature_enabled.bb
+++ b/doc/de/hook/feature_enabled.bb
diff --git a/doc/en/hook/feature_settings.bb b/doc/de/hook/feature_settings.bb
index d1691eb38..d1691eb38 100644
--- a/doc/en/hook/feature_settings.bb
+++ b/doc/de/hook/feature_settings.bb
diff --git a/doc/en/hook/feature_settings_post.bb b/doc/de/hook/feature_settings_post.bb
index eecf941ff..eecf941ff 100644
--- a/doc/en/hook/feature_settings_post.bb
+++ b/doc/de/hook/feature_settings_post.bb
diff --git a/doc/en/hook/fetch_and_store.bb b/doc/de/hook/fetch_and_store.bb
index afece11a6..afece11a6 100644
--- a/doc/en/hook/fetch_and_store.bb
+++ b/doc/de/hook/fetch_and_store.bb
diff --git a/doc/en/hook/follow.bb b/doc/de/hook/follow.bb
index a97632b06..a97632b06 100644
--- a/doc/en/hook/follow.bb
+++ b/doc/de/hook/follow.bb
diff --git a/doc/en/hook/follow_allow.bb b/doc/de/hook/follow_allow.bb
index fdab1865c..fdab1865c 100644
--- a/doc/en/hook/follow_allow.bb
+++ b/doc/de/hook/follow_allow.bb
diff --git a/doc/en/hook/gender_selector.bb b/doc/de/hook/gender_selector.bb
index 0b56b5c9b..0b56b5c9b 100644
--- a/doc/en/hook/gender_selector.bb
+++ b/doc/de/hook/gender_selector.bb
diff --git a/doc/en/hook/gender_selector_min.bb b/doc/de/hook/gender_selector_min.bb
index 9d143855a..9d143855a 100644
--- a/doc/en/hook/gender_selector_min.bb
+++ b/doc/de/hook/gender_selector_min.bb
diff --git a/doc/en/hook/generate_map.bb b/doc/de/hook/generate_map.bb
index 33672d552..33672d552 100644
--- a/doc/en/hook/generate_map.bb
+++ b/doc/de/hook/generate_map.bb
diff --git a/doc/en/hook/generate_named_map.bb b/doc/de/hook/generate_named_map.bb
index 5bacb846d..5bacb846d 100644
--- a/doc/en/hook/generate_named_map.bb
+++ b/doc/de/hook/generate_named_map.bb
diff --git a/doc/en/hook/get_all_api_perms.bb b/doc/de/hook/get_all_api_perms.bb
index eb41f8a02..eb41f8a02 100644
--- a/doc/en/hook/get_all_api_perms.bb
+++ b/doc/de/hook/get_all_api_perms.bb
diff --git a/doc/en/hook/get_all_perms.bb b/doc/de/hook/get_all_perms.bb
index 149f8c78c..149f8c78c 100644
--- a/doc/en/hook/get_all_perms.bb
+++ b/doc/de/hook/get_all_perms.bb
diff --git a/doc/en/hook/get_default_export_sections b/doc/de/hook/get_default_export_sections
index 09b146643..09b146643 100644
--- a/doc/en/hook/get_default_export_sections
+++ b/doc/de/hook/get_default_export_sections
diff --git a/doc/en/hook/get_features.bb b/doc/de/hook/get_features.bb
index 66e81f13c..66e81f13c 100644
--- a/doc/en/hook/get_features.bb
+++ b/doc/de/hook/get_features.bb
diff --git a/doc/en/hook/get_photo.bb b/doc/de/hook/get_photo.bb
index eaf3beffb..eaf3beffb 100644
--- a/doc/en/hook/get_photo.bb
+++ b/doc/de/hook/get_photo.bb
diff --git a/doc/en/hook/get_profile_photo.bb b/doc/de/hook/get_profile_photo.bb
index ab07179ae..ab07179ae 100644
--- a/doc/en/hook/get_profile_photo.bb
+++ b/doc/de/hook/get_profile_photo.bb
diff --git a/doc/en/hook/get_role_perms.bb b/doc/de/hook/get_role_perms.bb
index 87830f8e3..87830f8e3 100644
--- a/doc/en/hook/get_role_perms.bb
+++ b/doc/de/hook/get_role_perms.bb
diff --git a/doc/en/hook/global_permissions.bb b/doc/de/hook/global_permissions.bb
index fe998ee9c..fe998ee9c 100644
--- a/doc/en/hook/global_permissions.bb
+++ b/doc/de/hook/global_permissions.bb
diff --git a/doc/en/hook/home_content.bb b/doc/de/hook/home_content.bb
index 7f32b3547..7f32b3547 100644
--- a/doc/en/hook/home_content.bb
+++ b/doc/de/hook/home_content.bb
diff --git a/doc/en/hook/home_init.bb b/doc/de/hook/home_init.bb
index 25dd72792..25dd72792 100644
--- a/doc/en/hook/home_init.bb
+++ b/doc/de/hook/home_init.bb
diff --git a/doc/en/hook/hostxrd.bb b/doc/de/hook/hostxrd.bb
index 2b67320cf..2b67320cf 100644
--- a/doc/en/hook/hostxrd.bb
+++ b/doc/de/hook/hostxrd.bb
diff --git a/doc/en/hook/html2bbcode.bb b/doc/de/hook/html2bbcode.bb
index 3061f05f0..3061f05f0 100644
--- a/doc/en/hook/html2bbcode.bb
+++ b/doc/de/hook/html2bbcode.bb
diff --git a/doc/en/hook/identity_basic_export.bb b/doc/de/hook/identity_basic_export.bb
index 71329ba1e..71329ba1e 100644
--- a/doc/en/hook/identity_basic_export.bb
+++ b/doc/de/hook/identity_basic_export.bb
diff --git a/doc/en/hook/import_author_xchan.bb b/doc/de/hook/import_author_xchan.bb
index e2340469d..e2340469d 100644
--- a/doc/en/hook/import_author_xchan.bb
+++ b/doc/de/hook/import_author_xchan.bb
diff --git a/doc/en/hook/import_channel.bb b/doc/de/hook/import_channel.bb
index b220b7415..b220b7415 100644
--- a/doc/en/hook/import_channel.bb
+++ b/doc/de/hook/import_channel.bb
diff --git a/doc/en/hook/import_directory_profile.bb b/doc/de/hook/import_directory_profile.bb
index e2fac59bc..e2fac59bc 100644
--- a/doc/en/hook/import_directory_profile.bb
+++ b/doc/de/hook/import_directory_profile.bb
diff --git a/doc/en/hook/import_xchan.bb b/doc/de/hook/import_xchan.bb
index 40e0783ce..40e0783ce 100644
--- a/doc/en/hook/import_xchan.bb
+++ b/doc/de/hook/import_xchan.bb
diff --git a/doc/en/hook/item_custom.bb b/doc/de/hook/item_custom.bb
index d20c7d76c..d20c7d76c 100644
--- a/doc/en/hook/item_custom.bb
+++ b/doc/de/hook/item_custom.bb
diff --git a/doc/en/hook/item_photo_menu.bb b/doc/de/hook/item_photo_menu.bb
index 8f9860a90..8f9860a90 100644
--- a/doc/en/hook/item_photo_menu.bb
+++ b/doc/de/hook/item_photo_menu.bb
diff --git a/doc/en/hook/item_store.bb b/doc/de/hook/item_store.bb
index 5d49b725c..5d49b725c 100644
--- a/doc/en/hook/item_store.bb
+++ b/doc/de/hook/item_store.bb
diff --git a/doc/en/hook/item_store_update.bb b/doc/de/hook/item_store_update.bb
index a7c58939a..a7c58939a 100644
--- a/doc/en/hook/item_store_update.bb
+++ b/doc/de/hook/item_store_update.bb
diff --git a/doc/en/hook/item_stored.bb b/doc/de/hook/item_stored.bb
index 8d706cb4e..8d706cb4e 100644
--- a/doc/en/hook/item_stored.bb
+++ b/doc/de/hook/item_stored.bb
diff --git a/doc/en/hook/item_stored_update.bb b/doc/de/hook/item_stored_update.bb
index 4532a347c..4532a347c 100644
--- a/doc/en/hook/item_stored_update.bb
+++ b/doc/de/hook/item_stored_update.bb
diff --git a/doc/en/hook/item_translate.bb b/doc/de/hook/item_translate.bb
index 695494b9c..695494b9c 100644
--- a/doc/en/hook/item_translate.bb
+++ b/doc/de/hook/item_translate.bb
diff --git a/doc/en/hook/jot_header_tpl_filter.bb b/doc/de/hook/jot_header_tpl_filter.bb
index b17d81d03..b17d81d03 100644
--- a/doc/en/hook/jot_header_tpl_filter.bb
+++ b/doc/de/hook/jot_header_tpl_filter.bb
diff --git a/doc/en/hook/jot_networks.bb b/doc/de/hook/jot_networks.bb
index 4c1629ba7..4c1629ba7 100644
--- a/doc/en/hook/jot_networks.bb
+++ b/doc/de/hook/jot_networks.bb
diff --git a/doc/en/hook/jot_tool.bb b/doc/de/hook/jot_tool.bb
index 22ba9701e..22ba9701e 100644
--- a/doc/en/hook/jot_tool.bb
+++ b/doc/de/hook/jot_tool.bb
diff --git a/doc/en/hook/jot_tpl_filter.bb b/doc/de/hook/jot_tpl_filter.bb
index 426da3c56..426da3c56 100644
--- a/doc/en/hook/jot_tpl_filter.bb
+++ b/doc/de/hook/jot_tpl_filter.bb
diff --git a/doc/en/hook/legal_webbie.bb b/doc/de/hook/legal_webbie.bb
index 8c7d32d56..8c7d32d56 100644
--- a/doc/en/hook/legal_webbie.bb
+++ b/doc/de/hook/legal_webbie.bb
diff --git a/doc/en/hook/legal_webbie_text.bb b/doc/de/hook/legal_webbie_text.bb
index 32c74c93b..32c74c93b 100644
--- a/doc/en/hook/legal_webbie_text.bb
+++ b/doc/de/hook/legal_webbie_text.bb
diff --git a/doc/en/hook/load_pdl.bb b/doc/de/hook/load_pdl.bb
index 149a3e766..149a3e766 100644
--- a/doc/en/hook/load_pdl.bb
+++ b/doc/de/hook/load_pdl.bb
diff --git a/doc/en/hook/local_dir_update.bb b/doc/de/hook/local_dir_update.bb
index d0b0f8ac1..d0b0f8ac1 100644
--- a/doc/en/hook/local_dir_update.bb
+++ b/doc/de/hook/local_dir_update.bb
diff --git a/doc/en/hook/logged_in.bb b/doc/de/hook/logged_in.bb
index b01041576..b01041576 100644
--- a/doc/en/hook/logged_in.bb
+++ b/doc/de/hook/logged_in.bb
diff --git a/doc/en/hook/logger.bb b/doc/de/hook/logger.bb
index 8fe989abd..8fe989abd 100644
--- a/doc/en/hook/logger.bb
+++ b/doc/de/hook/logger.bb
diff --git a/doc/en/hook/logging_out.bb b/doc/de/hook/logging_out.bb
index d47b9f1df..d47b9f1df 100644
--- a/doc/en/hook/logging_out.bb
+++ b/doc/de/hook/logging_out.bb
diff --git a/doc/en/hook/login_hook.bb b/doc/de/hook/login_hook.bb
index 156a0afcd..156a0afcd 100644
--- a/doc/en/hook/login_hook.bb
+++ b/doc/de/hook/login_hook.bb
diff --git a/doc/en/hook/magic_auth.bb b/doc/de/hook/magic_auth.bb
index 80d6edb27..80d6edb27 100644
--- a/doc/en/hook/magic_auth.bb
+++ b/doc/de/hook/magic_auth.bb
diff --git a/doc/en/hook/magic_auth_openid_success.bb b/doc/de/hook/magic_auth_openid_success.bb
index 810f2e06d..810f2e06d 100644
--- a/doc/en/hook/magic_auth_openid_success.bb
+++ b/doc/de/hook/magic_auth_openid_success.bb
diff --git a/doc/en/hook/magic_auth_success.bb b/doc/de/hook/magic_auth_success.bb
index d795e43e5..d795e43e5 100644
--- a/doc/en/hook/magic_auth_success.bb
+++ b/doc/de/hook/magic_auth_success.bb
diff --git a/doc/en/hook/main_slider.bb b/doc/de/hook/main_slider.bb
index a63c2170a..a63c2170a 100644
--- a/doc/en/hook/main_slider.bb
+++ b/doc/de/hook/main_slider.bb
diff --git a/doc/en/hook/marital_selector.bb b/doc/de/hook/marital_selector.bb
index 0f76c3f5a..0f76c3f5a 100644
--- a/doc/en/hook/marital_selector.bb
+++ b/doc/de/hook/marital_selector.bb
diff --git a/doc/en/hook/marital_selector_min.bb b/doc/de/hook/marital_selector_min.bb
index f02d21f20..f02d21f20 100644
--- a/doc/en/hook/marital_selector_min.bb
+++ b/doc/de/hook/marital_selector_min.bb
diff --git a/doc/en/hook/markdown_to_bb.bb b/doc/de/hook/markdown_to_bb.bb
index 8af637c8c..8af637c8c 100644
--- a/doc/en/hook/markdown_to_bb.bb
+++ b/doc/de/hook/markdown_to_bb.bb
diff --git a/doc/en/hook/module_loaded.bb b/doc/de/hook/module_loaded.bb
index cb0d2302d..cb0d2302d 100644
--- a/doc/en/hook/module_loaded.bb
+++ b/doc/de/hook/module_loaded.bb
diff --git a/doc/en/hook/module_mod_aftercontent.bb b/doc/de/hook/module_mod_aftercontent.bb
index 04e3c8d88..04e3c8d88 100644
--- a/doc/en/hook/module_mod_aftercontent.bb
+++ b/doc/de/hook/module_mod_aftercontent.bb
diff --git a/doc/en/hook/module_mod_content.bb b/doc/de/hook/module_mod_content.bb
index eef5b7ba5..eef5b7ba5 100644
--- a/doc/en/hook/module_mod_content.bb
+++ b/doc/de/hook/module_mod_content.bb
diff --git a/doc/en/hook/module_mod_init.bb b/doc/de/hook/module_mod_init.bb
index 52fe5a616..52fe5a616 100644
--- a/doc/en/hook/module_mod_init.bb
+++ b/doc/de/hook/module_mod_init.bb
diff --git a/doc/en/hook/module_mod_post.bb b/doc/de/hook/module_mod_post.bb
index 3adb0e737..3adb0e737 100644
--- a/doc/en/hook/module_mod_post.bb
+++ b/doc/de/hook/module_mod_post.bb
diff --git a/doc/en/hook/mood_verbs.bb b/doc/de/hook/mood_verbs.bb
index 67fb719dd..67fb719dd 100644
--- a/doc/en/hook/mood_verbs.bb
+++ b/doc/de/hook/mood_verbs.bb
diff --git a/doc/en/hook/nav.bb b/doc/de/hook/nav.bb
index b52f90602..b52f90602 100644
--- a/doc/en/hook/nav.bb
+++ b/doc/de/hook/nav.bb
diff --git a/doc/en/hook/network_content_init.bb b/doc/de/hook/network_content_init.bb
index 224da393a..224da393a 100644
--- a/doc/en/hook/network_content_init.bb
+++ b/doc/de/hook/network_content_init.bb
diff --git a/doc/en/hook/network_ping.bb b/doc/de/hook/network_ping.bb
index 78deefe78..78deefe78 100644
--- a/doc/en/hook/network_ping.bb
+++ b/doc/de/hook/network_ping.bb
diff --git a/doc/en/hook/network_to_name.bb b/doc/de/hook/network_to_name.bb
index eea4a1841..eea4a1841 100644
--- a/doc/en/hook/network_to_name.bb
+++ b/doc/de/hook/network_to_name.bb
diff --git a/doc/en/hook/notifier_end.bb b/doc/de/hook/notifier_end.bb
index df9d852bd..df9d852bd 100644
--- a/doc/en/hook/notifier_end.bb
+++ b/doc/de/hook/notifier_end.bb
diff --git a/doc/en/hook/notifier_hub.bb b/doc/de/hook/notifier_hub.bb
index 4255ce446..4255ce446 100644
--- a/doc/en/hook/notifier_hub.bb
+++ b/doc/de/hook/notifier_hub.bb
diff --git a/doc/en/hook/notifier_normal.bb b/doc/de/hook/notifier_normal.bb
index 0059baa47..0059baa47 100644
--- a/doc/en/hook/notifier_normal.bb
+++ b/doc/de/hook/notifier_normal.bb
diff --git a/doc/en/hook/obj_verbs.bb b/doc/de/hook/obj_verbs.bb
index ca98229aa..ca98229aa 100644
--- a/doc/en/hook/obj_verbs.bb
+++ b/doc/de/hook/obj_verbs.bb
diff --git a/doc/en/hook/oembed_probe.bb b/doc/de/hook/oembed_probe.bb
index 4f32ac267..4f32ac267 100644
--- a/doc/en/hook/oembed_probe.bb
+++ b/doc/de/hook/oembed_probe.bb
diff --git a/doc/en/hook/other_encapsulate.bb b/doc/de/hook/other_encapsulate.bb
index ea0cdf622..ea0cdf622 100644
--- a/doc/en/hook/other_encapsulate.bb
+++ b/doc/de/hook/other_encapsulate.bb
diff --git a/doc/en/hook/other_unencapsulate.bb b/doc/de/hook/other_unencapsulate.bb
index c8b0b617f..c8b0b617f 100644
--- a/doc/en/hook/other_unencapsulate.bb
+++ b/doc/de/hook/other_unencapsulate.bb
diff --git a/doc/en/hook/page_content_top.bb b/doc/de/hook/page_content_top.bb
index 137e3abfd..137e3abfd 100644
--- a/doc/en/hook/page_content_top.bb
+++ b/doc/de/hook/page_content_top.bb
diff --git a/doc/en/hook/page_end.bb b/doc/de/hook/page_end.bb
index 09293cf50..09293cf50 100644
--- a/doc/en/hook/page_end.bb
+++ b/doc/de/hook/page_end.bb
diff --git a/doc/en/hook/page_header.bb b/doc/de/hook/page_header.bb
index ffaa791c9..ffaa791c9 100644
--- a/doc/en/hook/page_header.bb
+++ b/doc/de/hook/page_header.bb
diff --git a/doc/en/hook/page_meta.bb b/doc/de/hook/page_meta.bb
index 30a8f9440..30a8f9440 100644
--- a/doc/en/hook/page_meta.bb
+++ b/doc/de/hook/page_meta.bb
diff --git a/doc/en/hook/parse_atom.bb b/doc/de/hook/parse_atom.bb
index c8037317f..c8037317f 100644
--- a/doc/en/hook/parse_atom.bb
+++ b/doc/de/hook/parse_atom.bb
diff --git a/doc/en/hook/parse_link.bb b/doc/de/hook/parse_link.bb
index 1c328059c..1c328059c 100644
--- a/doc/en/hook/parse_link.bb
+++ b/doc/de/hook/parse_link.bb
diff --git a/doc/en/hook/pdl_selector.bb b/doc/de/hook/pdl_selector.bb
index bee01c843..bee01c843 100644
--- a/doc/en/hook/pdl_selector.bb
+++ b/doc/de/hook/pdl_selector.bb
diff --git a/doc/en/hook/perm_is_allowed.bb b/doc/de/hook/perm_is_allowed.bb
index aac647609..aac647609 100644
--- a/doc/en/hook/perm_is_allowed.bb
+++ b/doc/de/hook/perm_is_allowed.bb
diff --git a/doc/en/hook/permissions_create.bb b/doc/de/hook/permissions_create.bb
index 22f80a80e..22f80a80e 100644
--- a/doc/en/hook/permissions_create.bb
+++ b/doc/de/hook/permissions_create.bb
diff --git a/doc/en/hook/permissions_update.bb b/doc/de/hook/permissions_update.bb
index 40366b33d..40366b33d 100644
--- a/doc/en/hook/permissions_update.bb
+++ b/doc/de/hook/permissions_update.bb
diff --git a/doc/en/hook/permit_hook.bb b/doc/de/hook/permit_hook.bb
index e69de29bb..e69de29bb 100644
--- a/doc/en/hook/permit_hook.bb
+++ b/doc/de/hook/permit_hook.bb
diff --git a/doc/en/hook/personal_xrd.bb b/doc/de/hook/personal_xrd.bb
index 71d4bd8dd..71d4bd8dd 100644
--- a/doc/en/hook/personal_xrd.bb
+++ b/doc/de/hook/personal_xrd.bb
diff --git a/doc/en/hook/photo_post_end.bb b/doc/de/hook/photo_post_end.bb
index 8a3291763..8a3291763 100644
--- a/doc/en/hook/photo_post_end.bb
+++ b/doc/de/hook/photo_post_end.bb
diff --git a/doc/en/hook/photo_upload_begin.bb b/doc/de/hook/photo_upload_begin.bb
index 5e441a12a..5e441a12a 100644
--- a/doc/en/hook/photo_upload_begin.bb
+++ b/doc/de/hook/photo_upload_begin.bb
diff --git a/doc/en/hook/photo_upload_end.bb b/doc/de/hook/photo_upload_end.bb
index 956175f1d..956175f1d 100644
--- a/doc/en/hook/photo_upload_end.bb
+++ b/doc/de/hook/photo_upload_end.bb
diff --git a/doc/en/hook/photo_upload_file.bb b/doc/de/hook/photo_upload_file.bb
index 726622ac0..726622ac0 100644
--- a/doc/en/hook/photo_upload_file.bb
+++ b/doc/de/hook/photo_upload_file.bb
diff --git a/doc/en/hook/photo_upload_form.bb b/doc/de/hook/photo_upload_form.bb
index 70b8318b8..70b8318b8 100644
--- a/doc/en/hook/photo_upload_form.bb
+++ b/doc/de/hook/photo_upload_form.bb
diff --git a/doc/en/hook/photo_view_filter.bb b/doc/de/hook/photo_view_filter.bb
index 0780c1edc..0780c1edc 100644
--- a/doc/en/hook/photo_view_filter.bb
+++ b/doc/de/hook/photo_view_filter.bb
diff --git a/doc/en/hook/poke_verbs.bb b/doc/de/hook/poke_verbs.bb
index 54d68c3a2..54d68c3a2 100644
--- a/doc/en/hook/poke_verbs.bb
+++ b/doc/de/hook/poke_verbs.bb
diff --git a/doc/en/hook/post_local.bb b/doc/de/hook/post_local.bb
index 5aa723cb9..5aa723cb9 100644
--- a/doc/en/hook/post_local.bb
+++ b/doc/de/hook/post_local.bb
diff --git a/doc/en/hook/post_local_end.bb b/doc/de/hook/post_local_end.bb
index 380166fdb..380166fdb 100644
--- a/doc/en/hook/post_local_end.bb
+++ b/doc/de/hook/post_local_end.bb
diff --git a/doc/en/hook/post_local_start.bb b/doc/de/hook/post_local_start.bb
index 2f684f67a..2f684f67a 100644
--- a/doc/en/hook/post_local_start.bb
+++ b/doc/de/hook/post_local_start.bb
diff --git a/doc/en/hook/post_mail.bb b/doc/de/hook/post_mail.bb
index 8f67ad4f0..8f67ad4f0 100644
--- a/doc/en/hook/post_mail.bb
+++ b/doc/de/hook/post_mail.bb
diff --git a/doc/en/hook/post_mail_end.bb b/doc/de/hook/post_mail_end.bb
index 7f0085773..7f0085773 100644
--- a/doc/en/hook/post_mail_end.bb
+++ b/doc/de/hook/post_mail_end.bb
diff --git a/doc/en/hook/post_remote.bb b/doc/de/hook/post_remote.bb
index f8e087eee..f8e087eee 100644
--- a/doc/en/hook/post_remote.bb
+++ b/doc/de/hook/post_remote.bb
diff --git a/doc/en/hook/post_remote_end.bb b/doc/de/hook/post_remote_end.bb
index 0fef20cbf..0fef20cbf 100644
--- a/doc/en/hook/post_remote_end.bb
+++ b/doc/de/hook/post_remote_end.bb
diff --git a/doc/en/hook/post_remote_update.bb b/doc/de/hook/post_remote_update.bb
index fd358db28..fd358db28 100644
--- a/doc/en/hook/post_remote_update.bb
+++ b/doc/de/hook/post_remote_update.bb
diff --git a/doc/en/hook/post_remote_update_end.bb b/doc/de/hook/post_remote_update_end.bb
index 95f1e6f78..95f1e6f78 100644
--- a/doc/en/hook/post_remote_update_end.bb
+++ b/doc/de/hook/post_remote_update_end.bb
diff --git a/doc/en/hook/prepare_body.bb b/doc/de/hook/prepare_body.bb
index 3f1eaef85..3f1eaef85 100644
--- a/doc/en/hook/prepare_body.bb
+++ b/doc/de/hook/prepare_body.bb
diff --git a/doc/en/hook/prepare_body_final.bb b/doc/de/hook/prepare_body_final.bb
index 96d1ae389..96d1ae389 100644
--- a/doc/en/hook/prepare_body_final.bb
+++ b/doc/de/hook/prepare_body_final.bb
diff --git a/doc/en/hook/prepare_body_init.bb b/doc/de/hook/prepare_body_init.bb
index f3de79970..f3de79970 100644
--- a/doc/en/hook/prepare_body_init.bb
+++ b/doc/de/hook/prepare_body_init.bb
diff --git a/doc/en/hook/privacygroup_extras.bb b/doc/de/hook/privacygroup_extras.bb
index bd67f2470..bd67f2470 100644
--- a/doc/en/hook/privacygroup_extras.bb
+++ b/doc/de/hook/privacygroup_extras.bb
diff --git a/doc/en/hook/privacygroup_extras_drop.bb b/doc/de/hook/privacygroup_extras_drop.bb
index fd27ab255..fd27ab255 100644
--- a/doc/en/hook/privacygroup_extras_drop.bb
+++ b/doc/de/hook/privacygroup_extras_drop.bb
diff --git a/doc/en/hook/privacygroup_extras_post.bb b/doc/de/hook/privacygroup_extras_post.bb
index 704db1997..704db1997 100644
--- a/doc/en/hook/privacygroup_extras_post.bb
+++ b/doc/de/hook/privacygroup_extras_post.bb
diff --git a/doc/en/hook/proc_run.bb b/doc/de/hook/proc_run.bb
index a3759794a..a3759794a 100644
--- a/doc/en/hook/proc_run.bb
+++ b/doc/de/hook/proc_run.bb
diff --git a/doc/en/hook/process_channel_sync_delivery.bb b/doc/de/hook/process_channel_sync_delivery.bb
index c0416c8cb..c0416c8cb 100644
--- a/doc/en/hook/process_channel_sync_delivery.bb
+++ b/doc/de/hook/process_channel_sync_delivery.bb
diff --git a/doc/en/hook/profile_advanced.bb b/doc/de/hook/profile_advanced.bb
index 65e56afd6..65e56afd6 100644
--- a/doc/en/hook/profile_advanced.bb
+++ b/doc/de/hook/profile_advanced.bb
diff --git a/doc/en/hook/profile_edit.bb b/doc/de/hook/profile_edit.bb
index e60663d4a..e60663d4a 100644
--- a/doc/en/hook/profile_edit.bb
+++ b/doc/de/hook/profile_edit.bb
diff --git a/doc/en/hook/profile_photo_content_end.bb b/doc/de/hook/profile_photo_content_end.bb
index 518415c4d..518415c4d 100644
--- a/doc/en/hook/profile_photo_content_end.bb
+++ b/doc/de/hook/profile_photo_content_end.bb
diff --git a/doc/en/hook/profile_post.bb b/doc/de/hook/profile_post.bb
index d22d8fbc7..d22d8fbc7 100644
--- a/doc/en/hook/profile_post.bb
+++ b/doc/de/hook/profile_post.bb
diff --git a/doc/en/hook/profile_sidebar.bb b/doc/de/hook/profile_sidebar.bb
index bfd059e4b..bfd059e4b 100644
--- a/doc/en/hook/profile_sidebar.bb
+++ b/doc/de/hook/profile_sidebar.bb
diff --git a/doc/en/hook/profile_sidebar_enter.bb b/doc/de/hook/profile_sidebar_enter.bb
index 9d6726a30..9d6726a30 100644
--- a/doc/en/hook/profile_sidebar_enter.bb
+++ b/doc/de/hook/profile_sidebar_enter.bb
diff --git a/doc/en/hook/register_account.bb b/doc/de/hook/register_account.bb
index df4de2b30..df4de2b30 100644
--- a/doc/en/hook/register_account.bb
+++ b/doc/de/hook/register_account.bb
diff --git a/doc/en/hook/render_location.bb b/doc/de/hook/render_location.bb
index 41501c087..41501c087 100644
--- a/doc/en/hook/render_location.bb
+++ b/doc/de/hook/render_location.bb
diff --git a/doc/en/hook/replace_macros.bb b/doc/de/hook/replace_macros.bb
index fac39dd7b..fac39dd7b 100644
--- a/doc/en/hook/replace_macros.bb
+++ b/doc/de/hook/replace_macros.bb
diff --git a/doc/en/hook/reverse_magic_auth.bb b/doc/de/hook/reverse_magic_auth.bb
index 4cbd84b93..4cbd84b93 100644
--- a/doc/en/hook/reverse_magic_auth.bb
+++ b/doc/de/hook/reverse_magic_auth.bb
diff --git a/doc/en/hook/settings_form.bb b/doc/de/hook/settings_form.bb
index d65341181..d65341181 100644
--- a/doc/en/hook/settings_form.bb
+++ b/doc/de/hook/settings_form.bb
diff --git a/doc/en/hook/settings_post.bb b/doc/de/hook/settings_post.bb
index f72546c11..f72546c11 100644
--- a/doc/en/hook/settings_post.bb
+++ b/doc/de/hook/settings_post.bb
diff --git a/doc/en/hook/sexpref_selector.bb b/doc/de/hook/sexpref_selector.bb
index b4dad6b38..b4dad6b38 100644
--- a/doc/en/hook/sexpref_selector.bb
+++ b/doc/de/hook/sexpref_selector.bb
diff --git a/doc/en/hook/sexpref_selector_min.bb b/doc/de/hook/sexpref_selector_min.bb
index 6f49946af..6f49946af 100644
--- a/doc/en/hook/sexpref_selector_min.bb
+++ b/doc/de/hook/sexpref_selector_min.bb
diff --git a/doc/en/hook/smilie.bb b/doc/de/hook/smilie.bb
index 575acc178..575acc178 100644
--- a/doc/en/hook/smilie.bb
+++ b/doc/de/hook/smilie.bb
diff --git a/doc/en/hook/status_editor.bb b/doc/de/hook/status_editor.bb
index 00e97a7c9..00e97a7c9 100644
--- a/doc/en/hook/status_editor.bb
+++ b/doc/de/hook/status_editor.bb
diff --git a/doc/en/hook/stream_item.bb b/doc/de/hook/stream_item.bb
index 30086961d..30086961d 100644
--- a/doc/en/hook/stream_item.bb
+++ b/doc/de/hook/stream_item.bb
diff --git a/doc/en/hook/system_app_installed_filter.bb b/doc/de/hook/system_app_installed_filter.bb
index a269a79a8..a269a79a8 100644
--- a/doc/en/hook/system_app_installed_filter.bb
+++ b/doc/de/hook/system_app_installed_filter.bb
diff --git a/doc/en/hook/tagged.bb b/doc/de/hook/tagged.bb
index 05d081d07..05d081d07 100644
--- a/doc/en/hook/tagged.bb
+++ b/doc/de/hook/tagged.bb
diff --git a/doc/en/hook/update_unseen.bb b/doc/de/hook/update_unseen.bb
index 8fb02c239..8fb02c239 100644
--- a/doc/en/hook/update_unseen.bb
+++ b/doc/de/hook/update_unseen.bb
diff --git a/doc/en/hook/validate_channelname.bb b/doc/de/hook/validate_channelname.bb
index 2ab12bbec..2ab12bbec 100644
--- a/doc/en/hook/validate_channelname.bb
+++ b/doc/de/hook/validate_channelname.bb
diff --git a/doc/en/hook/webfinger.bb b/doc/de/hook/webfinger.bb
index 7cc24322f..7cc24322f 100644
--- a/doc/en/hook/webfinger.bb
+++ b/doc/de/hook/webfinger.bb
diff --git a/doc/en/hook/well_known.bb b/doc/de/hook/well_known.bb
index 778b27a02..778b27a02 100644
--- a/doc/en/hook/well_known.bb
+++ b/doc/de/hook/well_known.bb
diff --git a/doc/en/hook/wiki_preprocess.bb b/doc/de/hook/wiki_preprocess.bb
index 913b601ba..913b601ba 100644
--- a/doc/en/hook/wiki_preprocess.bb
+++ b/doc/de/hook/wiki_preprocess.bb
diff --git a/doc/en/hook/zid.bb b/doc/de/hook/zid.bb
index 2210c1342..2210c1342 100644
--- a/doc/en/hook/zid.bb
+++ b/doc/de/hook/zid.bb
diff --git a/doc/en/hook/zid_init.bb b/doc/de/hook/zid_init.bb
index 131dd8f72..131dd8f72 100644
--- a/doc/en/hook/zid_init.bb
+++ b/doc/de/hook/zid_init.bb
diff --git a/doc/en/hook/zot_best_algorithm.bb b/doc/de/hook/zot_best_algorithm.bb
index ccde505cb..ccde505cb 100644
--- a/doc/en/hook/zot_best_algorithm.bb
+++ b/doc/de/hook/zot_best_algorithm.bb
diff --git a/doc/en/hook/zot_finger.bb b/doc/de/hook/zot_finger.bb
index 9383b4c31..9383b4c31 100644
--- a/doc/en/hook/zot_finger.bb
+++ b/doc/de/hook/zot_finger.bb
diff --git a/doc/en/hooks.html b/doc/de/hooks.html
index a7ee314e7..a7ee314e7 100644
--- a/doc/en/hooks.html
+++ b/doc/de/hooks.html
diff --git a/doc/de/main.bb b/doc/de/main.bb
deleted file mode 100644
index eee2e85fe..000000000
--- a/doc/de/main.bb
+++ /dev/null
@@ -1,11 +0,0 @@
-[img][baseurl]/images/hubzilla-banner.png[/img]
-
-[zrl=[baseurl]/help/about]Was ist $Projectname?[/zrl]
-$Projectname ist eine dezentrale Kommunikations- und Publishing-Plattform. Es ermöglicht Dir die volle Kontrolle über all Deine Kommunikation mit Hilfe von automatischer Verschlüsselung und detaillierter Zugriffskontrolle. Du, und [i]nur[/i] Du, entscheidest, wer Deine Beiträge sehen darf. $Projectname ist der Nachfolger, der seit einigen Jahren erfolgreichen Plattformen Friendica und RedMatrix.
-
-[zrl=[baseurl]/help/features]Features von $Projectname[/zrl]
-$Projectname funktioniert schon heute als ein globales verteiltes Netzwerk und beweist täglich seine Vielseitigkeit und Skalierbarkeit - auf kleinen Privatservern wie auch auf riesigen Sites.
-Kommunikationsplattformen für Familien, verteilte Online-Communities, Support-Foren, Blogs und Homepages. Oder auch professionelle Inhalte-Anbieter mit kommerziellen Premium-Kanälen und eingeschränktem Zugriff – was immer Du willst, $Projectname unterstützt Dich in Deinem kreativen Schaffen.
-
-[zrl=[baseurl]/help/what_is_zot]Got Zot? Hast Du schon Zot? Wenn nicht wird es Zeit.[/zrl]
-Zot ist ein großartiges neues Kommunikationsprotokoll, das für $Projectname entwickelt wurde. Als Mitglied bist Du dank „Nomadischer Identität“ nicht länger an einen einzigen Server oder einen einzigen Anbieter gebunden. Ziehe einfach auf einen anderen Server um und behalte dabei alle Deine Kontakte, oder klone Deinen Kanal und lasse ihn auf mehreren Servern gleichzeitig laufen – sollte einer davon plötzlich geschlossen werden, ist das kein Problem für Dich. Und bist Du erst Teil des $Projectname-Netzwerkes, musst Du Dich nie wieder mehrfach anmelden, selbst wenn Du Seiten auf einem andere Hub (den $Projectname-Servern) betrachtest. Zot ist, was das $Projectname-Netzwerk besonders macht.
diff --git a/doc/de/member/AdvancedSearch.md b/doc/de/member/AdvancedSearch.md
new file mode 100644
index 000000000..35479dba1
--- /dev/null
+++ b/doc/de/member/AdvancedSearch.md
@@ -0,0 +1,30 @@
+### Erweiterte Verzeichnissuche
+
+Die erweiterte Verzeichnissuche wird im „Expertenmodus“ auf der Seite Einstellungen => Zusätzliche Funktionen aktiviert.
+
+Auf der Verzeichnisseite wird eine Option namens „Erweitert“ im Widget „Kanäle suchen“ (normalerweise in der Seitenleiste) angezeigt. Wenn Sie auf „Erweitert“ klicken, wird ein weiteres Suchfeld geöffnet, in das Sie erweiterte Suchanfragen eingeben können.
+Zu den erweiterten Suchanfragen gehören
+
+- name=xxx [Kanalname enthält xxx]
+- address=xxx [Kanaladresse (Webbie) enthält xxx]
+- locale=xxx [Gebietsschema (typischerweise 'Stadt') enthält xxx]
+- region=xxx [Region (Bundesland/Territorium) enthält xxx]
+- postcode=xxx [Postleitzahl enthält xxx]
+- country=xxx [Ländername enthält xxx]
+- gender=xxx [Geschlecht enthält xxx]
+- marital=xxx [Familienstand enthält xxx]
+- sexual=xxx [Sexuelle Präferenz enthält xxx]
+- keywords=xxx [Schlüsselwörter enthalten xxx]
+
+Es gibt viele Gründe, warum ein Treffer nicht das liefert, wonach Sie suchen, da viele Kanäle in ihrem (öffentlichen) Standardprofil keine detaillierten Informationen bereitstellen und viele dieser Felder Freitext-Eingaben in mehreren Sprachen zulassen - und dies kann schwierig sein, einen genauen Treffer zu erzielen. Zum Beispiel können Sie bessere Ergebnisse erzielen, wenn Sie jemanden in den USA mit 'country=u' finden (zusammen mit einigen seltsamen Kanälen aus Deutschland, Bulgarien und Australien), da dies in einem Profil als US, U.S.A, USA, Vereinigte Staaten, usw. dargestellt werden könnte.
+
+Künftige Überarbeitungen dieses Tools könnten versuchen, einige dieser Schwierigkeiten zu beseitigen.
+Anfragen können mit 'und', 'oder' und 'und nicht' verbunden werden.
+
+Begriffe, die Leerzeichen enthalten, müssen in Anführungszeichen gesetzt werden.
+
+Beispiel:
+
+```
+name=„charlie brown“ und country=canada und nicht gender=female
+``` \ No newline at end of file
diff --git a/doc/de/member/NSFW.md b/doc/de/member/NSFW.md
new file mode 100644
index 000000000..031bc0f47
--- /dev/null
+++ b/doc/de/member/NSFW.md
@@ -0,0 +1,21 @@
+### Inhaltswarnung/NSFW
+
+Inhaltswarnungen bzw. das Verbergen von bestimmten Inhalten wird bei Hubzilla mit der App "NSFW" realisiert.
+
+Während Sie bei anderen Diensten im Fediverse darauf angewiesen sind, dass die Verfasser von Postings möglicherweise "sensible" Inhalte hinter einem Contentwarning (einer Inhalts- bzw. Triggerwarnung) verbergen, haben Sie bei Hubzilla diese Funktionalität als Rezipient selbst in der Hand. Mit der App NSFW können Sie Filter erstellen, die dafür sorgen, dass Beiträge, die unter die Filterregeln fallen, eingeklappt angezeigt werden. Erst ein Klick auf den Button zeigt den Inhalt des Postings.
+
+Starten Sie die App, so erscheint die Eingabemaske für die Filter.
+
+![NSFW 01](/help/de/member/pic/nsfw01.png)
+
+Hier können Sie nun Schlüsselwörter und sogar [reguläre Ausdrücke](https://de.wikipedia.org/wiki/Regulärer_Ausdruck) eingeben, auf welche das Posting durchsucht wird. Wird eines der Wörter oder ein Textmuster gefunden, so wird der Inhalt im Stream zugeklappt angezeigt.
+
+Es ist auch möglich, nach Sprachen zu filtern (lang=xx oder lang!=xx).
+
+Findet sich nun ein Ausdruck, welcher zu einem der eingegebenen Filtern passt, in einem Posting, so wird das Posting für Sie hinter einer Inhaltswarnung zunächst verborgen.
+
+![NSFW 02](/help/de/member/pic/nsfw02.png)
+
+![NSFW 03](/help/de/member/pic/nsfw03.png)
+
+![NSFW 04](/help/de/member/pic/nsfw04.png)
diff --git a/doc/de/member/account_settings.md b/doc/de/member/account_settings.md
new file mode 100644
index 000000000..ac7263925
--- /dev/null
+++ b/doc/de/member/account_settings.md
@@ -0,0 +1,5 @@
+#### Konto-Einstellungen
+
+Mit den Konto-Einstellungen können Sie die Daten Ihres Accounts ändern.
+
+![Konto-Einstellungen](/help/de/member/pic/ktoeinst01.png)
diff --git a/doc/de/member/accounts_profiles_channels_basics.md b/doc/de/member/accounts_profiles_channels_basics.md
new file mode 100644
index 000000000..fd5fac77b
--- /dev/null
+++ b/doc/de/member/accounts_profiles_channels_basics.md
@@ -0,0 +1,30 @@
+### Konten, Profile und Kanäle
+
+Sobald Sie ein *Konto* im Grid registriert haben, haben Sie auch ein *Profil* und einen *Kanal* erstellt.
+
+**Konto**
+
+Sie haben *ein* Konto. Dieses besteht aus Ihrem E-Mail-Konto und Ihrem Passwort. Mit Ihrem Konto haben Sie Zugang zu Ihrem Profil und Ihrem Kanal.
+
+*Betrachten Sie Ihr Konto als die Art und Weise, wie Sie sich bei einer Hubzilla-Website authentifizieren.* *Damit können Sie Dinge tun, wie z. B. Profile und Kanäle erstellen, mit denen Sie sich mit anderen Personen verbinden können.*
+
+**Profil**
+
+Sicherlich haben Sie sich auch bei anderen Internetdiensten registriert, z. B. bei Foren oder Online-Communities. Bei all diesen Diensten haben Sie einige Informationen über sich selbst angegeben, z. B. Geburtsdatum, Land, Alter und Ähnliches.
+
+Wenn Sie möchten, können Sie Ihr Profil hier einsehen: `[baseurl]/profile/[webname]` und bearbeiten, indem Sie auf das Bleistiftsymbol neben Ihrem Avatarbild klicken.
+
+Im Gegensatz zu anderen Diensten bietet dir hubzilla den Vorteil, dass du *viele weitere Profile* anlegen kannst. Auf diese Weise können Sie zwischen Profilen unterscheiden, die sich speziell an jedermann (Ihr öffentliches Profil), Ihre Arbeitskollegen, Ihre Familie und Ihren Partner richten.
+
+*Betrachten Sie Ihr Profil als die grundlegenden Informationen über sich selbst, die Sie anderen Menschen mitteilen.*
+
+**Kanal**
+
+Bei der Registrierung haben Sie Ihren ersten *Kanal* erstellt. Ja, neben mehreren Profilen können Sie auch mehrere Channels haben. Das mag am Anfang etwas verwirrend sein, aber lassen Sie uns die Dinge klarstellen. Sie haben bereits einen Kanal erstellt. Diesen können Sie für die Öffentlichkeit nutzen, um mit Leuten über das tägliche Leben zu kommunizieren. Aber vielleicht sind Sie ein begeisterter Leser von Büchern und das langweilt viele Leute. Also eröffnen Sie einen *zweiten Kanal* nur für Buchliebhaber, in dem Sie alle so viel über Bücher reden können, wie Sie wollen. Natürlich ist dies ein neuer Strom von Beiträgen, mit einem neuen Profil (... oder neuen*Profilen*...) und völlig anderen Kontakten. Einige Verbindungen können in beiden Kanälen bestehen, aber es wird auch welche geben, die nur in einem der beiden Kanäle bestehen. Sie selbst wechseln einfach zwischen den beiden Kanälen hin und her, so wie Sie auch im wirklichen Leben zwischen den Kanälen hin und her wechseln würden, wenn Sie sich mit Leuten unterhalten, die Sie auf der Straße treffen, oder mit Leuten, die Sie speziell für ein Gespräch über Bücher treffen. Sie können sich sogar mit sich selbst verbinden, oder besser: mit Ihrem anderen Kanal :)
+
+*Stellen Sie sich einen Kanal als verschiedene Bereiche vor, die verschiedenen Themen gewidmet sind und in denen Sie sich mit verschiedenen Menschen treffen.*
+
+#include doc/de/member/channels.md;
+#include doc/de/member/profiles.md;
+#include doc/de/member/settings.md;
+#include doc/de/member/connecting_with_channels.md;
diff --git a/doc/de/member/additional_features.md b/doc/de/member/additional_features.md
new file mode 100644
index 000000000..554c52af1
--- /dev/null
+++ b/doc/de/member/additional_features.md
@@ -0,0 +1,31 @@
+#### Zusätzliche Funktionen (verborgene Einstellungen)
+
+Die Einstellungen "Zusätzliche-Funktionen" sind in der Gesamtheit weder über das Menü, noch über ein Symbol/Icon erreichbar. Sämtliche einzelnen Funktions-Einstellungen kann man aber auch in der jeweiligen App über das Zahnrad neben dem Hauptmenü (Avatar-Bild) erreichen.
+
+Es handelt sich um Einstellungen zu weiteren Funktionen in allen mögliche Bereichen von Hubzilla. Um die Einstellungen aufzurufen, muss man im Browser an die URL des Hubs `/settings/features` anhängen, also z.B. `https://klacker.org/settings/features`.
+
+![Features Einstellungen 01](/help/de/member/pic/feateinst01.png)
+
+Die Voreinstellungen für sämtliche dieser Optionen werden vom Administrator des Hubs vorgenommen. Diese Voreinstellung kann durch den Nutzer in den "Zusätzlichen Funktionen" überschrieben werden.
+
+Adlerdings hat der Administrator bei jeder Option auch die Möglichkeit, die Voreinstellung gegen Änderung zu sperren. Der Nutzer kann den Schalter für die Option zwar weiterhin umlegen, die Auswahl wird aber nicht gespeichert, sondern die Option wieder auf die Voreinstellung zurückgesetzt.
+
+![Features Einstellungen 02](/help/de/member/pic/feateinst02.png)
+
+![Features Einstellungen 03](/help/de/member/pic/feateinst03.png)
+
+![Features Einstellungen 04](/help/de/member/pic/feateinst04.png)
+
+![Features Einstellungen 05](/help/de/member/pic/feateinst05.png)
+
+![Features Einstellungen 06](/help/de/member/pic/feateinst06.png)
+
+![Features Einstellungen 07](/help/de/member/pic/feateinst07.png)
+
+![Features Einstellungen 08](/help/de/member/pic/feateinst08.png)
+
+![Features Einstellungen 09](/help/de/member/pic/feateinst09.png)
+
+![Features Einstellungen 10](/help/de/member/pic/feateinst10.png)
+
+![Features Einstellungen 11](/help/de/member/pic/feateinst11.png)
diff --git a/doc/de/member/addressbook.md b/doc/de/member/addressbook.md
new file mode 100644
index 000000000..fa53339fe
--- /dev/null
+++ b/doc/de/member/addressbook.md
@@ -0,0 +1,23 @@
+### Adressbuch (CardDAV)
+
+Hubzilla bietet Ihnen mit der App "CardDAV" eine Adressverwaltung. Sie können beliebig viele Adressbücher anlegen.
+
+![carddav01](/help/de/member/pic/carddav01.png)
+
+Die Einträge sind im vCards-Format abgelegt.
+
+![carddav02](/help/de/member/pic/carddav02.png)
+
+![carddav03](/help/de/member/pic/carddav03.png)
+
+![carddav04](/help/de/member/pic/carddav04.png)
+
+![carddav05](/help/de/member/pic/carddav05.png)
+
+![carddav06](/help/de/member/pic/carddav06.png)
+
+Die App ermöglicht auch das Importieren von Adressbüchern oder einzelnen vCards aus einer Datei.
+
+![carddav07](/help/de/member/pic/carddav07.png)
+
+Adressbücher sind generell privat und können - auch nicht mittels Fernautorisierung - geteilt werden.
diff --git a/doc/de/member/apps.md b/doc/de/member/apps.md
new file mode 100644
index 000000000..8715e8a68
--- /dev/null
+++ b/doc/de/member/apps.md
@@ -0,0 +1,59 @@
+### Apps
+
+Im frisch installierten Zustand verfügt eine Hubzilla-Instanz über etliche Grundfunktionalitäten. Es gibt jedoch viele Features, die nicht zur Basis-Installation gehören und auch nicht für einen neu erstellten Kanal sofort zur Verfügung stehen.
+
+Die überwiegende Zahl der Funktionen sind als "Applikationen" (kurz "Apps") realisiert.
+
+Die Apps erreichen Sie mit dem "App-Menü", das mit der Schaltfläche "⋮" symbolisiert wird (und sich bei den meisten Hubs ganz rechts in der Navigationsleiste befindet). Häufig benutzte Appskönnen Sie auch an der Navigationsleiste anpinnen, so dass Sie nicht bei jedem Aufruf erst das App-Menü öffnen müssen.
+
+Wie viele und welche Apps Ihnen zur Verfügung stehen, hängt davon ab, wie der Administrator den Hub konfiguriert hat.
+
+#### Appverwaltung
+
+Sie können die Apps für Ihren Kanal mit der Appverwaltung verwalten. Diese erreichen Sie ebenfalls im App-Menü unter dem untersten Menüpunkt "+ Apps"
+
+Nach dem Aufrufen der Appverwaltung werden Ihnen die bereits installierten Apps angezeigt.
+
+In der linken Seitenleiste können Sie nun zwischen "Installierte Apps" und "Verfügbare Apps" umschalten.
+
+##### Verfügbare Apps
+
+Bei den verfügbaren Apps finden Sie sämtliche auf Ihrem Hub zur Verfügung stehenden Apps. Einige davon sind bereits installiert. Über den Button neben der App kann man Apps installieren oder bereits installierte Apps aktualisieren.
+
+![verfügbare Apps](/help/de/member/pic/apps02.png)
+
+##### Installierte Apps
+
+Im Tab der installierten Apps finden Sie sämtliche Apps, die für Ihren Kanal installiert sind. Rechts von jeder App befinden sich zwei oder drei Buttons mit Symbolen: ein Sternsymbol, ein Pinnadelsymbol und ggf. ein Zahnradsymbol.
+
+![verfügbare Apps](/help/de/member/pic/apps01.png)
+
+Das Sternsymbol dient dazu, die App im App-Menü als Menüeintrag zugänglich zu machen.
+
+![verfügbare Apps](/help/de/member/pic/apps03.png)
+
+![verfügbare Apps](/help/de/member/pic/apps04.png)
+
+Mit dem Pinnadelsymbol kann man die App in der Navigationsleiste anpinnen.
+
+![verfügbare Apps](/help/de/member/pic/apps05.png)
+
+Gibt es für eine App einen Dialog für app-spezifische Einstellungen, so erreichen Sie diesen über den Button mit dem Zahnradsymbol.
+
+![verfügbare Apps](/help/de/member/pic/apps06.png)
+
+##### Apps verwalten
+
+Im Tab der installierten Apps ist genz oben auch ein Button vorhanden, welcher mit "Apps verwalten" beschriftet ist. Mit diesem Button gelangen Sie auf die Seite "Apps verwalten", auf welcher Sie Apps wieder deinstallieren und installierte in gewissen Grenzen auch bearbeiten können.
+
+![verfügbare Apps](/help/de/member/pic/apps08.png)
+
+Dort ist es auch möglich eigene Apps zu erstellen (Nur für fortgeschrittene Nutzer!).
+
+##### Apps im Menü sortieren
+
+Die Apps, welche Sie dem App-Menü zugefügt haben, können sie dort ganz einfach per Drag-and-drop sortieren und Ihre bevorzugte Reihenfolge festlegen.
+
+![verfügbare Apps](/help/de/member/pic/apps07.png)
+
+#include doc/de/member/important_apps.md;
diff --git a/doc/de/member/article.md b/doc/de/member/article.md
new file mode 100644
index 000000000..81fee5881
--- /dev/null
+++ b/doc/de/member/article.md
@@ -0,0 +1,27 @@
+### Artikel
+
+Der Artikel ist eine Macroblogging-Beitragsform bei Hubzilla und z.B. geeignet für echte Blogbeiträge. Im Gegensatz zu normalen Beiträgen, die im gesamten Netzwerk (inklusive Fediverse) verteilt werden, verbleiben Artikel beim eigenen Hub. Sie sind für Nutzer anderer Instanzen und Nutzer, die keinen Account im Fediverse haben also nur über ihre URL erreichbar. Die URL kann selbstverständlich geteilt werden, so dass der Artikel trotzdem im Fediverse bekannt wird und abgerufen werden kann.
+
+Einen Artikel erstellt man über die App (App-Menü ⋮) "Artikel". Ruft man diese auf, werden sämtliche erstellten Artikel angezeigt und man hat die Möglichkeit, einen neuen Artikel anzulegen ("Artikel hinzufügen").
+
+![Artikel 01](/help/de/member/pic/artikel01.png)
+
+Das Erstellen eines Artikels ähnelt dem Erstellen eines normalen Beitrags. Das Eingabe-Formular weist aber ein zusätzliches Feld auf: "Link zur Seite".
+
+Hier kann man eine leicht lesbare Linkbezeichnung eingeben. Lässt man das Feld frei, wird eine Bezeichnung automatisch vergeben (länger und "kryptischer").
+
+![Artikel 02](/help/de/member/pic/artikel02.png)
+
+Hat man das optionale Feld Summary ("Zusammenfassung") ausgefüllt, wird bei einem Artikel, genauso wie bei normalen Beiträgen, zunächst nur die Zusammenfassung angezeigt.
+
+![Artikel 03](/help/de/member/pic/artikel03.png)
+
+Wenn Sie auf "Artikel ansehen" klicken, wir der Artikel selbst angezeigt.
+
+![Artikel 04](/help/de/member/pic/artikel04.png)
+
+Der Direktlink zum Artikel setzt sich so zusammen:
+
+`URL-Ihres-Hubs/articles/Kanalname/Link-zurSeite`
+
+![Artikel Link](/help/de/member/pic/artikel05.png)
diff --git a/doc/de/member/bbcode.md b/doc/de/member/bbcode.md
new file mode 100644
index 000000000..b25168323
--- /dev/null
+++ b/doc/de/member/bbcode.md
@@ -0,0 +1,79 @@
+#### Text Dekoration
+
+| BBcode syntax | Rendered text |
+| ------------------------------------------------------------ | --------------------------------- |
+| `[b]bold[/b]` | **bold** |
+| `[i]italic[/i]` | *italic* |
+| `[u]underlined[/u]` | underlined |
+| `[s]strike[/s]` | ~~strike~~ |
+| `[color=red]red[/color]` | ![red](/help/de/member/pic/red.png) |
+| `[hl]highlighted[/hl]` | ![highlited](/help/de/member/pic/highlited.png) |
+| `[font=courier]some text[/font] ` | ![font](/help/de/member/pic/font.png) |
+| `[quote]quote[/quote]` | ![quote](/help/de/member/pic/quote.png) |
+| `[quote=Author]Author? Me? No, no, no...[/quote]` | ![author](/help/de/member/pic/author.png) |
+| ` [size=small]small text[/size]`<br />`[size=xx-large]xx-large text[/size]`<br />` [size=20]20px exactly[/size] `<br />Size options include: **xx-small, small, medium, large, xx-large** | ![size](/help/de/member/pic/size.png) |
+| `Add a horizontal bar [hr] Like this ` | ![hbar](/help/de/member/pic/hbar.png) |
+| `This is [center]centered[/center] text` | ![center](/help/de/member/pic/center.png) |
+
+#### Code-Blöcke
+
+Code kann generisch in einem Block- oder Inline-Format dargestellt werden (je nachdem, ob es neue Zeilenzeichen im Text gibt), oder Sie können eine unterstützte Sprache für erweiterte Syntaxhervorhebung angeben. Die Syntaxhervorhebung erfordert ein geeignetes Rendering-Plugin wie **hilite**. Unterstützte Sprachen mit dem hilite-Plugin sind **php, css, mysql, sql, abap, diff, html, perl, ruby, vbscript, avrc, dtd, java, xml, cpp, python, javascript, js, json, sh**.
+
+Wenn kein Rendering-Plugin installiert ist oder eine nicht unterstützte Sprache angegeben wird, entspricht die Ausgabe für syntaxmarkierte Codeblöcke dem Code-Tag des Blockformats.
+
+| BBcode syntax | Output |
+| ------------------------------------------------------------ | ------------------------------------------------------------ |
+| `[code]function bbcode() { }[/code]` | `function bbcode() { }` |
+| `[code=php]function bbcode() { $variable = true; if( $variable ) { echo "true"; } }[/code]` | ![code](/help/de/member/pic/code.png) |
+| `[nobb][nobb]This is how [i]you[/i] can [u]show[/u] how to use [hl]BBcode[/hl] syntax[/nobb][/nobb]` | This is how [i]you[/i] can [u]show[/u] how to use [hl]BBcode[/hl] syntax |
+
+#### Listen
+
+| BBcode syntax | Rendered list |
+| ------------------------------------------------------------ | ------------------------------------------------------------ |
+| `[ul] [*] First list element [*] Second list element [/ul]` | - First list element<br />- Second list element |
+| `[ol] [*] First list element [*] Second list element [/ol]` | 1. First list element<br />2. Second list element |
+| `[list=A] [*] First list element [*] Second list element [/list]` The list type options are `1, i, I, a, A`. | A. First list element<br />B. Second list element |
+| `[dl terms="b"] [*= First element term] First element description [*= Second element term] Second element description [/dl]` The **terms** style options can be any combination of: bbold iitalic uunderline mmonospace llarge hhorizontal — like *this* defintion list | First element term<br />First element description <br />Second element term<br />Second element description |
+
+#### Tabellen
+
+| BBcode syntax | Rendered table |
+| ------------------------------------------------------------ | --------------------------- |
+| `[table border=0] [tr] [th]Header 1[/th][th]Header 2[/th] [/tr] [tr][td]Content[/td][td]Content[/td][/tr] [tr][td]Content[/td][td]Content[/td][/tr] [/table]` | ![table1](/help/de/member/pic/table1.png) |
+| `[table border=1] [tr] [th]Header 1[/th][th]Header 2[/th] [/tr] [tr][td]Content[/td][td]Content[/td][/tr] [tr][td]Content[/td][td]Content[/td][/tr] [/table]` | ![table2](/help/de/member/pic/table2.png) |
+| `[table] [tr] [th]Header 1[/th][th]Header 2[/th] [/tr] [tr][td]Content[/td][td]Content[/td][/tr] [tr][td]Content[/td][td]Content[/td][/tr] [/table]` | ![table3](/help/de/member/pic/table3.png) |
+
+#### Links und eingebettete Inhalte
+
+| BBcode syntax | Output |
+| ------------------------------------------------------------ | ------------------------------------------------------------ |
+| `[video]video URL[/video] [audio]audio URL[/audio]` | VIDEO<br />AUDIO |
+| `[video='URL_TO_POSTER']video_link[/video]` | <img src="/help/de/member/pic/video_poster.png" alt="image" style="zoom:100%;" /> |
+| `[url=https://hubzilla.org]Hubzilla[/url]` | [Hubzilla](https://hubzilla.org) |
+| `An image [img]https://example.org/image.jpg[/img] in some text ` | <img src="/help/de/member/pic/image.png" alt="image" style="zoom:80%;" /> |
+
+#### Hubzilla-spezifische codes
+
+| BBcode syntax | Output |
+| ------------------------------------------------------------ | ------------------------------------------------------------ |
+| Magic-auth version of [url] tag `[zrl=https://hubzilla.org]Identity-aware link[/zrl]` | ![mauth](/help/de/member/pic/mauth.png) |
+| Magic-auth version of [img] tag `[zmg]https://hubzilla.org/some/photo.jpg[/zmg]` | Image is only viewable by those authenticated and with permission. |
+| Observer-dependent output:`[observer=1]Text to display if observer IS authenticated[/observer]` | |
+| ` [observer=0]Text to display if observer IS NOT authenticated[/observer]` | |
+| `[observer.language=en]Text to display if observer language is English[/observer]` | |
+| `[observer.language!=de]Text to display if observer language is not German[/observer]` | |
+| `[observer.url]` | channel URL of observer |
+| `[observer.baseurl]` | website of observer |
+| `[observer.name]` | name of observer |
+| ` [observer.webname]` | short name in the url of the observer |
+| `[observer.address]` | address (Nomad/Zot-id) of observer |
+| `[observer.photo]` | profile photo of observer |
+| `What is a spoiler? [spoiler]Text you want to hide.[/spoiler]` | What is a spoiler? Click to open/close |
+| `[toc data-toc='div.page-body' data-toc-headings='h1,h2']` Create a table of content in a webpage or wiki page. Please refer to the [original jQuery toc](http://ndabas.github.io/toc/) to get more explanations. Optional param: 'data-toc'. If omitted the default is 'body' Optional param: 'data-toc-headings'. If omitted the default is 'h1,h2,h3' | |
+| `[rpost=title]Text to post[/rpost]` The observer will be returned to their home hub to enter a post with the specified title and body. Both are optional | [[baseurl\]/rpost?f=&title=title&body=Text+to+post](file:///home/daniel/Fediverse/orig doc/member/[baseurl]/rpost?f=&title=title&body=Text+to+post) |
+| This requires the [**qrator**](https://framagit.org/hubzilla/addons/tree/master/qrator) plugin. `[qr]text to post[/qr]` | ![qrcode](/help/de/member/pic/qrcode.png) |
+| This requires a suitable map plugin such as **[openstreetmap](https://framagit.org/hubzilla/addons/tree/master/openstreetmap)**. `[map]` | Generate an inline map using the current browser coordinates of the poster, if browser location is enabled |
+| This requires a suitable map plugin such as **[openstreetmap](https://framagit.org/hubzilla/addons/tree/master/openstreetmap)**. `[map=latitude,longitude]` | Generate a map using global coordinates. |
+| This requires a suitable map plugin such as **[openstreetmap](https://framagit.org/hubzilla/addons/tree/master/openstreetmap)**. `[map]Place Name[/map]` | Generate a map for a given named location. The first matching location is returned. For instance "Sydney" will usually return Sydney, Australia and not Sydney, Nova Scotia, Canada unless the more precise location is specified. It is highly recommended to use the post preview utility to ensure you have the correct location before submitting the post. |
+| `[©]` | © |
diff --git a/doc/de/member/blocking_channels.md b/doc/de/member/blocking_channels.md
new file mode 100644
index 000000000..467017c11
--- /dev/null
+++ b/doc/de/member/blocking_channels.md
@@ -0,0 +1,19 @@
+### Kanäle blockieren/ignorieren/archivieren/verstecken
+
+Kanäle in Ihrem Adressbuch können den Status „ *blockiert*“, „ *ignoriert“*, „ *archiviert“* oder „ *versteckt“* haben. Auf Ihrer Verbindungsseite gibt es einen Filter, der die Kanäle mit diesen Status anzeigen.
+
+![Statusfilter](/help/de/member/pic/statusfilter.png)
+
+Auf den Seiten zum Bearbeiten von Verbindungen können Sie den Status eines Kanals ändern.
+
+<u>Die Bedeutung:</u>
+
+**Blockiert:** Der Kanal kann Ihre Elemente nicht lesen, unabhängig von den Berechtigungen, und er kann auch nicht in Ihren Kanal schreiben.
+
+**Ignoriert**: Der Kanal kann Ihre Elemente lesen, wenn er dazu berechtigt ist, aber er kann nicht in Ihren Kanal schreiben.
+
+**Versteckt:** Der Kanal wird nicht in der Verbindungsliste Ihres Profils angezeigt. Niemand kann sehen, dass Sie mit ihm verbunden sind. Beachte: Er ist trotzdem für deine anderen Verbindungen sichtbar, zum Beispiel in Antworten auf Beiträge.
+
+**Archiviert:** Wenn ein Kanal 30 Tage lang nicht erreicht werden kann, wird er automatisch als archiviert markiert. Dadurch bleiben alle Daten erhalten, aber der Kanal wird nicht mehr nach neuen Informationen abgefragt und aus der automatischen Vervollständigung entfernt. Wenn Sie später erfahren, dass der Kanal wieder online ist, können Sie ihn manuell aus dem Archiv entfernen.
+
+#include doc/de/member/superblock.md;
diff --git a/doc/de/member/bookmarks.md b/doc/de/member/bookmarks.md
new file mode 100644
index 000000000..f1483add2
--- /dev/null
+++ b/doc/de/member/bookmarks.md
@@ -0,0 +1,27 @@
+### Bookmarks (Lesezeichen))
+
+Lesezeichen geben einen Link an, der in Ihrem Lesezeichenordner gespeichert werden kann. Sie verwenden die Zeichenfolge `#^` gefolgt von dem Link. Oft werden diese automatisch generiert. Wenn der Administrator des Hub das Addon "bookmarker" installiert hat, wird diese Sequenz beim Online-Ansehen des Beitrags oder Kommentars in ein Lesezeichensymbol konvertiert.
+
+![Bookmarks01](/help/de/member/pic/bookm01.png)
+
+![Bookmarks02](/help/de/member/pic/bookm02.png)
+
+Wenn Sie auf das Symbol klicken, wird das Lesezeichen gespeichert. Wenn das Add-In für das Lesezeichen nicht installiert ist, enthält das Post-Dropdown-Menü einen Link zum Speichern des Lesezeichens oder der Lesezeichen.
+
+Um Lesezeichen zu verwenden, müssen Sie die App "Lesezeichen" installieren.
+
+Die App führt dann alle von Ihnen gesetzten Lesezeichen auf.
+
+Um ein Lesezeichen unabhängig von einem Link im Stream oder einem Beitrag hinzuzufügen, können Sie die Seite `<URL_Ihres_hub>/rbmark` aufrufen, welche Ihnen eine Maske zur manuellen Eingabe eines Lesezeichens zur Verfügung stellt.
+
+![Bookmarks03](/help/de/member/pic/bookm03.png)
+
+Sie können auch ein [Bookmarklet](https://de.wikipedia.org/wiki/Bookmarklet) erstellen und z.B. in der Lesezeichenleiste Ihres Webbrowsers ablegen:
+
+```javascript
+javascript:javascript:(function(){var%20url=location.href;var%20title=document.title||url;window.open('[observer.baseurl]/rbmark?&url='+encodeURIComponent(url)+'&title='+encodeURIComponent(title)+'&source=bookmarklet','_blank','menubar=no,height=390,width=600,toolbar=no,scrollbars=no,status=no,dialog=1');})();
+```
+
+<u>Wichtig:</u> Ersetzen Sie den Ausdruck `[observer.baseurl]` durch die URL Ihres Hub, also wenn Sie z.B. Ihren Kanal bei Klackerhub haben, müssen Sie für `[observer.baseurl]` einfach `https://klacker.org` eintragen.
+
+Nun können Sie jede Webseite, welche Sie aufsuchen mit einem Klick auf das Bookmarklet in die Lesezeichen Ihres Kanals übernehmen.
diff --git a/doc/de/member/calendar.md b/doc/de/member/calendar.md
new file mode 100644
index 000000000..5a15747f3
--- /dev/null
+++ b/doc/de/member/calendar.md
@@ -0,0 +1,27 @@
+### Kalender
+
+Mit der App "Kalender" kann man seine Termine verwalten.
+
+Nach Aufruf der App erscheint eine Kalender-Übersicht (ein Monat).
+
+![Kalender 01](/help/de/member/pic/kalender01.png)
+
+Durch Klicken auf einen Tag können Sie ein Ereignis erzeugen. In der Eingabemaske (verkürzte Ansicht, lässt sich durch Klicken auf "mehr" erweitern) kann man nun die wesentlichen Inhalte eingeben.
+
+![Kalender 02](/help/de/member/pic/kalender02.png)
+
+![Kalender 03](/help/de/member/pic/kalender02.png)
+
+![Kalender 04](/help/de/member/pic/kalender03.png)
+
+Für diese Einträge können Sie ebenfalls wieder detaillierte [Berechtigungen](/help/de/member/permissions_content.md) festlegen, so dass man private Termine und öffentliche / gemeinsame Termine in ein und demselben Kalender erfassen kann.
+
+![Kalender 05](/help/de/member/pic/kalender05.png)
+
+##### CalDAV-Zugang mit Android
+
+Sie können Ihren Kalender auf Android mit Ihrem Hub synchronisieren.
+
+Verwenden Sie "URL" und "Benutzername" zur Anmeldung. Die Basis-URL ist `<URL-Ihres-Hubs>/cdav`, der Benutzername ist Ihr Kanalname (ohne führendes"@" und ohne Hub-Adresse "`@<Ihr-Hub>`").
+
+Um Ihren Kalender zu teilen, besuchen Sie `<URL-Ihres-Hubs>/cdav/calendar`.
diff --git a/doc/de/member/channel_locations.md b/doc/de/member/channel_locations.md
new file mode 100644
index 000000000..93b5ab871
--- /dev/null
+++ b/doc/de/member/channel_locations.md
@@ -0,0 +1,7 @@
+#### Klon-Adressen verwalten
+
+Existieren vom aktuellen Kanal Klone auf anderen Hubs, so wird als weiterer Menüeintrag "Klon-Adressen verwalten" angezeigt. Damit können Sie festlegen, auf welchem Hub der der Hauptkanal (primärer Kanal) liegt (das legt auch den Teil des Handles hinter dem "@" fest).
+
+Außerdem kann man Klone an dieser Stelle löschen. Für Kanäle auf fremden Servern empfiehlt es sich allerdings, den Kanal auf dem eigentlichen Hub zu löschen. Das Löschen aus der Klon-Verwaltung sollte nur genutzt werden, wenn der Hub des Klons nicht mehr existiert.
+
+![Klon-Einstellungen 01](/help/de/member/pic/kloneinst01.png)
diff --git a/doc/de/member/channel_roles.md b/doc/de/member/channel_roles.md
new file mode 100644
index 000000000..f793b7c4e
--- /dev/null
+++ b/doc/de/member/channel_roles.md
@@ -0,0 +1,41 @@
+### Kanalrollen
+
+Wenn Sie einen neuen Kanal erstellen, werden Sie aufgefordert, eine Berechtigungsrolle auszuwählen, je nachdem, wie Sie diesen Kanal nutzen möchten. Die beliebtesten Berechtigungsrollen sind die Rollen für soziale Netzwerke. Sie haben viele weitere Auswahlmöglichkeiten, die mit Facebook-Gruppen und -Seiten, kollaborativen Bereichen, Newsfeeds und mehr vergleichbar sind. Diese Rollen konfigurieren automatisch verschiedene Systemvariablen, von den Berechtigungen, die Freunden gewährt werden, bis hin zu den Standardeinstellungen für Privatsphäre und Sichtbarkeit. Es stehen erweiterte Konfigurationen zur Verfügung, mit denen Sie jeden dieser Parameter an Ihre Bedürfnisse anpassen können, aber wir haben die Erfahrung gemacht, dass die meisten Nutzer es vorziehen, die Einstellungen vorzunehmen und sie zu vergessen. Im Folgenden werden einige der verschiedenen Rollen beschrieben, die derzeit verfügbar sind, und wie sie sich auf Ihre Privatsphäre und Ihre Interaktionsmöglichkeiten auswirken.
+
+**Es gibt vier Kanal-Rollen:**
+
+- Öffentlich
+- Persönlich
+- Community Forum
+- Benutzerdefiniert
+
+#### Öffentlich
+
+Der Kanal ist ein sehr freizügiges soziales Netzwerkprofil, das mit anderen föderierten sozialen Netzwerken kompatibel ist. Die Privatsphäre hat eine geringere Priorität als der einfache Zugang und die Verbindung mit anderen. Jeder im Netzwerk kann Ihre öffentlichen Beiträge kommentieren und Ihnen private Nachrichten schicken. Standardmäßig sind Beiträge und veröffentlichte Artikel öffentlich, aber Sie können dies bei der Erstellung des Artikels überschreiben und einschränken. Sie sind im Verzeichnis aufgeführt. Ihre Online-Präsenz und Ihre Verbindungen sind für andere sichtbar. Dieser Modus kann Ihre Anfälligkeit für unerwünschte Mitteilungen und Spam erhöhen. Der "klassische" Social-Media-Account.
+
+#### Persönlich
+
+Standardmäßig sind Beiträge und veröffentlichte Elemente öffentlich, aber Sie können dies bei der Erstellung des Elements außer Kraft setzen und einschränken. Sie sind im Verzeichnis aufgeführt. Ihre Online-Präsenz und Ihre Verbindungen sind für andere sichtbar. Nur Ihre unmittelbaren Verbindungen können Ihre öffentlichen Beiträge kommentieren und Ihnen private Nachrichten schicken.
+
+#### Community Forum
+
+Der Kanal ist ein typisches Forum. Standardmäßig sind Beiträge und veröffentlichte Artikel öffentlich. Mitglieder können Beiträge per !mention oder wall-to-wall posten. Das Einstellen von Fotos und anderen veröffentlichten Beiträgen ist gesperrt. Der Kanal ist im Verzeichnis sichtbar. Mitglieder werden automatisch hinzugefügt.
+
+Um als Nutzer eines Forums Medien, die zum Teil eventuell mit eingeschränkten Berechtigungen versehen sind, sehen zu können, ist es erforderlich, im eigenen Nutzer-Kanal unter Einstellungen → Privacy-Einstellungen (`<hub>/settings/privacy`) die Option "Enable OCAP access") einzuschalten.
+
+#### Benutzerdefiniert
+
+Dies ist die genaueste Einstellung für Kanal-Rechte. Alle Rechte können fein-granular festgelegt werden. Vorsicht: Wer hier die falschen Einstellungen wählt, kann seinen Kanal unbrauchbar machen. Zum Glück lassen sich die Rechte auch wieder ändern, so dass man solche Fehlfunktionen beheben kann. Es ist sinnvoll, sich bei jedem einzelnen Recht zu überlegen, welche Auswirkungen es für einen selbst, als Kanalbesitzer, aber auch für andere Nutzer hat.
+
+**Für jeden Regelungspunkt sind folgende Einstellungen möglich:**
+
+- Nur ich
+- Nur die, denen Du es explizit erlaubst
+- Angenommene Verbindungen
+- Beliebige Verbindungen
+- Jeder auf dieser Webseite
+- Alle Hubzilla-Mitglieder
+- Jeder authentifizierte
+- Jeder im Internet
+
+Um die **benutzerdefinierte Rolle** zu bearbeiten, wählt man in den Einstellungen den Punkt "Privacy-Einstellungen". Rechts unten findet man den Button "Benutzerdefinierte Konfiguration der Kanal-Rolle". Klickt man darauf, erscheint ein Warn-Dialog, der auf die Risiken einer fehlerhaften Konfiguration aufmerksam macht. Bestätigt man, dass man die Rechte bearbeiten möchte, öffnet sich der Einstellungs-Dialog für die benutzerdefinierten Rollenrechte. \ No newline at end of file
diff --git a/doc/de/member/channel_settings.md b/doc/de/member/channel_settings.md
new file mode 100644
index 000000000..3cb0d6f7f
--- /dev/null
+++ b/doc/de/member/channel_settings.md
@@ -0,0 +1,15 @@
+#### Kanal-Einstellungen
+
+Rufen Sie die Einstellungen über den Menüeintrag auf, so werden als Standard die Kanal-Einstellungen gezeigt. Die Grundeinstellungen dienen dazu, die Eigenschaften und Funktionen des aktuell ausgewählten (genutzten) Kanals einzustellen. Neben der Kanalrolle können Sie hier auch den Standard für automatisch erstellte Verzeichnisse in der Cloud (diese werden z.B. erzeugt, wenn Sie in einem Beitrag ein Bild als Anhang hochladen) festlegen.
+
+Außerdem ist es möglich, den Kanal zu löschen (roter Button "Kanal löschen").
+
+<u>Wichtiger Hinweis:</u> Es ist nicht ohne weiteres möglich, einen Kanal unter dem Namen des gelöschten Kanals neu auf diesem Hub zu erstellen (auch nicht durch Klonen). Das dient dem Schutz vor "Identitäts-Missbrauch". Möchte man auch wichtigen Gründen dennoch wieder einen Kanal mit diesem Namen installieren, dann kann man mit dem Admin des Hub Kontakt aufnehmen, ihm die Gründe darlegen (so dass er sicher sein kann, dass man ein berechtigtes Interesse hat) und bitten, die Sperre aufzuheben. Das kann nur er mit wenigen Handgriffen in der Datenbank tun.
+
+Sie können außerdem den Verfallszeitraum für importierte Inhalte anderer Kanäle festlegen und diesen Import über zwei Filter regeln.
+
+![Kanaleinstellungen 01](/help/de/member/pic/kaneinst01.png)
+
+Die Benachrichtigungs-Einstellungen erlauben es, ganz genau festzulegen, ob man über bestimmte Ereignisse und Vorgänge benachrichtigt wird. Und ob die Benachrichtigung auch per E-Mail erfolgen soll.
+
+![Kanaleinstellungen 02](/help/de/member/pic/kaneinst02.png)
diff --git a/doc/de/member/channels.md b/doc/de/member/channels.md
new file mode 100644
index 000000000..48f3292af
--- /dev/null
+++ b/doc/de/member/channels.md
@@ -0,0 +1,22 @@
+### Kanäle
+
+Kanäle sind einfach Sammlungen von Inhalten, die an einem Ort gespeichert werden. Ein Kanal kann für alles Mögliche stehen. Er kann für Sie stehen, für eine Website, ein Forum, Fotoalben, einfach für alles. Für die meisten Menschen ist ihr erster Kanal „Ich“.
+
+Die wichtigsten Funktionen für einen Kanal, der „mich“ repräsentiert, sind:
+
+- Sichere und private „spamfreie“ Kommunikation
+
+- Identität und „Single-Signon“ über das gesamte Netzwerk
+
+- Datenschutzkontrollen und Berechtigungen, die sich auf das gesamte Netz erstrecken
+
+- Verzeichnisdienste (wie ein Telefonbuch)
+
+
+
+Kurz gesagt, ein Kanal, der Sie repräsentiert, ist „ich im Internet“.
+
+Mit einem Account bei einem Hub können mehrere verschiedene Kanäle mit jeweils individueller Konfiguration angelegt und genutzt werden.
+
+#include doc/de/member/create_channels.md;
+#include doc/de/member/channel_roles.md;
diff --git a/doc/de/member/chat_rooms.md b/doc/de/member/chat_rooms.md
new file mode 100644
index 000000000..ff5757608
--- /dev/null
+++ b/doc/de/member/chat_rooms.md
@@ -0,0 +1,17 @@
+#### Chaträume
+
+Die App "Chaträume" erlaubt es, Chaträume für Instant-Messaging innerhalb eines Hubs einzurichten und mit anderen Kanälen des Hubs zu chatten.
+
+Ruft man die App aus dem Hamburger-Menü auf, werden die eigenen Chaträume angezeigt.
+
+Um einen neuen Chatraum anzulegen, kickt man auf den Button "Raum hinzufügen".
+
+![Chatraum anlegen](/help/de/member/pic/chat01.png)
+
+Hier muss man einen Namen für den Taum vergeben und man kann wählen, nach wie viel Minuten die Chatinhalte verfallen. Außerdem ist es möglich, über das [Privacy-Tool](/help/de/member/permissions_content.md) (🔒) festzulegen, wer berechtigt ist, den Chatraum zu sehen und zu nutzen.
+
+Die URL zum Chatraum wird im Browser angezeigt und ist auch über den Verweis auf den Chatraum in der linken Seitenleiste verfügbar. Nun kann man einem anderen Nutzer des eigenen Hubs die URL mitteilen und einen Chat mit diesem beginnen.
+
+![Chat Nutzer 1](/help/de/member/pic/chat02.png)
+
+![Chat Nutzer 2](/help/de/member/pic/chat03.png)
diff --git a/doc/de/member/clone.md b/doc/de/member/clone.md
new file mode 100644
index 000000000..50db62f8a
--- /dev/null
+++ b/doc/de/member/clone.md
@@ -0,0 +1,53 @@
+### Klonen
+
+Hubzilla-Kanäle verfügen über eine sogenannten "nomadische Identität". Das ist eine Spezialität des Nomad-Protokolls, auf welchem Hubzilla basiert und mit welchem Hubzilla-Hubs untereinander kommunizieren.
+
+Durch die nomadische Identität ist es möglich, Klone des eigenen Kanals zu erzeugen, was die Zensur- und Ausfallresistenz enorm erhöht.
+
+Wenn Sie Ihren Kanal geklont haben, ist es kein Problem, wenn Ihr "Heimat-Hub" einmal ausfällt oder nicht korrekt funktioniert. Sie können übergangslos mit einem Kanal-Klon, welcher auf einem anderen Hub liegt, weiter am Fediverse teilnehmen.
+
+Sämtliche Kanal-Klone werden automatisch im Hintergrund synchronisiert.
+
+Um einen Klon Ihres Kanals anzulegen, benötigen sie einen Account auf einem weiteren (anderen) Hubzilla-Hub.
+
+Es gibt nun mehrere Möglichkeiten, auf diesem anderen Hub, einen Klon Ihres Kanals anzulegen.
+
+Sie können mit der App "Kanalexport" den Kanal auf Ihrem "Heimat-Hub" exportieren.
+
+![Klone 01](/help/de/member/pic/klone01.png)
+
+Durch einen Klick auf den Button "Kanal exportieren", exportieren Sie Ihre Identität und Ihren sozialen Graph in eine Datei, welche sie herunterladen können.
+
+Da Dateien, Webseiten, Wikis, Kalender und Chaträume immer lokal auf den eigenen Hub (also den jeweiligen Server) beschränkt sind, können sie mit der App "Kanalexport" auch Archive dieser Daten lokal herunterladen.
+
+Loggen Sie sich nun auf dem neuen Hub ein und wählen Sie entweder im Hauptmenü (Ihr Profilbild; links oben) den Menüpunkt "Kanäle" und auf der erscheinenden Kanalauswahl-Seite den Button "+ Neu anlegen" oder rufen Sie direkt die Kanalerstellungs-Seite unter `<URL-Ihres-Hubs>/new_channel` auf.
+
+![Klone 02](/help/de/member/pic/klone02.png)
+
+![Klone 03](/help/de/member/pic/klone03.png)
+
+Auf dieser Seite geben Sie nun aber keine Informationen für das Erstellen eines neuen Kanals ein, sondern wählen am unteren Ende des Dialogs den Link "importiere einen bestehenden Kanal von einem anderen Server".
+
+![Klone 04](/help/de/member/pic/klone04.png)
+
+Nun öffnet sich der Dialog für den Kanal-Import.
+
+![Klone 05](/help/de/member/pic/klone05.png)
+
+Mit dem Button "Hochzuladende Datei: Durchsuchen..." öffnen Sie einen Dateidialog, mit welchem Sie die zuvor gespeicherte Kanal-Datei auswählen können.
+
+Alternativ zu dieser Methode können Sie Ihren Kanal auch direkt von der Quelle, also dem Ursprungs-Hub klonen. Dafür müssen Sie im Kanal-Import-Dialog das Handle des zu klonenden Kanals, die Mailadresse für den Login beim Ursprungs-Hub, sowie das dazugehörige Passwort angeben. Zusätzlich können sie per Schalter auswählen, ob Dateien und Objekte des Ursprungs-Hubs mit importiert werden sollen (sofern Ihr neuer Hub das zulässt und das Speicherlimit ausreicht).
+
+Im Dialog können Sie außerdem noch festlegen, ob der neue Hub Ihr "primärer Hub sein soll". Das bedeutet, dass der neue Kanal (also der nun anzulegende) Ihr primärer Kanal wird. Das hat Auswirkungen auf das Handle Ihres Kanals, welches damit künftig mit der URL des neuen Hubs endet.
+
+In der Regel, sofern man nicht komplett auf einen anderen Hub umziehen möchte, lässt man den Schalter bei "Nein" und der Primäre Hub bleibt derjenige, der es aktuell ist (das Handle bleibt unverändert).
+
+Sollten Sie eine veränderte Kanalbezeichnung (Kurzname) wünschen, können Sie diese in einem weiteren Eingabefeld eingeben. Lassen Sie das Feld leer, bleibt der Kanal-Kurzname unverändert.
+
+*<u>Hinweis:</u> Sollte der Kanal-Kurzname auf dem neuen Hub bereits vergeben sein (oder gesperrt, weil ein gleichlautender Kanal dort bereits existierte, aber wieder gelöscht wurde), wird der Kurzname vom System automatisch modifiziert.*
+
+Schließlich klicken Sie auf "Absenden" und verlassen Sie die Seite NICHT, bis der Import abgeschlossen ist. Dies kann, je nach Umfang des Ursprungskanals einige Zeit in Anspruch nehmen.
+
+Über Einstellungen → Klon-Adressen verwalten können Sie jederzeit Ihre Klone verwalten. Sie können festlegen, welches der "primäre Hub" ist und Sie können Klone löschen, wobei empfohlen wird, geklonte Kanäle besser direkt auf dem jeweiligen Hub zu löschen.
+
+![Klone 06](/help/de/member/pic/klone06.png)
diff --git a/doc/de/member/cloud_storage.md b/doc/de/member/cloud_storage.md
new file mode 100644
index 000000000..02914d1dd
--- /dev/null
+++ b/doc/de/member/cloud_storage.md
@@ -0,0 +1,10 @@
+#### Cloudspeicher
+
+Ihre Dateien sind im Internet unter der Adresse `<URL-Ihres-Hubs>/cloud/<Ihr-Kanalname>` für jeden sichtbar, der sie einsehen darf. Wenn der Betrachter über ausreichende Berechtigungen verfügt, kann er auch neue Dateien und Ordner/Verzeichnisse erstellen. Diese Möglichkeit sollte nur für kleinere Dateien und Fotos (bis zu einigen Megabyte) genutzt werden, da sie den internen Speicher beansprucht. Größere Dateien (Videos, Musik usw.) laden Sie bitte mit WebDAV hoch. Diese Dateien können weiterhin über den Web-Zugang abgerufen werden.
+
+Mit WebDAV können Sie Dateien direkt in das Betriebssystem Ihres Computers oder aus diesem heraus kopieren, wobei Ihre Cloud-Dateien wie ein virtuelles Laufwerk erscheinen. Dies sollte zum Hochladen großer Dateien wie Video- und Audiodateien verwendet werden.
+
+Die URL für das Cloud-Verzeichnis lautet `<URL-Ihres-Hubs>/dav/<kanalname>`.
+
+Sollte, je nach Einbindung des DAV-Dateisystems (abhängig vom verwendeten Betriebssystem und ggf. der Anwendung) die Angabe eine Benutzernamens erforderlich sein, so ist dies der Kanal-Kurzname (also ohne führendes "@" und ohne folgende "@Hub-Adresse"). Ein eventuell erforderliches Passwort entspricht Ihrem Login-Passwort.
+
diff --git a/doc/de/member/comanche.md b/doc/de/member/comanche.md
new file mode 100644
index 000000000..8e449e939
--- /dev/null
+++ b/doc/de/member/comanche.md
@@ -0,0 +1,278 @@
+### Comanche Seitenbeschreibungssprache
+
+Comanche ist eine bbCode-ähnliche Auszeichnungssprache, mit der aufwendige und komplexe Webseiten erstellt werden können, indem sie aus einer Reihe von Komponenten zusammengesetzt werden, von denen einige vorgefertigt sind und andere spontan definiert werden können. Comanche verwendet eine Seitenbeschreibungssprache, um diese Seiten zu erstellen.
+
+
+Comanche wählt in erster Linie aus, welche Inhalte in den verschiedenen Bereichen der Seite erscheinen sollen. Die verschiedenen Bereiche haben Namen, und diese Namen können sich je nach gewählter Layoutvorlage ändern.
+
+#### Seitenvorlagen
+
+Derzeit gibt es fünf Layout-Vorlagen, es sei denn, Ihre Website bietet zusätzliche Layouts.
+
+
+**Standardvorlage**
+
+Die Standardvorlage definiert einen "nav"-Bereich am oberen Rand, "aside" als Seitenleiste mit fester Breite, "content" für den Hauptinhaltsbereich und "footer" für einen Seitenfuß.
+
+
+**Vollständig**
+
+Die vollständige Vorlage entspricht der Standardvorlage mit der Ausnahme, dass es keinen "aside"-Bereich gibt.
+
+**Choklet**
+
+Die Choklet-Vorlage bietet eine Reihe von fließenden Layoutstilen, die nach Geschmack festgelegt werden können:
+
+- (default flavour) - ein zweispaltiges Layout, das der "default"-Vorlage ähnelt, aber flexibler ist
+- bannertwo - ein zweispaltiges Layout mit einem Bannerbereich, kompatibel mit der "default"-Vorlage auf kleinen Displays
+- three - dreispaltiges Layout (ergänzt die Standardvorlage um einen "right_aside"-Bereich)
+- edgestwo - zweispaltiges Layout mit festen Seitenrändern
+- edgesthree - dreispaltiges Layout mit festen Seitenrändern
+- full - dreispaltiges Layout mit festen Seitenrändern und Hinzufügen eines "Header"-Bereichs unter der Navigationsleiste
+
+
+**Redable**
+
+Eine Vorlage zum Lesen längerer Texte im Vollbildmodus (also ohne Navigationsleiste). Drei Spalten: aside, content und right_aside.
+
+Für maximale Lesbarkeit ist es ratsam, nur die mittlere Inhaltsspalte zu verwenden.
+
+
+**Zen**
+
+Gibt Ihnen die Freiheit, alles selbst zu machen. Nur eine leere Seite mit einem Inhaltsbereich.
+
+
+Um eine Layout-Vorlage auszuwählen, verwenden Sie den Tag 'template'.
+
+```
+[template]full[/template]
+```
+
+So wählen Sie die Vorlage "choklet" mit der Geschmacksrichtung "three":
+
+```
+[template=three]choklet[/template]
+```
+
+
+Die Standardvorlage wird verwendet, wenn keine andere Vorlage angegeben wird. Die Vorlage kann beliebige Namen für die Inhaltsbereiche verwenden. Sie werden "region"-Tags verwenden, um zu entscheiden, welche Inhalte in den jeweiligen Regionen platziert werden sollen.
+
+Es wurden drei "Makros" für Ihre Verwendung definiert.
+
+```
+$htmlhead - replaced with the site head content.
+$nav - replaced with the site navigation bar content.
+$content - replaced with the main page content.
+```
+
+Standardmäßig wird `$nav` in den Seitenbereich "nav" und `$content` in den Bereich "content" eingefügt. Sie brauchen diese Makros nur zu verwenden, wenn Sie die Reihenfolge der Elemente ändern oder sie in andere Bereiche verschieben möchten.
+
+
+Um ein Thema für Ihre Seite auszuwählen, verwenden Sie den Tag "theme".
+
+```
+[theme]suckerberg[/theme]
+```
+
+Dadurch wird das Thema "suckerberg" ausgewählt. Standardmäßig wird das von Ihrem Kanal bevorzugte Thema verwendet.
+
+```
+[theme=passion]suckerberg[/theme]
+```
+
+Dadurch wird das Thema mit dem Namen "suckerberg" ausgewählt und das Schema "passion" (Themenvariante) gewählt. Alternativ ist es auch möglich, eine komprimierte Themennotation zu verwenden.
+
+```
+[theme]suckerberg:passion[/theme]
+```
+
+Die komprimierte Notation ist nicht Teil von Comanche selbst, wird aber von der Hubzilla-Plattform als Themenspezifizierer erkannt.
+
+
+**Navbar**
+
+```
+[navbar]tucson[/navbar]
+```
+
+Verwenden Sie die Vorlage "tucson" für die Navigationsleiste und die CSS-Regeln. Standardmäßig wird die Vorlage "default" für die Navigationsleiste verwendet.
+
+
+**Regions**
+
+Jede Region hat, wie oben erwähnt, einen Namen. Sie spezifizieren die Region, die Sie interessiert, mit einem "region"-Tag, der den Namen enthält. Alle Inhalte, die Sie in diesem Bereich platzieren möchten, sollten zwischen dem öffnenden Region-Tag und dem schließenden Tag platziert werden.
+
+```
+[region=htmlhead]....content goes here....[/region]
+[region=aside]....content goes here....[/region]
+[region=nav]....content goes here....[/region]
+[region=content]....content goes here....[/region]
+```
+
+
+**CSS und Javascript**
+
+Wir haben die Möglichkeit, Javascript- und CSS-Bibliotheken in den htmlhead-Bereich einzubinden. Zurzeit verwenden wir jquery (js), bootstrap (css/js) und foundation (css/js).
+
+Dies überschreibt den htmlhead des ausgewählten Themas.
+
+```
+[region=htmlhead]
+ [css]bootstrap[/css]
+ [js]jquery[/js]
+ [js]bootstrap[/js]
+[/region]
+```
+
+
+**Menüs und Blöcke**
+
+Mit den Werkzeugen zur Erstellung von Webseiten können Sie neben dem Seiteninhalt auch Menüs und Blöcke erstellen. Diese bieten einen Teil des vorhandenen Inhalts, der in den von Ihnen festgelegten Bereichen und in der von Ihnen festgelegten Reihenfolge platziert werden kann. Jedes dieser Elemente hat einen Namen, den Sie bei der Erstellung des Menüs oder Blocks festlegen.
+
+```
+[menu]mymenu[/menu]
+```
+
+Dadurch wird das Menü "mymenu" an dieser Stelle auf der Seite platziert, die sich innerhalb eines Bereichs befinden muss.
+
+```
+[menu=horizontal]mymenu[/menu]
+```
+
+Dadurch wird das Menü mit dem Namen "mymenu" an dieser Stelle auf der Seite platziert, die sich innerhalb eines Bereichs befinden muss. Außerdem wird dem Menü die Klasse "horizontal" zugewiesen. Die Klasse "horizontal" ist im redbasic-Theme definiert. Sie kann in anderen Themes verfügbar sein, muss es aber nicht.
+
+```
+[menu][var=wrap]none[/var]mymenu[/menu]
+```
+
+Die Variable `[var=wrap]none[/var]` in einem Block entfernt das umschließende div-Element aus dem Menü.
+
+```
+[block]contributors[/block]
+```
+
+Dadurch wird ein Block mit dem Namen "contributors" in dieser Region platziert.
+
+```
+[block=someclass]contributors[/block]
+```
+
+Dadurch wird ein Block mit dem Namen "contributors" in dieser Region platziert. Zusätzlich wird die Klasse "someclass" auf den Block angewendet. Dies ersetzt die Standard-Blockklassen "bblock widget".
+
+```
+[block][var=wrap]none[/var]contributors[/block]
+```
+
+Die Variable `[var=wrap]none[/var]` in einem Block entfernt das umschließende div-Element aus dem Block.
+
+
+**Widgets**
+
+Widgets sind ausführbare Anwendungen, die vom System bereitgestellt werden und die Sie auf Ihrer Seite platzieren können. Einige Widgets benötigen Argumente, mit denen Sie das Widget an Ihren Zweck anpassen können. System-Widgets sind hier aufgelistet. Widgets können auch von Plugins, Themes oder Ihrem Website-Administrator erstellt werden, um zusätzliche Funktionen bereitzustellen.
+
+Widgets und Argumente werden mit den Tags "widget" und "var" angegeben.
+
+```
+[widget=recent_visitors][var=count]24[/var][/widget]
+```
+
+Damit wird das Widget "recent_visitors" geladen und mit dem Argument "count" auf "24" gesetzt.
+
+
+**Kommentare**
+
+Das Tag "comment" wird zur Abgrenzung von Kommentaren verwendet. Diese Kommentare werden auf der gerenderten Seite nicht angezeigt.
+
+```
+[comment]This is a comment[/comment]
+```
+
+
+**Bedingte Ausführung**
+
+Sie können ein 'if'-Konstrukt verwenden, um Entscheidungen zu treffen. Diese basieren derzeit auf der Systemkonfigurationsvariable oder dem aktuellen Beobachter.
+
+```
+[if $config.system.foo]
+ ... the configuration variable system.foo evaluates to 'true'.
+[else]
+ ... the configuration variable system.foo evaluates to 'false'.
+[/if]
+
+[if $observer]
+ ... this content will only be show to authenticated viewers
+[/if]
+```
+
+Die 'else'-Klausel ist optional.
+
+Neben der booleschen Auswertung werden mehrere Tests unterstützt.
+
+```
+[if $config.system.foo == bar]
+ ... the configuration variable system.foo is equal to the string 'bar'
+[/if]
+[if $config.system.foo != bar]
+ ... the configuration variable system.foo is not equal to the string 'bar'
+[/if]
+[if $config.system.foo {} bar ]
+ ... the configuration variable system.foo is a simple array containing a value 'bar'
+[/if]
+[if $config.system.foo {*} bar]
+ ... the configuration variable system.foo is a simple array containing a key named 'bar'
+[/if]
+```
+
+
+**Komplexes Beispiel**
+
+```
+[comment]use an existing page template which provides a banner region plus 3 columns beneath it[/comment]
+
+[template]3-column-with-header[/template]
+
+[comment]Use the "darknight" theme[/comment]
+
+[theme]darkknight[/theme]
+
+[comment]Use the existing site navigation menu[/comment]
+
+[region=nav]$nav[/region]
+
+[region=side]
+
+ [comment]Use my chosen menu and a couple of widgets[/comment]
+
+ [menu]myfavouritemenu[/menu]
+
+ [widget=recent_visitors]
+ [var=count]24[/var]
+ [var=names_only]1[/var]
+ [/widget]
+
+ [widget=tagcloud][/widget]
+ [block]donate[/block]
+
+[/region]
+
+
+
+[region=middle]
+
+ [comment]Show the normal page content[/comment]
+
+ $content
+
+[/region]
+
+
+
+[region=right]
+
+ [comment]Show my condensed channel "wall" feed and allow interaction if the observer is allowed to interact[/comment]
+
+ [widget]channel[/widget]
+
+[/region]
+``` \ No newline at end of file
diff --git a/doc/de/member/commenting.md b/doc/de/member/commenting.md
new file mode 100644
index 000000000..74c80c954
--- /dev/null
+++ b/doc/de/member/commenting.md
@@ -0,0 +1,9 @@
+### Kommentieren
+
+Möchte man einen Beitrag kommentieren, also auf diesen antworten, klickt man in das Feld am unteren Rand des Beitrag ("Kommentar"). Es öffnet sich an dieser Stelle der Kommentar-Editor, welcher dem Beitrags-Editor ähnelt. Es gibt hier aber keine Felder für einen Titel, eine Zusammenfassung und Kategorien. Unter dem Eingabe-Feld für den Inhalt des Kommentars befinden sich wiederum Schaltflächen für bestimmte Formatierungen (nicht alle, die es im Beitrags-Editor gibt, weil nicht alles in einem Kommentar möglich ist) und rechts wieder eine Schaltfläche für eine Vorschau, sowie einen Button "Absenden", um den Kommentar zu veröffentlichen.
+
+![kommentieren](/help/de/member/pic/kommentieren.png)
+
+Auch im Feld für den Kommentar-Inhalt kann man den Text mit Markdown, bbCode und HTML auszeichnen.
+
+Da es kein Feld für eine Zusammenfassung gibt, ist es nicht möglich, diese für eine Inhaltswarnung bezüglich des Kommentars zu verwenden. Man kann das aber durch bbCode erreichen, indem man die Inhaltswarnung in `[summary][/summary]` einschießt. Dadurch wird der gesamte nachfolgende Inhalt zunächst verborgen und kann per Klick angezeigt werden.
diff --git a/doc/de/member/connecting_with_channels.md b/doc/de/member/connecting_with_channels.md
new file mode 100644
index 000000000..1e98977b9
--- /dev/null
+++ b/doc/de/member/connecting_with_channels.md
@@ -0,0 +1,33 @@
+### Verbinden mit Kanälen
+
+Verbindungen in Hubzilla können sehr viele verschiedene Bedeutungen haben. Eine Verbindung ist genauer definiert als eine Reihe von Berechtigungen, die Sie einer anderen Person erteilt haben. In herkömmlichen sozialen Netzwerken erhalten alle Verbindungen die gleichen Berechtigungen oder höchstens zwei Stufen (Freunde und „Follower“). In Hubzilla kann eine Reihe von separaten Berechtigungen festgelegt/angepasst werden, abhängig von der Situation und der Beziehung, die Sie mit dem anderen Kanal haben. Sie können jemandem erlauben, Ihre Beiträge zu sehen, aber nicht Ihre Fotos. Sie können ihnen auch die Erlaubnis verweigern, Ihre Beiträge zu kommentieren oder private Nachrichten an Sie zu senden. Aber machen wir es uns einfach: Sie wollen mit jemandem befreundet sein, den Sie aus sozialen Netzwerken kennen. Wie machen Sie das?
+
+Sie können sich das Verzeichnis ansehen. Das Verzeichnis ist auf allen Hubzilla-Websites verfügbar, d. h., wenn Sie von Ihrer eigenen Seite aus suchen, erhalten Sie Ergebnisse aus dem gesamten Netzwerk. Sie können nach Name, Interesse, Standort und Schlüsselwort suchen.
+
+Wenn Sie das „Handle“ von jemandem bereits kennen, können Sie direkt mit ihm in Verbindung treten. Ein Handle sieht genauso aus wie eine E-Mail-Adresse (z. B. `bob@example.com`), verweist aber auf eine Person im offenen sozialen Netz. Um eine Verbindung herstellen zu können, muss ein kompatibles Netzwerkprotokoll verwendet werden. Standardmäßig unterstützt diese Software das Nomad-Protokoll, weitere Protokolle können jedoch über Plugins/Addons bereitgestellt werden. Weitere Informationen zur Verbindung mit Kanälen in anderen Netzwerken finden Sie weiter unten.
+
+#### So verbinden Sie sich mit anderen Hubzilla-Kanälen:
+
+Besuchen Sie das Profil des gewünschten Kanals, indem Sie auf ihr Foto im Verzeichnis, im Stream oder in den Kommentaren klicken, und es öffnet sich ihre Kanal-Homepage im Kanal-Betrachter. Auf der linken Seite des Bildschirms sehen Sie normalerweise einen Link mit der Bezeichnung „Verbinden“. Klicken Sie darauf, und schon sind Sie fertig. Je nach den Einstellungen des Kanals, mit dem Sie eine Verbindung herstellen möchten, müssen Sie möglicherweise warten, bis der Kanal Ihre Verbindung genehmigt hat, aber es sind keine weiteren Aktionen Ihrerseits erforderlich. Sobald Sie die Verbindung initiiert haben, werden Sie zum Verbindungseditor weitergeleitet. Hier können Sie bestimmte Berechtigungen für diesen Kanal zuweisen, wenn Sie Änderungen vornehmen möchten.
+
+Sie können auch eine Verbindung zu einem beliebigen Kanal herstellen, indem Sie die Seite „Verbindungen“ Ihrer Website oder des Verzeichnisses aufrufen und den „Handle“ in das Feld „Neue Verbindung hinzufügen“ eingeben. Verwenden Sie diese Methode, wenn Ihnen jemand sein Handle mitteilt und Sie sich mit ihm verbinden möchten. Der Vorgang ist derselbe wie bei der Verbindung über die Schaltfläche „Verbinden“ - Sie werden dann zum Verbindungseditor weitergeleitet, um die Berechtigungen festzulegen.
+
+#### So stellen Sie eine Verbindung zu Kanälen in anderen Netzwerken her:
+
+Das Verfahren zum Verbinden mit "Kanälen" in anderen Netzwerken (wie z.B GNU-Social, Mastodon, Misskey, Pleroma und Diaspora) ist ähnlich - geben Sie deren „Handle“ in das Feld „+Add“ auf der Seite „Verbindungen“ ein. Bevor Sie dies jedoch tun, besuchen Sie bitte die App-Verwaltung im App-Menü und vergewissern Sie sich, dass das entsprechende Protokoll (Diaspora, GNU-Social/OStatus oder ActivityPub) in Ihrem Hub bereitgestellt und **für Ihren Kanal** ***aktiviert*** ist. Diese Netzwerke/Protokolle unterstützen keine Kontomigration und Standortunabhängigkeit. Wenn Sie also den Standort wechseln oder Ihren Kanal anderswo klonen, kann die Kommunikation mit diesen Verbindungen fehlschlagen. Aus diesem Grund sind diese Protokolle nicht standardmäßig aktiviert, sondern nur mit Ihrer Zustimmung. Die Aktivierung dieser Protokolle ist eine wichtige Entscheidung zwischen der Kommunikation mit Freunden in diesen Netzwerken und der Ausfallsicherheit des Kontos, falls Ihr Server ausfällt.
+
+Einige Kommunikationsnetze bieten mehr als ein Protokoll an. So können Sie sich beispielsweise mit jemandem verbinden, der sowohl das „ostatus“- alsauch das „activitypub“-Protokoll für die Kommunikation verwendet. Im Allgemeinen bietet das 'activitypub'-Protokoll eine bessere Erfahrung als das 'ostatus'-Protokoll, aber Hubzilla wählt oft das erste Protokoll, das es entdeckt, und das ist vielleicht nicht das, was Sie wollen. Sie können sich mit jemandem über ein bestimmtes Protokoll verbinden, indem Sie den Protokollnamen in eckigen Klammern vor dessen „Handle“ setzen. Zum Beispiel
+
+`[activitypub]https://foo.bar/foobar`
+
+`[ostatus]foobar@foo.bar`
+
+`[diaspora]foobar@foo.bar`
+
+`[zot]foobar@foo.bar`
+
+`[feed]https://foo.bar/foobar`
+
+#### So verbinden Sie sich mit RSS-Feeds:
+
+Ihr Hub-Administrator kann die Verbindung mit RSS-Feeds erlauben. Das Verfahren für die Verbindung mit einem RSS-Feed ist dasselbe, außer dass Sie die URL des Feeds in das Feld „Neue Verbindung hinzufügen“ eingeben (oder einfügen). Die Möglichkeiten können durch den Administrator Ihres Hubs eingeschränkt sein, weil Verbindungen mit Feeds teilweise für hohe Systemlast sorgen. \ No newline at end of file
diff --git a/doc/de/member/connection_editor.md b/doc/de/member/connection_editor.md
new file mode 100644
index 000000000..c96f5e9b3
--- /dev/null
+++ b/doc/de/member/connection_editor.md
@@ -0,0 +1,32 @@
+#### Verbindungs-Editor
+
+Klicken Sie in der App "Verbindungen" auf den Button "Bearbeiten" an einem Kontakt, öffnet sich der Verbindungs-Editor.
+
+Mit dem Editor können Sie einem Kontakt eine bestimmte Kontaktrolle zuweisen. Bei Bedarf können Sie sich über den Button "Contact Roles" die vorhandenen Rollen anzeigen lassen und auch neue Rollen erstellen. Ein weiterer Button (Berechtigungen vergleichen) können Sie die zugewiesenen Berechtigungen mit denen der Standard-Berechtigungsrolle vergleichen.
+
+![Verbindungen 02](/help/de/member/pic/verbindungen02.png)
+
+Mit dem Tab "Privacy Gruppen" können Sie einen Kontakt einer oder mehreren Privacy-Gruppen zuordnen.
+
+![Verbindungen 06](/help/de/member/pic/verbindungen06.png)
+
+Über den Tab "Profile" können Sie festlegen, welches Ihrer Profile (sofern Sie mehrere angelegt haben) dem Kontakt angezeigt wird.
+
+![Verbindungen 03](/help/de/member/pic/verbindungen03.png)
+
+Mit den Inhaltsfiltern ist es Ihnen möglich, Beiträge eines Kontakts mit bestimmten Inhalten herauszufiltern bzw. über Filter festzulegen, dass nur Beiträge mit definierten Inhalten im Stream landen.
+
+![Verbindungen 04](/help/de/member/pic/verbindungen04.png)
+
+Mit den Kontakt-Tools können sie den Kontakt
+
+- blockieren,
+- ignorieren,
+- archivieren,
+- verstecken
+
+oder
+
+- löschen.
+
+![Verbindungen 05](/help/de/member/pic/verbindungen05.png)
diff --git a/doc/de/member/connections.md b/doc/de/member/connections.md
new file mode 100644
index 000000000..7d03bfd48
--- /dev/null
+++ b/doc/de/member/connections.md
@@ -0,0 +1,24 @@
+### Verbindungen
+
+Mit der App "Verbindungen" können Sie sich alle Ihre Verbindungen anzeigen lassen.
+
+![Verbindungen 01](/help/de/member/pic/verbindungen01.png)
+
+In der Übersicht kann man für jede Verbindung
+
+- den Kanalnamen
+- das Datum der Verbindung
+- die Kanaladresse (Handle)
+- das Netzwerk des Kontakts (ActivityPub, Zot (Nomad), RSS, Diaspora...)
+ - man kann sich über einen daneben stehenden Filter die kürzlichen Aktivitäten des Kanals im Stream anzeigen lassen
+- das Profilbild
+- und durch einen farbigen Punkt (Ampelfarben) die vom Kontakt eingeräumten Rechte (lassen Sie den Mauszeiger über dem farbigen Punkt ruhen, werden Ihnen die eingeräumten Rechte angezeigt)
+
+sehen.
+
+Außerdem ist wird ein Label angezeigt, welches den Typ der Verbindung anzeigt oder warnt, dass (bei Klonen) an diesem Ort noch keine Verbindung besteht.
+
+Rechts befindet sich an jedem Kontakteintrag ein Button "Bearbeiten", mit welchem man die Verbindung mittels Verbindungs-Editor bearbeiten kann.
+
+#include doc/de/member/connection_editor.md;
+#include doc/de/member/diaspora_compat.md;
diff --git a/doc/de/member/content_filters.md b/doc/de/member/content_filters.md
new file mode 100644
index 000000000..0e44b63ae
--- /dev/null
+++ b/doc/de/member/content_filters.md
@@ -0,0 +1,141 @@
+### Inhaltsfilter
+
+Mit dem Inhaltsfilter können Sie eingehende Inhalte aus allen Quellen oder von bestimmten Verbindungen filtern. Die Filterung kann auf Wörtern, Tags, regulären Ausdrücken oder Sprache basieren.
+
+In den Stream-Einstellungen können Sie globale Filter für alle eingehenden Inhalte festlegen.
+
+![contfilter01](/help/de/member/pic/contfilter01.png)
+
+Sie können auch individuelle Filter für jede Ihrer Verbindungen festlegen. Wählen Sie „Bearbeiten“ für eine beliebige Verbindung und suchen Sie dann unter „Filter für den Inhalt“.
+
+![contfilter02](/help/de/member/pic/contfilter02.png)
+
+Wenn Sie Filter unter **„Beiträge mit diesem Text nicht importieren**“ („Filterung ablehnen“) hinzufügen, werden diese zuerst angewendet. Alle übereinstimmenden Inhalte werden entfernt.
+
+Anschließend werden alle Filter unter **„Nur Beiträge mit diesem Text importieren“** („Filterung akzeptieren“) angewendet. Nur übereinstimmende Inhalte werden beibehalten und alles, was nicht übereinstimmt, wird entfernt.
+
+Im Allgemeinen werden Sie den einen oder den anderen verwenden und selten, wenn überhaupt, beide gleichzeitig.
+
+
+
+#### Grundlegende Filter
+
+**TEXT**
+
+Jeder Text, der nicht **mit einem der folgenden Zeichen beginnt**: '#', '$', '?', '/', '@', 'lang=', 'lang!=' führt eine Textübereinstimmung durch, bei der die Groß-/Kleinschreibung nicht berücksichtigt wird.
+
+Beispiel: `apple` (stimmt mit "apple", "APPLE", "Apples", "pineapple", "applesauce", etc. überein)
+
+Beispiel: `low sub` (stimmt mit "low sub", "low sub-zero", "Yellow Submarine", etc. überein)
+
+HINWEIS: Achten Sie darauf, keine kurzen Zeichenfolgen zu verwenden, die mit vielen unterschiedlichen Wörtern übereinstimmen. Sie können Reguläre Ausdrücke verwenden (siehe unten).
+
+
+
+**SPRACHE**
+
+**lang=** Übereinstimmung der Sprache (sofern diese identifiziert werden kann) Beispiel: `lang=de` (stimmt mit deutschsprachigen Inhalten überein)
+
+**lang!=** Alles außer dieser Sprache (sofern diese identifiziert werden kann) Beispiel: `lang!=en` (stimmt mit nicht-englischen Inhalten überein)
+
+
+
+**HASHTAG**
+
+**#**
+
+Hashtag abgleichen
+
+Beispiel: `#cats`
+
+**#>n**
+
+Größer als die Gesamtzahl der Hashtags abgleichen
+
+Beispiel: `#>10`
+
+
+
+**ERWÄHNUNG**
+
+**@**
+Erwähnung stimmt überein
+Beispiel: `@alice`
+
+**@>n**
+Größer als die Gesamtzahl der Erwähnungen
+Beispiel: `@>10`
+
+
+
+**KATEGORIE**
+
+**$**
+Übereinstimmung der Kategorie
+Beispiel: `$Science`
+
+
+
+**ITEMS UND FELDER**
+
+**?**
+Nachstehend unter „**Erweiterte Filter“** erläutert
+
+
+
+**REGULÄRE AUSDRÜCKE (REGEX)**
+
+**/**
+Übereinstimmung einer "regular expression". Zahlreiche Online-Hilfeseiten wie [Regular-Expressions.info](https://www.regular-expressions.info/) und [regexr.com](https://regexr.com/) bieten Unterstützung bei der Verwendung von regulären Ausdrücken.
+
+Beispiel: `/gr[ae]y/` (entspricht "gray" and "grey")
+Beipiel: `/\b[Ww]ar\b/` (entspricht dem gesamten Worten "war" und "War", jedoch nicht "wars", "warning", "software", etc.)
+
+
+
+#### Erweiterte Filter
+
+**ITEMS**
+
+**?**
+Sie können eine Zeichenketten-/Zahlen-/Array-/Boolesche-Abgleichung für die Datenbankfelder eines Elements (eines Beitrags, eines Kommentars usw.) durchführen. Eine vollständige Auflistung würde den Rahmen dieses Dokuments sprengen, aber sehen Sie sich `install/schema_mysql.sql` an und suchen Sie nach `CREATE TABLE IF NOT EXISTS `item``. Hier ist ein paar Beispiele:
+
+- `body` (Inhalt der Nachricht)
+- `verb` (in der Regel identisch mit dem ActivityPub-Aktivitätstyp, z. B. Create, Listen)
+- `obj_type` (normalerweise dasselbe wie das ActivityPub-Objekt „object.type“, z. B. Note, Event)
+- `item_thread_top` (erster Beitrag in einem Thread, boolean)
+- `item_private` (0 = öffentliche Nachricht, 1 = eingeschränkte Nachricht, 2 = Direktnachricht)
+- `ua` (Benutzeragenten-Zeichenfolge des Zustellungsagenten – benutzerdefiniertes Artikelattribut, das nicht in der Datenbank enthalten ist)
+- ...etc...
+
+Verfügbare Vergleichsoperatoren sind:
+
+- `?foo ~= baz` -- item.foo enthält den String 'baz'
+- `?foo == baz` -- item.foo ist der String 'baz'
+- `?foo != baz` -- item.foo ist nicht der String 'baz'
+- `?foo // regex` -- item.foo erfüllt die regular expression 'regex' - automatisch in Schrägstriche eingeschlossen, es sei denn, Sie schließen es mit einem anderen Zeichen ein, z. B. '=regex='.
+- `?foo >= 3` -- item.foo ist größer oder gleich 3
+- `?foo > 3` -- item.foo ist größer 3
+- `?foo <= 3` -- item.foo ist kleiner oder gleich 3
+- `?foo < 3` -- item.foo ist kleiner 3
+- `?foo {} baz` -- 'baz' ist ein Array-Element in item.foo
+- `?foo {*} baz` -- 'baz' ist ein Array-Schlüssel in item.foo
+- `?foo` -- wahre Bedingung für item.foo
+- `?!foo` -- falsche Bedingung für item.foo (Die Werte 0, '', ein leeres Array, und ein ungesetzter Wert werden alle zu falsch aufgelöst)
+
+Beispiel: `?verb == Announce` (entspricht ActivityPub "boosts")
+
+
+
+**FELDER**
+
+**?+**
+Übereinstimmung von ActivityPub/ActivityStreams-Objekten. Dies ist buchstäblich das kopierte Objektattribut aus der eingehenden Aktivität.
+
+Unterstützt alle oben aufgeführten Operatoren.
+
+Beispiele:
+
+`?+type == Question` -- entspricht ActivityPub Umfragen
+
+`?+attributedTo ~= spammer` -- entspricht ActivityPub attributedTo: https://o3r56t3c.example.com/u/cryptospammer2355
diff --git a/doc/de/member/conversation_features.md b/doc/de/member/conversation_features.md
new file mode 100644
index 000000000..86dff8b3d
--- /dev/null
+++ b/doc/de/member/conversation_features.md
@@ -0,0 +1,9 @@
+#### Konversationsmerkmale
+
+Über diesen Dialog ist es möglich, bestimmte Konversationsmerkmale für einen Beitrag festzulegen:
+
+- Zulassen von Emoji-Reaktionen
+- Zulassen von Dislikes
+- Zulassen des lokalen Markierens (Sternchen)
+- Zulassen von Antworten auf Kommentare
+
diff --git a/doc/de/member/create_channels.md b/doc/de/member/create_channels.md
new file mode 100644
index 000000000..47cdbe6d6
--- /dev/null
+++ b/doc/de/member/create_channels.md
@@ -0,0 +1,9 @@
+### Kanäle erstellen
+
+Nachdem Sie Ihr Konto erstellt haben, wird Ihnen der Bildschirm „Kanal hinzufügen“ angezeigt. Normalerweise wird Ihr erster Kanal einer sein, der Sie repräsentiert - es ist also eine gute Idee, Ihren eigenen Namen (oder ein Pseudonym) als Kanalnamen zu verwenden. Der Kanalname sollte als Titel oder kurze Beschreibung deines Kanals betrachtet werden. Das Feld „Wählen Sie einen kurzen Spitznamen“ ist vergleichbar mit einem „Benutzernamen“. Mit dem, was Sie hier eingeben, erstellen Sie eine Kanaladresse (im Fediverse auch als "Handle" bezeichnet), mit der sich andere Personen mit Ihnen verbinden können und mit der Sie sich auf anderen Websites anmelden können. Diese Adresse sieht aus wie eine E-Mail-Adresse und hat die Form `<nickname>@<ihr_hub>`.
+
+**Hinweis**: ***Bei anderen Diensten im Fediverse wird dem Handle ein "@" vorangestellt. Bei Hubzilla muss dieses Zeichen weggelassen werden, wenn man sich z.B. mit einem anderen Nutzer verbinden oder nach einem Handle suchen möchte.***
+
+Sie können weitere Kanäle über den Link „Kanalmanager“ erstellen.
+
+Sobald Sie dies getan haben, ist Ihr Kanal einsatzbereit. Unter `<ihr_hub>/channel/<Nickname>` finden Sie Ihren Kanal „Stream“. Hier werden Ihre jüngsten Aktivitäten in umgekehrter chronologischer Reihenfolge angezeigt. \ No newline at end of file
diff --git a/doc/de/member/delete.md b/doc/de/member/delete.md
new file mode 100644
index 000000000..2700cee7a
--- /dev/null
+++ b/doc/de/member/delete.md
@@ -0,0 +1,3 @@
+#### Löschen
+
+Mit dieser Funktion kann man Beiträge aus dem Stream löschen. Ein normaler Nutzer hat diese Möglichkeit nicht beim globalen Stream. In diesem kann nur ein Administrator Beiträge entfernen. \ No newline at end of file
diff --git a/doc/de/member/delete_account.md b/doc/de/member/delete_account.md
new file mode 100644
index 000000000..7e9c44e8a
--- /dev/null
+++ b/doc/de/member/delete_account.md
@@ -0,0 +1,8 @@
+### Account löschen
+
+Möchten Sie Ihren Account, also den gesamten Zugang zum Hub, löschen, nutzen Sie die Einstellungen im Hauptmenü (oben links; Pofilbild): Einstellungen → Konto-Einstellungen.
+
+Dort befindet sich ganz oben ein Button mit der Beschriftung "Konto entfernen". Ein Klick darauf und Ihr Account (Konto) wird, nach Eingabe Ihres Account-Passworts (zur Absicherung) inklusive aller Inhalte gelöscht.
+
+![Account löschen](/help/de/member/pic/ktoloe.png)
+
diff --git a/doc/de/member/deleting_channel.md b/doc/de/member/deleting_channel.md
new file mode 100644
index 000000000..4fe3aaf39
--- /dev/null
+++ b/doc/de/member/deleting_channel.md
@@ -0,0 +1,9 @@
+### Kanal löschen
+
+Möchten Sie Ihren Kanal löschen, nutzen Sie die Einstellungen im Hauptmenü (oben links; Pofilbild): Einstellungen → Kanal-Einstellungen.
+
+Dort befindet sich ganz oben ein Button mit der Beschriftung "Kanal löschen". Ein Klick darauf und Ihr Kanal wird, nach Eingabe Ihres Account-Passworts (zur Absicherung) inklusive aller Inhalte gelöscht.
+
+![Kanal löschen](/help/de/member/pic/kanloe.png)
+
+***Beachte:*** Es ist nun nicht mehr möglich, einen neuen Kanal mit der selben Kanalbezeichnung auf diesem Hub anzulegen. Der Grund dafür ist, dass der Kanal-Kurzname in der Datenbank gesperrt ist, um eine Identitätsübernahme durch dritte zu verhindern. Sollte es aber dennoch erforderlich sein, den alten Kanal (durch Klonen) auf dem Hub wiederherzustellen, müssen Sie den Administrator darum bitte, den gesperrten Kurznamen aus der Hubzilla-Datenbank zu löschen.
diff --git a/doc/de/member/diaspora_compat.md b/doc/de/member/diaspora_compat.md
new file mode 100644
index 000000000..36d5075be
--- /dev/null
+++ b/doc/de/member/diaspora_compat.md
@@ -0,0 +1,33 @@
+### Diaspora-Kompatibilität
+
+Das Diaspora Protocol Addon ermöglicht es einer Website, mit dem Diaspora-Protokoll zu kommunizieren, was die Kommunikation und Verbindungen mit Diaspora-Mitgliedern (und auch Friendica-Mitgliedern, da dieses Netzwerk ebenfalls das Diaspora-Protokoll anbietet) ermöglicht.
+Dieses Addon ist in den Serverkonfigurationen 'Basic' und 'Standard' verfügbar. Es ist nicht verfügbar mit und das Plugin ist komplett deaktiviert, wenn Sie die 'pro' Serverkonfiguration verwenden. Der Grund dafür ist, dass das Diaspora-Protokoll nicht sehr ausgereift ist und viele Funktionen von $projectname nicht gut damit funktionieren.
+Die Mitglieder müssen sich der Einschränkungen des Protokolls bewusst sein eigenen Aktivitäten auf diejenigen beschränken, die mit Diaspora kompatibel sind. Die „Pro“-Serverkonfiguration ist frei von diesen Einschränkungen und Sie können alle Projektfunktionen und -fähigkeiten nutzen, ohne sich Gedanken darüber zu machen, wie sie sich auf andere Netzwerke übertragen lassen. Viele Funktionen sind einzigartig für Hubzilla und werden durch das Nomad-Protokoll unterstützt, das unsere native Kommunikationssprache zwischen Servern/Hubs ist.
+Wenn Sie eine Konfiguration verwenden, die direkte Diaspora-Kommunikation erlaubt, sollten Sie sich der hier dargestellten Einschränkungen bewusst sein.
+
+- Das Zurückziehen von privaten Nachrichten (unsend) ist bei Diaspora-Verbindungen nicht möglich.
+- Private Nachrichten und die dazugehörigen Kommentare werden in Diaspora und Friendica als E-Mail-Benachrichtigungen im Klartext gesendet. Dies ist ein großes Problem für die Privatsphäre und betrifft alle privaten Kommunikationen, bei denen sich *ein* Mitglied der Konversation in einem anderen Netzwerk befindet. Sei dir dessen bewusst.
+- Die Zugangskontrolle funktioniert nur für Beiträge und Kommentare. Diaspora-Mitgliedern wird die Erlaubnis verweigert, wenn sie versuchen, auf andere zugriffskontrollierte hubzilla-Objekte wie Dateien, Fotos, Webseiten, Chatrooms usw. zuzugreifen. Im Falle von privaten Fotos, die mit Beiträgen verlinkt sind, sehen sie ein „Verbotszeichen“ anstelle des Fotos. Diaspora hat kein Konzept für private Medien und bietet eine Illusion von Fotoprivatsphäre, indem es unkenntliche URLs verwendet, anstatt das Foto vor dem Schnüffeln durch unbefugte Betrachter zu schützen.
+
+Es gibt keine andere Möglichkeit, als Ihre Medienressourcen öffentlich zu machen (für jeden im Internet).
+
+- Bearbeitete Beiträge werden nicht zugestellt. Die Diaspora-Mitglieder sehen den ursprünglichen Beitrag/Kommentar ohne Bearbeitungen. Das Protokoll enthält keinen Mechanismus, um einen bestehenden Beitrag zu aktualisieren. Es ist nicht möglich, einen Beitrag zu löschen und einen neuen unsichtbar zu übermitteln, da sich die Message-ID ändert und wir dieselbe Message-ID in unserem eigenen Netzwerk behalten müssen. Die einzige Abhilfe besteht darin, den Beitrag/Kommentar zu löschen und ihn erneut zu übermitteln. (Wenn es sich um einen Beitrag handelt, werden dabei alle vorhandenen Likes/Kommentare gelöscht). Eventuell werden wir eine Möglichkeit anbieten, die veraltete Kopie nur in Diaspora zu löschen und sie in Netzwerken, die Änderungen verarbeiten können, intakt zu lassen.
+- Nomadische Identität (nur $projectname 'standard') wird nicht mit Diaspora funktionieren. Wir werden eventuell eine **Option** bereitstellen, die es Ihnen ermöglicht, die Freigabe von allen Ihren Klonen zu starten, wenn Sie die erste Verbindung herstellen. Die Diaspora-Person muss dies nicht akzeptieren, aber wenn sie die Verbindung akzeptiert, kann die Kommunikation fortgesetzt werden. Ohne diese Option müssen Sie, wenn Sie zu einem anderen Server gehen, von dem aus Sie die Verbindung ursprünglich hergestellt haben, oder wenn Sie die Verbindung herstellen, bevor Sie den Klon erstellt haben, die Verbindung von dem neuen Ort aus erneut herstellen.
+- Das Ablaufen der Post wird auf Diaspora nicht unterstützt. Wir können Ihnen eine Option anbieten, um ablaufende Beiträge nicht an das Netzwerk zu senden. In Zukunft kann dies mit einer Remote-Löschanforderung bereitgestellt werden.
+- Eine Ende-zu-Ende-Verschlüsselung wird nicht unterstützt. Wir werden diese Beiträge in ein Schlosssymbol übersetzen, das von der Diaspora-Seite aus nicht entsperrt werden kann.
+- Die Überprüfung von Nachrichten wird in Zukunft unterstützt werden.
+- Mehrere Profile werden nicht unterstützt. Diaspora-Mitglieder können nur Ihr Standardprofil sehen.
+- Geburtstagsereignisse werden in Diaspora nicht angezeigt. Andere Ereignisse werden übersetzt und als Post verschickt, aber alle Zeiten werden entweder in der Zeitzone des Ursprungskanals oder in GMT angegeben. Wir kennen die Zeitzone des Empfängers nicht, da Diaspora dieses Konzept nicht hat.
+- Wir erlauben derzeit standardmäßig, dass Tags gekapert werden können. Es gibt eine Option, mit der Sie verhindern können, dass das andere Ende des Netzwerks Ihre Tags entführt und sie auf seine eigenen Ressourcen zeigt.
+- Community-Tags werden nicht funktionieren. Wir werden eine Tagging-Aktivität als Kommentar senden. Das wird nichts bewirken.
+- Privatsphären-Tags (@!somebody) werden für Diaspora-Mitglieder nicht verfügbar sein. Diese Tags müssen möglicherweise entfernt oder unkenntlich gemacht werden, um zu verhindern, dass sie gekapert werden - was zu Datenschutzproblemen führen könnte.
+- Plus-getaggte hubzilla-Foren sollten von Diaspora aus funktionieren.
+- Du kannst keine Diaspora-Kanäle als Kanalquellen verwenden.
+- Ablehnungen von Beiträgen werden in Kommentare umgewandelt und Du wirst die Möglichkeit haben, diese als Kommentare zu senden oder sie nicht an Diaspora zu senden (das keine Ablehnungen bietet). Derzeit werden sie nicht gesendet.
+- Wir werden das Gleiche für Likes und Dislikes von ***Kommentaren*** tun. Sie können entweder als Kommentar gesendet werden oder Sie werden die Möglichkeit haben, zu verhindern, dass sie an Diaspora übermittelt werden. Derzeit werden sie nicht gesendet.
+- Emojis sind derzeit nicht übersetzt.
+- „Beobachter-Tags“ werden in leeren Text umgewandelt.
+- Eingebettete Apps werden in Links umgewandelt.
+- Eingebettete Seitendesign-Elemente (in Arbeit) werden entweder entfernt oder in eine Fehlermeldung umgewandelt.
+- Diaspora-Mitglieder werden nicht im Verzeichnis angezeigt.
+- Es gibt Unterschiede in der Kompatibilität von Oembed zwischen den Netzwerken. Einige eingebettete Ressourcen werden auf der einen oder anderen Seite in einen Link umgewandelt. \ No newline at end of file
diff --git a/doc/de/member/direct_messages.md b/doc/de/member/direct_messages.md
new file mode 100644
index 000000000..dc0f77775
--- /dev/null
+++ b/doc/de/member/direct_messages.md
@@ -0,0 +1,9 @@
+### Direktnachrichten
+
+Direktnachrichten sind Nachrichten, die an eine oder mehrere Einzelverbindungen adressiert sind. Sie sind über den Netzwerkstream zugänglich. Ein Filter für Direktnachrichten wurde dem Stream-Filter-Widget für den schnellen Zugriff hinzugefügt.
+
+Möchte man eine Direktnachricht an einen (oder mehrere) andere Nutzer versenden (Direktnachrichten können nur von den Adressaten und vom Versender gelesen werden), so verfasst man einen normalen Beitrag und adressiert diesen per spezieller Erwähnung ausschließlich an den / die Empfänger. Dies geschieht mit der privaten Erwähnung (Privacy-Tag). Ein Privacy-Tag ist ein Name, dem die beiden Zeichen `@!` vorangestellt sind und der zusätzlich zur Markierung dieser Kanäle auch die Datenschutzberechtigungen des Beitrags so ändert, dass nur diese berücksichtigt werden.
+
+Um auf eine DN auch wieder "privat", also als DN zu antworten, muss man kein Privacy-Tag nutzen. Man antwortet einfach direkt auf die eingegangene DN, wodurch die Antwort an alle ursprünglichen Adressaten verteilt wird.
+
+Alternativ zum Privacy-Tag kann man auch Kanäle oder Privacygruppen aus dem [Privacy-Tool](/help/de/member/permissions_content.md) (🔒) auswählen. Dies ist der umständlichere Weg, der aber auch funktioniert. Die Nutzung eines Privacy-Tags überschreibt allerdings eine ggf. getroffene Auswahl im Privacy-Tool. Schreibt man also einen Beitrag, der als Direktnachricht verschickt werden soll, kann man den Privacy-Tag weglassen und stattdessen auf das Vorhangschloss-Symbol neben dem Button "Teilen" klicken, so dass man in die Berechtigungs-Einstellungen gelangt. \ No newline at end of file
diff --git a/doc/de/member/directory.md b/doc/de/member/directory.md
new file mode 100644
index 000000000..d8732f0ee
--- /dev/null
+++ b/doc/de/member/directory.md
@@ -0,0 +1,15 @@
+### Verzeichnis
+
+Hubzilla bietet über die App (im App-Menü ⋮) ein Kanalverzeichnis an. Im Verzeichnis werden die Kanäle des Hubzilla-Grid aufgeführt.
+
+![verzeichnis](/help/de/member/pic/verzeichnis.png)
+
+Über die Verzeichnisoptionen in der linken Seitenleiste kann der Umfang des Verzeichnisses gefiltert werden. So können Sie z.B. die Auflistung auf Kanäle des eigenen Hub beschränken.
+
+Außerdem gibt es in der linken Seitenleiste einen Schlüsselwörter-Wolke, über welche Sie Kanäle mit entsprechenden Interessen/Schwerpunkten finden können.
+
+Ebenfalls in der linken Seitenleiste befindet sich ein Suchfeld, um Kanäle durch Namen / Namensbestandteile oder Interessen (Schlagwörter) zu finden.
+
+Sie können sich dann durch Klick auf den entsprechenden Button direkt mit einem gefundenen Kanal verbinden.
+
+#include doc/de/member/AdvancedSearch.md;
diff --git a/doc/de/member/display_settings.md b/doc/de/member/display_settings.md
new file mode 100644
index 000000000..b484b4012
--- /dev/null
+++ b/doc/de/member/display_settings.md
@@ -0,0 +1,19 @@
+#### Anzeige-Einstellungen
+
+Mit den Anzeige-Einstellungen kann das Design des Kanals eingestellt werden. Überdies kann in gewissen Grenzen festgelegt werden, welche Inhalte dargestellt werden.
+
+In den Design-Einstellungen kann man aus den installierten Themes auswählen und dein Design-Schema für das Thema festlegen.
+
+![Anzeige-Einstellungen 01](/help/de/member/pic/anzeinst01.png)
+
+Mit den Benutzerdefinierten Design-Einstellungen ist es möglich, das Farbschema den eigenen Vorstellungen anzupassen und Eckenradien, Standardgrößen sowie Standardmaße für Avatare festzulegen. Es werden als Standard zunächst die vereinfachten Einstellungen angezeigt, mit denen es nur möglich ist, den dunklen Modus festzulegen, eine schmale Navigationsleiste zu wählen, sowie die Breite des Inhaltsbereichs und die Schriftgröße.
+
+![Anzeige-Einstellungen 02](/help/de/member/pic/anzeinst02.png)
+
+Wenn Sie den Schalter "Erweiterte Einstellungen anzeigen" auf "Ja" stellen und die Auswahl absenden, werden die erweiterten Einstellungen angezeigt, mit denen man Farben, Avatarmaße und Hintergrundbilder festlegen kann.
+
+![Anzeige-Einstellungen 03](/help/de/member/pic/anzeinst03.png)
+
+Mit den Inhalts-Einstellungen lassen sich verschiedene Parameter auswählen (z.B. die Zeit bis zur Aktualisierung der Ansicht) und die Anzeige der "Links für neue Mitglieder", die bei neu angelegten Kanälen angezeigt werden, auszuschalten.
+
+![Anzeige-Einstellungen 04](/help/de/member/pic/anzeinst04.png)
diff --git a/doc/de/member/encryption.md b/doc/de/member/encryption.md
new file mode 100644
index 000000000..b8c238b07
--- /dev/null
+++ b/doc/de/member/encryption.md
@@ -0,0 +1,15 @@
+### Eingebaute automatische Verschlüsselung
+
+Vollständige Offenlegung: Die Verschlüsselung, die hubzilla standardmäßig verwendet, ist nicht absolut wasserdicht. Es *gibt* bekannte Verfahren, um sie zu umgehen. Das ist*aber* sehr aufwändig und muss für jeden Kanal einzeln durchgeführt werden. Und um das klarzustellen: Andere Dienste speichern Ihre Nachrichten im Klartext, daher sehen wir diesen Ansatz als eine *deutliche* Verbesserung für Ihre Privatsphäre an. Außerdem steht es Ihnen jederzeit frei, weitere Verschlüsselungen und Passwortschutz zu verwenden, wenn Sie dies wünschen.
+Um dies näher zu erläutern:
+
+- jeder Kanal hat sein Schlüsselpaar
+- jeder nicht-öffentliche Beitrag wird automatisch verschlüsselt
+- optionaler Passwortschutz für Inhalte über Krypto-Javascript, Browser-zu-Browser-Verschlüsselung (muss in den Einstellungen aktiviert werden) Vollständige Offenlegung: Ein böswilliger Hub-Administrator könnte bösartigen Javascript-Code (z.B. Keylogging-Fähigkeiten) in den Code einschleusen. Verschlüsseln Sie unsere Daten mit GPG, werden Sie selbst Hub-Administrator oder verwenden Sie andere Kommunikationsmittel, wenn Sie das stört.
+
+Was ist also der Umfang der Sicherheit? Um es ganz offen zu sagen: Das mag toll sein, aber es ist nicht perfekt.
+
+- Jeder nicht öffentliche Beitrag wird automatisch verschlüsselt, aber Personen, die Zugriff auf die Datenbank und die Dateien der Website haben *, können* mit Hilfe dieser Schlüssel, die natürlich auf dem Server gespeichert werden müssen, alles entschlüsseln. Um es klar zu sagen: Die Verschlüsselungsschlüssel sind für jeden Kanal unterschiedlich und es ist ein *ziemlicher Aufwand*, dies zu tun. Und nochmals: Andere Dienste speichern Ihre Nachrichten im Klartext und unverschlüsselt. Das ist also *ein* ganz erheblicher Gewinn für Ihre Privatsphäre.
+
+Wir glauben, dass eine Massenüberwachung auf NSA-Niveau mit Klartextextraktion aufgrund des Designs des Nomad-Protokolls wahrscheinlich nicht möglich ist. Gezielte Angriffe, wie z. B. das Hacken eines Hubs, um an die Serverprotokolle und die Datenbank zu gelangen, geben nur teilweise Aufschluss darüber, was zwischen der Kommunikation von Personen zwischen verschiedenen Hubs vor sich geht. Wir glauben, dass dies es für Angreifer auf staatlicher Ebene viel teurer macht, auf Ihre Inhalte in hubzilla zuzugreifen.
+Wir nehmen gerne Hilfe an, um die Sicherheit des Systems zu verbessern und es auch zu überprüfen. \ No newline at end of file
diff --git a/doc/de/member/files.md b/doc/de/member/files.md
new file mode 100644
index 000000000..3a1b3206e
--- /dev/null
+++ b/doc/de/member/files.md
@@ -0,0 +1,31 @@
+### Dateien
+
+Hubzilla bietet Cloud-Funktionalität. Das bedeutet, dass man für jeden Kanal ein Verzeichnis besitzt, in dem man weitere Unterverzeichnisse erzeugen und Dateien ablegen kann. Für jedes Verzeichnis, ja sogar für jede einzelne Datei kann man genaue Zugriffsrechte festlegen. Das geht von einer Sichtbarkeit für die Allgemeinheit, eine Sichtbarkeit für Mitglieder bestimmter Gruppen bis hin zur einzelnen Freigabe für einzelne Mitglieder aus den eigenen Verbindungen. Es ist sogar möglich, Dateien mit Leuten zu teilen, die keine Hubzilla-Identität haben. Dazu verwendet man Gastzugangs-Token.
+
+Das Anlegen und Löschen von Verzeichnissen und das Anlegen bzw. Löschen von Dateien ist wirklich einfach.
+
+Seinen Cloudspeicher erreicht man über das „App-Menü“ (⋮) → Dateien. Man kann auch Bilder über den Dateien-Bereich hochladen, was ebenfalls über den Bereich „Fotos“ möglich ist.
+
+Die Dateien können in einer Listenansicht
+
+![Dateien - Listenansicht](/help/de/member/pic/dateien01.png)
+
+oder einer Kachelansicht
+
+![Dateien - Kachelansicht](/help/de/member/pic/dateien02.png)
+
+dargestellt werden.
+
+Neue Verzeichnisse / Ordner erstellt man mit dem Button "Erstelle". Beim Erstellen kann man auch sofort die [Berechtigungen](/help/de/member/permissions_content.md) für den neuen Ordner festlegen (🔓).
+
+Mit dem Button "+ Dateien hinzufügen" kann man Dateien in seine Cloud hochladen. Auch hier ist es wieder möglich die Zugriffsberechtigungen direkt festzulegen.
+
+Um Zugriffsrechte (Berechtigungen) für Verzeichnisse oder Dateien nachträglich festzulegen oder zu ändern, klickt man auf das Kontextmenü (︙) der Datei oder des Verzeichnisses. Hier lassen sich außerdem noch verschiedene Dateioperationen durchführen.
+
+![Dateioperationen](/help/de/member/pic/dateien03.png)
+
+Fügt man beim Erstellen eines Beitrags oder eines Kommentars eine Datei mit dem Button "📎" an, so wird diese Datei in einem ggf. neu erstellten Verzeichnis (Muster: `Jahr-Monat`) in der Cloud abgelegt.
+
+#include doc/de/member/photos.md;
+#include doc/de/member/gallery.md;
+#include doc/de/member/cloud_storage.md;
diff --git a/doc/de/member/follow_conversation.md b/doc/de/member/follow_conversation.md
new file mode 100644
index 000000000..0c5581613
--- /dev/null
+++ b/doc/de/member/follow_conversation.md
@@ -0,0 +1,3 @@
+#### Unterhaltung folgen / nicht mehr folgen
+
+Mittels Unterhaltung folgen / Unterhaltung nicht mehr folgen kann man umschalten, ob man einem Thread folgen möchte, also ob man Benachrichtigungen über Kommentare/Antworten, über Likes, Dislikes, Emoji-Reaktionen oder darüber, dass der Beitrag geteilt oder wiederholt wurde, erhalten möchte.
diff --git a/doc/de/member/gallery.md b/doc/de/member/gallery.md
new file mode 100644
index 000000000..764bd5959
--- /dev/null
+++ b/doc/de/member/gallery.md
@@ -0,0 +1,7 @@
+#### Galerie
+
+Bei der App "Galerie" handelt es sich um eine einfache Fotogalerie mit welcher alle Ihre Bilder aus dem Cloudspeicher dargestellt werden.
+
+Die dargestellten Bilder werden passend skaliert, was bei kleineren Bildern zu einer etwas unscharfen Darstellung führen kann. Praktischer ist die Nutzung der App "[Fotos](/help/de/member/photos.md)".
+
+![Galerie](/help/de/member/pic/galerie01.png)
diff --git a/doc/de/member/guest_access.md b/doc/de/member/guest_access.md
new file mode 100644
index 000000000..29d5c4ddd
--- /dev/null
+++ b/doc/de/member/guest_access.md
@@ -0,0 +1,15 @@
+### Gastzugang
+
+Wenn Sie private (also für die Öffentlichkeit nicht zugängliche) Inhalte mit Menschen teilen möchten, die über keinen Hubzilla-Account verfügen, so haben Sie die Möglichkeit dies mittels eines Gastzugangs zu verwirklichen.
+
+Mit dem Gastzugang erstellen Sie einen (ggf. temporären) Zugang, der es dem Nutzer, welcher sich mit diesen Daten einloggt, ermöglicht, auf ihre öffentlich zugänglichen Inhalte, aber auch nicht-öffentliche Inhalte, welche sie speziell für den Gastzugang freigeben, zuzugreifen.
+
+Rufen Sie die App "Gastzugang" auf, so wird ein Webformular angezeigt, mit welchem Sie einen solchen Gastzugang einrichten können. Sie geben einen selbst gewählten Anmeldenamen ein. Hubzilla hat bereits automatisch ein Kennwort für den Gastzugang generiert. Diese beiden Informationen können Sie nun demjenigen weitergeben, dem sie Zugriff auf Inhalte geben möchten.
+
+Im Feld "Läuft ab" können Sie außerdem ein Verfallsdatum eingeben, ab welchem der Gastzugang automatisch wieder gelöscht wird. Wenn Sie dieses Feld leer lassen, wird der Gastzugang ohne zeitliche Beschränkung erstellt. Er läuft dann nie automatisch ab und muss ggf. per Hand gelöscht werden.
+
+Außerdem können Sie für den Gastzugang eine [Kontakt-Rolle](/help/de/member/permissions_contact_roles.md) festlegen.
+
+In der linken Seitenleiste werden sämtliche Gastzugänge aufgelistet. Wählen Sie dort einen Gastzugang auf, so können sie ihn bearbeiten oder wieder löschen (auch vor Ablauf der Frist).
+
+Sobald ein Gastzugang angelegt ist, taucht er auch in den "[Berechtigungs-Einstellungen](/help/de/member/permissions_content.md)" (Privacy-Tool) unter "Benutzerdefinierte Auswahl" auf. Sie können damit einzelne private Inhalte explizit für den Gastzugang (aber natürlich zusätzlich auch für andere Kontakte) freigeben, so dass der Gast auf die Inhalte zugreifen kann. \ No newline at end of file
diff --git a/doc/de/member/important_apps.md b/doc/de/member/important_apps.md
new file mode 100644
index 000000000..fa2c22693
--- /dev/null
+++ b/doc/de/member/important_apps.md
@@ -0,0 +1,17 @@
+#### Wichtige Apps
+
+Wenn Sie Ihren Kanal hauptsächlich für social Networking nutzen möchten, gibt es einige Apps, die nicht als Standard installiert oder aktiviert sind, welche teilweise essenziell wichtig sind.
+
+Um am gesamten Fediverse teilzunehmen, müssen Sie die App "**ActivityPub-Protokoll**" installieren.
+
+Um einfacher lohnende Kontakte zu finden und zu erleben, was im Fediverse gerade passiert, kann man die App "**[Öffentlicher Beitragsstream](/hlp/de/member/public_stream.md)**" installieren und nutzen.
+
+Ebenfalls sinnvoll und empfehlenswert ist die App "**[NSFW](/help/de/member/NSFW.md)**", mit welcher man eigenen Contentwarning-Filter erstellen und nutzen kann.
+
+Außerdem sollten Sie die App "**[Superblock](/help/de/member/superblock.md)**" installieren, welche es Ihnen ermöglicht, bestimmte Nutzer komplett zu blockieren.
+
+Als ebenfalls wichtig ist die App "**[Privacy Groups](/help/de/member/privacy_groups.md)**" zu erachten. Mit dieser ist es möglich, Kontaktgruppen zu erstellen und sowohl nach diesen zu filtern, als auch mit den Kontakten aus den Gruppen geschlossene Kommunikation zu betreiben.
+
+----
+
+**<u>Achtung:</u>** Die App "Öffentlicher Beitragsstream" steht zur Installation nur zur Verfügung, wenn der Administrator des Hub diesen Stream aktiviert hat.
diff --git a/doc/de/member/insert_images.md b/doc/de/member/insert_images.md
new file mode 100644
index 000000000..6b713e0a1
--- /dev/null
+++ b/doc/de/member/insert_images.md
@@ -0,0 +1,53 @@
+#### Bilder einfügen
+
+Es gibt verschiedene Möglichkeiten, Bilder in Beiträge und Kommentare einzufügen.
+
+##### Im Beitragseditor
+
+Im Beitragseditor existieren zwei Schaltflächen, die es ermöglichen, Bilder einzufügen: "Bild einbetten" und "Datei anhängen/hochladen".
+
+Mit "Bild einbetten" kann man ein bereits in der Cloud existierendes Bild in den Beitrag einfügen. Das Bild muss also entweder vorhanden sein, oder mal lädt es zu genau diesem Zweck z.B. mit der App "Dateien" hoch.
+
+![bild01](/help/de/member/pic/bild01.png)
+
+Wählt man diese Schaltfläche, öffnet sich eine Übersicht mit den vorhandenen ´Bilddateien und man muss nur das passende Bild auswählen und anklicken. <u>Beachte:</u> Nach der Auswahl des einzufügenden Bildes muss das Auswahlfenster wieder geschlossen werden. Es schließt sich nicht automatisch nach dem Einfügen eines Bildes, um die Möglichkeit zu haben, in einem Durchgang ggf. mehrere Bilder einzufügen.
+
+Mit dem Auswählen wird das Bild, auf Originalgröße skaliert, als anklickbarer Link zur Quelldatei mittels der bbCode-Tags `[zrl][zmg] [/zmg][/zrl]` am Ende des bisherigen Beitrags (nicht an der aktuellen Textcursor-Position) eingefügt.
+
+![bild03](/help/de/member/pic/bild03.png)
+
+Mit "Datei anhängen/hochladen" kann ein Bild direkt vom eigenen Gerät hochgeladen und am Ende des Beitrags eingebettet.
+
+![bild02](/help/de/member/pic/bild02.png)
+
+Es öffnet sich ein Dateiauswahl-Dialog mit dem man das Bild auf dem eigenen Gerät auswählen und hochladen kann. Es wird in ein ggf. automatisch erstelltes Unterverzeichnis in der Cloud hochgeladen und wieder mit den bbCode-Tags `[zrl][zmg] [/zmg][/zrl]` am Ende des bisherigen Beitrags eingefügt.
+
+<u>Beachte:</u> Bei dieser Methode wird das Bild, sofern kein Fließtext vorhanden ist, nicht auf Originalgröße skaliert, sondern als kleiners Vorschaubild dargestellt. Das führt dazu, dass um das Bild herum ggf. ein Karomuster-Hintergrund zu sehen ist.
+
+![bild04](/help/de/member/pic/bild04.png)
+
+##### Im Kommentareditor
+
+Im Kommentareditor existiert lediglich die Möglichkeit, ein Bild hochzuladen und einzubetten. Die Schaltfläche für das Einfügen eines vorhandenen Bildes existiert dort nicht.
+
+![bild05](/help/de/member/pic/bild05.png)
+
+Möchte man dennoch ein bereits vorhandenes Bild einfügen, muss man das per Hand mit den Tags `[img][/img]` oder `[img=URL][/img]` tun. Dafür muss man die URL des Bildes kennen. Auf diese Weise kann man auch Bilder aus externen Quellen im Web einfügen.
+
+Verwendet man `[img=BREITExHÖHE][/img]`, kann man das Bild skalieren.
+
+##### Alt-Text
+
+Möchte man einen Alternativtext (Alt-Text), welcher angezeigt wird, wenn das Bild nicht dargestellt werden kann, oder - als Popup - wenn man mit dem Mauszeiger über das Bild fährt, so kann man diesen zwischen die beiden Tags `[img=URL]ALT_TEXT[/img]` tun. Hat man das Bild in einen Beitrag nach der erstem Methode ("Bild einbetten"), muss man selbst Hand anlegen. Man muss die Skalierung, die im öffnenden zmg-Tag eingefügt wurde mit der URL des Bildes ersetzen und die automatisch eingefügte Bild-URL zwischen dem öffnenden und dem schließenden zmg-Tag durch den Alt-Text ersetzen.
+
+Beispiel:
+
+Das Bild wurde eingebettet und es wurde damit der Code
+
+`[zrl=https://klacker.org/photos/tutorial01/image/cd747cd9-3f05-42cd-94cc-91c7368c5a18][zmg=520x520]https://klacker.org/photo/cd747cd9-3f05-42cd-94cc-91c7368c5a18-2.png[/zmg][/zrl]`
+
+automatisch erstellt, dann muss er, um z.B. den Alt-Text "Hubzilla-Symbol auf hellgrauem Hintergrund" einzufügen, so geändert werden:
+
+`[zrl=https://klacker.org/photos/tutorial01/image/cd747cd9-3f05-42cd-94cc-91c7368c5a18][zmg=https://klacker.org/photo/cd747cd9-3f05-42cd-94cc-91c7368c5a18-2.png520x520]Hubzilla-Symbol auf hellgrauem Hintergrund[/zmg][/zrl]`
+
+![bild06](/help/de/member/pic/bild06.png)
diff --git a/doc/de/member/interact.md b/doc/de/member/interact.md
new file mode 100644
index 000000000..9e10e2a59
--- /dev/null
+++ b/doc/de/member/interact.md
@@ -0,0 +1,25 @@
+### Mit Postings interagieren
+
+Mit Postings, die man im Stream sieht, kann man interagieren. Das ist einer der Zwecke von Sozialen Netzwerken. Man kann einen solchen Beitrag kommentieren, es sind aber noch weitere Möglichkeiten der Interaktion möglich.
+
+Die Schaltflächen (Buttons) dafür findet man im Beitrag unten rechts.
+
+![Schaltflächen](/help/de/member/pic/interag01.png)
+
+Man kann einen Beitrag "mögen" 🖒 oder "nicht mögen" 🖓 oder man kann mit einem Emoji auf einen Beitrag reagieren:
+
+![Emoji-Reaktion](/help/de/member/pic/interag02.png)
+
+Weitere Funktionen befinden sich im Menü **⚙**.
+
+![Menü](/help/de/member/pic/interag03.png)
+
+#include doc/de/member/repeat.md;
+#include doc/de/member/share.md;
+#include doc/de/member/link_to_source.md;
+#include doc/de/member/save_to_folder.md;
+#include doc/de/member/toggle_star_status.md;
+#include doc/de/member/show_source_code.md;
+#include doc/de/member/follow_conversation.md;
+#include doc/de/member/delete.md;
+#include doc/de/member/conversation_features.md;
diff --git a/doc/de/member/link_to_source.md b/doc/de/member/link_to_source.md
new file mode 100644
index 000000000..39b344f2c
--- /dev/null
+++ b/doc/de/member/link_to_source.md
@@ -0,0 +1,3 @@
+#### Link zur Quelle
+
+Mit "Link zur Quelle" gelangt man zur eigentlichen Quelle eines Beitrags. Man landet beim Original-Beitrag auf der Instanz des Beitragserstellers. \ No newline at end of file
diff --git a/doc/de/member/mentions.md b/doc/de/member/mentions.md
new file mode 100644
index 000000000..5f1c714c1
--- /dev/null
+++ b/doc/de/member/mentions.md
@@ -0,0 +1,9 @@
+### Erwähnungen
+
+Kanäle (Nutzer) werden gekennzeichnet, indem ihrem Namen (Handle) einfach das @ -Zeichen vorangestellt wird. `@Jack`
+
+Wenn Sie jemanden erwähnen, wird ein Feld für die automatische Vervollständigung erstellt, aus dem Sie Ihre unmittelbaren Verbindungen auswählen können. Wählen Sie entsprechend aus.
+
+Ist der Kontakt berechtigt, Ihr Posting zu empfangen, erhält er eine Tagbenachrichtigung.
+
+Befindet sich der Adressat nicht in Ihrer Kontaktliste, so müssen sie das Handle hinter dem "@"-Zeichen ausschreiben. Sofern der Empfänger auch Erwähnungen Fremder erlaubt, wird er ebenfalls über die Erwähnung benachrichtigt. \ No newline at end of file
diff --git a/doc/de/member/overview.md b/doc/de/member/overview.md
new file mode 100644
index 000000000..9bd2f6c0c
--- /dev/null
+++ b/doc/de/member/overview.md
@@ -0,0 +1,3 @@
+### Überblick
+
+Während viele Funktionen und Möglichkeiten von Hubzilla denjenigen vertraut sind, die bereits Social-Networking-Websites und Blogging-Software verwendet haben, gibt es auch einige neue Konzepte und Funktionen, die den meisten Menschen noch nicht begegnet sind. Einige der neuen Ideen hängen mit der dezentralen Natur des Grids zusammen, andere mit dem fortschrittlichen Berechtigungssystem, das zum Schutz Ihrer Daten notwendig ist. Dieser Leitfaden soll Ihnen helfen zu verstehen, wie Sie Ihre nomadische Identität erstellen, konfigurieren und nutzen können. \ No newline at end of file
diff --git a/doc/de/member/permissions.md b/doc/de/member/permissions.md
new file mode 100644
index 000000000..f5d9fb33c
--- /dev/null
+++ b/doc/de/member/permissions.md
@@ -0,0 +1,7 @@
+### Berechtigungen
+
+Berechtigungen sind ein Kernelement von Hubzilla. Sie erlauben sehr fein abgestufte Möglichkeiten, Inhalte zugänglich zu machen, zu verbergen oder in der Nutzung zu beschränken. So dienen sie auch dazu, Direktnachrichten möglich zu machen, indem über Berechtigungen festgelegt wird, wer den Beitrag (nichts anderes sind Direktnachrichten) sehen kann, und wer nicht.
+
+#include doc/de/member/permissions_content.md;
+#include doc/de/member/permissions_channel_roles.md;
+#include doc/de/member/permissions_contact_roles.md;
diff --git a/doc/de/member/permissions_channel_roles.md b/doc/de/member/permissions_channel_roles.md
new file mode 100644
index 000000000..db0cbe062
--- /dev/null
+++ b/doc/de/member/permissions_channel_roles.md
@@ -0,0 +1,45 @@
+#### Berechtigungen - Benutzerdefinierte Kanalrollen
+
+[Kanal-Rollen](/help/de/member/channel_roles.md) legen fest, welche Rechte bei der Interaktion mit einem Kanal gegeben sind. Sie sind unter "Einstellungen" → "Kanal Einstellungen" erreichbar.
+
+Hier kann die Rolle für einen Kanal festgelegt werden. Kanal-Rollen haben auch Einfluss auf Kontakt-Rollen, weil individuelle Rechte, die von den Kanal-Rollen vorgegeben sind und vererbt werden, dort die eigenen Einstellungen überschreiben.
+
+Für eine wirklich individuelle Anpassung der Rollenberechtigungen Ihres Kanals, müssen Sie als Kanal-Rolle "Benutzerdefiniert" auswählen. Die anderen Rollen ("Öffentlich", "Persönlich", "Community Forum") sind vordefinierte Berechtigungsrollen (siehe: [Kanal-Rollen](/help/de/member/channel_roles.md)).
+
+Mit den benutzerdefinierten Kanal-Rollen können Sie für folgende Interaktionen festlegen, wer sie wie ausüben kann:
+
+- Kann meinen Kanal-Stream und meine Beiträge sehen
+- Kann mir die Beiträge aus seinem Kanal schicken
+- Kann mein Standardprofil sehen
+- Kann meine Verbindungen sehen
+- Kann meine Datei- und Bilderordner sehen
+- Kann in meine Datei- und Bilderordner hochladen/ändern
+- Kann die Webseiten meines Kanals sehen
+- Kann meine Wiki-Seiten sehen
+- Kann Webseiten in meinem Kanal erstellen/ändern
+- Kann meine Wiki-Seiten bearbeiten
+- Kann auf meiner Kanal-Seite ("wall") Beiträge veröffentlichen
+- Kann mir direkte Nachrichten schicken
+- Kann Profile und Profilsachen mögen/nicht mögen
+- Kann mit mir chatten
+- Kann meine öffentlichen Beiträge in anderen Kanälen zitieren/spiegeln
+- Kann meinen Kanal administrieren
+
+Für diese Interaktionen stehen dann folgende Berechtigungen zur Wahl:
+
+- Nur ich
+- Nur die, denen Du es explizit erlaubst
+- Angenommene Verbindungen
+- Beliebige Verbindungen
+- Jeder auf dieser Webseite
+- Alle Hubzilla-Mitglieder
+- Jeder authentifizierte
+- Jeder im Internet
+
+Um die **benutzerdefinierte Rolle** zu bearbeiten, wählt man in den Einstellungen den Punkt "Privacy-Einstellungen". Rechts unten findet man den Button "Benutzerdefinierte Konfiguration der Channel Role". Klickt man darauf, erscheint ein Warn-Dialog, der auf die Risiken einer fehlerhaften Konfiguration aufmerksam macht. Bestätigt man, dass man die Rechte bearbeiten möchte, öffnet sich der Einstellungs-Dialog für die benutzerdefinierten Rollenrechte.
+
+----
+
+**<u>Wichtiger Hinweis:</u>**
+
+Die benutzerdefinierten Rollen sollten mit Bedacht eingestellt werden und bergen die Gefahr, dass sich bei bestimmten Konfigurationen der Kanal nicht mehr wie gewünscht verhält. \ No newline at end of file
diff --git a/doc/de/member/permissions_contact_roles.md b/doc/de/member/permissions_contact_roles.md
new file mode 100644
index 000000000..fae1981ae
--- /dev/null
+++ b/doc/de/member/permissions_contact_roles.md
@@ -0,0 +1,17 @@
+#### Berechtigungen - Kontaktrollen
+
+Kontakt-Rollen dient dazu Rollen (also eine Sammlung von Rechten und Möglichkeiten) für Kontakte zu erstellen. Diese Rollen können dann einem Kontakt oder allen Kontakten einer Privacy Gruppe (nicht der Gruppe selbst) zugewiesen werden. Damit werden die Möglichkeiten von Kontakten eingeschränkt oder erweitert.
+
+Mittels der App "Kontakt-Rollen" kann man Rollen, die den [Kanal-Rollen](/help/de/member/permissions_channel_roles.md) entsprechen, zusammenstellen. Im Kontakt-Editor oder im Privacy Gruppen Editor kann dann diese Berechtigungsrolle einzelnen Kontakten oder allen Kontakten einer Privacy Gruppe zuweisen.
+
+Jeder Kanal verfügt nach Erstellung automatisch über die Kontakt-Rolle "Standard" ("Systemrolle - nicht editierbar"). Neuen Kontakten wird automatisch diese Kontaktrolle zugewiesen (es sei denn, man legt eine eigene Kontaktrolle an, ändert diese Standard-Einstellung und weist neuen Kontakten künftig die neue, eigene Rolle zu). Die Standard-Kontaktrolle umfasst Berechtigungen aufgrund der gewählten Kanal-Rolle. Zusätzlich zu den durch die Kanal-Rolle gewährten Rechte, werden noch einige andere Rechte eingeräumt, damit sich der Kanal so verhält, wie man es aufgrund der Kanal-Rolle erwarten würde (so entspricht "Öffentlich" am ehesten einem "normalen" Social-Network-Kanal).
+
+![croles1](/help/de/member/pic/croles1.png)
+
+<u>Beachte:</u> Etliche der Rechte einer Kanalrolle (ob Standard oder selbst erstellt) werden von der Kanalrolle geerbt. Diese Rechte können in der Kontakt-Rolle nicht wieder entzogen werden. Es handelt sich bei der Kontakt-Rolle um eine Whitelist, bei welcher nur zusätzliche Rechte gewährt werden können.
+
+Im [Verbindungs-Editor](/help/de/member/connection_editor.md) kann man einem Kontakt eine Kontakt-Rolle zuweisen. Dieser Dialog erscheint auch, wenn man einen neuen Kontakt zufügt. Als Standard ist hier dann die Kontakt-Rolle ausgewählt, bei welcher man den Schalter "Neuen Kontakten automatisch diese Rolle zuweisen" eingeschaltet hat.
+
+![croles2](/help/de/member/pic/croles2.png)
+
+Bei Kanälen ohne selbst definierte Kontakt-Rollen ist dies immer "Standard". Im Verbindungs-Editor befindet sich auch ein Button "Contact Roles" mit welchem man zum Kontaktrollen-Editor gelangt, falls man für den Kontakt eine neue Kontaktrolle erstellen möchte.
diff --git a/doc/de/member/permissions_content.md b/doc/de/member/permissions_content.md
new file mode 100644
index 000000000..dc16477d9
--- /dev/null
+++ b/doc/de/member/permissions_content.md
@@ -0,0 +1,38 @@
+#### Berechtigungen für Inhalte
+
+Wenn Sie Inhalte bei Hubzilla teilen, also Postings veröffentlichen, Bilder oder Texte hochladen, Termine im Kalender eintragen, können Sie ganz exakt festlegen, wer Zugriff auf diese Inhalte hat.
+
+Die Berechtigungs-Einstellungen für Inhalte erreichen Sie über einen Button ("Privacy Tool") mit einem Vorhangschloss-Symbol 🔒bzw.🔓.
+
+Für das Teilen von Beiträgen: ![Beitragsberechtigung](/help/de/member/pic/inhber01.png)
+
+Für das Erstellen von Ordnern/Verzeichnissen im Cloud-Speicher: ![Ordnerberechtigung](/help/de/member/pic/inhber02.png)
+
+Für das Hochladen von Dateien: ![Dateiberechtigung](/help/de/member/pic/inhber03.png)
+
+Für das Teilen von Terminen/Ereignissen: ![Terminberechtigung](/help/de/member/pic/inhber04.png)
+
+Auch für Webseiten, Wikiseiten und verschiedene andere Inhalte gibt es entsprechende Einstellungsmöglichkeiten für die Berechtigung.
+
+Wenn Sie auf das Symbol klicken, öffnet sich der Berechtigungsdialog, mit welchem Sie die Berechtigung für andere Nutzer festlegen können (hier geht es in der Regel um die Sichtbarkeit der Inhalte).
+
+![Berechtigungsdialog](/help/de/member/pic/inhber05.png)
+
+Sie haben die Wahl zwischen
+
+- **Öffentlich** - Wie die Bezeichnung schon nahelegt, ist der Inhalt für jeden im Internet sichtbar. Also selbst für Nutzer, die gar keinen Fediverse-Dienst nutzen.
+
+- **Nur ich** - Hier kann nur der erstellende Nutzer den Inhalt sehen. Sie "teilen" ihn mit sich selbst.
+
+- **Privacy Gruppen** - Der Inhalt ist für alle Nutzer sichtbar, die sich in einer Ihrer Privacy Gruppen befinden.
+
+- **Benutzerdefinierte Auswahl** - Hier kann exakt festgelegt werden, wer den Inhalt sehen kann. Es sind auch Kobinationen von Privacy Gruppen und einzelnen Kontakten möglich, indem man bei dem jeweiligen Eintrag "Erlauben" oder "Verweigern" anwählt.
+
+ ![Benutzerdefiniert](/help/de/member/pic/inhber06.png)
+
+----
+
+<u>**Wichtiger Hinweis:**</u>
+
+Die einmal festgelegten Berechtigungen für Postings können nachträglich nicht mehr geändert werden! Ein Posting wird sofort an eine nicht wirklich bestimmbare Zahl von anderen Servern verteilt, so dass Berechtigungen nachträglich nicht erteilt oder entzogen werden können.Die Berechtigungen für andere Inhalte, wie Dateien, Bilder etc. können hingegen nachträglich bearbeitet werden, weil diese Inhalte nur auf der eigenen Instanz (Hub) gespeichert werden und beim Teilen lediglich die Referenz zum Inhalt weitergegeben wird.
+
diff --git a/doc/de/member/photos.md b/doc/de/member/photos.md
new file mode 100644
index 000000000..a52c23017
--- /dev/null
+++ b/doc/de/member/photos.md
@@ -0,0 +1,17 @@
+#### Fotos
+
+Die App "Fotos" ist eine spezielle Verwaltung/Ansicht für in die eigene Cloud hochgeladene Bilder. Es werden Vorschaubilder, statt Kacheln oder Dateibezeichnungen angezeigt, was das Auffinden bestimmter Bilder einfacher macht.
+
+![Foto-App](/help/de/member/pic/fotos01.png)
+
+Ein Klick auf das Bild führt zur Bildansicht. Hier gibt es zwei Steuerungssymbole, um zum nächsten oder vorherigen Foto zu wechseln.
+
+![Foto-App Steuerung](/help/de/member/pic/fotos02.png)
+
+Außerdem gibt es einen Button "Fotowerkzeuge", der es mittels eines Menüs möglich macht, Das Bild als Profilbild oder Banner festzulegen, sowie das Bild zu bearbeiten.
+
+![Foto-App Bearbeiten](/help/de/member/pic/fotos03.png)
+
+Ein weiterer Klick auf das Bild führt zur Vollansicht.
+
+![Foto-App Volansicht](/help/de/member/pic/fotos04.png)
diff --git a/doc/de/member/pic/anzeinst01.png b/doc/de/member/pic/anzeinst01.png
new file mode 100644
index 000000000..0ed30700a
--- /dev/null
+++ b/doc/de/member/pic/anzeinst01.png
Binary files differ
diff --git a/doc/de/member/pic/anzeinst02.png b/doc/de/member/pic/anzeinst02.png
new file mode 100644
index 000000000..00c7369ea
--- /dev/null
+++ b/doc/de/member/pic/anzeinst02.png
Binary files differ
diff --git a/doc/de/member/pic/anzeinst03.png b/doc/de/member/pic/anzeinst03.png
new file mode 100644
index 000000000..fe82a0a0f
--- /dev/null
+++ b/doc/de/member/pic/anzeinst03.png
Binary files differ
diff --git a/doc/de/member/pic/anzeinst04.png b/doc/de/member/pic/anzeinst04.png
new file mode 100644
index 000000000..6c0e36d75
--- /dev/null
+++ b/doc/de/member/pic/anzeinst04.png
Binary files differ
diff --git a/doc/de/member/pic/apps01.png b/doc/de/member/pic/apps01.png
new file mode 100644
index 000000000..cd36e7f3a
--- /dev/null
+++ b/doc/de/member/pic/apps01.png
Binary files differ
diff --git a/doc/de/member/pic/apps02.png b/doc/de/member/pic/apps02.png
new file mode 100644
index 000000000..99c8211d3
--- /dev/null
+++ b/doc/de/member/pic/apps02.png
Binary files differ
diff --git a/doc/de/member/pic/apps03.png b/doc/de/member/pic/apps03.png
new file mode 100644
index 000000000..3db58133c
--- /dev/null
+++ b/doc/de/member/pic/apps03.png
Binary files differ
diff --git a/doc/de/member/pic/apps04.png b/doc/de/member/pic/apps04.png
new file mode 100644
index 000000000..adc8ca5e9
--- /dev/null
+++ b/doc/de/member/pic/apps04.png
Binary files differ
diff --git a/doc/de/member/pic/apps05.png b/doc/de/member/pic/apps05.png
new file mode 100644
index 000000000..7dcf8f69c
--- /dev/null
+++ b/doc/de/member/pic/apps05.png
Binary files differ
diff --git a/doc/de/member/pic/apps06.png b/doc/de/member/pic/apps06.png
new file mode 100644
index 000000000..4662aef0b
--- /dev/null
+++ b/doc/de/member/pic/apps06.png
Binary files differ
diff --git a/doc/de/member/pic/apps07.png b/doc/de/member/pic/apps07.png
new file mode 100644
index 000000000..e32fb4bb9
--- /dev/null
+++ b/doc/de/member/pic/apps07.png
Binary files differ
diff --git a/doc/de/member/pic/apps08.png b/doc/de/member/pic/apps08.png
new file mode 100644
index 000000000..db3c0c8c8
--- /dev/null
+++ b/doc/de/member/pic/apps08.png
Binary files differ
diff --git a/doc/de/member/pic/artikel01.png b/doc/de/member/pic/artikel01.png
new file mode 100644
index 000000000..f92f00eb3
--- /dev/null
+++ b/doc/de/member/pic/artikel01.png
Binary files differ
diff --git a/doc/de/member/pic/artikel02.png b/doc/de/member/pic/artikel02.png
new file mode 100644
index 000000000..60a4902cd
--- /dev/null
+++ b/doc/de/member/pic/artikel02.png
Binary files differ
diff --git a/doc/de/member/pic/artikel03.png b/doc/de/member/pic/artikel03.png
new file mode 100644
index 000000000..21ecfccec
--- /dev/null
+++ b/doc/de/member/pic/artikel03.png
Binary files differ
diff --git a/doc/de/member/pic/artikel04.png b/doc/de/member/pic/artikel04.png
new file mode 100644
index 000000000..514722303
--- /dev/null
+++ b/doc/de/member/pic/artikel04.png
Binary files differ
diff --git a/doc/de/member/pic/artikel05.png b/doc/de/member/pic/artikel05.png
new file mode 100644
index 000000000..feb6b44f9
--- /dev/null
+++ b/doc/de/member/pic/artikel05.png
Binary files differ
diff --git a/doc/de/member/pic/author.png b/doc/de/member/pic/author.png
new file mode 100644
index 000000000..75ec9e636
--- /dev/null
+++ b/doc/de/member/pic/author.png
Binary files differ
diff --git a/doc/de/member/pic/bild01.png b/doc/de/member/pic/bild01.png
new file mode 100644
index 000000000..b6c9df8f0
--- /dev/null
+++ b/doc/de/member/pic/bild01.png
Binary files differ
diff --git a/doc/de/member/pic/bild02.png b/doc/de/member/pic/bild02.png
new file mode 100644
index 000000000..35fd8b549
--- /dev/null
+++ b/doc/de/member/pic/bild02.png
Binary files differ
diff --git a/doc/de/member/pic/bild03.png b/doc/de/member/pic/bild03.png
new file mode 100644
index 000000000..ea47a710c
--- /dev/null
+++ b/doc/de/member/pic/bild03.png
Binary files differ
diff --git a/doc/de/member/pic/bild04.png b/doc/de/member/pic/bild04.png
new file mode 100644
index 000000000..b5ad1eaf6
--- /dev/null
+++ b/doc/de/member/pic/bild04.png
Binary files differ
diff --git a/doc/de/member/pic/bild05.png b/doc/de/member/pic/bild05.png
new file mode 100644
index 000000000..b2be326c2
--- /dev/null
+++ b/doc/de/member/pic/bild05.png
Binary files differ
diff --git a/doc/de/member/pic/bild06.png b/doc/de/member/pic/bild06.png
new file mode 100644
index 000000000..f7020dc1e
--- /dev/null
+++ b/doc/de/member/pic/bild06.png
Binary files differ
diff --git a/doc/de/member/pic/bookm01.png b/doc/de/member/pic/bookm01.png
new file mode 100644
index 000000000..97cdcab11
--- /dev/null
+++ b/doc/de/member/pic/bookm01.png
Binary files differ
diff --git a/doc/de/member/pic/bookm02.png b/doc/de/member/pic/bookm02.png
new file mode 100644
index 000000000..b57aa435e
--- /dev/null
+++ b/doc/de/member/pic/bookm02.png
Binary files differ
diff --git a/doc/de/member/pic/bookm03.png b/doc/de/member/pic/bookm03.png
new file mode 100644
index 000000000..69ddbff7a
--- /dev/null
+++ b/doc/de/member/pic/bookm03.png
Binary files differ
diff --git a/doc/de/member/pic/carddav01.png b/doc/de/member/pic/carddav01.png
new file mode 100644
index 000000000..174c57c92
--- /dev/null
+++ b/doc/de/member/pic/carddav01.png
Binary files differ
diff --git a/doc/de/member/pic/carddav02.png b/doc/de/member/pic/carddav02.png
new file mode 100644
index 000000000..af1de5720
--- /dev/null
+++ b/doc/de/member/pic/carddav02.png
Binary files differ
diff --git a/doc/de/member/pic/carddav03.png b/doc/de/member/pic/carddav03.png
new file mode 100644
index 000000000..dbbd0028a
--- /dev/null
+++ b/doc/de/member/pic/carddav03.png
Binary files differ
diff --git a/doc/de/member/pic/carddav04.png b/doc/de/member/pic/carddav04.png
new file mode 100644
index 000000000..5ce71e5c9
--- /dev/null
+++ b/doc/de/member/pic/carddav04.png
Binary files differ
diff --git a/doc/de/member/pic/carddav05.png b/doc/de/member/pic/carddav05.png
new file mode 100644
index 000000000..9292cb5e9
--- /dev/null
+++ b/doc/de/member/pic/carddav05.png
Binary files differ
diff --git a/doc/de/member/pic/carddav06.png b/doc/de/member/pic/carddav06.png
new file mode 100644
index 000000000..a329be64b
--- /dev/null
+++ b/doc/de/member/pic/carddav06.png
Binary files differ
diff --git a/doc/de/member/pic/carddav07.png b/doc/de/member/pic/carddav07.png
new file mode 100644
index 000000000..828a9aa71
--- /dev/null
+++ b/doc/de/member/pic/carddav07.png
Binary files differ
diff --git a/doc/de/member/pic/center.png b/doc/de/member/pic/center.png
new file mode 100644
index 000000000..c3ce7b125
--- /dev/null
+++ b/doc/de/member/pic/center.png
Binary files differ
diff --git a/doc/de/member/pic/chat01.png b/doc/de/member/pic/chat01.png
new file mode 100644
index 000000000..1ed934cac
--- /dev/null
+++ b/doc/de/member/pic/chat01.png
Binary files differ
diff --git a/doc/de/member/pic/chat02.png b/doc/de/member/pic/chat02.png
new file mode 100644
index 000000000..070d6d544
--- /dev/null
+++ b/doc/de/member/pic/chat02.png
Binary files differ
diff --git a/doc/de/member/pic/chat03.png b/doc/de/member/pic/chat03.png
new file mode 100644
index 000000000..e92c19272
--- /dev/null
+++ b/doc/de/member/pic/chat03.png
Binary files differ
diff --git a/doc/de/member/pic/code.png b/doc/de/member/pic/code.png
new file mode 100644
index 000000000..2bed8e01a
--- /dev/null
+++ b/doc/de/member/pic/code.png
Binary files differ
diff --git a/doc/de/member/pic/contfilter01.png b/doc/de/member/pic/contfilter01.png
new file mode 100644
index 000000000..e6510c75e
--- /dev/null
+++ b/doc/de/member/pic/contfilter01.png
Binary files differ
diff --git a/doc/de/member/pic/contfilter02.png b/doc/de/member/pic/contfilter02.png
new file mode 100644
index 000000000..84c7726d2
--- /dev/null
+++ b/doc/de/member/pic/contfilter02.png
Binary files differ
diff --git a/doc/de/member/pic/croles1.png b/doc/de/member/pic/croles1.png
new file mode 100644
index 000000000..d1d6d6fed
--- /dev/null
+++ b/doc/de/member/pic/croles1.png
Binary files differ
diff --git a/doc/de/member/pic/croles2.png b/doc/de/member/pic/croles2.png
new file mode 100644
index 000000000..39f5c0d4f
--- /dev/null
+++ b/doc/de/member/pic/croles2.png
Binary files differ
diff --git a/doc/de/member/pic/dateien01.png b/doc/de/member/pic/dateien01.png
new file mode 100644
index 000000000..bd5792ce8
--- /dev/null
+++ b/doc/de/member/pic/dateien01.png
Binary files differ
diff --git a/doc/de/member/pic/dateien02.png b/doc/de/member/pic/dateien02.png
new file mode 100644
index 000000000..1891bbdf5
--- /dev/null
+++ b/doc/de/member/pic/dateien02.png
Binary files differ
diff --git a/doc/de/member/pic/dateien03.png b/doc/de/member/pic/dateien03.png
new file mode 100644
index 000000000..6a9cd8f49
--- /dev/null
+++ b/doc/de/member/pic/dateien03.png
Binary files differ
diff --git a/doc/de/member/pic/einst01.png b/doc/de/member/pic/einst01.png
new file mode 100644
index 000000000..aa3855cc4
--- /dev/null
+++ b/doc/de/member/pic/einst01.png
Binary files differ
diff --git a/doc/de/member/pic/einst02.png b/doc/de/member/pic/einst02.png
new file mode 100644
index 000000000..c2661f3f0
--- /dev/null
+++ b/doc/de/member/pic/einst02.png
Binary files differ
diff --git a/doc/de/member/pic/einst03.png b/doc/de/member/pic/einst03.png
new file mode 100644
index 000000000..2409c006d
--- /dev/null
+++ b/doc/de/member/pic/einst03.png
Binary files differ
diff --git a/doc/de/member/pic/feateinst01.png b/doc/de/member/pic/feateinst01.png
new file mode 100644
index 000000000..133c3543a
--- /dev/null
+++ b/doc/de/member/pic/feateinst01.png
Binary files differ
diff --git a/doc/de/member/pic/feateinst02.png b/doc/de/member/pic/feateinst02.png
new file mode 100644
index 000000000..317d2704d
--- /dev/null
+++ b/doc/de/member/pic/feateinst02.png
Binary files differ
diff --git a/doc/de/member/pic/feateinst03.png b/doc/de/member/pic/feateinst03.png
new file mode 100644
index 000000000..860fd3802
--- /dev/null
+++ b/doc/de/member/pic/feateinst03.png
Binary files differ
diff --git a/doc/de/member/pic/feateinst04.png b/doc/de/member/pic/feateinst04.png
new file mode 100644
index 000000000..ea5235fd8
--- /dev/null
+++ b/doc/de/member/pic/feateinst04.png
Binary files differ
diff --git a/doc/de/member/pic/feateinst05.png b/doc/de/member/pic/feateinst05.png
new file mode 100644
index 000000000..3aacf571a
--- /dev/null
+++ b/doc/de/member/pic/feateinst05.png
Binary files differ
diff --git a/doc/de/member/pic/feateinst06.png b/doc/de/member/pic/feateinst06.png
new file mode 100644
index 000000000..0de2cc05a
--- /dev/null
+++ b/doc/de/member/pic/feateinst06.png
Binary files differ
diff --git a/doc/de/member/pic/feateinst07.png b/doc/de/member/pic/feateinst07.png
new file mode 100644
index 000000000..434c98ffe
--- /dev/null
+++ b/doc/de/member/pic/feateinst07.png
Binary files differ
diff --git a/doc/de/member/pic/feateinst08.png b/doc/de/member/pic/feateinst08.png
new file mode 100644
index 000000000..1dd96e460
--- /dev/null
+++ b/doc/de/member/pic/feateinst08.png
Binary files differ
diff --git a/doc/de/member/pic/feateinst09.png b/doc/de/member/pic/feateinst09.png
new file mode 100644
index 000000000..8babe1599
--- /dev/null
+++ b/doc/de/member/pic/feateinst09.png
Binary files differ
diff --git a/doc/de/member/pic/feateinst10.png b/doc/de/member/pic/feateinst10.png
new file mode 100644
index 000000000..12a87d72a
--- /dev/null
+++ b/doc/de/member/pic/feateinst10.png
Binary files differ
diff --git a/doc/de/member/pic/feateinst11.png b/doc/de/member/pic/feateinst11.png
new file mode 100644
index 000000000..26d1d371f
--- /dev/null
+++ b/doc/de/member/pic/feateinst11.png
Binary files differ
diff --git a/doc/de/member/pic/font.png b/doc/de/member/pic/font.png
new file mode 100644
index 000000000..598448ce7
--- /dev/null
+++ b/doc/de/member/pic/font.png
Binary files differ
diff --git a/doc/de/member/pic/fotos01.png b/doc/de/member/pic/fotos01.png
new file mode 100644
index 000000000..2a617e74a
--- /dev/null
+++ b/doc/de/member/pic/fotos01.png
Binary files differ
diff --git a/doc/de/member/pic/fotos02.png b/doc/de/member/pic/fotos02.png
new file mode 100644
index 000000000..ead6a7b82
--- /dev/null
+++ b/doc/de/member/pic/fotos02.png
Binary files differ
diff --git a/doc/de/member/pic/fotos03.png b/doc/de/member/pic/fotos03.png
new file mode 100644
index 000000000..541ebecb4
--- /dev/null
+++ b/doc/de/member/pic/fotos03.png
Binary files differ
diff --git a/doc/de/member/pic/fotos04.png b/doc/de/member/pic/fotos04.png
new file mode 100644
index 000000000..2f180346c
--- /dev/null
+++ b/doc/de/member/pic/fotos04.png
Binary files differ
diff --git a/doc/de/member/pic/galerie01.png b/doc/de/member/pic/galerie01.png
new file mode 100644
index 000000000..6746cc154
--- /dev/null
+++ b/doc/de/member/pic/galerie01.png
Binary files differ
diff --git a/doc/de/member/pic/hbar.png b/doc/de/member/pic/hbar.png
new file mode 100644
index 000000000..10594f231
--- /dev/null
+++ b/doc/de/member/pic/hbar.png
Binary files differ
diff --git a/doc/de/member/pic/highlited.png b/doc/de/member/pic/highlited.png
new file mode 100644
index 000000000..4dc0224f1
--- /dev/null
+++ b/doc/de/member/pic/highlited.png
Binary files differ
diff --git a/doc/de/member/pic/image.png b/doc/de/member/pic/image.png
new file mode 100644
index 000000000..ecb300cd2
--- /dev/null
+++ b/doc/de/member/pic/image.png
Binary files differ
diff --git a/doc/de/member/pic/inhber01.png b/doc/de/member/pic/inhber01.png
new file mode 100644
index 000000000..03f7f7113
--- /dev/null
+++ b/doc/de/member/pic/inhber01.png
Binary files differ
diff --git a/doc/de/member/pic/inhber02.png b/doc/de/member/pic/inhber02.png
new file mode 100644
index 000000000..d302f8730
--- /dev/null
+++ b/doc/de/member/pic/inhber02.png
Binary files differ
diff --git a/doc/de/member/pic/inhber03.png b/doc/de/member/pic/inhber03.png
new file mode 100644
index 000000000..5910fbb44
--- /dev/null
+++ b/doc/de/member/pic/inhber03.png
Binary files differ
diff --git a/doc/de/member/pic/inhber04.png b/doc/de/member/pic/inhber04.png
new file mode 100644
index 000000000..e77d8e27a
--- /dev/null
+++ b/doc/de/member/pic/inhber04.png
Binary files differ
diff --git a/doc/de/member/pic/inhber05.png b/doc/de/member/pic/inhber05.png
new file mode 100644
index 000000000..a407f2d92
--- /dev/null
+++ b/doc/de/member/pic/inhber05.png
Binary files differ
diff --git a/doc/de/member/pic/inhber06.png b/doc/de/member/pic/inhber06.png
new file mode 100644
index 000000000..9b740ffd1
--- /dev/null
+++ b/doc/de/member/pic/inhber06.png
Binary files differ
diff --git a/doc/de/member/pic/interag01.png b/doc/de/member/pic/interag01.png
new file mode 100644
index 000000000..3a0eeac65
--- /dev/null
+++ b/doc/de/member/pic/interag01.png
Binary files differ
diff --git a/doc/de/member/pic/interag02.png b/doc/de/member/pic/interag02.png
new file mode 100644
index 000000000..94cd80cad
--- /dev/null
+++ b/doc/de/member/pic/interag02.png
Binary files differ
diff --git a/doc/de/member/pic/interag03.png b/doc/de/member/pic/interag03.png
new file mode 100644
index 000000000..c97f4eaab
--- /dev/null
+++ b/doc/de/member/pic/interag03.png
Binary files differ
diff --git a/doc/de/member/pic/kalender01.png b/doc/de/member/pic/kalender01.png
new file mode 100644
index 000000000..8fbd2a75a
--- /dev/null
+++ b/doc/de/member/pic/kalender01.png
Binary files differ
diff --git a/doc/de/member/pic/kalender02.png b/doc/de/member/pic/kalender02.png
new file mode 100644
index 000000000..f09858820
--- /dev/null
+++ b/doc/de/member/pic/kalender02.png
Binary files differ
diff --git a/doc/de/member/pic/kalender03.png b/doc/de/member/pic/kalender03.png
new file mode 100644
index 000000000..88009d2f7
--- /dev/null
+++ b/doc/de/member/pic/kalender03.png
Binary files differ
diff --git a/doc/de/member/pic/kalender04.png b/doc/de/member/pic/kalender04.png
new file mode 100644
index 000000000..1d91f90d9
--- /dev/null
+++ b/doc/de/member/pic/kalender04.png
Binary files differ
diff --git a/doc/de/member/pic/kalender05.png b/doc/de/member/pic/kalender05.png
new file mode 100644
index 000000000..965e43e7c
--- /dev/null
+++ b/doc/de/member/pic/kalender05.png
Binary files differ
diff --git a/doc/de/member/pic/kaneinst01.png b/doc/de/member/pic/kaneinst01.png
new file mode 100644
index 000000000..943cb1bf5
--- /dev/null
+++ b/doc/de/member/pic/kaneinst01.png
Binary files differ
diff --git a/doc/de/member/pic/kaneinst02.png b/doc/de/member/pic/kaneinst02.png
new file mode 100644
index 000000000..5ef0f309f
--- /dev/null
+++ b/doc/de/member/pic/kaneinst02.png
Binary files differ
diff --git a/doc/de/member/pic/kanloe.png b/doc/de/member/pic/kanloe.png
new file mode 100644
index 000000000..0cf340d88
--- /dev/null
+++ b/doc/de/member/pic/kanloe.png
Binary files differ
diff --git a/doc/de/member/pic/klone01.png b/doc/de/member/pic/klone01.png
new file mode 100644
index 000000000..33092c473
--- /dev/null
+++ b/doc/de/member/pic/klone01.png
Binary files differ
diff --git a/doc/de/member/pic/klone02.png b/doc/de/member/pic/klone02.png
new file mode 100644
index 000000000..648f5d431
--- /dev/null
+++ b/doc/de/member/pic/klone02.png
Binary files differ
diff --git a/doc/de/member/pic/klone03.png b/doc/de/member/pic/klone03.png
new file mode 100644
index 000000000..5d05b08cb
--- /dev/null
+++ b/doc/de/member/pic/klone03.png
Binary files differ
diff --git a/doc/de/member/pic/klone04.png b/doc/de/member/pic/klone04.png
new file mode 100644
index 000000000..a4347a608
--- /dev/null
+++ b/doc/de/member/pic/klone04.png
Binary files differ
diff --git a/doc/de/member/pic/klone05.png b/doc/de/member/pic/klone05.png
new file mode 100644
index 000000000..49c08b4ef
--- /dev/null
+++ b/doc/de/member/pic/klone05.png
Binary files differ
diff --git a/doc/de/member/pic/klone06.png b/doc/de/member/pic/klone06.png
new file mode 100644
index 000000000..0d3ad85d7
--- /dev/null
+++ b/doc/de/member/pic/klone06.png
Binary files differ
diff --git a/doc/de/member/pic/kloneinst01.png b/doc/de/member/pic/kloneinst01.png
new file mode 100644
index 000000000..f87a25b00
--- /dev/null
+++ b/doc/de/member/pic/kloneinst01.png
Binary files differ
diff --git a/doc/de/member/pic/kmerkmale.png b/doc/de/member/pic/kmerkmale.png
new file mode 100644
index 000000000..f9d603f5c
--- /dev/null
+++ b/doc/de/member/pic/kmerkmale.png
Binary files differ
diff --git a/doc/de/member/pic/kommentieren.png b/doc/de/member/pic/kommentieren.png
new file mode 100644
index 000000000..1f8bb790e
--- /dev/null
+++ b/doc/de/member/pic/kommentieren.png
Binary files differ
diff --git a/doc/de/member/pic/ktoeinst01.png b/doc/de/member/pic/ktoeinst01.png
new file mode 100644
index 000000000..4081e7b58
--- /dev/null
+++ b/doc/de/member/pic/ktoeinst01.png
Binary files differ
diff --git a/doc/de/member/pic/ktoloe.png b/doc/de/member/pic/ktoloe.png
new file mode 100644
index 000000000..51e82e7b7
--- /dev/null
+++ b/doc/de/member/pic/ktoloe.png
Binary files differ
diff --git a/doc/de/member/pic/mauth.png b/doc/de/member/pic/mauth.png
new file mode 100644
index 000000000..5e624124b
--- /dev/null
+++ b/doc/de/member/pic/mauth.png
Binary files differ
diff --git a/doc/de/member/pic/nsfw01.png b/doc/de/member/pic/nsfw01.png
new file mode 100644
index 000000000..17509786f
--- /dev/null
+++ b/doc/de/member/pic/nsfw01.png
Binary files differ
diff --git a/doc/de/member/pic/nsfw02.png b/doc/de/member/pic/nsfw02.png
new file mode 100644
index 000000000..9b5285a27
--- /dev/null
+++ b/doc/de/member/pic/nsfw02.png
Binary files differ
diff --git a/doc/de/member/pic/nsfw03.png b/doc/de/member/pic/nsfw03.png
new file mode 100644
index 000000000..6777a5b5a
--- /dev/null
+++ b/doc/de/member/pic/nsfw03.png
Binary files differ
diff --git a/doc/de/member/pic/nsfw04.png b/doc/de/member/pic/nsfw04.png
new file mode 100644
index 000000000..578776870
--- /dev/null
+++ b/doc/de/member/pic/nsfw04.png
Binary files differ
diff --git a/doc/de/member/pic/ordner01.png b/doc/de/member/pic/ordner01.png
new file mode 100644
index 000000000..3c5c5d181
--- /dev/null
+++ b/doc/de/member/pic/ordner01.png
Binary files differ
diff --git a/doc/de/member/pic/ordner02.png b/doc/de/member/pic/ordner02.png
new file mode 100644
index 000000000..d7340c2a1
--- /dev/null
+++ b/doc/de/member/pic/ordner02.png
Binary files differ
diff --git a/doc/de/member/pic/ordner03.png b/doc/de/member/pic/ordner03.png
new file mode 100644
index 000000000..81577e40a
--- /dev/null
+++ b/doc/de/member/pic/ordner03.png
Binary files differ
diff --git a/doc/de/member/pic/ordner04.png b/doc/de/member/pic/ordner04.png
new file mode 100644
index 000000000..f20440b85
--- /dev/null
+++ b/doc/de/member/pic/ordner04.png
Binary files differ
diff --git a/doc/de/member/pic/pgroups01.png b/doc/de/member/pic/pgroups01.png
new file mode 100644
index 000000000..2c2654f40
--- /dev/null
+++ b/doc/de/member/pic/pgroups01.png
Binary files differ
diff --git a/doc/de/member/pic/pgroups02.png b/doc/de/member/pic/pgroups02.png
new file mode 100644
index 000000000..0a35c3ded
--- /dev/null
+++ b/doc/de/member/pic/pgroups02.png
Binary files differ
diff --git a/doc/de/member/pic/pgroups03.png b/doc/de/member/pic/pgroups03.png
new file mode 100644
index 000000000..fba19a8cc
--- /dev/null
+++ b/doc/de/member/pic/pgroups03.png
Binary files differ
diff --git a/doc/de/member/pic/priveinst01.png b/doc/de/member/pic/priveinst01.png
new file mode 100644
index 000000000..bdf645d07
--- /dev/null
+++ b/doc/de/member/pic/priveinst01.png
Binary files differ
diff --git a/doc/de/member/pic/qrcode.png b/doc/de/member/pic/qrcode.png
new file mode 100644
index 000000000..0db383feb
--- /dev/null
+++ b/doc/de/member/pic/qrcode.png
Binary files differ
diff --git a/doc/de/member/pic/quellcode.png b/doc/de/member/pic/quellcode.png
new file mode 100644
index 000000000..d21df87f5
--- /dev/null
+++ b/doc/de/member/pic/quellcode.png
Binary files differ
diff --git a/doc/de/member/pic/quote.png b/doc/de/member/pic/quote.png
new file mode 100644
index 000000000..cef0df619
--- /dev/null
+++ b/doc/de/member/pic/quote.png
Binary files differ
diff --git a/doc/de/member/pic/red.png b/doc/de/member/pic/red.png
new file mode 100644
index 000000000..227f8ccda
--- /dev/null
+++ b/doc/de/member/pic/red.png
Binary files differ
diff --git a/doc/de/member/pic/size.png b/doc/de/member/pic/size.png
new file mode 100644
index 000000000..f87ab74cf
--- /dev/null
+++ b/doc/de/member/pic/size.png
Binary files differ
diff --git a/doc/de/member/pic/statusfilter.png b/doc/de/member/pic/statusfilter.png
new file mode 100644
index 000000000..3e5225535
--- /dev/null
+++ b/doc/de/member/pic/statusfilter.png
Binary files differ
diff --git a/doc/de/member/pic/stern.png b/doc/de/member/pic/stern.png
new file mode 100644
index 000000000..f289ba55d
--- /dev/null
+++ b/doc/de/member/pic/stern.png
Binary files differ
diff --git a/doc/de/member/pic/streinst01.png b/doc/de/member/pic/streinst01.png
new file mode 100644
index 000000000..79499a782
--- /dev/null
+++ b/doc/de/member/pic/streinst01.png
Binary files differ
diff --git a/doc/de/member/pic/suche01.png b/doc/de/member/pic/suche01.png
new file mode 100644
index 000000000..5c35a1e0e
--- /dev/null
+++ b/doc/de/member/pic/suche01.png
Binary files differ
diff --git a/doc/de/member/pic/suche02.png b/doc/de/member/pic/suche02.png
new file mode 100644
index 000000000..5ec69f0f0
--- /dev/null
+++ b/doc/de/member/pic/suche02.png
Binary files differ
diff --git a/doc/de/member/pic/suche03.png b/doc/de/member/pic/suche03.png
new file mode 100644
index 000000000..45373bddf
--- /dev/null
+++ b/doc/de/member/pic/suche03.png
Binary files differ
diff --git a/doc/de/member/pic/table1.png b/doc/de/member/pic/table1.png
new file mode 100644
index 000000000..99a4fb9a0
--- /dev/null
+++ b/doc/de/member/pic/table1.png
Binary files differ
diff --git a/doc/de/member/pic/table2.png b/doc/de/member/pic/table2.png
new file mode 100644
index 000000000..ed241a6d6
--- /dev/null
+++ b/doc/de/member/pic/table2.png
Binary files differ
diff --git a/doc/de/member/pic/table3.png b/doc/de/member/pic/table3.png
new file mode 100644
index 000000000..9b7acceb3
--- /dev/null
+++ b/doc/de/member/pic/table3.png
Binary files differ
diff --git a/doc/de/member/pic/teilen.png b/doc/de/member/pic/teilen.png
new file mode 100644
index 000000000..1481c9ccf
--- /dev/null
+++ b/doc/de/member/pic/teilen.png
Binary files differ
diff --git a/doc/de/member/pic/verbindungen01.png b/doc/de/member/pic/verbindungen01.png
new file mode 100644
index 000000000..519370df2
--- /dev/null
+++ b/doc/de/member/pic/verbindungen01.png
Binary files differ
diff --git a/doc/de/member/pic/verbindungen02.png b/doc/de/member/pic/verbindungen02.png
new file mode 100644
index 000000000..37333e87f
--- /dev/null
+++ b/doc/de/member/pic/verbindungen02.png
Binary files differ
diff --git a/doc/de/member/pic/verbindungen03.png b/doc/de/member/pic/verbindungen03.png
new file mode 100644
index 000000000..69e32a688
--- /dev/null
+++ b/doc/de/member/pic/verbindungen03.png
Binary files differ
diff --git a/doc/de/member/pic/verbindungen04.png b/doc/de/member/pic/verbindungen04.png
new file mode 100644
index 000000000..0b032ef3d
--- /dev/null
+++ b/doc/de/member/pic/verbindungen04.png
Binary files differ
diff --git a/doc/de/member/pic/verbindungen05.png b/doc/de/member/pic/verbindungen05.png
new file mode 100644
index 000000000..0a48cd425
--- /dev/null
+++ b/doc/de/member/pic/verbindungen05.png
Binary files differ
diff --git a/doc/de/member/pic/verbindungen06.png b/doc/de/member/pic/verbindungen06.png
new file mode 100644
index 000000000..3ac1ba628
--- /dev/null
+++ b/doc/de/member/pic/verbindungen06.png
Binary files differ
diff --git a/doc/de/member/pic/verzeichnis.png b/doc/de/member/pic/verzeichnis.png
new file mode 100644
index 000000000..397d42efb
--- /dev/null
+++ b/doc/de/member/pic/verzeichnis.png
Binary files differ
diff --git a/doc/de/member/pic/video_poster.png b/doc/de/member/pic/video_poster.png
new file mode 100644
index 000000000..f2151abd1
--- /dev/null
+++ b/doc/de/member/pic/video_poster.png
Binary files differ
diff --git a/doc/de/member/pic/webseiten01.png b/doc/de/member/pic/webseiten01.png
new file mode 100644
index 000000000..9ab33fe6e
--- /dev/null
+++ b/doc/de/member/pic/webseiten01.png
Binary files differ
diff --git a/doc/de/member/pic/webseiten02.png b/doc/de/member/pic/webseiten02.png
new file mode 100644
index 000000000..aab2db3ce
--- /dev/null
+++ b/doc/de/member/pic/webseiten02.png
Binary files differ
diff --git a/doc/de/member/pic/webseiten03.png b/doc/de/member/pic/webseiten03.png
new file mode 100644
index 000000000..424aa702e
--- /dev/null
+++ b/doc/de/member/pic/webseiten03.png
Binary files differ
diff --git a/doc/de/member/pic/webseiten04.png b/doc/de/member/pic/webseiten04.png
new file mode 100644
index 000000000..c0c34bc5c
--- /dev/null
+++ b/doc/de/member/pic/webseiten04.png
Binary files differ
diff --git a/doc/de/member/pic/webseiten05.png b/doc/de/member/pic/webseiten05.png
new file mode 100644
index 000000000..7f16b1e35
--- /dev/null
+++ b/doc/de/member/pic/webseiten05.png
Binary files differ
diff --git a/doc/de/member/pic/webseiten06.png b/doc/de/member/pic/webseiten06.png
new file mode 100644
index 000000000..dd22b6ed7
--- /dev/null
+++ b/doc/de/member/pic/webseiten06.png
Binary files differ
diff --git a/doc/de/member/pic/wiki01.png b/doc/de/member/pic/wiki01.png
new file mode 100644
index 000000000..bd59ed0cf
--- /dev/null
+++ b/doc/de/member/pic/wiki01.png
Binary files differ
diff --git a/doc/de/member/pic/wiki02.png b/doc/de/member/pic/wiki02.png
new file mode 100644
index 000000000..f55a66706
--- /dev/null
+++ b/doc/de/member/pic/wiki02.png
Binary files differ
diff --git a/doc/de/member/pic/wiki03.png b/doc/de/member/pic/wiki03.png
new file mode 100644
index 000000000..97dfad139
--- /dev/null
+++ b/doc/de/member/pic/wiki03.png
Binary files differ
diff --git a/doc/de/member/pic/wiki04.png b/doc/de/member/pic/wiki04.png
new file mode 100644
index 000000000..a48aec8b4
--- /dev/null
+++ b/doc/de/member/pic/wiki04.png
Binary files differ
diff --git a/doc/de/member/posting.md b/doc/de/member/posting.md
new file mode 100644
index 000000000..81a6ed99b
--- /dev/null
+++ b/doc/de/member/posting.md
@@ -0,0 +1,17 @@
+### Posten
+
+Möchten Sie einen Beitrag verfassen und teilen (veröffentlichen, wobei der Kreis der Empfänger bzw. derer, welche den Beitrag sehen können, eingeschränkt sein kann), so tun Sie dies in der Regel über das Feld "Teilen", das sich über dem Stream befindet. Klicken Sie in dieses Feld, öffnet sich der Beitragseditor.
+
+Zu oberst gibt es das Feld für den Beitragstitel (optional), darunter befindet sich das Feld für die Zusammenfassung (Summary, ebenfalls optional nutzbar), sofern der Administrator Ihres Hubs diese Funktion erlaubt. Die Zusammenfassung kann auch für den Zweck einer Inhaltswarnung verwendet werden. Unter dem Feld für die Zusammenfassung befindet sich (sofern vom Admin aktiviert) ein Feld für Kategorien.
+
+Darunter folgt das Textfeld, in welchem Sie den Beitragsinhalt erstellen können. Sie können, je nach Einstellung des Hubs, einfachen Text, Markdown, bbCode oder HTML für Formatierungen des Textes nutzen.
+
+Am unteren Rand des Beitragseditors befinden sich einige Schaltflächen zur einfacheren Formatierung des Inhalts und für das einfügen von Elementen und das Nutzen zusätzlicher Funktionen: fett, kursiv, unterstrichen, Zitat, Code, Datei anhängen/hochladen, Link einfügen, Bild einfügen (ein unter Dateien bereits existierendes Bild), Standort einfügen, Verfallsdatum für den Beitrag festlegen, Veröffentlichungsdatum festlegen, Text verschlüsseln, Abstimmung (Umfrage) ein/aus, Kommentare deaktivieren. Rechts daneben befindet sich ein weiterer Block mit Schaltflächen. Hier kann man sich eine Vorschau des Beitrags anzeigen lassen, festlegen, ob der Beitrag ggf. noch bei anderen Netzwerken veröffentlicht wird, die Berechtigungs-Einstellungen (wer kann das Posting sehen) vornehmen und mit dem Button "Teilen" schließlich veröffentlichen.
+
+![teilen](/help/de/member/pic/teilen.png)
+
+Man erreicht den Beitrags-Editor auch, wenn man den entsprechenden Menüpunkt im App-Menü (oben rechts ⋮) auswählt oder das entsprechende Icon in der Navigationsleiste (sofern man die App "Beitrag schreiben" angepinnt hat).
+
+#include doc/de/member/commenting.md;
+#include doc/de/member/insert_images.md;
+#include doc/de/member/bbcode.md;
diff --git a/doc/de/member/privacy_groups.md b/doc/de/member/privacy_groups.md
new file mode 100644
index 000000000..4a398b8e9
--- /dev/null
+++ b/doc/de/member/privacy_groups.md
@@ -0,0 +1,23 @@
+### Privacy Gruppen
+
+Die App "Privacy Gruppen" erlaubt Ihnen das Anlegen von Gruppen, denen Sie Kontakte zuordnen können. Sie dienen einerseits dem Filtern des Streams (so kann man sich nur Beiträge von Nutzern anzeigen lassen, welche sich in einer Privacy Gruppe befinden) und andererseits erlauben sie es, in Bezug auf [Berechtigungen](/help/de/member/permissions_contact_roles.md), bestimmten Gruppen Rechte an Inhalten einzuräumen.
+
+Die erste Funktion ist einfach zu verstehen. Hat man Kontakte (ein Kontakt kann durchaus auch in mehreren Gruppen vorhanden sein) in einer Gruppe und wählt man in der linken Seitenleiste bei der Stream-Ansicht eine bestimmte Gruppe aus, so werden nur Beiträge von Kontakten angezeigt, die sich in der Gruppe befinden. Diese Funktion fungiert also als Stream-Filter.
+
+Die zweite Funktion ist auch einfach zu begreifen, aber eher ungewohnt für viele Fediverse-Nutzer, da es sie in dieser Form nur bei Hubzilla und verwandten Diensten (Streams, Friendica etc.) gibt. Wie die Bezeichnung "Privacy Gruppen" nahelegt, geht es hier auch um eine beschränkte Kommunikation. Wählt man beim Verfassen eines Beitrags als Berechtigung eine Gruppe aus, so wird das Posting nur an die Kontakte, die in dieser Gruppe enthalten sind, verteilt und nur diese können es sehen. Es ist den Empfängern (Gruppenmitglieder) auch nicht möglich, ein solches Posting öffentlich zu teilen. Damit kann eine geschlossene Gruppenkommunikation realisiert werden.
+
+Ruft man die App auf, werden in der linken Seitenleiste bereits vorhandene Gruppen angezeigt und in der Hauptansicht das Eingabeformular zum Erstellen einer neuen Gruppe.
+
+![Privacy Gruppen 01](/help/de/member/pic/pgroups01.png)
+
+Wählt man in der Seitenleiste eine der Gruppen auf, so kann man diese bearbeiten.
+
+![Privacy Gruppen 02](/help/de/member/pic/pgroups02.png)
+
+Hier kann auch die Mitgliedschaft für Kontakte festgelegt werden. Ein Klick auf einen Eintrag wechselt die Mitgliedschaft zwischen "Nicht in der Gruppe" und "Gruppenmitgliedschaft". So kann man Mitglieder aus einer Gruppe entfernen oder Nutzer als Gruppenmitglieder hinzufügen.
+
+Das Hinzufügen eines Kontakt zu einer Gruppe kann auch in der App "Verbindungen" mit dem Kontakt-Tool erfolgen:
+
+![Privacy Gruppen 03](/help/de/member/pic/pgroups03.png)
+
+Um eine neue Gruppe hinzuzufügen, klickt man in der Seitenleiste auf den Eintrag "+ Neue Gruppe hinzufügen".
diff --git a/doc/de/member/privacy_settings.md b/doc/de/member/privacy_settings.md
new file mode 100644
index 000000000..ad5aed9c7
--- /dev/null
+++ b/doc/de/member/privacy_settings.md
@@ -0,0 +1,6 @@
+#### Privacy Einstellungen
+
+In den Privacy-Einstellungen können Sie festlegen, ob Ihre eigenen Beiträge durch Suchmaschinen indiziert werden dürfen, ob Sie Kontaktanfragen automatisch (ohne manuelle Genehmigung) akzeptieren, ob alle Nachrichten in denen Sie erwähnt werden, automatisch akzeptiert werden, ob Kommentare von Nutzern, die nicht zu Ihren Kontakten gehören, zur Moderation (freigeben / verwerfen) gestellt oder gelöscht werden und ob Sie OCAP-Zugriff erlauben.
+
+![Privacy-Einstellungen 01](/help/de/member/pic/priveinst01.png)
+
diff --git a/doc/de/member/profiles.md b/doc/de/member/profiles.md
new file mode 100644
index 000000000..0fbcebe7b
--- /dev/null
+++ b/doc/de/member/profiles.md
@@ -0,0 +1,23 @@
+### Profile
+
+Hubzilla hat unbegrenzte Profile. Sie können verschiedene Profile verwenden, um verschiedene „Seiten von sich selbst“ für verschiedene Zielgruppen zu zeigen. Das ist etwas anderes als verschiedene Kanäle zu haben. Verschiedene Kanäle ermöglichen völlig unterschiedliche Informationen. Sie können einen Kanal für sich selbst, einen Kanal für Ihr Sportteam, einen Kanal für Ihre Website oder etwas anderes haben. Ein Profil ermöglicht fein abgestufte „Seiten“ eines jeden Kanals. Verschiedene Profile könnte man mit verschiedenen Visitenkarten einer Person vergleichen. Je nach Verwendungszweck werden auf der jeweiligen Visitenkarte unterschiedliche Informationen gegeben. Ihr öffentliches Standardprofil könnte zum Beispiel lauten: „Hallo, ich bin Fred und ich lache gerne“. Ihren engen Freunden können Sie ein Profil zeigen, auf dem steht „und ich werfe auch gerne Zwerge“.
+
+Sie haben immer ein Profil, das als Ihr „Standard-“ oder „öffentliches“ Profil bezeichnet wird. Dieses Profil ist immer für die Allgemeinheit zugänglich und kann nicht versteckt werden (es kann seltene Ausnahmen auf privat betriebenen oder nicht angeschlossenen Sites geben). Sie können und sollten die Informationen, die Sie in Ihrem öffentlichen Profil zur Verfügung stellen, einschränken.
+
+Wenn Sie möchten, dass Ihre Freunde Sie finden können, ist es hilfreich, wenn Sie die folgenden Informationen in Ihrem öffentlichen Profil angeben...
+
+- Ihr richtiger Name oder zumindest ein Spitzname, den jeder kennt
+- Ein Foto von Ihnen
+- Ihr Standort auf der Erde, zumindest auf Länderebene.
+
+Wenn Sie außerdem Leute treffen möchten, die allgemeine Interessen mit Ihnen teilen, nehmen Sie sich bitte einen Moment Zeit und fügen Sie Ihrem Profil einige „Schlüsselwörter“ hinzu. Zum Beispiel „Musik, Linux, Fotografie“ oder ähnliches. Sie können so viele Stichwörter hinzufügen, wie Sie möchten.
+
+Wählen Sie „Profile bearbeiten“ aus dem Menü Ihrer Hubzilla-Site. Sie können ein bestehendes Profil bearbeiten, das Profilfoto ändern, Dinge zu einem Profil hinzufügen oder ein neues Profil erstellen. Sie können auch einen „Klon“ eines bestehenden Profils erstellen, wenn Sie nur ein paar Dinge ändern möchten, aber nicht alle Informationen erneut eingeben wollen. Klicken Sie dazu auf das Profil, das Sie klonen möchten, und wählen Sie dort „Dieses Profil klonen“.
+
+In der Liste Ihrer Profile können Sie auch die Kontakte auswählen, die ein bestimmtes Profil sehen können. Klicken Sie einfach auf „Sichtbarkeit bearbeiten“ neben dem betreffenden Profil (nur verfügbar für Profile, die nicht Ihr Standardprofil sind) und klicken Sie dann auf bestimmte Verbindungen, um sie zu der Gruppe von Personen, die dieses Profil sehen können, hinzuzufügen oder sie daraus zu entfernen.
+
+Sobald ein Profil ausgewählt wurde, sieht die Person, die Ihr Profil anschaut, das private Profil, das Sie zugewiesen haben. Wenn die Person nicht authentifiziert ist, wird sie Ihr öffentliches Profil sehen.
+
+Es gibt eine Einstellung, die es Ihnen ermöglicht, Ihr Profil in einem Verzeichnis zu veröffentlichen und sicherzustellen, dass es von anderen gefunden werden kann. Sie können diese Einstellung auf der Seite „Einstellungen“ ändern.
+
+Wenn Sie nicht möchten, dass Sie von anderen gefunden werden, ohne ihnen Ihre Kanal-Adresse mitzuteilen, können Sie Ihr Profil unveröffentlicht lassen. \ No newline at end of file
diff --git a/doc/de/member/protection_of_privacy.md b/doc/de/member/protection_of_privacy.md
new file mode 100644
index 000000000..e28b61da5
--- /dev/null
+++ b/doc/de/member/protection_of_privacy.md
@@ -0,0 +1,67 @@
+### Tipps zum Schutz der Privatsphäre
+
+Wenn Sie großen Wert auf Ihre Privatsphäre legen und trotzdem am Fediverse teilnehmen möchten, ist es erforderlich, dass Sie sich vor und während der Erstellung eines persönlichen Kanals konkrete Gedanken darüber machen, was Sie von sich preisgeben wollen. Das ist bei jedem Fediverse-Dienst so. Bei Hubzilla kommt aber ein anderer, wesentlicher Aspekt dazu. Sie müssen sich nicht nur die Frage über das "Was?" stellen, sondern auch noch über das "Wem?" und das "Welche?".
+
+Mit Hubzilla legen Sie also nicht nur fest, was Sie von sich preisgeben, sondern auch, wem Sie es erlauben, die Informationen und Inhalte zu sehen. Und wem Sie welche Interaktion erlauben.
+
+Der Vorteil ist, dass Sie nicht auf einen "Regelsatz" angewiesen sind, sondern verschiedene Regeln für verschiedene Anwendungszwecke und verschiedenen Kontakte festlegen können.
+
+Ein typischer Anwendungsfall wäre, dass Sie am Fediverse ganz normal so teilnehmen möchten,wie man es von anderen Sozialen Netzwerken kennt.
+
+Beim Erstellen eines Kanals müssen Sie die erste relevante Entscheidung treffen: **die Kanalrolle**.
+
+Hier haben Sie die Wahl zwischen "Öffentlich", "Persönlich", "Community Forum" und "Benutzerdefiniert".
+
+Abgesehen vom "Community Forum", das für andere Anwendungszwecke gedacht ist, haben Sie also die Wahl zwischen drei Rollen.
+
+Bei der Rolle "Öffentlich" erlauben Sie, dass andere
+
+1. Ihren Kanal-Stream (also die Beiträge, welche Sie öffentlich teilen) und allgemein Ihre Beiträge sehen können,
+2. Ihr Standard-Profil sehen können,
+3. Ihre Verbindungen sehen können,
+4. Ihre Datei- und Bilderordner sehen können,
+5. die Webseiten Ihres Kanals sehen können,
+6. die Wiki-Seiten Ihres Kanals sehen können,
+7. Ihre Beiträge kommentieren, liken oder disliken dürfen,
+8. Ihnen Direktnachrichten schicken dürfen,
+9. Ihre Profile und Profilinhalte liken oder disliken dürfen und
+10. mit Ihnen chatten dürfen.
+
+Diese rollenbasierten Regeln bilden recht gut die "normale" Nutzung eines Sozialen Netzwerks ab.
+
+Die Rolle "Persönlich" ist ähnlich und verweigert nur einige Erlaubnisse der Rolle "Öffentlich". Sie erlaubt, dass andere
+
+1. Ihren Kanal-Stream (also die Beiträge, welche Sie öffentlich teilen) und allgemein Ihre Beiträge sehen können,
+2. Ihr Standard-Profil sehen können,
+3. ./.
+4. Ihre Datei- und Bilderordner sehen können,
+5. die Webseiten Ihres Kanals sehen können,
+6. die Wiki-Seiten Ihres Kanals sehen können,
+7. ./.
+8. ./.
+9. ./.
+10. ./.
+
+Mit diesem Profil ist die Interaktion durch andere Nutzer eingeschränkt, denn diese dürfen Ihre Beiträge nicht kommentieren, liken, disliken (letzteres auch nicht in Bezug auf Ihre Profile/Profilinhalte). Sie dürfen Ihnen auch keine Direktnachrichten schicken oder mit Ihnen chatten.
+
+Die Rolle "Benutzerdefiniert" erlaubt es Ihnen, sämtliche Berechtigungen einzeln festzulegen. Hier ist Vorsicht geboten, weil unpassende Regeln einen Kanal halbwegs "unbenutzbar" machen können.
+
+Für die angedachte Nutzung als "typischer Social Network Account" empfiehlt sich die Wahl der Rolle "Öffentlich" oder "Persönlich".
+
+Wenn Sie sich nun eher für die Rolle "persönlich" entscheiden würden, aber trotzdem gerne weitergehende Interaktion mit bestimmten Nutzern (Freund, Familie, Kollegen, o.ä.) erlauben würden, müssen Sie deshalb nicht auf die Rolle "Öffentlich" zurückgreifen.
+
+Hubzilla funktioniert bei den Berechtigungen mit Whitelists (Erlaubnislisten). Die Kanalrolle legt also die grundsätzlichen Berechtigungen fest. Diese können Sie nachträglich (abgesehen von der Festlegung von Zugriffsrechten im speziellen Einzelfall eines Inhalts) nicht mit anderen Mechanismen wieder entziehen.
+
+Sie können aber mittels verschiedener **Kontaktrollen** die Whitelist um verschiedene Berechtigungen erweitern. So könnten Sie z.B. eine Kontaktrolle "Familie" erstellen, in welcher - zusätzlich zu den Berechtigungen, welche die Rolle "Persönlich" einräumt - weitere Berechtigungen (z.B. das Kommentieren, Linke, Disliken und das Senden von Direktnachrichten) gewährt werden. Wenn Sie nun in diesem Beispiel Ihren Kontakten, die Sie als "Familie" definieren, diese Kontaktrolle zuordnen, können Ihre Familienmitglieder - im Gegensatz zu allen anderen - Kommentare verfassen, den Daumen nach oben oder unten zeigen lassen und nicht-öffentlich (Direktnachricht) mit Ihnen kommunizieren.
+
+Sie können beliebig viele Kontaktrollen für die verschiedensten Zwecke und Kontakte anlegen und dort weitere Berechtigungen, zusätzlich zu denen der Kanalrolle, erteilen. Aber(!): Sie können keine Berechtigung aus der Kanalrolle dort entziehen.
+
+Es empfiehlt sich also, bei der Kanalrolle nicht zu großzügig zu sein und diese - je nach dem Nutzungszweck des Kanals - entsprechend auszuwählen. Mit der Auswahl "Benutzerdefiniert" könnten Sie z.B. eine noch restriktivere Kanalrolle als die "Persönlich" festlegen und dann für bestimmte Nutzer weitere Berechtigungen mit den Kontaktrollen festlegen (empfehlenswert erst, wenn Sie mit dem Berechtigungssystem wirklich vertraut sind).
+
+Ein weiterer Aspekt bezüglich der Privatsphäre sind die **Profil**-Informationen. Für eine "typische" Nutzung als Social Network Account, sollten im Profil einige Informationen preisgegeben werden. Ansonsten werden andere Nutzer nicht auf die Idee kommen, sich mit Ihnen zu verbinden. Oder sie wollen eine Verbindung zu einem anderen Nutzer herstellen, doch dieser lehnt dies ab, weil er keinerlei Informationen zu Ihnen hat (es sei denn, er kennt Sie und ihren Kanalnamen). Einige Informationen sollten dort also hinein.
+
+So viele wie nötig, so wenige wie möglich.
+
+Genau nach diesem Prinzip sollten sie Ihr Standard-Profil, über welches jeder Kanal verfügt, mit Informationen bestücken.
+
+Hubzilla erlaubt es aber, mehrere Profile anzulegen. In solchen Profilen können Sie dann weitergehende Informationen angeben, die für bestimmte Verbindungen interessant sein könnten. Sie haben dann die Möglichkeit, solche speziellen Profile für bestimmte Verbindungen freizugeben. Die Informationen tauchen also nicht für jeden sichtbar und öffentlich auf, sondern sie sind nur für die ausgewählten Nutzer verfügbar. \ No newline at end of file
diff --git a/doc/de/member/public_stream.md b/doc/de/member/public_stream.md
new file mode 100644
index 000000000..1b9b7774f
--- /dev/null
+++ b/doc/de/member/public_stream.md
@@ -0,0 +1,12 @@
+### Öffentlicher Beitrags-Stream
+
+Sofern der Administrator eines Hub den öffentlichen Beitrags-Stream aktiviert hat, können Sie als Nutzer die App "Öffentlicher Beitrags-Stream" installieren und aktivieren.
+
+Während im "normalen" Stream alle Beiträge und Aktivitäten von Ihnen selbst und all Ihren Verbindungen erscheinen, ist der öffentliche Beitrags-Stream umfassender.
+
+Es gibt zwei Möglichkeiten:
+
+1. Hat der Administrator den öffentlichen Beitrags-Stream auf den eigenen Hub beschränkt, so erscheinen dort sämtliche öffentlichen Beiträge und Aktivitäten aus den Streams aller Nutzer, die einen Account auf eben diesem Hub haben.
+2. Hat der Administrator den öffentlichen Beitrags-Stream nicht auf den eigenen Hub beschränkt, so werden alle öffentlichen Beiträge aller Kanäle des eigene Hub, öffentliche Inhalte, die beim eigenen Hub eintreffen (also .B. Kommentare auf Beiträge der Nutzer des Hubs, die von anderen Instanzen stammen), sowie zufällig eingesammelte Inhalte von Kanälen, die dem eigenen Hub bekannt sind (also alle Kontakte aller Kanäle auf dem eigenen Hub).
+
+Der öffentliche Beitrags-Stream ist nicht unmoderiert. Der Administrator eines Hub hat die Möglichkeit, Beiträge aus dem öffentlichen Beitrags-Stream zu löschen ("Admin delete"). Diese Beiträge sind dann tatsächlich aus dem öffentlichen Beitragsstream des eigenen Hub gelöscht. Sie erscheinen auch nicht in der öffentlichen Stream-Ansicht anderer Kanäle des Hub. \ No newline at end of file
diff --git a/doc/de/member/registration.md b/doc/de/member/registration.md
new file mode 100644
index 000000000..96cbdc574
--- /dev/null
+++ b/doc/de/member/registration.md
@@ -0,0 +1,19 @@
+### Anmeldung / Registrierung
+
+Nicht alle Hubzilla-Sites erlauben eine offene Registrierung. Wenn die Registrierung erlaubt ist, sehen Sie einen Link „Registrieren“ direkt neben der Anmeldeaufforderung auf der Homepage der Site. Wenn Sie diesem Link folgen, gelangen Sie auf die Registrierungsseite der Site. Auf einigen Sites werden Sie möglicherweise zu einer anderen Site weitergeleitet, auf der Hubs aufgeführt sind, bei denen eine Registrierung möglich ist. Da alle Hubzilla-Sites miteinander verbunden sind, spielt es keine Rolle, wo sich Ihr Account befindet.
+
+**Ihre E-Mail-Adresse**
+
+Bitte geben Sie eine gültige E-Mail-Adresse an. Ihre E-Mail-Adresse wird niemals veröffentlicht. Diese Adresse wird verwendet, um Ihr Konto zu aktivieren, um (optional) E-Mail-Benachrichtigungen für eingehende Nachrichten oder Artikel zu versenden *und um verlorene Passwörter wiederherzustellen*.
+
+**Kennwort**
+
+Geben Sie ein Passwort Ihrer Wahl ein und wiederholen Sie es im zweiten Feld, um sicherzustellen, dass es richtig eingegeben wurde. Da Hubzilla eine dezentrale Identität bietet, können Sie sich mit Ihrem Konto bei vielen anderen Websites anmelden.
+
+**Nutzungsbedingungen**
+
+Klicken Sie auf den Link, um die <u>Nutzungsbedingungen</u> der Website zu lesen. Wenn Sie sie gelesen haben, bestätigen Sie sie durch Anklicken des Kästchens im Anmeldeformular.
+
+**Anmelden**
+
+Sobald Sie die erforderlichen Angaben gemacht haben, klicken Sie auf die Schaltfläche „Registrieren“. Bei einigen Websites ist möglicherweise die Zustimmung des Administrators erforderlich, bevor die Registrierung bearbeitet werden kann; in diesem Fall werden Sie benachrichtigt. Bitte achten Sie auf Ihre E-Mail (einschließlich Spam-Ordner), um die Genehmigung für Ihre Registrierung zu erhalten. \ No newline at end of file
diff --git a/doc/de/member/repeat.md b/doc/de/member/repeat.md
new file mode 100644
index 000000000..e354f8059
--- /dev/null
+++ b/doc/de/member/repeat.md
@@ -0,0 +1,5 @@
+#### Repeat
+
+Mit dem Repeaten (Wiederholen) von Beiträgen, wird der Beitrag an die eigenen Verbindungen verteilt. Kommentare landen (im Gegensatz zu [geteilten Beiträgen](/help/de/member/share.md)) beim Original-Beitrag.
+
+Dieses Verhalten entspricht dem "Boosten", wie man es z.B. von Mastodon oder anderen Fediverse-Diensten kennt. \ No newline at end of file
diff --git a/doc/de/member/save_to_folder.md b/doc/de/member/save_to_folder.md
new file mode 100644
index 000000000..483959bd2
--- /dev/null
+++ b/doc/de/member/save_to_folder.md
@@ -0,0 +1,25 @@
+#### In Ordner speichern
+
+Möchte man sich Postings für später merken, so kann man das durch [Markierung](/help/de/member/toggle_star_status.md) tun. Bei einer großen Zahl solchermaßen gemerkter Postings wird es aber leicht unübersichtlich.
+
+Praktischer ist es, solche Beiträge in verschiedenen "Ordner" zu speichern. Das sind kategorisierte Markierungen.
+
+Wählt man "In Ordner speichern", so öffnet sich ein Dialogfenster zur Auswahl des Ordners.
+
+![Ordnername](/help/de/member/pic/ordner01.png)
+
+In dem Textfeld kann man einen Ordnernamen vergeben. Sind schon Ordner vorhanden, sorgt ein zweifacher Kick in das Textfeld dafür, dass eine Auswahlliste der vorhandenen Ordner angezeigt wird, um einen davon auszuwählen.
+
+![Ordnerauswahl](/help/de/member/pic/ordner02.png)
+
+Hat man einen Beitrag in einem Ordner abgelegt, erkennt man dies an Ordersymbol (inklusive Ordnernamen) unten links am Beitrag.
+
+![Ordnerauswahl](/help/de/member/pic/ordner03.png)
+
+Dieses Symbol dient auch dazu, einen Beitrag wieder aus eienm Ordner zu entfernen, indem man auf das "X" im Symbol klickt.
+
+![Ordnerauswahl](/help/de/member/pic/ordner04.png)
+
+In der Stream-Ansicht findet man in der linken Seitenleiste den Eintrag "Gespeicherte Ordner". Klickt man darauf werden sämtliche vorhandenen Ordner angezeigt. Wählt man nun einen Ordner aus, so werden sämtliche Postings, die man in diesem Ordner abgelegt hat in (umgekehrter) chronologischer Reihenfolge im Stream angezeigt.
+
+Dieses Feature ist vergleichbar mit den "Clips" (= kategorisierte Lesezeichen), wie man sie von Misskey und den Forkeys kennt.
diff --git a/doc/de/member/search.md b/doc/de/member/search.md
new file mode 100644
index 000000000..0fb0a4a6d
--- /dev/null
+++ b/doc/de/member/search.md
@@ -0,0 +1,15 @@
+### Suche
+
+Um schnell Informationen zu finden, kann die Suche verwendet werden.
+
+Eine Suche ist über das Symbol in der Navigationsleiste möglich.
+
+![Suche Nav-Leiste 01](/help/de/member/pic/suche01.png)
+
+![Suche Nav-Leiste 02](/help/de/member/pic/suche02.png)
+
+Hier wird der gesamte Hub durchsucht. Es kann nach Hashtags, Handles und nach Texten gesucht werden.
+
+In der Kanalansicht ist in der linken Seitenleiste ebenfalls ein Suchfeld vorhanden. Es durchsucht nur den Stream des eigenen Kanals. Suchen, welche in diesem Widget durchgeführt wurden, können auch gespeichert werden, indem man auf das Diskettensymbol neben dem Suchfeld klickt. Die gespeicherte Suche wird dann in einer Liste von Suchbegriffen unter dem Suchfeld angezeigt und kann jederzeit durch einen einfachen Klick wiederholt werden.
+
+![Such-Widget](/help/de/member/pic/suche03.png)
diff --git a/doc/de/member/settings.md b/doc/de/member/settings.md
new file mode 100644
index 000000000..910c6e03d
--- /dev/null
+++ b/doc/de/member/settings.md
@@ -0,0 +1,37 @@
+### Einstellungen
+
+Hubzilla erlaubt vielfältige Einstellungen zu Verhalten, Optik, Features,Kanälen etc.
+
+Sie erreichen die meisten Einstellungen über das Hauptmenü, wo Sie den Menüpunkt Einstellungen finden.
+
+![Einstellungen 01](/help/de/member/pic/einst01.png)
+
+![Einstellungen 02](/help/de/member/pic/einst02.png)
+
+Es werden verschiedene Einstellungs-Kategorien zur Verfügung gestellt:
+
+- Konto-Einstellungen
+- Kanal-Einstellungen
+- Privacy-Einstellungen
+- Anzeige-Einstellungen
+- sofern Klone des eigenen Kanals existieren: Klon-Adressen verwalten
+
+Befinden Sie sich in der Stream Ansicht, ist ein neben dem Hauptmenü ein kleines Zahnrad (⚙)zu sehen,über welches Sie die
+
+- Stream-Einstellungen
+
+erreichen.
+
+Außerdem gibt es noch verborgene Einstellungen
+
+- Zusätzliche Funktionen
+
+welche Sie jedoch nicht über das Menü oder ein Icon erreichen können.
+
+#include doc/de/usermanual/account_settings.md;
+#include doc/de/usermanual/channel_settings.md;
+#include doc/de/usermanual/privacy_settings.md;
+#include doc/de/usermanual/display_settings.md;
+#include doc/de/usermanual/channel_locations.md;
+#include doc/de/usermanual/stream_settings.md;
+#include doc/de/usermanual/additional_features.md;
diff --git a/doc/de/member/share.md b/doc/de/member/share.md
new file mode 100644
index 000000000..a7148f731
--- /dev/null
+++ b/doc/de/member/share.md
@@ -0,0 +1,5 @@
+#### Teilen
+
+Mit dem Teilen (Weitersagen) von Beiträgen wird ein Beitrag eines anderen Nutzers auf dem eigenen Kanal erneut gepostet. Es wird eine neue Konversation im eigenen Kanal erzeugt. Kommentare dazu landen bei der neuen Konversation und nicht bei der ursprünglichen.
+
+Das Teilen von Beiträgen funktioniert nur aus dem Stream oder dem eigenen Kanal, nicht jedoch aus einem „fremden“ Kanal. \ No newline at end of file
diff --git a/doc/de/member/show_source_code.md b/doc/de/member/show_source_code.md
new file mode 100644
index 000000000..b5c7139a8
--- /dev/null
+++ b/doc/de/member/show_source_code.md
@@ -0,0 +1,15 @@
+#### Quellcode anzeigen
+
+Mit dieser Funktion kann man sich den Quelltext eines Beitrags anzeigen lassen. Der Inhalt wird also nicht in formatierter Form gerendert, sondern umfasst den Text inklusive aller Auszeichnungs-Tags (Markdown, bbCode, HTML).
+
+Die Funktion scheint deshalb eher etwas für fortgeschrittene oder sehr neugierige Nutzer zu sein.
+
+Allerdings enthält sie ein Feature, das für jeden hilfreich sein kann.
+
+Neben der internen Beitrags-Id findet man zwei Hyperlinks: "plink" und "llink".
+
+![Quellcode](/help/de/member/pic/quellcode.png)
+
+"plink" bedeutet "Permalink" und entspricht dem [Link zur Quelle](/help/de/member/link_to_source.md).
+
+"llink" bedeutet "lokaler Link" und verweist auf den Ort des Postings auf der eigenen Instanz (Hub). Ein Klick darauf führt nicht dazu, dass man seine eigene Instanz verlässt, sondern zeigt den Beitrag in der Einzelansicht an.
diff --git a/doc/de/member/stream_settings.md b/doc/de/member/stream_settings.md
new file mode 100644
index 000000000..0b4aeb673
--- /dev/null
+++ b/doc/de/member/stream_settings.md
@@ -0,0 +1,9 @@
+#### Stream-Einstellungen
+
+Die Stream-Einstellungen erreicht man nicht über Hauptmenü → Einstellungen, sonder über das kleine Zahnradsymbol (⚙) neben dem Hauptmenü, das dort erscheint, sobald man die Stream-Ansicht aufruft.
+
+![Einstellungen 03](/help/de/member/pic/einst03.png)
+
+Mit den Stream-Einstellungen kann man die Ansicht des Streams und dort zur Verfügung stehende Features (z.B. Stream-Filter, das Speichern von Suchanfragen etc.) auswählen.
+
+![Stream-Einstellungen 01](/help/de/member/pic/streinst01.png)
diff --git a/doc/de/member/superblock.md b/doc/de/member/superblock.md
new file mode 100644
index 000000000..e3833c890
--- /dev/null
+++ b/doc/de/member/superblock.md
@@ -0,0 +1,15 @@
+### Superblock
+
+Die App "Superblock" ist eine Moderationsmethode für den eigenen Stream. Während man mit der normalen Funktionalität von Hubzilla mittels Kontakt-Tool ausschließlich Nutzer, mit denen man verbunden ist, blockieren kann, funktioniert Superblock unabhängig davon, ob man mit einem Kontakt verbunden ist, oder nicht.
+
+Bemerkt man im Stream einen Nutzer, mit welchem man nicht verbunden ist (weil dessen Beiträge von einem Kontakt aus dem eigenen Adressbuch geteilt werden), und möchte man - aus welchem Grund auch immer - keinerlei Beiträge von diesem Fediversenutzer im Stream haben, so kann man dies mit der App "Superblock" erreichen.
+
+Dafür klicken Sie man auf das kleine weiße Dreieck im Avatar des zu blockenden Nutzers.
+
+Es öffnet sich ein Pulldown-Menü, welches zu unterst den Menüpunkt "Vollständig blockieren" enthält. Mit einem Klick auf diesen Menüpunkt landet der Nutzer in der Superblock-Liste. Beiträge dieses Nutzers erscheinen künftig nicht mehr im eigenen Stream. Betroffene Beiträge werden sofort im Stream ausgeblendet. Außerdem ist es diesem Nutzer nicht mehr möglich, Ihre Beiträge zu lesen, unabhängig von den Berechtigungen, noch kann er in Ihren Kanal schreiben.
+
+Wählt man im App-Menü (oben rechts ⋮) die App "Superblock", so wird die Liste sämtlicher blockierter Kontakte angezeigt.
+
+Neben jedem Kontakt ist ein "Mülltonnen-Icon" zu sehen. Ein Klick auf dieses Icon entfernt den Nutzer von der Blockliste. Anschließend kann der Nutzer Ihnen wieder folgen, Ihre Beiträge sehen und auch bei Ihnen kommentieren und seine Postings erscheinen (z.B. durch Teilen von einem Kontakt) auch wieder im Stream.
+
+Superblock ist bei neuen Kanälen nicht per Standard installiert und aktiviert. \ No newline at end of file
diff --git a/doc/de/member/tags.md b/doc/de/member/tags.md
new file mode 100644
index 000000000..1f2fd79f4
--- /dev/null
+++ b/doc/de/member/tags.md
@@ -0,0 +1,5 @@
+### Tags
+
+Tags (auch thematische Tags, Hashtags oder topical tags genannt) Tags werden angezeigt, indem Sie dem Tagnamen das Zeichen "#" voranstellen. Dadurch wird im Beitrag ein Link zu einer verallgemeinerten Website-Suche nach dem angegebenen Begriff erstellt. Zum Beispiel, #cars stellt einen Suchlink für alle Beiträge bereit, die „Autos“ auf Ihrer Website erwähnen. Aktuelle Tags sind in der Regel mindestens drei Zeichen lang. Kürzere Suchbegriffe führen wahrscheinlich nicht zu Suchergebnissen, dies hängt jedoch von der Datenbankkonfiguration ab.
+
+Thematische Tags werden normalerweise auch nicht verknüpft, wenn sie rein numerisch sind, z. #1. Wenn Sie einen numerischen Hashtag verwenden möchten, fügen Sie bitte einen beschreibenden Text wie z.B. #2012-Wahlen oder das gesamte Tag in doppelte Anführungszeichen setzen (z. B. #“2012″). Doppelte Anführungszeichen sind auch erforderlich, wenn das Tag Leerzeichen enthält (# „Mein Tag“) und möglicherweise erforderlich, wenn das Tag Satzzeichen enthält (# „EndsWithPeriod.“ Oder # „Exciting !!!“).
diff --git a/doc/de/member/the_grid.md b/doc/de/member/the_grid.md
new file mode 100644
index 000000000..d49b199f6
--- /dev/null
+++ b/doc/de/member/the_grid.md
@@ -0,0 +1,3 @@
+### Das Grid
+
+Als das "Grid" wird das Netzwerk aller Hubzilla-Hubs bezeichnet, die miteinander über das Nomad-Protokoll kommunizieren. Das Grid ist quasi eine Untermenge des Fediverse und umfasst alle Hubzilla-Server. \ No newline at end of file
diff --git a/doc/de/member/the_stream.md b/doc/de/member/the_stream.md
new file mode 100644
index 000000000..08a6abbe8
--- /dev/null
+++ b/doc/de/member/the_stream.md
@@ -0,0 +1,7 @@
+### Der Stream
+
+Der Stream ist die Liste der Beiträge, Kommentare und Boosts von Nutzern im Fediverse. Er ist umgekehrt chronologisch sortiert (die neuesten Beiträge erscheinen zuoberst). Welche Beiträge Ihnen hier genau gezeigt werden, hängt weitgehend von Ihren Berechtigungseinstellungen ab.
+
+Der Stream (bei anderen Fediversedienste wird er auch "Timeline" genannt) kann durch verschiedene Filter gefiltert werden: nach Direktnachrichten, Ereignissen, Umfragen, Privacy Gruppen, Foren, Markierten Beiträgen, den eigenen Beiträgen, gespeicherte Ordner, Namen.
+
+Der Stream im Fediverse wird nicht durch Algorithmen, die vermeintlich interessante Inhalte für den Nutzer auswählt, erstellt, sondern wird ausschließlich durch den Nutzer selbst bestimmt. Im Fediverse sind Sie, der Nutzer, der Algorithmus für den Stream. \ No newline at end of file
diff --git a/doc/de/member/toggle_star_status.md b/doc/de/member/toggle_star_status.md
new file mode 100644
index 000000000..4c901d536
--- /dev/null
+++ b/doc/de/member/toggle_star_status.md
@@ -0,0 +1,9 @@
+#### Markierungsstatus (Stern) umschalten
+
+Das Umschalten des Markierungssterns darf nicht mit dem "Sternen" (= Favorisieren) anderer Fediversedienste (z.B. Mastodon) verwechselt werden. Während das "Sternen" bei diesen Diensten eine positive Wertung des Beitrags darstellt, die am ehesten einem "Link" entspricht, ist das Markieren mit einem Stern bei Hubzilla mit dem Setzen eines Lesezeichens vergleichbar. Mit dem Umschalten (ein Klick setzt den Stern, ein weiterer entfernt ihn wieder) wird der Beitrag für den Nutzer selbst als eine Art Lesezeichen gesetzt.
+
+Einen "gesternten" Beitrag erkennt man am Sternsymbol unten links.
+
+![Stern](/help/de/member/pic/stern.png)
+
+In der Stream-Ansicht findet man in der linken Seitenleiste den Eintrag "Markierte Beiträge". Klickt man darauf werden sämtliche markierte Postings in (umgekehrter) chronologischer Reihenfolge im Stream angezeigt.
diff --git a/doc/de/member/websites.md b/doc/de/member/websites.md
new file mode 100644
index 000000000..f56b69f0e
--- /dev/null
+++ b/doc/de/member/websites.md
@@ -0,0 +1,143 @@
+### Webseiten
+
+Mit der App "Webseiten" ist es Ihnen möglich, statische Webseiten in Ihrem Kanal zu erstellen. Webseiten verbleiben auf Ihren Hub und werden nicht föderiert. Sie können aber den Link zur Webseite teilen und es allen Nutzern im Fediverse ermöglichen, Ihre Webseite zu besuchen.
+
+Wenn Sie die App aufrufen, gelangen Sie zur Webseiten-Übersicht. Die Seiten werden unter `<URL-Ihrer-Instanz>/page/<Ihr-Kanalname>/<Seiten-Link-Titel>` zugänglich sein.
+
+![Webseiten 01](/help/de/member/pic/webseiten01.png)
+
+In der linken Seitenleiste befindet sich ein Widget mit den Gestaltungswerkzeugen für "Blöcke", "Menüs", "Layouts" und "Seiten".
+
+Darunter ist ein weiteres Widget vorhanden, mit der Möglichkeit Webseiten zu ex- und importieren.
+
+In der Mitte werden die vorhandenen Webseiten aufgeführt. Sie können diese bearbeiten, teilen und löschen. Außerdem ist dort ein Button vorhanden, um eine neue Webseite zu erstellen: "Erstelle".
+
+Wenn Sie auf diesen Button klicken, öffnet sich der Webseiten-Editor.
+
+![Webseiten 02](/help/de/member/pic/webseiten02.png)
+
+Sie haben nun die Wahl, auf welche Art Sie die Webseite gestalten wollen: mit bbCode, mit HTML, mit Markdown, mit reinem Text oder mit der Comanche-Layout-Sprache.
+
+Sie können außerdem festlegen, welches Layout (sofern Sie eines oder Mehrere mit dem Layout-Gestaltungswerkzeug erstellt haben) für die Darstellung der Webseite verwendet werden soll.
+
+Es folgt das Eingabefeld für den optionalen Seitentitel, sowie (ebenfalls optional) eine Zusammenfassung, sowie (Pflichtfeld) die Seiten-URL.
+
+Darunter befindet sich der Texteditor für den Inhalt der Webseite.
+
+Möchte man lediglich eine ganz einfache Webseite mit Formatierungen und anderen Auszeichnungselementen erstellen, genügt es, diese im Website-Editor mittel reinem Text, HTML, bbCode oder Markdown zu erstellen. Sie erhalten damit eine Webseite ohne besonderes Layout (ohne Seitenleisten, ohne Menüs etc.).
+
+Für anspruchsvollere Webseiten, empfiehlt es sich, dass Sie mit Blöcken, Layouts und Menüs arbeiten.
+
+#### Blöcke
+
+Blöcke können Teile von Webseiten sein. Der grundlegende HTML-Code eines Blocks sieht wie folgt aus
+
+```
+ <div>
+ Block-Inhalt
+ </div>
+```
+
+Wenn ein Block den Inhaltstyp text/html hat, kann er auch Menüelemente enthalten. Der Beispielinhalt von
+
+```
+ <p>HTML Block-Inhalt</p>
+ [menu]menuname[/menu]
+```
+
+wird HTML wie dieses erzeugen
+
+```
+ <div>
+ <p>HTML Block-Inhalt</p>
+ <div>
+ <ul>
+ <li><a href="#">Link 1</a></li>
+ <li><a href="#">Link 2</a></li>
+ <li><a href="#">Link 3</a></li>
+ </ul>
+ </div>
+ </div>
+```
+
+Über das Makro `$content` kann ein Block auch den eigentlichen Inhalt der Webseite enthalten.
+
+Erstellen Sie dazu einen Block nur mit
+
+```
+ $content
+```
+
+als Inhalt.
+
+Damit ein Block auf der Webseite erscheint, muss er im Seitenlayout innerhalb einer Region definiert werden.
+
+```
+ [region=aside]
+ [block]blockname[/block]
+ [/region]
+```
+
+Das Aussehen des Blocks kann im Seitenlayout manipuliert werden.
+
+Es können eigene Klassen zugewiesen werden
+
+```
+ [region=aside]
+ [block=myclass]blockname[/block]
+ [/region]
+```
+
+wird folgendes HTML erzeugen
+
+```
+ <div class="myclass">
+ Block Content
+ </div>
+```
+
+Über die wrap-Variable kann ein Block von seinem umschließenden `<div></div>`-Tag befreit werden
+
+```
+ [region=aside]
+ [block][var=wrap]none[/var]blockname[/block]
+ [/region]
+```
+
+wird dieses HTML erzeugt
+
+```
+Blockinhalt
+```
+
+Mit dem Block-Editor können Blöcke ebenso einfach erzeugt werden, wie Webseiten.
+
+![Webseiten 03](/help/de/member/pic/webseiten03.png)
+
+#### Menüs
+
+Der Menüeditor dient zum einfachen Erstellen von Navigations-Menüs.
+
+![Webseiten 04](/help/de/member/pic/webseiten04.png)
+
+Dem Menü muss ein eindeutiger Name gegeben werden (mit diesem kann es später in der Webseite und in Blöcken referenziert werden). Die Vergabe des Titels ist optional. Außerdem kann man wählen, ob das Menü für das Hinzufügen von Bookmarks zur Verfügung steht. Mit diesem Feature ist es möglich, als Bookmarks markierte Links aus dem Stream mit einem Klick zum Menü hinzuzufügen.
+
+Klicken Sie auf "Absenden und fortfahren", wird das Menü erstellt.
+
+![Webseiten 05](/help/de/member/pic/webseiten05.png)
+
+Nun öffnet sich der Dialog für das Hinzufügen eines Menüeintrags. Es muss eine Bezeichnung für den Menüeintrag eingegeben werden ("Name des Links") und das Ziel des Links. Dies kann eine URL oder der Name eines anderen Menüs sein (welches dann als Untermenü eingebunden wird).
+
+Über die Eingabe einer Zahl bei "Reihenfolge in der Liste" kann man die Sortierung der Menüeinträge beeinflussen.
+
+Handelt es sich bei der URL um einen externen Link zu einer Quelle auf einem anderen Hub, so kann durch setzen des Schalters "Magic-Auth verwenden, falls verfügbar" dafür gesorgt werden, dass man beim Ziel authentifiziert wird und ggf. eingeschränkte Inhalte verfügbar sind.
+
+Außerdem können Sie festlegen, ob Links in einem neuen Fenster/Tab geöffnet werden sollen.
+
+Mit Klick auf "Absenden und fortfahren" kann man weitere Einträge erstellen. "Absenden und fertigstellen" beendet die Eingabe von Menüeinträgen. Menüs können aber jederzeit nachträglich bearbeitet und ergänzt werden.
+
+#### Layouts
+
+Mit den Layouts kann man den generellen Aufbau von Webseiten festlegen. Die Gestaltung erfolgt mit der Comanche Seitenbeschriebungssprache, einer Variante von bbCode. Man muss einen Namen für das Layout vergeben. Im Textfeld erfolgt dann die Definition des Layouts. Hier können auch die Inhalte der verschiedenen Regionen festgelegt werden.
+
+![Webseiten 06](/help/de/member/pic/webseiten06.png)
diff --git a/doc/de/member/wikis.md b/doc/de/member/wikis.md
new file mode 100644
index 000000000..da3a7a829
--- /dev/null
+++ b/doc/de/member/wikis.md
@@ -0,0 +1,39 @@
+### Wikis
+
+Die App "Wiki" ermöglicht es, im eigenen Kanal Wikis anzulegen.
+
+Wiki-Seiten werden nicht föderiert und verbleiben auf dem eigenen Hub.
+
+Die Wiki-App bietet eine einfache, klassische Wiki-Funktionalität. Wiki-Beiträge können als reiner Text, als Markdown-Text oder als BBcode-Text erstellt werden.
+
+![Wikis 01](/help/de/member/pic/wiki01.png)
+
+Um ein Wiki anzuschauen (oder zu bearbeiten), wählt man auf dieser Seite das entsprechende Wiki aus der Liste.
+
+Möchte man ein neues Wiki erstellen, klickt man auf den Button "+ Neu anlegen".
+
+Es öffnet sich ein Eingabe-Formular, in welchem man den Namen des Wiki eingibt und den Inhaltstyp (als Standard) festlegt. Man kann über einen Schalter bestimmen, dass nur der gewählte Inhaltstyp (Text, Markdown, BBcode) für alle Wiki-Einträge verwendet werden muss. Außerdem kann man per Schalter die Erzeugung eines Statusbeitrags über die Wikierstellung ein- oder ausschalten.
+
+![Wikis 02](/help/de/member/pic/wiki02.png)
+
+Auch für ein Wiki kann man detaillierte [Berechtigungen](/help/de/member/permissions_content.md) festlegen.
+
+Klickt man auf "Absenden", wird das Wiki erstellt und die Startseite (Home) geöffnet.
+
+Die Standard-Ansicht einer Wiki-Seite ist immer "Ansicht", bei welcher der Text gemäß dem Quelltext gerendert wird. Über Reiter (Tabs) am oberen Rand kann man in die Ansicht "Bearbeiten" wechseln, um in den Editor-Modus zu gelangen.
+
+Haben Sie die Seite bearbeitet und schalten Sie dann wieder auf Ansicht, werden die Änderungen sofort angezeigt.
+
+Wenn Sie die Seite speichern möchten, geben Sie das Eingabefeld unter dem Text eine passende Bemerkung ein und klicken auf "Save". Die Wiki-Seite ist erstellt.
+
+Über den dritten Reite mit der Bezeichnung "Historie" können Sie sich den Änderungsverlauf der Wiki-Seite anschauen und, sofern gewünscht, Änderungen rückgängig machen. Es handelt sich also um eine wiki-typische Form der Versionskontrolle.
+
+![Wikis 03](/help/de/member/pic/wiki03.png)
+
+Wenn Sie weitere Wiki-Seiten erstellen, werden diese in der linken Seitenleiste aufgeführt, von wo aus sie auch aufrufbar sind.
+
+![Wikis 04](/help/de/member/pic/wiki04.png)
+
+Für eine kollaborative Bearbeitung eines Wikis ist es erforderlich, den Nutzern, welche an dem Wiki mitarbeiten dürfen, entsprechende Rechte einzuräumen. Dies tut man in den Fällen eines öffentlichen, persönlichen oder Foren-Kanals mittels einer entsprechenden Kontaktrolle, in welcher man die Bearbeitung der Wiki-Seiten erlaubt. Diese Berechtigung ist nämlich bei den genannten Kanalrollen als Standard nicht erteilt (bei einer benutzerdefinierten Kanalrolle kann man die Genehmigung erteilen, sie gilt dann aber generell und kann mittels einer Kontaktrolle nicht wieder entzogen werden).
+
+Will man einzelne Wikis davon ausnehmen, muss man deren Sichtbarkeit über die Berechtigungs-Einstellungen des Wikis (Vorhangschloss) einschränken.
diff --git a/doc/de/member_faq.md b/doc/de/member_faq.md
new file mode 100644
index 000000000..029d45eba
--- /dev/null
+++ b/doc/de/member_faq.md
@@ -0,0 +1,17 @@
+### Hubzilla FAQ
+
+#### Ich kann den Text eines Beitrags nach dem Speichern bearbeiten, aber gibt es eine Möglichkeit, die Berechtigungen zu ändern?
+
+Kurze Antwort: Nein, das geht nicht. Dafür gibt es Gründe. Sie können die Berechtigungen für Ihre Dateien, Fotos und Ähnliches ändern, aber nicht für Beiträge, nachdem Sie diese gespeichert haben. Der Hauptgrund dafür ist: Sobald Sie einen Beitrag gespeichert haben, wird er entweder an den öffentlichen Kanal und von dort an andere Hubzilla-Server oder an diejenigen verteilt, für die er bestimmt war. Genauso wie Sie etwas, das Sie einer anderen Person gegeben haben, nicht zurückfordern können, können Sie die Berechtigungen für Hubzilla-Beiträge nicht ändern. Wir müssten überall nachverfolgen, wohin Ihre Beiträge gelangen, alle Personen erfassen, denen Sie die Anzeige erlaubt haben, und dann nachverfolgen, von wem wir sie löschen müssen. Bei öffentlichen Beiträgen ist dies noch schwieriger, da Hubzilla ein globales Netzwerk ist und es keine Möglichkeit gibt, einen Beitrag zu verfolgen, geschweige denn zuverlässig zurückzuholen. Andere Netzwerke, die Ihren Beitrag erhalten haben, haben keine zuverlässige Möglichkeit, ihn zu löschen oder zurückzuholen.
+
+#### Ich habe meinen Kanal heruntergeladen und auf eine andere Website importiert (meine Identität geklont), aber es gibt keine Inhalte, keine Beiträge, keine Fotos. Was ist los???
+
+Beiträge und Fotos/Dateien werden getrennt von den grundlegenden Kanalinformationen bereitgestellt. Dies ist aufgrund von Speicherbeschränkungen im Zusammenhang mit jahrelangen Unterhaltungen und Fotoarchiven erforderlich. Beiträge und Unterhaltungen können separat von den grundlegenden Kanalinformationen synchronisiert werden. Fotos und Dateiarchive können mit einem Plugin-Tool wie „redfiles“ übertragen werden, das derzeit als „experimentell“ gekennzeichnet ist. Bei der Entwicklung dieser Funktion war es uns wichtig, dass alle Ihre Kontakte erhalten bleiben. Ihre Freunde haben Ihre alten Inhalte bereits gesehen. Beiträge/Konversationen hatten die nächste Priorität und können nun synchronisiert werden. Dateien und Fotos sind der letzte Punkt, der noch vollständig funktioniert. Sobald wir jemanden finden, der die Implementierung fertigstellen möchte, wird dies umgesetzt. :)
+
+#### Ich kann private Ressourcen nicht sehen
+
+Sie haben wahrscheinlich Cookies von Drittanbietern deaktiviert. Sie müssen diese aktivieren, damit die Remote-Authentifizierung funktioniert.
+
+#### Es gibt viele Beiträge in Fremdsprachen. Lassen Sie uns diese automatisch übersetzen.
+
+Es gibt auch viele **private** Beiträge in Fremdsprachen, und automatische Übersetzungsdienste würden erfordern, dass wir diese privaten Nachrichten an den Übersetzungsdienst übermitteln; und wir wissen nicht, was dieser mit ihnen auf seinen Servern macht. Dank Edward Snowden wissen wir das eigentlich. Unsere beste Option ist ein Projekt namens ***Apertium\***, ein Open-Source-Übersetzungsprogramm, das wir lokal installieren können. Derzeit fehlen noch deutsche Übersetzungen – die am häufigsten nachgefragten Übersetzungen in der Matrix. Auch dies wird implementiert, sobald wir jemanden finden, der sich wirklich dafür engagieren möchte. \ No newline at end of file
diff --git a/doc/de/members.bb b/doc/de/members.bb
deleted file mode 100644
index c85855f62..000000000
--- a/doc/de/members.bb
+++ /dev/null
@@ -1,25 +0,0 @@
-[h2]Dokumentation für Hub-Mitglieder[/h2]
-
-[h3]Erste Schritte[/h3]
-[zrl=[baseurl]/help/registration]Ein Konto registrieren[/zrl]
-[zrl=[baseurl]/help/accounts_profiles_channels_basics]Du im Hubzilla-Netzwerk: Konten, Profile und Kanäle kurz erklärt[/zrl]
-[zrl=[baseurl]/help/profiles]Profile[/zrl]
-[zrl=[baseurl]/help/channels]Kanäle[/zrl]
-[zrl=[baseurl]/help/roles]Zugriffsrechte-Kategorien und Kanaltypen[/zrl]
-[zrl=[baseurl]/help/first-post]Dein erster Beitrag[/zrl]
-[zrl=[baseurl]/help/connecting_to_channels]Sich mit anderen Kanälen verbinden[/zrl]
-[zrl=[baseurl]/help/permissions]Zugriffsrechte und Verschlüsselung: Du hast alles unter Kontrolle[/zrl]
-[zrl=[baseurl]/help/cloud]Cloud-Speicher[/zrl]
-[zrl=[baseurl]/help/remove_account]Einen Kanal oder das ganze Konto löschen[/zrl]
-
-[h3]Hilfe für $Projectname-Mitglieder[/h3]
-[zrl=[baseurl]/help/tags_and_mentions]Tags und Erwähnungen[/zrl]
-[zrl=[baseurl]/help/webpages]Webseiten[/zrl]
-[zrl=[baseurl]/help/bbcode]BBcode-Referenz für Beiträge und Kommentare[/zrl]
-[zrl=[baseurl]/help/checking_account_quota_usage]Überprüfung der Kontenlimits[/zrl]
-[zrl=[baseurl]/help/cloud_desktop_clients]Desktop-Anwendungen und die Cloud[/zrl]
-[zrl=[baseurl]/help/AdvancedSearch]Fortgeschrittene Suche im Kanalverzeichnis[/zrl]
-[zrl=[baseurl]/help/addons]Hilfe zu Addons[/zrl]
-[zrl=[baseurl]/help/diaspora_compat]Kompatibilität zum Diaspora-Protokoll (zur Kommunikation mit Kontakten aus Diaspora und Friendica)[/zrl]
-[zrl=[baseurl]/help/faq_members]FAQ für Mitglieder[/zrl]
-[zrl=[baseurl]/help/bugs]Bugs, Probleme und Sachen, die einem um die Ohren fliegen können[/zrl]
diff --git a/doc/de/pic/ui01.png b/doc/de/pic/ui01.png
new file mode 100644
index 000000000..1bae614be
--- /dev/null
+++ b/doc/de/pic/ui01.png
Binary files differ
diff --git a/doc/de/pic/ui02.png b/doc/de/pic/ui02.png
new file mode 100644
index 000000000..59db0c314
--- /dev/null
+++ b/doc/de/pic/ui02.png
Binary files differ
diff --git a/doc/de/platzhalter.md b/doc/de/platzhalter.md
new file mode 100644
index 000000000..7abe6f7ce
--- /dev/null
+++ b/doc/de/platzhalter.md
@@ -0,0 +1 @@
+# Platzhalter \ No newline at end of file
diff --git a/doc/de/profiles.bb b/doc/de/profiles.bb
deleted file mode 100644
index 7860ad47c..000000000
--- a/doc/de/profiles.bb
+++ /dev/null
@@ -1,40 +0,0 @@
-[h3]Profile[/h3]
-
-In $Projectname kannst Du beliebig viele Profile anlegen. Du kannst mehrere Profile nutzen, um verschiedenen Kontakten und Profilbesuchern unterschiedliche Seiten Deiner Persönlichkeit zu zeigen. Das ist nicht das gleiche wie das Anlegen mehrerer [i]Kanäle.[/i]
-
-Mehrere Kanäle erlauben es, komplett voneinander getrennte Informationen zu verwalten. Du könntest zum Beispiel einen Kanal für Dich selbst anlegen, einen für Deinen Schwimmverein, einen für Dein Blog und so weiter und so fort.
-
-Ein Profil erlaubt es, unterschiedlichen Leuten unterschiedliche Informationen über einen Kanal zu präsentieren. Beispiel: In Deinem Standard-Profil, das alle Profilbesucher zu sehen bekommen, könnte nur Dein Vorname stehen, sonst keine weiteren Informationen. Freunde und Bekannte könnten Deinen kompletten Realnamen und Deine Postadresse zu sehen bekommen, und im Profil für Deinen engsten Freundeskreis könnten peinliche Hobbies und Trinkrekorde ihren Platz finden.
-
-Jeder Kanal hat ein Standard-Profil (öffentliches Profil), das vor niemandem versteckt werden kann (außer evtl. auf privaten Servern, die nicht mit der Matrix verbunden sind). Du kannst und solltest Dich in der Auswahl dessen, was Du im Standard-Profil von Dir preisgibst, stark einschränken.
-
-Trotzdem: Wenn Du willst, dass Freunde Dich finden können, macht es Sinn, folgende Informationen ins öffentliche Profil einzutragen:
-
-[ul][*] Deinen Realnamen oder zumindest einen Spitznamen, unter dem Dich jeder kennt
-[*] Ein Foto, das auch wirklich Dich zeigt
-[*] Deinen Wohnort, oder zumindest Land oder Bundesland[/ul]
-
-Wenn Du Leute kennenlernen möchtest, die ähnliche Interessen haben wie Du, nimm Dir einen Moment Zeit und füge einige Schlüsselwörter hinzu. Zum Beispiel „Musik, Linux, Fotografie“ oder was auch immer. Danach können andere dann im Verzeichnis suchen. Du kannst so viele Schlüsselwörter eingeben wie Du möchtest.
-
-Um alternative Profile zu erstellen, besuche zunächst die Seite [zrl=[baseurl]/settings/features]Einstellungen > Zusätzliche Funktionen[/zrl] und aktiviere dort „Mehrfachprofile“. Ohne diese Aktivierung hast Du nur ein Profil, nämlich Dein Standard-Profil.
-
-Klicke dann auf „Profile bearbeiten“ im Menü Deines $Projectname-Servers. Dort kannst Du existierende Profile bearbeiten, Dein Profilfoto verändern, Dinge zu einem Profil hinzufügen oder ein neues Profil erstellen. Du kannst auch ein Profil „klonen“, wenn Du nur einige wenige Einträge ändern willst, ohne die ganzen Informationen noch einmal einzugeben. Klicke dazu auf das Profil, das Du klonen willst, und wähle dann „Dieses Profil klonen“.
-
-In der Liste Deiner Profile kannst Du auch bestimmen, wer ein bestimmtes Profil zu sehen bekommt. Klicke dazu auf „Sichtbarkeit bearbeiten“ neben dem Profil, um das es geht (gibt es nur bei Profilen, die nicht Dein Standard-Profil sind). Klicke dann auf die Bilder derjenigen Kontakte, die dieses Profil sehen sollen – sie sind dann oben zu sehen. Wenn Du oben auf ein Bild klickst, wird dieser Kontakt wieder aus der Gruppe derjenigen herausgenommen, die dieses Profil zu sehen bekommen.
-
-Hast Du einem Kontakt ein Profil zugeordnet, wird er immer dieses Profil sehen, wenn er sich Dein Profil ansieht. Besucht er Deinen $Projectname-Server, ohne sich anzumelden, sieht er aber weiterhin Dein Standard-Profil.
-
-Auf der allgemeinen „Einstellungen“-Seite gibt es eine Einstellung, mit der Du festlegen kannst, ob Dein Standard-Profil in den $Projectname-Verzeichnissen veröffentlicht werden soll.
-
-Wenn Du nicht möchtest, dass andere Dich finden können, ohne dass Du ihnen Deine Kanal-Adresse gibst, kannst Du so verhindern, dass Dein Profil veröffentlicht wird.
-
-[b]Schlüsselwörter und Verzeichnissuche[/b]
-
-Im Verzeichnis (Kanal-Verzeichnis) kannst Du nach Leuten suchen, die ihre Profile veröffentlichen. Zum Beispiel, indem Du Namen oder Spitznamen eingibst. Aktuell werden nur das Namensfeld und die Schlüsselwörter durchsucht. Wenn Du Schlüsselwörter in Dein Standard-Profil einträgst, können Dich Leute mit ähnlichen Interessen finden. Sie werden außerdem bei den Kanal-Vorschlägen benutzt. Sie sind im Verzeichnis nicht direkt sichtbar, wohl aber auf Deiner Profil-Seite.
-
-Auf Deiner „Verbindungen“-Seite und im Verzeichnis gibt es einen Link „Vorschläge“ bzw. „Kanal-Vorschläge“. Dort findest Du Kanäle, die gleiche oder ähnliche Schlüsselwörter im Profil haben wie Du. Je mehr Schlüsselwörter Du in Dein Standard-Profil einträgst, desto besser werden die Suchergebnisse. Sie sind nach Relevanz sortiert.
-
-Siehe auch:
-
-[zrl=[baseurl]/help/AdvancedSearch]Fortgeschrittene Suche[/zrl]
-#include doc/macros/main_footer.bb;
diff --git a/doc/de/registration.bb b/doc/de/registration.bb
deleted file mode 100644
index ac24782a6..000000000
--- a/doc/de/registration.bb
+++ /dev/null
@@ -1,36 +0,0 @@
-[h3]Registrieren[/h3]
-
-Nicht alle $Projectname-Hubs erlauben jedem, sich zu registrieren. Wenn eine Registrierung möglich ist, erscheint unter dem Anmelde-Formular ein Link mit dem Titel „Registrieren“, der Dich zur Registrierungs-Seite des Hubs führt. Auf manchen Hubs wirst Du auf einen anderen Hub weitergeleitet, der Registrierungen erlaubt. Da alle $Projectname-Hubs miteinander verbunden sind, ist es egal, auf welchem Du Dich registrierst.
-
-[b]Deine E-Mail-Adresse[/b]
-
-Bitte gib eine funktionierende E-Mail-Adresse an. Sie wird [b]nie[/b] veröffentlicht. Sie wird benutzt, um Dein Konto zu aktivieren, und wenn Du willst, kannst Du Dir an diese Adresse Benachrichtigungen schicken lassen, wenn Du neue Nachrichten oder Beiträge erhalten hast. Sie wird außerdem benutzt, um vergessene Passwörter wiederherstellen zu können.
-
-[b]Passwort[/b]
-
-Gib ein Passwort Deiner Wahl ein und wiederhole es in der zweiten Box, um sicherzugehen, dass Du Dich nicht vertippt hast. Da $Projectname dezentralisierten Identitäsnachweis beherrscht, kannst Du Dich mit Deinem Konto auf vielen anderen Webseiten anmelden.
-
-[b]Nutzungsbedingungen[/b]
-
-Klicke auf den Link, um die [zrl=[baseurl]/help/TermsOfService]Nutzungsbedingungen[/zrl] dieses Servers zu lesen. Wenn Du sie gelesen hast, setze den Haken im Registrierungsformular, um sie zu akzeptieren.
-
-[b]Registrieren[/b]
-
-Wenn Du alles ausgefüllt hast, klicke auf den „Registrieren“-Knopf. Bei manchen Servern muss der Administrator Deiner Registrierung erst noch zustimmen, bevor Du Dein Konto nutzen kannst. Falls das der Fall ist, wird Dir das entsprechend angezeigt. Du erhältst dann eine E-Mail, wenn die Registrierung vollendet wurde. (Sicherheitshalber auch Spam-Ordner überprüfen!)
-
-[b]Einen Kanal anlegen[/b]
-
-Als nächstes kommst Du direkt auf den „Kanal hinzufügen“-Bildschirm. Dein erster Kanal wird normalerweise der sein, der Dich selbst repräsentiert. Es ist also sinnvoll, Deinen Namen oder Dein Pseudonym als Kanal-Namen zu verwenden.
-
-Der Kanal-Name ist der Titel oder eine kurze Beschreibung des Kanals. Der „Spitzname“ ist so etwas wie ein Benutzername. Was Du hier eingibst wird Teil Deiner Kanal-Adresse, die Andere benutzen, um sich mit Dir zu verbinden, und die Du selbst benutzt, um Dich auf anderen Servern anzumelden. Die Kanal-Adresse sieht aus wie eine E-Mail-Adresse, zum Beispiel so: spitzname@deinserver.xyz
-
-Wenn Dein Kanal angelegt ist, geht es direkt weiter zu den Einstellungen. Dort kannst Du Zugriffsrechte setzen, Funktionen zu- oder abschalten und so weiter. Diese Punkte werden auf den entsprechenden Hilfeseiten erklärt.
-
-Siehe auch
-[zrl=[baseurl]/help/accounts_profiles_channels_basics]Grundlagen zu Identitäten in $Projectname[/zrl]
-[zrl=[baseurl]/help/accounts]Konten[/zrl]
-[zrl=[baseurl]/help/profiles]Profile[/zrl]
-[zrl=[baseurl]/help/permissions]Zugriffsrechte[/zrl]
-[zrl=[baseurl]/help/remove_account]Konto löschen[/zrl]
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/de/roles.bb b/doc/de/roles.bb
deleted file mode 100644
index d1591ff17..000000000
--- a/doc/de/roles.bb
+++ /dev/null
@@ -1,60 +0,0 @@
-[h3]Kanal-Berechtigungsrollen[/h3]
-
-Wenn Du einen neuen Kanal erstellst, wirst Du aufgefordert, eine Berechtigungsrolle auszuwählen, je nachdem, wie Du diesen Kanal nutzen möchtest. Die beliebtesten Berechtigungsrollen sind die Rollen für soziale Netzwerke. Sie haben viele weitere Auswahlmöglichkeiten, die mit Facebook-Gruppen und -Seiten, kollaborativen Bereichen, Newsfeeds und mehr vergleichbar sind. Diese Rollen konfigurieren automatisch verschiedene Systemvariablen, von den Berechtigungen, die Freunden gewährt werden, bis hin zu den Standardeinstellungen für Privatsphäre und Sichtbarkeit. Es stehen erweiterte Konfigurationen zur Verfügung, mit denen Du jeden dieser Parameter an Ihre Bedürfnisse anpassen kannst, aber wir haben die Erfahrung gemacht, dass die meisten Nutzer es vorziehen, die Einstellungen vorzunehmen und sie vergessen. Im Folgenden werden einige der verschiedenen Rollen beschrieben, die derzeit verfügbar sind, und wie sie sich auf Ihre Privatsphäre und Ihre Interaktionsmöglichkeiten auswirken.
-
-
-[h4]Soziales Netzwerk[/h4]
-[b]Föderation[/b]
-
-Der Kanal ist ein sehr freizügiges soziales Netzwerkprofil, das mit anderen föderierten sozialen Netzwerken kompatibel ist. Die Genehmigungsrichtlinien sind ähnlich wie bei Twitter und größtenteils kompatibel mit Diaspora und Mastodon. Die Privatsphäre hat eine geringere Priorität als der einfache Zugang und die Verbindung mit anderen. Jeder im Netzwerk kann Deine öffentlichen Beiträge kommentieren und Dir private Nachrichten schicken. Standardmäßig sind Beiträge und veröffentlichte Artikel öffentlich, aber Du Kannst dies bei der Erstellung des Artikels überschreiben und einschränken. Du bist im Verzeichnis aufgeführt. Deine Online-Präsenz und Deine Verbindungen sind für andere sichtbar. Dieser Modus kann dazu führen, dass Du unerwünschten Mitteilungen und Spam ausgesetzt bist. Diese Rolle wird im Allgemeinen nicht empfohlen, es sei denn, Du musst regelmäßig mit Mitgliedern anderer Netzwerke interagieren.
-
-[b]Weitgehend öffentlich[/b]
-
-Der Kanal ist ein typisches Profil eines sozialen Netzwerks. Standardmäßig sind Beiträge und veröffentlichte Elemente öffentlich, aber Du kannst dies bei der Erstellung des Elements/Beitrags außer Kraft setzen und einschränken. Du bist im Verzeichnis aufgeführt. Deine Online-Präsenz und Deine Verbindungen sind für andere sichtbar. Nur Ihre unmittelbaren Verbindungen können Deine öffentlichen Beiträge kommentieren und Dir private Nachrichten schicken. Die Berechtigungsrichtlinien sind ähnlich wie bei Facebook.
-
-[b]Eingeschränkt[/b]
-
-Standardmäßig werden alle Beiträge und veröffentlichten Elemente an Deine Privatsphärengruppe "Freunde" gesendet und nicht öffentlich gemacht. Neue Freunde werden zu dieser privaten Gruppe hinzugefügt. Du kannst dies außer Kraft setzen und einen öffentlichen Beitrag oder ein veröffentlichtes Element erstellen, wenn Du dies wünschst. Du bist im Verzeichnis aufgeführt. Deine Online-Präsenz (für den Chat) und Deine Verbindungen (Freunde) sind für die Betrachter Deines Profils sichtbar.
-
-[b]Privat[/b]
-
-Standardmäßig werden alle Beiträge und veröffentlichten Elemente an Deine private Gruppe "Freunde" gesendet. Neue Freunde werden zu dieser privaten Gruppe hinzugefügt. Du kannst dies außer Kraft setzen und einen öffentlichen Beitrag oder ein öffentliches Element erstellen, wenn Du dies wünschst. Du bist NICHT im Verzeichnis aufgeführt. Nur Deine Verbindungen können Deine anderen Verbindungen sehen. Deine Online-Präsenz ist verborgen.
-
-
-[h4]Forum[/h4]
-[b]Weitgehend öffentlich[/b]
-
-Der Kanal ist ein typisches Forum. Standardmäßig sind Beiträge und veröffentlichte Artikel öffentlich. Mitglieder können durch @!mention oder wall-to-wall posten. Das Einstellen von Fotos und anderen veröffentlichten Beiträgen ist gesperrt. Der Kanal ist im Verzeichnis sichtbar. Mitglieder werden automatisch hinzugefügt.
-
-[b]Eingeschränkt[/b]
-
-Standardmäßig werden alle Beiträge und veröffentlichten Elemente an die Datenschutzgruppe "Freunde" des Channels gesendet. Neue Freunde werden zu dieser privaten Gruppe hinzugefügt. Mitglieder können Beiträge per @!mention oder wall-to-wall posten, aber Beiträge und Antworten können auch von anderen Empfängern des Top-Level-Posts gesehen werden, die keine Mitglieder sind. Der Channel ist im Verzeichnis sichtbar. Mitglieder müssen manuell durch den Forenbesitzer hinzugefügt werden.
-
-[b]Privat[/b]
-
-Standardmäßig werden alle Beiträge und veröffentlichten Nachrichten an Deine Privatsphärengruppe "Freunde" gesendet. Neue Freunde werden zu dieser privaten Gruppe hinzugefügt. Der Eigentümer kann dies außer Kraft setzen und einen öffentlichen Beitrag oder ein öffentliches Element erstellen, falls gewünscht. Mitglieder können das nicht. Sie sind NICHT im Verzeichnis aufgeführt. Nur Deine Verbindungen können Deine anderen Verbindungen sehen. Deine Online-Präsenz ist verborgen. Mitglieder müssen manuell vom Forumsbesitzer hinzugefügt werden. Das Posten per @!mention ist deaktiviert. Beiträge können nur über Wand-zu-Wand-Beiträge erstellt und an Mitglieder der Privatsphärengruppe "Freunde" gesendet werden. Du bist nicht öffentlich sichtbar.
-
-
-[h4]Teilen von Feed[/h4]
-[b]Weitgehend öffentlich[/b]
-
-Ähnlich wie Sozial: Meistens öffentlich, aber für RSS-Feed-Quellen zugeschnitten. Artikel können frei wiederveröffentlicht und weiterverwendet werden. Die Online-Präsenz ist bedeutungslos, daher verborgen. Neue Verbindungen werden automatisch genehmigt.
-
-[b]Eingeschränkt[/b]
-
-Nicht im Verzeichnis aufgeführt. Die Online-Präsenz ist bedeutungslos und daher verborgen. Der Feed wird nur für Mitglieder der Datenschutzgruppe "Freunde" veröffentlicht. Neue Verbindungen werden automatisch zu dieser privaten Gruppe hinzugefügt. Mitglieder müssen manuell vom Kanalbesitzer genehmigt werden.
-
-
-[h4]Für spezielle Zwecke[/h4]
-[b]Speziell - Mitteilungskanal (keine Kommentare)[/b]
-
-Aufgeführt im Verzeichnis. Die Kommunikation ist standardmäßig öffentlich. Die Online-Präsenz ist verborgen. Kommentare oder Rückmeldungen jeglicher Art sind nicht erlaubt, obwohl Verbindungen die Möglichkeit haben, Ihr Profil zu "mögen".
-
-[b]Speziell - Gruppen-Archiv[/b]
-
-Ein öffentliches Forum, das es Mitgliedern erlaubt, Dateien/Fotos/Webseiten zu veröffentlichen.
-
-
-[h4]Benutzerdefinierter/Expertenmodus[/h4]
-Legen Sie alle Datenschutzeinstellungen und Berechtigungen manuell fest, um sie an Ihre speziellen Bedürfnisse anzupassen.
-
diff --git a/doc/de/the_hubzilla_project.md b/doc/de/the_hubzilla_project.md
new file mode 100644
index 000000000..6f7eec745
--- /dev/null
+++ b/doc/de/the_hubzilla_project.md
@@ -0,0 +1,116 @@
+## Das Hubzilla-Projekt
+
+### Hubzilla Verwaltung
+
+Governance bezieht sich auf die Verwaltung eines Projekts und insbesondere darauf, wie diese mit der Konfliktlösung zusammenhängt.
+
+
+
+#### Gemeinschaftliche Verwaltung
+
+Das Projekt wird von der „Gemeinschaft“ verwaltet und Entscheidungen werden von ihr getroffen. Die Governance-Struktur ist noch in der Entwicklung begriffen. Bis zur endgültigen Festlegung der Struktur werden Entscheidungen in der folgenden Reihenfolge getroffen:
+
+
+
+1. Fauler Konsens
+
+ Wenn ein Projektvorschlag in einem der Community-Governance-Foren unterbreitet wird und innerhalb einer „angemessenen“ Zeitspanne ab dem Datum des Vorschlags (wir geben in der Regel allen interessierten Parteien 2 bis 3 Tage Zeit, sich zu äußern) keine ernsthaften Einwände erhoben werden, muss nicht abgestimmt werden und der Vorschlag gilt als angenommen. Zu diesem Zeitpunkt können zwar Bedenken geäußert werden, aber wenn diese in der Diskussion ausgeräumt werden und Lösungsmöglichkeiten angeboten werden, gilt der Vorschlag dennoch als genehmigt.
+
+
+
+2. Veto
+
+ Erfahrene Entwickler, die bereits viele Projekte zugesagt haben, können gegen jede Entscheidung ein Veto einlegen. Die Entscheidung kann nicht weitergeführt werden, bis das Veto aufgehoben oder ein alternativer Vorschlag vorgelegt wird.
+
+
+
+3. Gemeinschaftsabstimmung
+
+ Eine Entscheidung, für die es kein klares Mandat oder keinen klaren Konsens gibt, gegen die aber kein Veto eingelegt wurde, kann der Gemeinschaft zur Abstimmung vorgelegt werden. Zurzeit ist dies eine einfache Volksabstimmung in einem der entsprechenden Gemeinschaftsforen. Zurzeit entscheidet das Volk über das Ergebnis. Dies kann sich in Zukunft ändern, wenn die Gemeinschaft ein „Rats“-Verwaltungsmodell einführt. Dieses Dokument wird zu diesem Zeitpunkt mit den aktualisierten Verwaltungsregeln aktualisiert werden.
+
+
+
+Die Abstimmung in der Gemeinschaft führt nicht immer zu einem angenehmen Ergebnis und kann zu polarisierten Fraktionen in der Gemeinschaft führen (daher werden auch andere Modelle in Betracht gezogen). Wenn der Vorschlag abgelehnt wird, gibt es immer noch verschiedene Möglichkeiten, den Vorschlag mit leicht veränderten Parametern erneut einzureichen (Umwandlung in ein Addon, Umwandlung in eine optionale Funktion, die standardmäßig deaktiviert ist, usw.). Wenn das Interesse an der Funktion groß ist und die Abstimmung „knapp“ ausfällt, kann dies bei den unterlegenen Wählern viele schlechte Gefühle hervorrufen. Bei solchen knappen Abstimmungen wird **dringend empfohlen**, dass der Antragsteller Maßnahmen ergreift, um alle Bedenken auszuräumen, und den Antrag erneut einreicht.
+
+
+
+
+
+#### Datenschutz
+
+F: Wer kann meinen Inhalt sehen?
+
+A: Standardmäßig JEDER im Internet, es sei denn, Sie schränken dies ein. Bei Hubzilla können Sie die von Ihnen gewünschte Datenschutzstufe wählen. Eingeschränkte Inhalte sind für „Spionagenetzwerke“ und Werbetreibende NICHT sichtbar. Sie werden gegen das Abhören durch Außenstehende geschützt - so gut es eben geht. Hub-Administratoren mit ausreichenden Kenntnissen und Geduld können möglicherweise einige private Kommunikationen abhören, aber sie müssen sich dafür anstrengen. Innerhalb von Hubzilla gibt es Privatsphären-Modi, die selbst für erfahrene und entschlossene Hub-Administratoren abhörsicher sind.
+
+F: Kann mein Inhalt zensiert werden?
+
+A: Hubzilla (das Netzwerk) kann Ihre Inhalte NICHT zensieren. Server- und Hub-Administratoren unterliegen den örtlichen Gesetzen und KÖNNEN anstößige Inhalte von ihrer Site/ihrem Hub entfernen. Jeder KANN Hub-Administrator werden, auch Sie, und daher Inhalte veröffentlichen, die andernfalls zensiert werden könnten. Sie KÖNNEN immer noch den örtlichen Gesetzen unterliegen.
+
+
+
+##### Definitionen
+
+**Hubzilla**
+
+Hubzilla, auch als „das Netzwerk“ bezeichnet, ist eine Sammlung von einzelnen Computern/Servern (auch **Hubs** genannt), die miteinander verbunden sind und ein größeres kooperatives Netzwerk bilden.
+
+**Hub**
+
+Ein einzelner Computer oder Server, der mit Hubzilla verbunden ist. Diese werden von einem **Hub-Administrator** bereitgestellt und können öffentlich oder privat, bezahlt oder kostenlos sein.
+
+**Hub-Administrator**
+
+Der Systembetreiber eines einzelnen Hubs.
+
+
+
+##### Richtlinien
+
+**Öffentliche Informationen**
+
+Alle von Ihnen innerhalb von Hubzilla eingestellten Informationen oder Inhalte KÖNNEN öffentlich oder für jeden im Internet sichtbar sein. Soweit dies möglich ist, erlaubt Hubzilla Ihnen, Inhalte zu schützen und einzuschränken, wer sie sehen kann.
+
+Ihr Profilfoto, Ihr Channelname und der Standort (URL oder Netzwerkadresse) Ihres Channels sind für jedermann im Internet sichtbar, und Datenschutzkontrollen haben keinen Einfluss auf die Anzeige dieser Elemente.
+
+Du KANNST zusätzlich andere Profilinformationen zur Verfügung stellen. Alle Informationen, die Sie in Ihrem „Standard“- oder **öffentlichen Profil** angeben, KÖNNEN an andere Hubs in Hubzilla übermittelt werden und zusätzlich im Channel-Verzeichnis angezeigt werden. Sie können die Einsicht in diese Profilinformationen einschränken. Sie können es nur auf Mitglieder Ihres Hubs, nur auf Verbindungen (Freunde) oder auf andere begrenzte Gruppen von Betrachtern beschränken, wie Sie es wünschen. Wenn Sie möchten, dass Ihr Profil eingeschränkt wird, müssen Sie die entsprechenden Datenschutzeinstellungen vornehmen oder einfach KEINE zusätzlichen Informationen angeben.
+
+**Inhalt**
+
+Die von Ihnen bereitgestellten Inhalte (Statusmeldungen, Fotos, Dateien usw.) gehören Ihnen. Die Standardeinstellung von Hubzilla ist, dass Inhalte offen und für jeden im Internet sichtbar veröffentlicht werden (PUBLIC). Sie KÖNNEN dies in Ihren Channel-Einstellungen steuern und die Standardberechtigungen einschränken oder Sie KÖNNEN die Sichtbarkeit jedes einzelnen veröffentlichten Elements separat einschränken (PRIVAT). Die Hubzilla-Entwickler stellen sicher, dass eingeschränkte Inhalte NUR für diejenigen sichtbar sind, die in der Einschränkungsliste aufgeführt sind - so gut sie können.
+
+Inhalte (insbesondere Statusmeldungen), die Sie mit anderen Netzwerken teilen oder die Sie für jeden im Internet sichtbar gemacht haben (PUBLIC), können nicht einfach zurückgenommen werden, nachdem sie veröffentlicht wurden. Sie KÖNNEN mit anderen Netzwerken geteilt und über RSS/Atom-Feeds verfügbar gemacht werden. Sie können auch auf anderen Hubzilla-Sites syndiziert werden. Sie KÖNNEN in anderen Netzwerken und Websites erscheinen und in der Internetsuche sichtbar sein. Wenn Sie dieses Standardverhalten nicht wünschen, passen Sie bitte Ihre Kanaleinstellungen an und schränken Sie ein, wer Ihre Inhalte sehen kann.
+
+**Kommentare und Forenbeiträge**
+
+Kommentare zu Beiträgen, die von anderen erstellt wurden, und Beiträge, die als Forenbeiträge gekennzeichnet sind, gehören Ihnen als Ersteller/Autor, aber die Verbreitung dieser Beiträge unterliegt nicht Ihrer direkten Kontrolle, und Sie geben EINIGE Rechte an diesen Elementen ab. Diese Beiträge/Kommentare KÖNNEN an andere weitergegeben werden und KÖNNEN für jeden im Internet sichtbar sein. Im Falle von Kommentaren kontrolliert der Ersteller der „ersten Nachricht“ in dem Thread (Gespräch), auf den Sie antworten, die Verbreitung aller Kommentare und Antworten auf diese Nachricht. Er ist „Eigentümer“ und hat daher bestimmte Rechte in Bezug auf die gesamte Unterhaltung (einschließlich aller darin enthaltenen Kommentare). Sie können den Kommentar immer noch bearbeiten oder löschen, aber der Eigentümer der Konversation hat auch das Recht, den gesamten Inhalt der Konversation zu bearbeiten, zu löschen, weiterzuverteilen und zu sichern/wiederherzustellen.
+
+**Private Informationen**
+
+Die Hubzilla-Entwickler stellen sicher, dass alle von Ihnen bereitgestellten Inhalte, die als PRIVAT gekennzeichnet sind, nach bestem Wissen und Gewissen gegen Abhören geschützt werden. Private Channel-Inhalte KÖNNEN in der Datenbank jedes beteiligten Hub-Administrators gesehen werden, aber private Nachrichten werden in der Datenbank unkenntlich gemacht. Letzteres bedeutet, dass es sehr schwierig, aber NICHT unmöglich ist, dass diese Inhalte von einem Hub-Administrator gesehen werden können. Private Kanalinhalte und private Nachrichten werden auch aus den E-Mail-Benachrichtigungen entfernt. Die Ende-zu-Ende-Verschlüsselung ist eine optionale Funktion, die selbst von einem entschlossenen Administrator NICHT eingesehen werden kann.
+
+
+
+##### Identitätsschutz
+
+Der Schutz Ihrer Identität ist ein weiterer Aspekt. Da Sie in Hubzilla eine dezentrale Identität haben, geht Ihre Privatsphäre über Ihren Home Hub hinaus. Wenn Sie die vollständige Kontrolle über Ihre Privatsphäre und Sicherheit haben möchten, sollten Sie Ihren eigenen Hub auf einem eigenen Server betreiben. Für viele Menschen ist dies kompliziert und kann ihre technischen Fähigkeiten überfordern. Lassen Sie uns daher einige Vorsichtsmaßnahmen auflisten, die Sie ergreifen können, um Ihre Privatsphäre so gut wie möglich zu schützen.
+
+Eine dezentralisierte Identität hat viele Vorteile und bietet Ihnen viele interessante Funktionen, aber Sie sollten sich der Tatsache bewusst sein, dass Ihre Identität anderen Hubs im Hubzilla-Netzwerk bekannt ist. Einer dieser Vorteile ist, dass andere Kanäle Ihnen maßgeschneiderte Inhalte anbieten und Ihnen erlauben können, private Dinge zu sehen (wie z.B. private Fotos, die andere mit Ihnen teilen möchten). Aus diesem Grund müssen diese Kanäle wissen, wer Sie sind. Aber wir verstehen, dass diese anderen Kanäle manchmal mehr von Ihnen wissen, als Sie vielleicht wünschen. Zum Beispiel das Plug-in Visage, das einem Kanalbesitzer mitteilen kann, wann du das letzte Mal sein Profil besucht hast. Sie können sich ganz einfach von dieser geringen und, wie wir meinen, harmlosen Verfolgung abmelden.
+
+\* Sie können [Do Not Track (DNT)][(http://donottrack.us/)](http://donottrack.us/)?f=&zid=pepecyb@hub.hubzilla.hu) in Ihrem Webbrowser aktivieren. Wir respektieren diesen neuen Vorschlag zum Datenschutz. Alle modernen Browser unterstützen DNT. Sie finden es in den Datenschutzeinstellungen Ihres Browsers oder Sie können das Handbuch Ihres Webbrowsers konsultieren. Die Funktionalität von Hubzilla wird dadurch nicht beeinträchtigt. Diese Einstellung ist wahrscheinlich für die meisten Menschen ausreichend.
+
+*Sie können die Veröffentlichung Ihres Channels in unserem Channel-Verzeichnis [deaktivieren](Einstellungen). Wenn Sie möchten, dass andere Ihren Channel finden, sollten Sie ihnen Ihre Channel-Adresse direkt mitteilen. Wir denken, dass dies ein guter Hinweis darauf ist, dass du zusätzliche Privatsphäre bevorzugst und aktivieren automatisch „Do Not Track“, wenn dies der Fall ist.
+
+\* Sie können einen blockierten Hub haben. Das bedeutet, dass alle Kanäle und Inhalte in diesem Hub nicht öffentlich und für die Außenwelt nicht sichtbar sind. Dies kann nur Ihr Hub-Administrator tun. Wir respektieren dies ebenfalls und aktivieren automatisch „Do Not Track“, wenn es eingestellt ist.
+
+
+
+##### Zensur
+
+Hubzilla ist ein globales Netzwerk, das alle Religionen und Kulturen einschließt. Das bedeutet nicht, dass jedes Mitglied des Netzwerks in Bezug auf strittige Themen genauso denkt wie Sie, und einige Leute könnten sich den von Ihnen geposteten Inhalten GEGENWINDLICH widersetzen. Wenn Sie etwas posten möchten, von dem Sie wissen, dass es möglicherweise nicht von allen akzeptiert wird, ist es am besten, wenn Sie die Öffentlichkeit mit Hilfe der Privatsphäre-Kontrollen auf einen kleinen Freundeskreis beschränken.
+
+Hubzilla als Netzwerkanbieter kann keine Inhalte zensieren. Hub-Administratoren KÖNNEN jedoch Inhalte, die auf ihrem Hub erscheinen, zensieren, um lokalen Gesetzen oder sogar persönlichem Urteilsvermögen zu entsprechen. Ihre Entscheidung ist endgültig. Wenn Sie Probleme mit einem Hub-Administrator haben, können Sie Ihr Konto und Ihre Beiträge auf eine andere Seite verschieben, die Ihren Erwartungen besser entspricht. Bitte überprüfen Sie (regelmäßig) die [Terms of Service](help/TermsOfService) Ihres Hubs, um sich über etwaige Regeln oder Richtlinien zu informieren. Wenn Ihr Inhalt aus illegalem oder problematischem Material besteht, empfehlen wir Ihnen DRINGEND, Ihren eigenen Hub zu betreiben (werden Sie Hub-Administrator). Es kann sein, dass Ihre Inhalte auf einigen Hubs blockiert werden, aber Hubzilla als Netzwerk kann nicht verhindern, dass sie veröffentlicht werden.
+
+Hubzilla EMPFIEHLT, dass Hub-Administratoren eine Frist von 1-2 Tagen zwischen der Warnung eines Account-Inhabers über zu entfernende Inhalte und der physischen Entfernung oder Deaktivierung des Accounts einräumen. Dies gibt dem Inhaltsinhaber die Möglichkeit, seine Channel-Metadaten zu exportieren und auf eine andere Site zu importieren. In seltenen Fällen kann der Inhalt so beschaffen sein, dass die sofortige Kündigung des Kontos gerechtfertigt ist. Dies ist eine Hub-Entscheidung, nicht eine Hubzilla-Entscheidung.
+
+Wenn Sie typischerweise und regelmäßig Inhalte jugendgefährdender oder anstößiger Natur posten, wird Ihnen DRINGEND empfohlen, Ihr Konto als „NSFW“ (Not Safe For Work) zu kennzeichnen. Dadurch wird die Anzeige Ihres Profilfotos im Verzeichnis verhindert, außer für Betrachter, die den „sicheren Modus“ deaktiviert haben. Wenn Ihr Profilfoto von den Verzeichnisadministratoren als nicht jugendfrei oder anstößig eingestuft wird, KANN der Verzeichnisadministrator Ihr Profilfoto als NSFW kennzeichnen. Derzeit gibt es keinen offiziellen Mechanismus, um diese Entscheidung anzufechten oder rückgängig zu machen. Deshalb SOLLTEN Sie Ihr eigenes Konto als NSFW markieren, wenn es für ein allgemeines Publikum unangemessen sein könnte.
+
diff --git a/doc/de/toc.html b/doc/de/toc.html
new file mode 100644
index 000000000..959ee7ffe
--- /dev/null
+++ b/doc/de/toc.html
@@ -0,0 +1,104 @@
+<div class="" id="accordion">
+ <div class="mb-3">
+ <div class="">
+ <h3 class="panel-title">
+ Benutzer
+ </h3>
+ </div>
+ <div id="members" class="doco-section">
+ <div class="vstack">
+ <a href="/help/member/overview">Überblick</a>
+ <a href="/help/member/registration">Login/Registierung</a>
+ <a href="/help/member/accounts_profiles_channels_basics">Konten, Profile und Kanäle</a>
+ <a href="/help/member/posting">Posten</a>
+ <a href="/help/member/the_grid">Das Grid</a>
+ <a href="/help/member/the_stream">Der Stream</a>
+ <a href="/help/member/apps">Apps</a>
+ <a href="/help/member/connections">Verbindungen</a>
+ <a href="/help/member/directory">Verzeichnis</a>
+ <a href="/help/member/blocking_channels">Kanäle blockieren/ignorieren/archivieren/verstecken</a>
+ <a href="/help/member/permissions">Berechtigungen</a>
+ <a href="/help/member/direct_messages">Direktnachrichten</a>
+ <a href="/help/member/mentions">Erwähnungen</a>
+ <a href="/help/member/tags">Tags</a>
+ <a href="/help/member/bookmarks">Bookmarks (Lesezeichen)</a>
+ <a href="/help/member/search">Suche</a>
+ <a href="/help/member/article">Artikel</a>
+ <a href="/help/member/files">Dateien</a>
+ <a href="/help/member/chat_rooms">Chaträume</a>
+ <a href="/help/member/guest_access">Gastzugang</a>
+ <a href="/help/member/privacy_groups">Privacy Gruppen</a>
+ <a href="/help/member/calendar">Kalender</a>
+ <a href="/help/member/addressbook">Adressbuch</a>
+ <a href="/help/member/wikis">Wikis</a>
+ <a href="/help/member/NSFW">Inhaltswarnung/NSFW</a>
+ <a href="/help/member/clone">Klonen</a>
+ <a href="/help/member/websites">Websiten</a>
+ <a href="/help/member/comanche">Comanche Seitenbeschreibungssprache</a>
+ <a href="/help/member/encryption">Verschlüsselung</a>
+ <a href="/help/member/protection_of_privacy">Tipps zum Schutz der Privatsphäre</a>
+ <a href="/help/member/deleting_channel">Kanal löschen</a>
+ <a href="/help/member/delete_account">Account löschen</a>
+ <hr>
+ <a href="/help/bugs">Bugs melden</a>
+ <a href="/help/member/member_faq">FAQ</a>
+ </div>
+ </div>
+ </div>
+ <div class="mb-3">
+ <div class="">
+ <h3 class="panel-title">
+ Tutorials
+ </h3>
+ </div>
+ <div id="tutorials" class="doco-section">
+ <div class="vstack">
+ <a href="/help/tutorials/step_with_hubzilla">Mit Hubzilla Schritt für Schritt ins Fediverse</a>
+ <a href="/help/tutorials/customise_look">Hubzilla optisch anpassen</a>
+ <a href="/help/tutorials/DerivedTheme">Hubzilla optisch anpassenEin abgeleitetes Thema erstellen</a>
+ </div>
+ </div>
+ </div>
+ <div class="mb-3">
+ <div class="">
+ <h3 class="panel-title">
+ Administratoren
+ </h3>
+ </div>
+ <div id="administrators" class="doco-section">
+ <div class="vstack">
+ <a class="" href="/help/adminmanual/manual_for_administrators">Handbuch für Administratoren</a>
+ </div>
+ </div>
+ </div>
+ <div class="mb-3">
+ <div class="">
+ <h3 class="panel-title">
+ Entwickler
+ </h3>
+ </div>
+ <div id="developers" class="doco-section">
+ <div class="vstack">
+ <a class="" href="/help/developer/developers_guide">Entwicklerhandbuch</a>
+ </div>
+ </div>
+ </div>
+ <div class="mb-3">
+ <div class="">
+ <h3 class="panel-title">
+ Über
+ </h3>
+ </div>
+ <div id="about" class="doco-section">
+ <div class="vstack">
+ <a href="/help/about">Über Hubzilla</a>
+ <a href="/help/functions">Funktionen</a>
+ <a href="/help/glossary">Glossar</a>
+ <a href="/help/ui">Benutzeroberfläche</a>
+ <a href="/help/the_hubzilla_project">Das Hubzilla-Projekt</a>
+ <a href="/help/credits">Credits</a>
+ <a href="/help/about_hub.bb">Über diesen Hub</a>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/doc/de/tutorials/DerivedTheme.md b/doc/de/tutorials/DerivedTheme.md
new file mode 100644
index 000000000..1aca621d7
--- /dev/null
+++ b/doc/de/tutorials/DerivedTheme.md
@@ -0,0 +1,96 @@
+### Ein abgeleitetes Thema erstellen
+
+**Lektion 1**
+Ein abgeleitetes Thema übernimmt die meisten Einstellungen des „übergeordneten“ Themas und ermöglicht es Ihnen, einige Dinge nach Ihren Wünschen zu ändern, ohne ein ganzes Themenpaket zu erstellen.
+Um ein abgeleitetes Thema zu erstellen, wählen Sie zunächst einen Namen. In unserem Beispiel nennen wir unser Thema „mytheme“. Hoffentlich werden Sie etwas kreativer sein. Wo immer Sie in diesem Dokument „mytheme“ sehen, ersetzen Sie es durch den von Ihnen gewählten Namen.
+
+**Verzeichnisstruktur**
+
+Zuerst müssen Sie eine Verzeichnisstruktur für das Thema erstellen. Wir werden es einfach halten. Wir brauchen ein php-Verzeichnis und ein css-Verzeichnis. Hier sind die Unix/Linux-Befehle, um dies zu tun. Gehen Sie davon aus, dass 'mywebsite' Ihr oberster Hubzilla-Ordner ist.
+
+```
+cd mywebsite
+mkdir view/theme/mytheme
+mkdir view/theme/mytheme/css
+mkdir view/theme/mytheme/php
+```
+
+Sehr gut. Jetzt brauchen wir noch ein paar Dateien. Die erste ist Ihre Theme-Info-Datei, die das Theme beschreibt.
+Sie heißt view/theme/mytheme/php/theme.php (cleverer Name, oder?)
+Fügen Sie darin die folgenden Informationen ein - bearbeiten Sie sie nach Bedarf
+
+```
+<?php
+
+/**
+ * * Name: Mytheme
+ * * Description: Beispiel Abgeleitetes Thema
+ * * Version: 1.0
+ * * Author: Ihr Name
+ * * Compat: Red [*]
+ *
+ */
+
+Funktion mytheme_init(&$a) {
+
+ App::$theme_info['extends'] = 'redbasic';
+}
+```
+
+Denken Sie daran, die Funktion mytheme_init mit dem Namen Ihres Themas zu benennen. In diesem Fall werden wir das Thema 'redbasic' erweitern.
+
+Erstellen Sie nun eine weitere Datei. Wir nennen dies eine PCSS-Datei, aber in Wirklichkeit ist es eine PHP-Datei.
+
+Die Datei heißt view/theme/mytheme/php/style.php
+Fügen Sie darin Folgendes ein:
+
+```
+<?php
+
+require_once('view/theme/redbasic/php/style.php');
+
+echo @file_get_contents('view/theme/mytheme/css/style.css');
+```
+
+Das war's. Dies weist die Software an, zuerst die PCSS-Informationen für das redbasic-Theme zu lesen und dann unsere CSS-Datei zu lesen, die nur aus den Änderungen besteht, die wir an unserem übergeordneten Thema (redbasic) vornehmen wollen.
+
+Erstellen Sie nun die eigentliche CSS-Datei für Ihr Thema. Legen Sie sie in view/theme/mytheme/css/style.css ab (wo wir der Software gerade gesagt haben, dass sie danach suchen soll). In unserem Beispiel ändern wir nur die Hintergrundfarbe des Körpers, damit Sie sehen können, dass es funktioniert. Sie können jedes CSS verwenden, das Sie möchten.
+
+```
+body {
+ background-color: #DDD;
+}
+```
+
+Sie haben soeben erfolgreich ein abgeleitetes Thema erstellt. Dieses muss im Adminbereich „Themen“ aktiviert werden und kann dann von jedem auf der Website verwendet werden, indem es unter Einstellungen->Anzeigeeinstellungen als Standardthema ausgewählt wird.
+
+**Lektion 2**
+
+Wenn Sie die redbasic-Schemata für Ihr abgeleitetes Thema verwenden möchten, müssen Sie etwas mehr tun.
+
+Machen Sie alles wie oben, aber erstellen Sie nicht view/theme/mytheme/php/style.php, sondern kopieren Sie stattdessen view/theme/redbasic/php/style.php nach view/theme/mytheme/php/style.php. Ändern Sie diese Datei und entfernen Sie diese beiden Zeilen (oder kommentieren Sie sie aus):
+
+```
+if(local_channel() && App::$channel && App::$channel['channel_theme'] != 'redbasic')
+ set_pconfig(local_channel(), 'redbasic', 'schema', '---');
+```
+
+Fügen Sie außerdem diese Zeile am Ende ein:
+
+```
+echo @file_get_contents('view/theme/mytheme/css/style.css');
+```
+
+Um den Schemaselektor anzuzeigen, müssen Sie view/theme/redbasic/tpl/theme_settings.tpl nach view/theme/mytheme/tpl/theme_settings.tpl kopieren. Ändern Sie diese Datei und ersetzen Sie die Zeilen:
+
+```
+{{if $theme == redbasic}}
+{{include file=„field_select.tpl“ field=$schema}}
+{{/if}}
+```
+
+mit:
+
+```
+{{include file=„field_select.tpl“ field=$schema}}
+``` \ No newline at end of file
diff --git a/doc/de/tutorials/customise_look.md b/doc/de/tutorials/customise_look.md
new file mode 100644
index 000000000..0b24fe832
--- /dev/null
+++ b/doc/de/tutorials/customise_look.md
@@ -0,0 +1,221 @@
+### Hubzilla optisch anpassen
+
+Nach dem Anlegen eines Kanals, ist dessen Optik nicht besonders ansprechend.
+
+![Opt 01](/help/de/tutorials/pic/tut_opt01.png)
+
+#### Apps anpinnen
+
+Als erstes pinnen Sie die wichtigsten Apps an die obere Navigationsleiste: „Beitrag schreiben“, „Kanal“, „Stream“, „Verbindungen“ und „Öffentlicher Beitrags-Stream“.
+
+Dazu wählen Sie im App-Menü (⋮ oben rechts) den untersten Menüeintrag „+ Apps“. Damit gelangen Sie in die App-Einstellung und sofort bei den bereits installierten Apps. Auf dieser Seite klicken Sie nun bei jeder gewünschten App auf das kleine Pin-Nadel-Symbol und sehen dann auch sofort, wie das jeweilige Icon oben in der Navigationsleiste erscheint.
+
+![Opt 02](/help/de/tutorials/pic/tut_opt02.png)
+
+![Opt 03](/help/de/tutorials/pic/tut_opt03.png)
+
+![Opt 04](/help/de/tutorials/pic/tut_opt04.png)
+
+#### Anzeige-Einstellungen
+
+Jetzt geht es an die Einstellungen für die Anzeige. Dafür klicken Sie im Hauptmenü (oben links, wo das eigene Avatarbild zu sehen ist) auf den Eintrag „Einstellungen“. In der linken Seitenleiste sind nun die verschiedenen Einstellungskategorien zu sehen. Hier wählen Sie „Anzeige-Einstellungen“.
+
+![Opt 05](/help/de/tutorials/pic/tut_opt05.png)
+
+![Opt 05a](/help/de/tutorials/pic/tut_opt05a.png)
+
+Die Seite für die Anzeige-Einstellungen wird angezeigt. Da Sie das Design anpassen wollen, ist der Tab „Benutzerdefinierte Design-Einstellungen“ die richtige Wahl.
+
+![Opt 06](/help/de/tutorials/pic/tut_opt06.png)
+
+![Opt 07](/help/de/tutorials/pic/tut_opt07.png)
+
+Die Eingabemaske weist zunächst aber nur wenige Möglichkeiten auf (fünf Einstellungen). Die letzte Einstellung ist diejenige, bei der Sie den Schalter als erstes einschalten müssen: „Erweiterte Einstellungen anzeigen“. Auf „Absenden“ klicken und erneut den Tab „Benutzerdefinierte Design-Einstellungen“ aufrufen. Nun sind viel mehr Einstellungen zu sehen.
+
+![Opt 08](/help/de/tutorials/pic/tut_opt08.png)
+
+![Opt 09](/help/de/tutorials/pic/tut_opt09.png)
+
+Als Beispiel werden hier einmal die Farben abgeändert, die Größe der Avatare im Stream angepasst und ein Hintergrundbild festgelegt.
+
+Unter den Haupteinstellungen finden Sie die Einstellungen für die Farben des Farbschemas. Unter jedem Eingabefeld ist als kleiner Kreis die Standard-Farbe angezeigt. Bei der Wahl einer eigenen Farbe, ist es sinnvoll sich zumindest bezüglich der Helligkeit ungefähr an der Standard-Farbe zu orientieren. Klickt man in eines der Eingabefelder, öffnet sich ein Farbwahl-Dialog, über welchen man nun die Farbe festlegen kann. Als Beispiel hier ein grünes Farbschema bei welchem die grundlegenden Farben geändert werden: „Primary theme color“, also die Grundfarbe des Themes, „Success theme color“, das ist die Farbe z.B. für anklickbare Links, „Info theme color“, die z.B. als Farbe für markierte Menüeinträge als Hintergrundfarbe angezeigt wird, sowie die „Hintergrundfarbe der Navigationsleiste“. Alle anderen Farben werden im Beispiel nicht verändert. Die Farbwahl ist natürlich jedem selbst überlassen.
+
+![Opt 10](/help/de/tutorials/pic/tut_opt10.png)
+
+![Opt 11](/help/de/tutorials/pic/tut_opt11.png)
+
+![Opt 12](/help/de/tutorials/pic/tut_opt12.png)
+
+![Opt 13](/help/de/tutorials/pic/tut_opt13.png)
+
+Schließlich die „Größe der Avatare von Themenstartern“ auf 48 Pixel festlegen.
+
+![Opt 14](/help/de/tutorials/pic/tut_opt14.png)
+
+Fehlt noch das Hintergrundbild. Dieses sollte relativ groß sein, ungefähr der Größe des hauptsächlich genutzten Bildschirms entsprechend. Außerdem ist es empfehlenswert, ein eher helles bzw. blasses Bild zu verwenden (ggf. mit dem Grafikprogramm aufhellen und den Kontrast verringern), damit es die eigentlichen Inhalte nicht „erschlägt“.
+
+Das Hintergrundbild muss irgendwo über eine URL erreichbar sein. Da bietet es sich an, das Bild in die Dateien (Cloud) des eigenen Kanals hochzuladen und von dort aus zu verwenden.
+
+Dafür öffnet man im App-Menü die App „Dateien“. Hier kann man sich, wenn man mag, einen extra Ordner anlegen (bitte beachten, dass der Ordner und auch das dort hochgeladene Bild öffentlich zugreifbar sind… dafür die Zugriffsrechte mit dem kleinen Vorhangschloss, dem Privacy Tool, evtl. anpassen) und anschließend das Bild hochladen.
+
+![Opt 15](/help/de/tutorials/pic/tut_opt15.png)
+
+![Opt 16](/help/de/tutorials/pic/tut_opt16.png)
+
+![Opt 17](/help/de/tutorials/pic/tut_opt17.png)
+
+![Opt 18](/help/de/tutorials/pic/tut_opt18.png)
+
+![Opt 19](/help/de/tutorials/pic/tut_opt19.png)
+
+![Opt 20](/help/de/tutorials/pic/tut_opt20.png)
+
+![Opt 21](/help/de/tutorials/pic/tut_opt21.png)
+
+![Opt 22](/help/de/tutorials/pic/tut_opt22.png)
+
+![Opt 23](/help/de/tutorials/pic/tut_opt23.png)
+
+![Opt 24](/help/de/tutorials/pic/tut_opt24.png)
+
+Nach dem Hochladen wird das Bild in der Dateiliste angezeigt. Ein Rechtsklick auf den Eintrag und die Auswahl, die URL zu kopieren befördert die URL für das Bild in die Zwischenablage.
+
+![Opt 25](/help/de/tutorials/pic/tut_opt25.png)
+
+Zurück bei „Benutzerdefinierte Design-Einstellungen“ kann man nun die URL in das Eingabefeld „Hintergrundbild“ einfügen.
+
+![Opt 26](/help/de/tutorials/pic/tut_opt26.png)
+
+![Opt 27](/help/de/tutorials/pic/tut_opt27.png)
+
+Ein letzter Klick und Hubzilla erstrahlt in der neuen Optik.
+
+![Opt 28](/help/de/tutorials/pic/tut_opt28.png)
+
+![Opt 29](/help/de/tutorials/pic/tut_opt29.png)
+
+#### PDL-Editor
+
+##### Grundlagen
+
+Verschiedene Apps und Grundfunktionen von Hubzilla basieren auf eigens dafür gestalteten Webseiten. Der Nutzer kommt mit den dahinter liegenden Mechanismen nicht in Berührung, er nutzt diese Seiten einfach.
+
+Ruft man beispielsweise die App „Kanal“ auf, so wird einem der eigene Kanal angezeigt.
+
+![PDL 01](/help/de/tutorials/pic/pdle01.png)
+
+Ganz oben kann man das Banner des Kanals sehen. In dieses Banner sind der Kanalname und die Kanaladresse (Handle) eingebettet.
+
+Darunter befindet sich die Navigationsleiste mit dem Hauptmenü, dem Titel des Hub, ggf. angepinnten Apps und dem App-Menü.
+
+Interessant wird es dann aber im Bereich unter der Navigationsleiste. Hier sind bei den verschiedenen Apps die größten Unterschiede festzustellen. Bei der Kanal-Seite findet sich in der linken Seitenleiste (im unveränderten Standard-Zustand) zuoberst eine Karte mit den Kanalinformationen (Banner, Profilbild, Kurzbeschreibung, Profilinfos).
+
+In der Mitte, im Inhaltsbereich, werden die von diesem Kanal erstellten Inhalte angezeigt.
+
+Unter der Profilinfo-Karte befindet sich in der linken Seitenleiste eine Karte mit einem Teil der Verbindungen (beim Aufruf fremder Kanäle werden hier die gemeinsamen Kontakte angezeigt).
+
+Darunter wiederum befindet sich eine Karte mit den Archiven der Inhalte (oberste Ebene sind die Jahre, eine Gliederungsebene darunter die Monate). Wählt man ein Archiv aus, so werden im Inhaltsbereich nur die Inhalte angezeigt, die im gewählten Archivzeitraum veröffentlicht wurden.
+
+Unter der Archivkarte befindet sich dann die Karte der Kategorien. Hier werden sämtliche Kategorien, unter welchen Inhalte veröffentlicht wurden, aufgeführt. Ein Klick auf eine solche Kategorie führt dazu, dass im Inhaltsbereich sämtliche Inhalte angezeigt werden, die von dem Kanal unter der entsprechenden Kategorie veröffentlicht wurden.
+
+Unter der Archiv-Karte befindet sich die Karte mit der Schlagwörter-Wolke, welche die genutzten Hashtags anzeigt und mit denen man den Inhalt des Inhaltsbereichs (Artikel des Kanals) filtern kann.
+
+In der rechten Seitenleiste werden, sofern ungesehene Benachrichtigungen vorhanden sind, diese in einer weiteren Karte gezeigt.
+
+So ist der "Normalzustand".
+
+##### Module mit dem PDL-Editor bearbeiten
+
+Die verschiedenen Seiten, die man über Apps erreichen kann, werden auch als "Module" bezeichnet.
+
+Das Aussehen dieser Seiten kann man nun als Nutzer in großen Teilen anpassen und gestalten. Intern wird der Aufbau einer solchen Seite durch eine PDL-Datei bestimmt. Diese Dateien sind Layout-Dateien, welche die Comanche Seitenbeschreibungssprache nutzen.
+
+Damit sich der Nutzer nicht mit einer solchen Sprache auseinandersetzen muss, gibt es die App "PDL Editor", mit welchem man den Seitenaufbau mit einem GUI verändern / erstellen kann.
+
+Die App muss man zunächst installieren und aktivieren. Dann kann man sie aus dem App-Menü heraus aufrufen.
+
+Ruft man den PDL-Editor auf, so wird als Standard die Seitenstruktur des HQ angezeigt.
+
+Zentral am unteren Bildschirmrand findet man das Hauptmenü des PDL-Editors. Hier gibt es die Einträge
+
+"MODULES", "TEMPLATES", "ITEMS", "SOURCE" und "APPLY".
+
+Aus dem Menü "MODULES" kann man das zu bearbeitende Modul (das entspricht der zu bearbeitenden Seite) auswählen.
+
+Angenommen, Sie möchten die Kanal-Seite (wie sie einem selbst, aber auch Besuchern angezeigt wird) anpassen, so wählt man hier das Modul "channel" aus.
+
+![PDL 02](/help/de/tutorials/pic/pdle02.png)
+
+Die PDL-Datei für die Kanalseite wird geladen und man sieht die entsprechenden, gerade beschriebenen Bestandteile ("ITEMS") dieser Seite.
+
+![PDL 03](/help/de/tutorials/pic/pdle03.png)
+
+Angenommen, Sie möchten nun unsere Kanalseite mit einer Anzeige der Uhrzeit in der rechten Seitenleiste "verfeinern", so wählt man unter "ITEMS" das Item "CLOCK" aus, "ergreift" es mit dem Mauszeiger am Kreuzpfeil-Symbol und zieht es nach rechts in die Seitenleiste.
+
+![PDL 04](/help/de/tutorials/pic/pdle04.png)
+
+![PDL 05](/help/de/tutorials/pic/pdle05.png)
+
+Damit die Änderungen auch übernommen werden, klickt man anschließend auf "APPLY" im Hauptmenü.
+
+![PDL 06](/help/de/tutorials/pic/pdle06.png)
+
+Ruft man nun die Kanalseite auf, so erscheint eine Karte mit der aktuellen Uhrzeit in der rechten Seitenleiste.
+
+![PDL 07](/help/de/tutorials/pic/pdle07.png)
+
+Auf diese Weise kann man alle Seiten, die man unter "MODULES" findet, nach dem eigenen Geschmack anpassen.
+
+Hat man seine Seite angepasst und sie ist irgendwie "so völlig zerhackstückt": Kein Grund zur Panik! Im Hauptmenü findet man bei geänderten Layoutseiten den zusätzlichen Eintrag "RESET". Ein Klick darauf setzt das Seitenlayout auf den Hubzilla-Standard zurück.
+
+Hier werden jetzt aber nicht sämtliche Items vorgestellt... jeder darf ein wenig experimentieren. Die meisten haben einen erklärenden Titel.
+
+Klickt man im PDL-Hauptmenü auf "SOURCE", so wird einem der Quelltext des aktuellen Layouts angezeigt. Ein Blick hier hinein hilft, mit PDL vertraut zu werden. Außerdem sind hier auch Änderungen direkt im Quelltext möglich... falls irgendwas nicht über die "ITEMS" erreichbar ist. Hierfür sollte man sich aber mit der Seitenauszeichnungssprache, den Blöcken und Modulen vorher vertraut machen.
+
+![PDL 08](/help/de/tutorials/pic/pdle08.png)
+
+##### PDL-Editor für Fortgeschrittene
+
+Angenommen, man hat ein paar Artikel erstellt und möchte einige davon über ein Menü in der rechten Seitenleiste der Kanalseite zugänglich machen.
+
+Das ist durchaus machbar.
+
+Doch dafür benötigt man zunächst einmal ein Menü. Um Menüs zu erstellen, muss man allerdings die App "Webseiten" installieren und aktivieren, denn das Erstellen von Menüs ist Teil der Webseiten-Funktionalität. Also selbst wenn man keine Webseiten in seinem Kanal erstellen möchte, braucht man zum Erstellen von Menüs die App "Webseiten". Wobei... so ganz stimmt das nicht. Man kann den Menüeditor auch anders erreichen, als über die App "Webseiten". Dafür gibt man `<url-des-hub>/menu/<kanalname>` ein. Nun landet man auch in der Menü-"App". Einfacher ist es allerdings mit der Webseiten-App.
+
+![PDL 09](/help/de/tutorials/pic/pdle09.png)
+
+Ein Klick auf "Erstelle" öffnet den Menü-Editor.
+
+![PDL 10](/help/de/tutorials/pic/pdle10.png)
+
+Hier muss man nun einen passenden Namen (über den man das Menü später ansprechen kann) und (optional) einen Titel für das Menü eingeben (dieser ist später auf der Webseite zu sehen).
+
+Danach klickt man auf "Absenden und fortfahren".
+
+Nun landet man im Link-Editor des gerade erstellten Menüs. Hier gibt man den Titel des Menüeintrags und die dazugehörige URL ein. Man kann auch die Reihenfolge der Sortierung der Menüeinträge über das Feld "Reihenfolge in der Liste" festlegen. Hat man die Eingabe erledigt und klickt auf "Absenden und fortfahren", kann man anschließend einen weiteren Menüeintrag eingeben. Ein Klick auf "Absenden und fertigstellen", fügt den Eintrag hinzu und beendet den Menüeditor (man kann Menüs selbstverständlich auch im nachhinein noch bearbeiten).
+
+Jetzt erscheint das neue Menü in der Liste der Menüs.
+
+![PDL 11](/help/de/tutorials/pic/pdle11.png)
+
+Jetzt zurück zum PDL-Editor und dort das Kanal-Modul aufrufen.
+
+Nun gibt es wieder zwei Möglichkeiten. Entweder, man öffnet den Quelltexteditor "SOURCE" und gibt den Eintrag für die Menükarte an der passenden Stelle per Hand ein...
+
+Hier wählt man, wenn das Menü in der rechten Seitenleiste erscheinen soll, die Region "right_aside" und gibt dort als neue Zeile `[menu]artikelmenu[/menu]` ein.
+
+![PDL 12](/help/de/tutorials/pic/pdle12.png)
+
+Nun noch auf "Submit" klicken und schon erscheint die neue Karte im visuellen PDL-Editor.
+
+![PDL 13](/help/de/tutorials/pic/pdle13.png)
+
+Mit "APPLY" übernehmen... und dann ist das Menü auf der Kanal-Webseite.
+
+![PDL 14](/help/de/tutorials/pic/pdle14.png)
+
+Die zweite Methode (mit der man nicht die richtige Stelle im Quelltext suchen muss) ist es, im PDL-Editor einfach ein beliebiges Item an die Stelle zu ziehen, wo das Menü erscheinen soll. Dann klickt man auf den "Edit"-Button bei diesem Item, ändert den vorhandenen Eintrag auf `[menu]artikelmenu[/menu]` und klickt auf "Submit". Dann noch ein "APPLY" und man hat das selbe Ergebnis.
+
+![PDL 15](/help/de/tutorials/pic/pdle15.png)
+
+Viel Spaß beim Experimentieren!
diff --git a/doc/de/tutorials/pic/02.png b/doc/de/tutorials/pic/02.png
new file mode 100644
index 000000000..38cf70c49
--- /dev/null
+++ b/doc/de/tutorials/pic/02.png
Binary files differ
diff --git a/doc/de/tutorials/pic/03.png b/doc/de/tutorials/pic/03.png
new file mode 100644
index 000000000..3164d0d17
--- /dev/null
+++ b/doc/de/tutorials/pic/03.png
Binary files differ
diff --git a/doc/de/tutorials/pic/04.png b/doc/de/tutorials/pic/04.png
new file mode 100644
index 000000000..1229c0a2e
--- /dev/null
+++ b/doc/de/tutorials/pic/04.png
Binary files differ
diff --git a/doc/de/tutorials/pic/05.png b/doc/de/tutorials/pic/05.png
new file mode 100644
index 000000000..434a4abbd
--- /dev/null
+++ b/doc/de/tutorials/pic/05.png
Binary files differ
diff --git a/doc/de/tutorials/pic/06.png b/doc/de/tutorials/pic/06.png
new file mode 100644
index 000000000..544b08694
--- /dev/null
+++ b/doc/de/tutorials/pic/06.png
Binary files differ
diff --git a/doc/de/tutorials/pic/07.png b/doc/de/tutorials/pic/07.png
new file mode 100644
index 000000000..25e7063fb
--- /dev/null
+++ b/doc/de/tutorials/pic/07.png
Binary files differ
diff --git a/doc/de/tutorials/pic/08.png b/doc/de/tutorials/pic/08.png
new file mode 100644
index 000000000..164aad0a0
--- /dev/null
+++ b/doc/de/tutorials/pic/08.png
Binary files differ
diff --git a/doc/de/tutorials/pic/09.png b/doc/de/tutorials/pic/09.png
new file mode 100644
index 000000000..767d45244
--- /dev/null
+++ b/doc/de/tutorials/pic/09.png
Binary files differ
diff --git a/doc/de/tutorials/pic/10.png b/doc/de/tutorials/pic/10.png
new file mode 100644
index 000000000..280d5d22c
--- /dev/null
+++ b/doc/de/tutorials/pic/10.png
Binary files differ
diff --git a/doc/de/tutorials/pic/11.png b/doc/de/tutorials/pic/11.png
new file mode 100644
index 000000000..c738c1a80
--- /dev/null
+++ b/doc/de/tutorials/pic/11.png
Binary files differ
diff --git a/doc/de/tutorials/pic/12.png b/doc/de/tutorials/pic/12.png
new file mode 100644
index 000000000..486e3e81a
--- /dev/null
+++ b/doc/de/tutorials/pic/12.png
Binary files differ
diff --git a/doc/de/tutorials/pic/13.png b/doc/de/tutorials/pic/13.png
new file mode 100644
index 000000000..47aa44c0e
--- /dev/null
+++ b/doc/de/tutorials/pic/13.png
Binary files differ
diff --git a/doc/de/tutorials/pic/14.png b/doc/de/tutorials/pic/14.png
new file mode 100644
index 000000000..fcc6684cc
--- /dev/null
+++ b/doc/de/tutorials/pic/14.png
Binary files differ
diff --git a/doc/de/tutorials/pic/15.png b/doc/de/tutorials/pic/15.png
new file mode 100644
index 000000000..69fa838ed
--- /dev/null
+++ b/doc/de/tutorials/pic/15.png
Binary files differ
diff --git a/doc/de/tutorials/pic/16.png b/doc/de/tutorials/pic/16.png
new file mode 100644
index 000000000..b7f0bd1ec
--- /dev/null
+++ b/doc/de/tutorials/pic/16.png
Binary files differ
diff --git a/doc/de/tutorials/pic/17.png b/doc/de/tutorials/pic/17.png
new file mode 100644
index 000000000..9501e6ab9
--- /dev/null
+++ b/doc/de/tutorials/pic/17.png
Binary files differ
diff --git a/doc/de/tutorials/pic/18.png b/doc/de/tutorials/pic/18.png
new file mode 100644
index 000000000..deb18f7de
--- /dev/null
+++ b/doc/de/tutorials/pic/18.png
Binary files differ
diff --git a/doc/de/tutorials/pic/19.png b/doc/de/tutorials/pic/19.png
new file mode 100644
index 000000000..833304712
--- /dev/null
+++ b/doc/de/tutorials/pic/19.png
Binary files differ
diff --git a/doc/de/tutorials/pic/20.png b/doc/de/tutorials/pic/20.png
new file mode 100644
index 000000000..5ca43f3cd
--- /dev/null
+++ b/doc/de/tutorials/pic/20.png
Binary files differ
diff --git a/doc/de/tutorials/pic/fedidb.png b/doc/de/tutorials/pic/fedidb.png
new file mode 100644
index 000000000..988658aff
--- /dev/null
+++ b/doc/de/tutorials/pic/fedidb.png
Binary files differ
diff --git a/doc/de/tutorials/pic/fedieverse-observer.png b/doc/de/tutorials/pic/fedieverse-observer.png
new file mode 100644
index 000000000..a80cbded9
--- /dev/null
+++ b/doc/de/tutorials/pic/fedieverse-observer.png
Binary files differ
diff --git a/doc/de/tutorials/pic/hzreg01.png b/doc/de/tutorials/pic/hzreg01.png
new file mode 100644
index 000000000..e11879307
--- /dev/null
+++ b/doc/de/tutorials/pic/hzreg01.png
Binary files differ
diff --git a/doc/de/tutorials/pic/hzreg01a.png b/doc/de/tutorials/pic/hzreg01a.png
new file mode 100644
index 000000000..714f3d9b4
--- /dev/null
+++ b/doc/de/tutorials/pic/hzreg01a.png
Binary files differ
diff --git a/doc/de/tutorials/pic/hzreg02.png b/doc/de/tutorials/pic/hzreg02.png
new file mode 100644
index 000000000..8a493147e
--- /dev/null
+++ b/doc/de/tutorials/pic/hzreg02.png
Binary files differ
diff --git a/doc/de/tutorials/pic/hzreg03.png b/doc/de/tutorials/pic/hzreg03.png
new file mode 100644
index 000000000..bbe4c4451
--- /dev/null
+++ b/doc/de/tutorials/pic/hzreg03.png
Binary files differ
diff --git a/doc/de/tutorials/pic/hzreg04.png b/doc/de/tutorials/pic/hzreg04.png
new file mode 100644
index 000000000..bdc00f637
--- /dev/null
+++ b/doc/de/tutorials/pic/hzreg04.png
Binary files differ
diff --git a/doc/de/tutorials/pic/nomadapp.png b/doc/de/tutorials/pic/nomadapp.png
new file mode 100644
index 000000000..f62e3d96d
--- /dev/null
+++ b/doc/de/tutorials/pic/nomadapp.png
Binary files differ
diff --git a/doc/de/tutorials/pic/pdle01.png b/doc/de/tutorials/pic/pdle01.png
new file mode 100644
index 000000000..3bb604397
--- /dev/null
+++ b/doc/de/tutorials/pic/pdle01.png
Binary files differ
diff --git a/doc/de/tutorials/pic/pdle02.png b/doc/de/tutorials/pic/pdle02.png
new file mode 100644
index 000000000..9efdf5bc8
--- /dev/null
+++ b/doc/de/tutorials/pic/pdle02.png
Binary files differ
diff --git a/doc/de/tutorials/pic/pdle03.png b/doc/de/tutorials/pic/pdle03.png
new file mode 100644
index 000000000..e82d0c859
--- /dev/null
+++ b/doc/de/tutorials/pic/pdle03.png
Binary files differ
diff --git a/doc/de/tutorials/pic/pdle04.png b/doc/de/tutorials/pic/pdle04.png
new file mode 100644
index 000000000..b5b409d69
--- /dev/null
+++ b/doc/de/tutorials/pic/pdle04.png
Binary files differ
diff --git a/doc/de/tutorials/pic/pdle05.png b/doc/de/tutorials/pic/pdle05.png
new file mode 100644
index 000000000..c78680649
--- /dev/null
+++ b/doc/de/tutorials/pic/pdle05.png
Binary files differ
diff --git a/doc/de/tutorials/pic/pdle06.png b/doc/de/tutorials/pic/pdle06.png
new file mode 100644
index 000000000..aa51dd345
--- /dev/null
+++ b/doc/de/tutorials/pic/pdle06.png
Binary files differ
diff --git a/doc/de/tutorials/pic/pdle07.png b/doc/de/tutorials/pic/pdle07.png
new file mode 100644
index 000000000..1a314ce73
--- /dev/null
+++ b/doc/de/tutorials/pic/pdle07.png
Binary files differ
diff --git a/doc/de/tutorials/pic/pdle08.png b/doc/de/tutorials/pic/pdle08.png
new file mode 100644
index 000000000..c5b62842f
--- /dev/null
+++ b/doc/de/tutorials/pic/pdle08.png
Binary files differ
diff --git a/doc/de/tutorials/pic/pdle09.png b/doc/de/tutorials/pic/pdle09.png
new file mode 100644
index 000000000..5a328cbbe
--- /dev/null
+++ b/doc/de/tutorials/pic/pdle09.png
Binary files differ
diff --git a/doc/de/tutorials/pic/pdle10.png b/doc/de/tutorials/pic/pdle10.png
new file mode 100644
index 000000000..812f28230
--- /dev/null
+++ b/doc/de/tutorials/pic/pdle10.png
Binary files differ
diff --git a/doc/de/tutorials/pic/pdle11.png b/doc/de/tutorials/pic/pdle11.png
new file mode 100644
index 000000000..21cbdf6c4
--- /dev/null
+++ b/doc/de/tutorials/pic/pdle11.png
Binary files differ
diff --git a/doc/de/tutorials/pic/pdle12.png b/doc/de/tutorials/pic/pdle12.png
new file mode 100644
index 000000000..308222766
--- /dev/null
+++ b/doc/de/tutorials/pic/pdle12.png
Binary files differ
diff --git a/doc/de/tutorials/pic/pdle13.png b/doc/de/tutorials/pic/pdle13.png
new file mode 100644
index 000000000..b857ae8d7
--- /dev/null
+++ b/doc/de/tutorials/pic/pdle13.png
Binary files differ
diff --git a/doc/de/tutorials/pic/pdle14.png b/doc/de/tutorials/pic/pdle14.png
new file mode 100644
index 000000000..0d479dea8
--- /dev/null
+++ b/doc/de/tutorials/pic/pdle14.png
Binary files differ
diff --git a/doc/de/tutorials/pic/pdle15.png b/doc/de/tutorials/pic/pdle15.png
new file mode 100644
index 000000000..e075df077
--- /dev/null
+++ b/doc/de/tutorials/pic/pdle15.png
Binary files differ
diff --git a/doc/de/tutorials/pic/pubsites.png b/doc/de/tutorials/pic/pubsites.png
new file mode 100644
index 000000000..6fe2e1e71
--- /dev/null
+++ b/doc/de/tutorials/pic/pubsites.png
Binary files differ
diff --git a/doc/de/tutorials/pic/rega.png b/doc/de/tutorials/pic/rega.png
new file mode 100644
index 000000000..9212a4f4b
--- /dev/null
+++ b/doc/de/tutorials/pic/rega.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt01.png b/doc/de/tutorials/pic/tut_opt01.png
new file mode 100644
index 000000000..a9ff138f2
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt01.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt02.png b/doc/de/tutorials/pic/tut_opt02.png
new file mode 100644
index 000000000..63ac59f82
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt02.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt03.png b/doc/de/tutorials/pic/tut_opt03.png
new file mode 100644
index 000000000..6b4cf07ee
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt03.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt04.png b/doc/de/tutorials/pic/tut_opt04.png
new file mode 100644
index 000000000..6d7a74e73
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt04.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt05.png b/doc/de/tutorials/pic/tut_opt05.png
new file mode 100644
index 000000000..391325332
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt05.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt05a.png b/doc/de/tutorials/pic/tut_opt05a.png
new file mode 100644
index 000000000..9b5f2771a
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt05a.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt06.png b/doc/de/tutorials/pic/tut_opt06.png
new file mode 100644
index 000000000..ab458fea0
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt06.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt07.png b/doc/de/tutorials/pic/tut_opt07.png
new file mode 100644
index 000000000..bb50b8abd
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt07.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt08.png b/doc/de/tutorials/pic/tut_opt08.png
new file mode 100644
index 000000000..3f6af6793
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt08.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt09.png b/doc/de/tutorials/pic/tut_opt09.png
new file mode 100644
index 000000000..2f99755ee
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt09.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt10.png b/doc/de/tutorials/pic/tut_opt10.png
new file mode 100644
index 000000000..959daa2fa
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt10.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt11.png b/doc/de/tutorials/pic/tut_opt11.png
new file mode 100644
index 000000000..367a48ab3
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt11.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt12.png b/doc/de/tutorials/pic/tut_opt12.png
new file mode 100644
index 000000000..42e52a6c0
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt12.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt13.png b/doc/de/tutorials/pic/tut_opt13.png
new file mode 100644
index 000000000..4bf7b012c
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt13.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt14.png b/doc/de/tutorials/pic/tut_opt14.png
new file mode 100644
index 000000000..a2b8e1e72
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt14.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt15.png b/doc/de/tutorials/pic/tut_opt15.png
new file mode 100644
index 000000000..690203699
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt15.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt16.png b/doc/de/tutorials/pic/tut_opt16.png
new file mode 100644
index 000000000..6c749c425
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt16.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt17.png b/doc/de/tutorials/pic/tut_opt17.png
new file mode 100644
index 000000000..a6794ac2d
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt17.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt18.png b/doc/de/tutorials/pic/tut_opt18.png
new file mode 100644
index 000000000..36b1c2401
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt18.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt19.png b/doc/de/tutorials/pic/tut_opt19.png
new file mode 100644
index 000000000..f232da9bb
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt19.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt20.png b/doc/de/tutorials/pic/tut_opt20.png
new file mode 100644
index 000000000..04565f65c
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt20.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt21.png b/doc/de/tutorials/pic/tut_opt21.png
new file mode 100644
index 000000000..76430b695
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt21.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt22.png b/doc/de/tutorials/pic/tut_opt22.png
new file mode 100644
index 000000000..8e26bacf9
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt22.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt23.png b/doc/de/tutorials/pic/tut_opt23.png
new file mode 100644
index 000000000..389ba7df0
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt23.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt24.png b/doc/de/tutorials/pic/tut_opt24.png
new file mode 100644
index 000000000..7a7ad0e30
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt24.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt25.png b/doc/de/tutorials/pic/tut_opt25.png
new file mode 100644
index 000000000..c8639a679
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt25.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt26.png b/doc/de/tutorials/pic/tut_opt26.png
new file mode 100644
index 000000000..20e64f61a
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt26.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt27.png b/doc/de/tutorials/pic/tut_opt27.png
new file mode 100644
index 000000000..3e161e20c
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt27.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt28.png b/doc/de/tutorials/pic/tut_opt28.png
new file mode 100644
index 000000000..2f76472c2
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt28.png
Binary files differ
diff --git a/doc/de/tutorials/pic/tut_opt29.png b/doc/de/tutorials/pic/tut_opt29.png
new file mode 100644
index 000000000..037cfdfa6
--- /dev/null
+++ b/doc/de/tutorials/pic/tut_opt29.png
Binary files differ
diff --git a/doc/de/tutorials/step_with_hubzilla.md b/doc/de/tutorials/step_with_hubzilla.md
new file mode 100644
index 000000000..b297082cb
--- /dev/null
+++ b/doc/de/tutorials/step_with_hubzilla.md
@@ -0,0 +1,160 @@
+## Mit Hubzilla Schritt für Schritt ins Fediverse
+
+### Der Einstieg
+
+Wie bei jedem anderen Fediverse-Dienst steht am Anfang die Wahl des Servers (Hub). Das ist im Fediverse so und essenzieller Teil der dortigen Freiheit.
+
+Um Hubs zu finden können Sie die üblichen Wege beschreiben: dieNutzung entsprechende Datenbanken oder Listen.
+
+Z.B.
+
+[FediDB](https://fedidb.org/software/hubzilla)
+
+![FediDB](/help/de/tutorials/pic/fedidb.png)
+
+[Fediverse Observer](https://hubzilla.fediverse.observer/list)
+
+![Fedieverse Observer](/help/de/tutorials/pic/fedieverse-observer.png)
+
+[Liste öffentlicher Hubs bei einem Hubzilla-Server](https://zotsite.net/pubsites)
+
+![Pubsites](/help/de/tutorials/pic/pubsites.png)
+
+Haben Sie sich für einen Hub entschieden, dann rufen Sie die URL auf. Damit gelangen Sie auf eine Standard-Seite. Diese kann von Hub zu Hub etwas variieren, aber in der Regel finden Sie am oberen Rand des Bildschirms zwei Menüeinträge: „Anmelden“ und „Registrieren“.
+
+![Registrierung 1](/help/de/tutorials/pic/hzreg01a.png)
+
+![Registrierung 2](/help/de/tutorials/pic/hzreg01.png)
+
+Ein Klick auf den Link führt zu einem Registrierungsformular. Hier gibt es mehrere mögliche Szenarien. Manche Hubs sind so eingestellt, dass man schon bei der Registrierung gleich einen Kanal mit erstellt (man kann – noch so eine Besonderheit von Hubzilla – mit einem Account mehrere Kanäle betreiben). Bei anderen Hubs legt man mit dem Formular zunächst nur einen Account an. Wenn man diesen erstellt hat und sich erstmalig einloggt, dann wird man auf das Formular zur Kanalerstellung geleitet.
+
+![Registrierung 3](/help/de/tutorials/pic/hzreg02.png)
+
+![Registrierung 4](/help/de/tutorials/pic/rega.png)
+
+Um einen Account anzulegen, brauchen Sie eine (funktionierende) E-Mail-Adresse und Sie müssen sich ein Passwort ausdenken. Außerdem müssen Sie noch Ihr Alter bestätigen und können dann die eingegebenen Informationen abschicken.
+
+Nun landen Sie (meist, es soll auch Hubs geben, die diesen Schritt aussparen… halte ich für riskant) bei einer Eingabemaske, bei welcher man einen Verifizierungscode eingeben muss. Den bekommen Sie nach dem Absenden des Registrierungs-Formulars per E-Mail zugeschickt.
+
+![Registrierung 5](/help/de/tutorials/pic/hzreg03.png)
+
+Mussten Sie bei der Registrierung noch keinen Kanal anlegen, so werden Sie zu diesem Zeitpunkt zur Seite „Erstelle einen Kanal“ weitergeleitet. Hier müssen Sie sich nun einen Namen für die eigene Identität ausdenken. Und zusätzlich eine Kurzbezeichnung („Spitzname“) für den Kanal (ein Vorschlag wird automatisch aus dem Kanalnamen erzeugt). Diese Kurzbezeichnung wird der wesentliche Bestandteil des Fediverse-Handles (also der eigenen „Fediverse-Adresse“).
+
+![Registrierung 6](/help/de/tutorials/pic/hzreg04.png)
+
+| **Hinweis zum Handle bei Hubzilla:** |
+| ------------------------------------------------------------ |
+| Hubzilla macht beim Handle im Vergleich zu anderen Fediverse-Diensten eine Ausnahme. Während beinahe überall vor das Handle ein „@“ gesetzt wird, entfällt dies bei Hubzilla. Das verinnerlicht man aber schnell. Sucht man mit Hubzilla z.B. einen Nutzer, der ein Mastodon-Konto hat, über dessen Handle, dann lässt man das führende „@“ einfach weg. Will man hingegen von einem anderen Fediverse-Dienst aus einem Hubzilla-Nutzer folgen oder diesen suchen, stellt man dem Hubzilla-Handle einfach ein „@“ voran. |
+
+Nach dem Anlegen das Kanals sind Sie dann auch endlich „drin“. Als Standard landet man bei Hubzilla im „Headquarter“ („HQ“), einer Übersichtsseite über den eigenen Kanal.
+
+![HQ](/help/de/tutorials/pic/10.png)
+
+Und es ist, wie immer beim Einstieg im Fediverse, ziemlich leer dort. In der linken Seitenleiste finden Sie Tabs mit verschiedenen Informationen:
+
+- Öffentliche (oder eingeschränkte) Nachrichten:
+ Das ist eine kompakte Ansicht der persönlichen Timeline (bei Hubzilla „Stream“ genannt)
+- Direktnachrichten:
+ Postings, die nur unter ausgewählten Teilnehmern ausgetauscht werden.
+- Favorisierte Postings:
+ Man kann ein Posting mit einem „Sternchen“ versehen, um sie zu markieren. Solchermaßen markierte Beiträge landen hier.
+- Benachrichtigungen
+
+In der Mitte wird der persönliche Stream (Timeline) angezeigt. In der rechten Seitenleiste werden wichtige Informationen (Mitteilung über neue Kontakte, neue Beiträge etc.) angezeigt. Bei einem neu erzeugten Kanal erscheint hier auch eine Auflistung für die ersten Schritte bei Hubzilla und Links, die zu den entsprechenden Funktionen führen.
+
+Benötigt man diese Einsteiger-Hilfe nicht mehr, lässt sie sich unter Einstellungen → Anzeige-Einstellungen → Inhaltseinstellungen per Schalter abschalten.
+
+![erste Schritte 1](/help/de/tutorials/pic/11.png)
+
+![erste Schritte 2](/help/de/tutorials/pic/12.png)
+
+![erste Schritte 3](/help/de/tutorials/pic/13.png)
+
+![erste Schritte 4](/help/de/tutorials/pic/14.png)
+
+Zunächst sollten Sie Ihr Profil sinnvoll befüllen… wie bei jedem Fediverse-Dienst. Keine schwarze Magie!
+
+Am oberen Bildschirmrand befindet sich die Navigationsleiste. Links ist das Menü zur Kanalauswahl, für das eigene Profil und für die Einstellungen zu sehen. Rechts befinden sich Icons für einige Funktionen und das sogenannte „App-Menü“ (⋮), über welches man zu den installierten Apps gelangt. Als Standard werden dort die wichtigsten Anwendungen bereits zur Verfügung gestellt:
+
+- Dateien:
+ Zugang zur eigenen Cloud
+- Fotos:
+ Zugang zum eigenen Fotoalbum
+- Hilfe
+- Kalender
+- Kanal:
+ Die Seite des eigenen Kanals mit den Kanalinformationen. Hier werden im Stream nur die eigenen Beiträge angezeigt.
+- Stream:
+ Es wird zur föderierten Streamansicht gewechselt.
+- Verbindungen:
+ Hier werden die vorhandenen Verbindungen aufgeführt („Follower“ und „Gefolgte“). Außerdem kann man im Verbindungsverzeichnis neue Verbindungen hinzufügen.
+- Verzeichnis:
+ Das Benutzerverzeichnis wird angezeigt. Beachte: Man kann das globale Verzeichnis anschauen oder auch nur ein Verzeichnis mit den Nutzern der eigenen Instanz. Auch hier kann man neue Verbindungen herstellen.
+
+**Ich empfehle dringend, noch einige weitere Apps zu installieren und zu aktivieren: ActivityPub, Superblock und Öffentlicher Beitragsstream.**
+
+**Die App ActivityPub ist essenziell, wenn man am Fediverse teilnehmen möchte.** Diese App ist ein **MUSS** und erfordert lediglich die Installation und Aktivierung. Weitere Einstellungen sind nicht erforderlich.
+
+Superblock ist ebenfalls sehr wichtig, denn damit ist es möglich, Postings von (auch nicht gefolgten) Nutzern aus dem Stream auszuschließen.
+
+Bei vielen (leider, aber auch verständlicher Weise, nicht bei allen) Hubs steht außerdem die App „Öffentlicher Beitragsstream“ zur Verfügung. Diese stellt die öffentliche Zeitleiste aller Fediverse-Instanzen, die mit dem eigenen Hub föderieren, zur Verfügung. Ein guter Ort, sich zu orientieren und neue Kontakte zu finden.
+
+Apps zu installieren und zu aktivieren ist kein Problem. Man wählt im App-Menü den letzten Eintrag „+ Apps“ um zur App-Verwaltung zu gelangen.
+
+![Apps installieren 1](/help/de/tutorials/pic/02.png)
+
+Hier können Sie sich die installierten Apps (also die voreingestellten Apps) und die generell verfügbaren Apps (alle, auch die nicht installierten) anzeigen lassen.
+
+![Apps installieren 2](/help/de/tutorials/pic/03.png)
+
+ActivityPub, Superblock und Öffentlicher Beitragsstream befinden sich noch ausschließlich unter „Verfügbare Apps“. Ein Klick auf „Installieren“ installiert diese und sie stehen dann auch bei „Installierte Apps“ zur Verfügung.
+
+![Apps installieren 3](/help/de/tutorials/pic/04.png)
+
+![Apps installieren 4](/help/de/tutorials/pic/05.png)
+
+Die neu installierten Apps müssen noch „aktiviert“, also auch durch das Menü nutzbar gemacht werden. Man findet in der Box der App ein „Sternchen-Symbol“. Klickt man darauf, färbt sich der Stern gelb und die App ist aktiv und erscheint nun auch im App-Menü.
+
+![Apps installieren 5](/help/de/tutorials/pic/06.png)
+
+![Apps installieren 6](/help/de/tutorials/pic/07.png)
+
+Es gibt auch noch ein Pinnadel-Symbol. Klicken Sie dies an, so erscheint die App dauerhaft auch in der Navigationsleiste oben rechts.![Apps installieren 7](/help/de/tutorials/pic/08.png)
+
+![Apps installieren 8](/help/de/tutorials/pic/09.png)
+
+Es ist außerdem empfehlenswert, bei dieser Gelegenheit die Apps“Kanal“ und „Stream“ an die Navigationsleiste anzupinnen, weil diese oft benötigt werden.
+
+### Wichtig noch, wie man Kontakte hinzufügt…
+
+Haben Sie z.B. im öffentlichen Stream einen interessanten Nutzer gefunden, können Sie einfach auf das Profilbild des Nutzers klicken und im aufklappenden Menü „Verbinden“ auswählen. Sie können aber auch auf das Handle des Nutzer klicken worauf Sie auf eine Seite mit einem Button „Verbinden“ geleitet werden..
+
+Kennt man das Handle eines Fediverse-Nutzers, dann kann man auch einfach auf die App „Verbindungen“ klicken. Es öffnet sich das Verbindungs-Verzeichnis. Ganz oben befindet sich ein Button „+ Add“. Klicken Sie auf diesen, öffnet sich ein Eingabefeld, in welches Sie das Handle eingeben können (dran denken: ohne führendes „@“). Ein Klick auf das daneben befindliche „+“ und der Kontakt wird hinzugefügt.
+
+![Verbindungen 01](/help/de/tutorials/pic/15.png)
+
+![Verbindungen 02](/help/de/tutorials/pic/16.png)
+
+![Verbindungen 03](/help/de/tutorials/pic/17.png)
+
+![Verbindungen 04](/help/de/tutorials/pic/18.png)
+
+Schließlich können Sie auch das Verzeichnis aufrufen (am besten „nur dieser Hub“ in der linken Seitenleiste deaktivieren, um das globale Verzeichnis zu nutzen). Hier können Sie einfach durchscrollen oder auch gezielt nach Namen oder Interessen suchen, oder auch nach Tags (Schnellzugriff auch über eine Schlagwörterwolke in der linken Seitenleiste). Das Verbinden erfolgt hier durch Klick auf den Button „Verbinden“.
+
+![Verbindungen 05](/help/de/tutorials/pic/20.png)
+
+![Verbindungen 06](/help/de/tutorials/pic/19.png)
+
+Haben Sie Kontakte, die Apps installiert und Ihr Profil vervollständigt, können Sie Hubzilla nun ganz einfach wie jeden anderen Fediversedienst nutzen.
+
+### Gibt es eine App?
+
+Jein…
+
+Grundsätzlich benötigt man keine. Man kann auf dem mobilen Endgerät einfach im Webbrowser den Hub aufrufen. Das responsive Design erlaubt eine ordentliche Bedienung.
+
+Allerdings gibt es für Android eine ältere App, die aber auch heute noch sehr gut funktioniert. Man findet sie z.B. bei [F-Droid](https://f-droid.org/de/) unter dem Namen [Nomad](https://f-droid.org/de/packages/com.dfa.hubzilla_android/). Ich verwende sie noch immer, wenn ich mal (was selten ist) auf dem Smartphone mit Hubzilla arbeiten möchte.
+
+![Nomad App](/help/de/tutorials/pic/nomadapp.png)
+
+Als Alternative ist es empfehlenswert, wenn man mobil unterwegs ist, Hubzilla als PWA auf dem Gerät zu installieren: [Eine Hubzilla-App](https://info.hubzilla.hu/de/Hubzilla-App.html)
diff --git a/doc/de/tutorials/tutorials.md b/doc/de/tutorials/tutorials.md
new file mode 100644
index 000000000..006a24bed
--- /dev/null
+++ b/doc/de/tutorials/tutorials.md
@@ -0,0 +1,7 @@
+## Tutorials
+
+#### [Mit Hubzilla Schritt für Schritt ins Fediverse](./step_with_hubzilla.md)
+
+#### [Hubzilla optisch anpassen](./customise_look.md)
+
+#### [Ein abgeleitetes Thema erstellen](./DerivedTheme1.md) \ No newline at end of file
diff --git a/doc/de/ui.md b/doc/de/ui.md
new file mode 100644
index 000000000..1399770b3
--- /dev/null
+++ b/doc/de/ui.md
@@ -0,0 +1,5 @@
+# Oberfläche / Bezeichnungen
+
+![ui01](/help/de/pic/ui01.png)
+
+![ui02](/help/de/pic/ui02.png)
diff --git a/doc/en/AdvancedSearch.md b/doc/en/AdvancedSearch.md
deleted file mode 100644
index a67c1fc1f..000000000
--- a/doc/en/AdvancedSearch.md
+++ /dev/null
@@ -1,53 +0,0 @@
-Advanced Directory Search
-=========================
-
-
-Advanced Directory Search is enabled in "Expert Mode" from your Settings => Additional features page.
-
-On the Directory page an option named "Advanced" will apear in the "Find Channels" widget (typically in the sidebar). Clicking "Advanced" will open another search box for entering advanced search requests.
-
-Advanced requests include
-
-* name=xxx
-[Channel name contains xxx]
-
-* address=xxx
-[Channel address (webbie) contains xxx]
-
-* locale=xxx
-[Locale (typically 'city') contains xxx]
-
-* region=xxx
-[Region (state/territory) contains xxx]
-
-* postcode=xxx
-[Postcode or zip code contains xxx]
-
-* country=xxx
-[Country name contains xxx]
-
-* gender=xxx
-[Gender contains xxx]
-
-* marital=xxx
-[Marital status contains xxx]
-
-* sexual=xxx
-[Sexual preference contains xxx]
-
-* keywords=xxx
-[Keywords contain xxx]
-
-There are many reasons why a match may not return what you're looking for, as many channels do not provide detailed information in their default (public) profile, and many of these fields allow free-text input in several languages - and this may be difficult to match precisely. For instance you may have better results finding somebody in the USA with 'country=u' (along with some odd channels from Deutschland and Bulgaria and Australia) because this could be represented in a profile as US, U.S.A, USA, United States, etc...
-
-Future revisions of this tool may try to smooth over some of these difficulties.
-
-Requests may be joined together with 'and', 'or', and 'and not'.
-
-Terms containing spaces must be quoted.
-
-Example:
-
- name="charlie brown" and country=canada and not gender=female
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/Comparison-of-activity-stream-networks.md b/doc/en/Comparison-of-activity-stream-networks.md
deleted file mode 100644
index d76ae1006..000000000
--- a/doc/en/Comparison-of-activity-stream-networks.md
+++ /dev/null
@@ -1,23 +0,0 @@
-Comparison of different server-client networks providing activity streams to users
-====================================
-The goal of this table was to provide an overview of the security and privacy provided by server-client networks providing activity-streams.
-
-| project | license | distributed | supports node isolation | server-to-server encryption | 1-click E2EE* | database encryption sceme | supports cloning[^5] | encryption of private messages | PFS chat | wall-to-wall interaction | supports post editing and unsend private message | other |
-|-----------+---------------+-------------+-------------------------+-------------------------------------------+------------------------------------------+-----------------------------------------------------+-------------------------+-------------------------------------------------------------------------------------------------+-----------------------------------------------+--------------------------------------------------------------+--------------------------------------------------+------------------------------------------------------------------------------|
-| hubzilla | ISC aka MIT | yes | yes | Zot (PKI) + TLS | yes (via JavaScript, AES-256) | content obfuscation, private keys hidden in the DB. | yes, partly implemented | impossible to message privately in plaintext | no | yes, multiple separated channels possible within one account | yes | privacy built in, run your own @ home, nodes are called hubs |
-| diaspora | AGPLv3orlater | yes | no[^1] | PKI + SSL/TLS[^1] | no[^2] | mostly plaintext | no | ? | no | yes, no naming policy | no | nodes are called pods |
-| facebook | proprietary | no | no | planned, probably not implemented yet[^3] | implemented but not offered to users[^4] | unknown | no, walled garden | no, 3-d party plugin Cryptocat and pidgin is availiable but the user is not informed about this | no, with Cryptocat: yes, with pidgin+OTR: yes | only one wall allowed | only post editing | "real name"-policy enforced, advertising-driven, for profit company US-based |
-| twitter | proprietary | no | no | unknown | no | unknown, probably none | no, walled garden | no | no | yes | only post editing | advertising-driven, for profit company US-based |
-| | | | | | | | | | | | | |
-
-This table was edited with emacs using org-mode.
-
-[^1]: https://wiki.diasporafoundation.org/Federation_protocol_overview
-
-[^2]: forks providing this exists
-
-[^3]: http://news.softpedia.com/news/Facebook-Aims-to-Encrypt-Everything-Between-Data-Centers-433091.shtml
-
-[^4]: http://www.computerworld.com/article/2488773/cybercrime-hacking/facebook-holds-back-on-end-to-end-encryption.html
-
-[^5]: see the hubzilla help files for details about this feature.
diff --git a/doc/en/Creating-Templates.md b/doc/en/Creating-Templates.md
deleted file mode 100644
index fc3f18dd3..000000000
--- a/doc/en/Creating-Templates.md
+++ /dev/null
@@ -1,93 +0,0 @@
-Creating Page Templates
-=======================
-
-
-A page template for use with Comanche requires two files - a PHP template and a CSS file. Page templates will need to be installed by the system administrator of your site.
-
-
-First choose a name. Here we'll create a template and call it "demo".
-
-You will need to create the files "view/php/demo.php" and "view/css/demo.css" to hold the PHP template and CSS respectively.
-
-To get a better idea of this process, let's look at an existing template - the "default" template. This is used by default throughout the application.
-
-view/php/default.php
-====================
-
- <!DOCTYPE html >
- <html>
- <head>
- <title><?php if(x($page,'title')) echo $page['title'] ?></title>
- <script>var baseurl="<?php echo z_root() ?>";</script>
- <?php if(x($page,'htmlhead')) echo $page['htmlhead'] ?>
- </head>
- <body>
- <?php if(x($page,'nav')) echo $page['nav']; ?>
- <aside id="region_1"><?php if(x($page,'aside')) echo $page['aside']; ?></aside>
- <section id="region_2"><?php if(x($page,'content')) echo $page['content']; ?>
- <div id="page-footer"></div>
- <div id="pause"></div>
- </section>
- <aside id="region_3"><?php if(x($page,'right_aside')) echo $page['right_aside']; ?></aside>
- <footer><?php if(x($page,'footer')) echo $page['footer']; ?></footer>
- </body>
- </html>
-
-
-Here's is the corresponding CSS file
-
-view/php/default.css
-====================
-
-
- aside#region_1 {
- display: block;
- width: 210px;
- position: absolute;
- top: 65px;
- left: 0;
- margin-left: 10px;
- }
-
- aside input[type='text'] {
- width: 174px;
- }
-
-
- section {
- position: absolute;
- top: 65px;
- left: 250px;
- display: block;
- right: 15px;
- padding-bottom: 350px;
- }
-
-
-Some things you may notice when looking at these definitions:
-
-* We have not specified any CSS for the "nav", "right_aside", or "footer" regions. In this template "nav" and "footer" will be the full page width and we will let the size and placement of these elements be controlled by the theme. "right_aside" is not currently used.
-
-* There are elements on the page such as "page-footer" and "pause" for which there is no apparent content. This content will come from Javascript elements.
-
-* Our default template uses absolute positioning. Modern web design often uses "float" div containers so that scrollbars aren't typically needed when viewing on small-screen devices.
-
-To design a new template, it is best to start with an existing template, and modify it as desired. That is what we will do here.
-
-The way that Comanche provides content inside a specific region is by using a region tag.
-
- [region=aside][widget=profile][/widget][/region]
-
-This example will place a "profile" widget in the "aside" region. But what it actually does is place the HTML for the widget into a code variable **$page['aside']**. Our default page template defines a region on the page (the CSS positions this as an absolute sidebar) and then inserts the contents of $page['aside'] (if it exists).
-
-So if you wanted to create a template with a region named "foo", you would provide a place for it on the page, then include the contents of $page['foo'] wherever you wanted to use it, and then using Comanche, you could specify
-
- [region=foo][widget=profile][/widget][/region]
-
-and this would place a profile widget into the "foo" region you created.
-
-Use the CSS file to position the region on the page where desired and optionally control its size.
-
-[To be continued]
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/DerivedTheme1.md b/doc/en/DerivedTheme1.md
deleted file mode 100644
index b120c628c..000000000
--- a/doc/en/DerivedTheme1.md
+++ /dev/null
@@ -1,101 +0,0 @@
-Creating a Derived Theme
-========================
-
-**Lesson 1**
-
-A derived theme takes most of the settings from its "parent" theme and lets you change a few things to your liking without creating an entire theme package.
-
-
-To create a derived theme, first choose a name. For our example we'll call our theme 'mytheme'. Hopefully you'll be a bit more creative. But throughout this document, wherever you see 'mytheme', replace that with the name you chose.
-
-**Directory Structure**
-
-First you need to create a theme directory structure. We'll keep it simple. We need a php directory and a css directory. Here are the Unix/Linux commands to do this. Assume that 'mywebsite' is your top level $Projectname folder.
-
-
- cd mywebsite
- mkdir view/theme/mytheme
- mkdir view/theme/mytheme/css
- mkdir view/theme/mytheme/php
-
-
-Great. Now we need a couple of files. The first one is your theme info file, which describes the theme.
-
-It will be called view/theme/mytheme/php/theme.php (clever name huh?)
-
-Inside it, put the following information - edit as needed
-
- <?php
-
- /**
- * * Name: Mytheme
- * * Description: Sample Derived theme
- * * Version: 1.0
- * * Author: Your Name
- * * Compat: Red [*]
- *
- */
-
- function mytheme_init(&$a) {
-
- App::$theme_info['extends'] = 'redbasic';
-
-
- }
-
-
-Remember to rename the mytheme_init function with your theme name. In this case we will be extending the theme 'redbasic'.
-
-
-Now create another file. We call this a PCSS file, but it's really a PHP file.
-
-The file is called view/theme/mytheme/php/style.php
-
-In it, put the following:
-
- <?php
-
- require_once('view/theme/redbasic/php/style.php');
-
- echo @file_get_contents('view/theme/mytheme/css/style.css');
-
-
-
-That's it. This tells the software to read the PCSS information for the redbasic theme first, and then read our CSS file which will just consist of changes we want to make from our parent theme (redbasic).
-
-Now create the actual CSS file for your theme. Put it in view/theme/mytheme/css/style.css (where we just told the software to look for it). For our example, we'll just change the body background color so you can see that it works. You can use any CSS you'd like.
-
-
- body {
- background-color: #DDD;
- }
-
-
-You've just successfully created a derived theme. This needs to be enabled in the admin "themes" panel, and then anybody on the site can use it by selecting it in Settings->Display Settings as their default theme.
-
-**Lesson 2**
-
-If you want to use the redbasic schemas for your derived theme, you have to do a bit more.
-
-Do everything as above, but don't create view/theme/mytheme/php/style.php, but copy instead view/theme/redbasic/php/style.php to view/theme/mytheme/php/style.php. Modify that file and remove (or comment out) these two lines:
-
- if(local_channel() && App::$channel && App::$channel['channel_theme'] != 'redbasic')
- set_pconfig(local_channel(), 'redbasic', 'schema', '---');
-
-Also add this line at the bottom:
-
- echo @file_get_contents('view/theme/mytheme/css/style.css');
-
-To show the schema selector you have to copy view/theme/redbasic/tpl/theme_settings.tpl to view/theme/mytheme/tpl/theme_settings.tpl. Modify that file and replace the lines:
-
- {{if $theme == redbasic}}
- {{include file="field_select.tpl" field=$schema}}
- {{/if}}
-
-with:
-
- {{include file="field_select.tpl" field=$schema}}
-
-
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/Developers.md b/doc/en/Developers.md
deleted file mode 100644
index 624c058d2..000000000
--- a/doc/en/Developers.md
+++ /dev/null
@@ -1,54 +0,0 @@
-Developer Guide
-===================
-
-**Here is how you can join us.**
-
-First, get yourself a working git package on the system where you will be
-doing development.
-
-Create your own github account.
-
-You may fork/clone the $Projectname repository from [https://framagit.org/hubzilla/core.git](https://framagit.org/hubzilla/core.git).
-
-Follow the instructions provided here: [http://help.github.com/fork-a-repo/](http://help.github.com/fork-a-repo/)
-to create and use your own tracking fork on framagit
-
-Then go to your framagit page and create a "Pull request" when you are ready
-to notify us to merge your work.
-
-**Translations**
-
-Our translations are managed through Transifex. If you wish to help out translating $Projectname to another language, sign up on transifex.com, visit [https://www.transifex.com/projects/p/hubzilla/](https://www.transifex.com/projects/p/hubzilla/) and request to join one of the existing language teams or create a new one. Notify one of the core developers when you have a translation update which requires merging, or ask about merging it yourself if you're comfortable with git and PHP. We have a string file called 'messages.po' which is gettext compliant and a handful of email templates, and from there we automatically generate the application's language files.
-
-[Translations - More Info](help/Translations)
-
-**Important**
-
-Please pull in any changes from the project repository and merge them with your work **before** issuing a pull request. We reserve the right to reject any patch which results in a large number of merge conflicts. This is especially true in the case of language translations - where we may not be able to understand the subtle differences between conflicting versions.
-
-Also - **test your changes**. Don't assume that a simple fix won't break something else. If possible get an experienced Red developer to review the code.
-
-
-**Licensing**
-
-All code contributed to the project falls under the MIT license, unless otherwise specified. We will accept third-party code which falls under MIT, BSD and LGPL, but copyleft licensing (GPL, and AGPL) is only permitted in addons. It must be possible to completely remove the GPL (copyleft) code from the main project without breaking anything.
-
-**Coding Style**
-
-In the interests of consistency we adopt the following code styling. We may accept patches using other styles, but where possible please try to provide a consistent code style. We aren't going to argue or debate the merits of this style, and it is irrelevant what project 'xyz' uses. This is not project 'xyz'. This is a baseline to try and keep the code readable now and in the future.
-
-* All comments should be in English.
-
-* We use doxygen to generate documentation. This hasn't been consistently applied, but learning it and using it are highly encouraged.
-
-* Indentation is accomplished primarily with tabs using a tab-width of 4.
-
-* String concatenation and operators should be separated by whitespace. e.g. "$foo = $bar . 'abc';" instead of "$foo=$bar.'abc';"
-
-* Generally speaking, we use single quotes for string variables and double quotes for SQL statements. "Here documents" should be avoided. Sometimes using double quoted strings with variable replacement is the most efficient means of creating the string. In most cases, you should be using single quotes.
-
-* Use whitespace liberally to enhance readability. When creating arrays with many elements, we will often set one key/value pair per line, indented from the parent line appropriately. Lining up the assignment operators takes a bit more work, but also increases readability.
-
-* Generally speaking, opening braces go on the same line as the thing which opens the brace. They are the last character on the line. Closing braces are on a line by themselves.
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/Features.md b/doc/en/Features.md
deleted file mode 100644
index a43fd73fa..000000000
--- a/doc/en/Features.md
+++ /dev/null
@@ -1,108 +0,0 @@
-Extra Features
-==============
-
-The default interface of $Projectname was designed to be uncluttered. There are a huge number of extra features (some of which are extremely useful) which you can turn on and get the most of the application. These are found under the [Extra Features](settings/features) link of your [Settings](settings) page.
-
-**Content Expiration**
-
-Remove posts/comments and/or private messages at a future time. An extra button is added to the post editor which asks you for an expiration. Typically this in "yyyy-mm-dd hh:mm" format, but in the English language you have a bit more freedom and can use most any recognisable date reference such as "next Thursday" or "+1 day". At the specified time (give or take approximately ten minutes based on the remote system's checking frequency) the post is removed.
-
-**Multiple Profiles**
-
-The ability to create multiple profiles which are visible only to specific persons or groups. Your default profile may be visible to anybody, but secondary profiles can all contain different or additional information and can only be seen by those to whom that profile is assigned.
-
-**Web Pages**
-
-Provides the ability to use web page design feaures and create custom webpages from your own content and also to design the pages with page layouts, custom menus, and content blocks.
-
-**Private Notes**
-
-On pages where it is available (your matrix page and personal web pages) provide a "widget" to create and store personal reminders and notes.
-
-**Enhanced Photo Albums**
-
-Provides a photo album viewer that is a bit prettier than the normal interface.
-
-**Extended Identity Sharing**
-
-By default your identity travels with you as you browse the matrix to remote sites - and they know who you are and can show you content that only you can see. With Extended Identity Sharing you can provide this information to any website you visit from within the matrix.
-
-**Expert Mode**
-
-This allows you to see some advanced configuration options that would confuse some people or cause support issues. In particular this can give you full control over theme features and colours - so that you can tweak a large number of settings of the display theme to your liking.
-
-**Premium Channel**
-
-This allows you to set restrictions and terms on those that connect with your channel. This may be used by celebrities or anybody else who wishes to describe their channel to people who wish to connect with it. In certain cases you may be asked for payment in order to connect.
-
-
-**Richtext Editor**
-
-The status post editor is plaintext, but the matrix allows a wide range of markup using BBcode. The visual editor provides "what you see is what you get" for many of the most frequently used markup tags.
-
-**Post Preview**
-
-Allows previewing posts and comments exactly as they would look on the page before publishing them.
-
-**Channel Sources**
-
-Automatically import and re-publish channel content from other channels or feeds. This allows you to create sub-channels and super-channels from content provided elsewhere. The rules are that the content must be public, and the channel owner must give you permission to source their channel.
-
-**Even More Encryption**
-
-Private messages are encrypted during transport and storage. In this day and age, this encyption may not be enough if your communications are extremely sensitive. This options lets you provide optional encryption of content "end-to-end" with a shared secret key. How the recipient learns the secret key is completely up to you. You can provide a hint such as "the name of aunt Claire's first dog".
-
-
-**Search by Date**
-
-This provides the ability to select posts by date ranges
-
-**Privacy Group Filter**
-
-Enable widget to display stream posts only from selected groups of connection. This also toggles the outbound permissions while you are viewing a privacy group. This is analogous to Google "circles" or Disapora "aspects".
-
-**Saved Searches**
-
-Provides a search widget on your matrix page which can save selected search terms for re-use.
-
-**Personal Tab**
-
-Enable tab to display only matrix posts that you've interacted with in some way, as an author or a contributor to the conversation.
-
-**New Tab**
-
-Enables a tab to display all new matrix activity as a firehose or timeline.
-
-**Affinity Tool**
-
-Filter matrix stream activity by the depth of your relationships
-
-**Edit Sent Posts**
-
-Edit and correct posts and comments after sending
-
-**Tagging**
-
-Ability to tag existing posts, including those written by others.
-
-**Post Categories**
-
-Add categories to your channel posts
-
-**Saved Folders**
-
-Ability to file posts under folders or tags for later recall
-
-**Dislike Posts**
-
-Ability to dislike posts/comments
-
-**Star Posts**
-
-Ability to mark special posts with a star indicator
-
-**Tag Cloud**
-
-Provide a personal tag cloud on your channel page
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/Hubzilla_on_OpenShift.bb b/doc/en/Hubzilla_on_OpenShift.bb
deleted file mode 100644
index 7bdd70955..000000000
--- a/doc/en/Hubzilla_on_OpenShift.bb
+++ /dev/null
@@ -1,105 +0,0 @@
-[b]$Projectname on OpenShift[/b]
-You will notice a new .openshift folder when you fetch from upstream, i.e. from [url=https://framagit.org/hubzilla/core.git]https://framagit.org/hubzilla/core.git[/url] , which contains a deploy script to set up Hubzilla on OpenShift with plugins and extra themes.
-
-As of this writing, 2015-10-28, you do not have to pay for OpenShift on the Free plan, which gives you three gears at no cost. The Bronze plan gives you three gears at no cost too, but you can expand to 16 gears by paying, and this requires you to register your payment card. The three gears can give three instances of Hubzilla with one gear each, or you can combine two gears into one high-availability Hubzilla instance and one extra gear. The main difference to be aware of is this: gears on the Free plan will go into hibernation if left idle for too long, this does not happen on the Bronze plan.
-
-Create an account on OpenShift, then use the registration e-mail and password to create your first Hubzilla instance. Install git and RedHat's command line tools - rhc - if you have not already done so. See for example https://developers.openshift.com/en/getting-started-debian-ubuntu.html on how to do this on Debian GNU/Linux, or in the menu on that page for other GNU/Linux distributions or other operating systems.
-
-[code]rhc app-create your_app_name php-5.4 mysql-5.5 cron phpmyadmin --namespace your_domain --from-code https://framagit.org/hubzilla/core.git -l your@email.address -p your_account_password
-[/code]
-
-Make a note of the database username and password OpenShift creates for your instance, and use these at [url=https://your_app_name-your_domain.rhcloud.com/]https://your_app_name-your_domain.rhcloud.com/[/url] to complete the setup. You MUST change server address from 127.0.0.1 to localhost.
-
-NOTE: PostgreSQL is NOT supported by the deploy script yet, see [zrl=https://zot-mor.rhcloud.com/display/3c7035f2a6febf87057d84ea0ae511223e9b38dc27913177bc0df053edecac7c@zot-mor.rhcloud.com?zid=haakon%40zot-mor.rhcloud.com]this thread[/zrl].
-
-[b]Update[/b]
-To update, consider your own workflow first. I have forked Hubzilla code into my GitHub account to be able to try things out, this remote repo is called origin. Here is how I fetch new code from upstream, merge into my local repo, then push the updated code both into origin and the remote repo called openshift.
-
-[code]git fetch upstream;git checkout master;git merge upstream/master;git push origin;git push openshift HEAD
-[/code]
-
-[b]Administration[/b]
-Symptoms of need for MySQL database administration are:
-[list]
-[*] you can visit your domain and see the Hubzilla frontpage, but trying to login throws you back to login. This can mean your session table is marked as crashed.
-[*] you can login, but your channel posts are not visible. This can mean your item table is marked as crashed.
-[*] you can login and you can see your channel posts, but apparently nobody is getting your posts, comments, likes and so on. This can mean your outq table is marked as crashed.
-[/list]
-
-You can check your OpenShift logs by doing
-
-[code]
-rhc tail -a your_app_name -n your_domain -l your@email.address -p your_account_password
-[/code]
-
-and you might be able to confirm the above suspicions about crashed tables, or other problems you need to fix.
-
-[b]How to fix crashed tables in MySQL[/b]
-Using MySQL and the MyISAM database engine can result in table indexes coming out of sync, and you have at least two options for fixing tables marked as crashed.
-[list]
-[*] Use the database username and password OpenShift creates for your instance at [url=https://your_app_name-your_domain.rhcloud.com/phpmyadmin/]https://your_app_name-your_domain.rhcloud.com/phpmyadmin/[/url] to login via the web into your phpMyAdmin web interface, click your database in the left column, in the right column scroll down to the bottom of the list of tables and click the checkbox for marking all tables, then select Check tables from the drop down menu. This will check the tables for problems, and you can then checkmark only those tables with problems, and select Repair table from the same drop down menu at the bottom.
-[*] You can port-forward the MySQL database service to your own machine and use the MySQL client called mysqlcheck to check, repair and optimize your database or individual database tables without stopping the MySQL service on OpenShift. Run the following in two separate console windows.
-
-To port-forward do
-
-[code]rhc port-forward -a your_app_name -n your_domain -l your@email.address -p your_password[/code]
-
-in one console window, then do either -o for optimize, -c for check or -r for repair, like this
-
-[code]mysqlcheck -h 127.0.0.1 -r your_app_name -u your_app_admin_name -p[/code]
-
-and give the app's password at the prompt. If all goes well you should see a number of table names with an OK behind them.
-
-You can now
-[code]Press CTRL-C to terminate port forwarding[/code]
-[*] You can do
-
-[code]rhc cartridge stop mysql-5.5 -a your_app_name[/code]
-
-to stop the MySQL service running in your app on OpenShift before running myisamchk - which should only be run when MySQL is stopped, and then
-login to your instance with SSH - see OpenShift for details - and do
-
-[code]cd mysql/data/your_database
-myisamchk -r *.MYI[/code]
-
-or if you get
-
-[code]Can't create new tempfile[/code]
-
-check your OpenShift's gear quota with
-
-[code]quota -gus[/code]
-
-and if you are short on space, then locally (not SSH) do
-
-[code]rhc app-tidy your_app_name -l your_login -p your_password[/code]
-
-to have rhc delete temporary files and OpenShift logs to free space first, then check the size of your local repo dir and execute
-
-[code]git gc[/code]
-
-against it and check the size again, and then to minimize your remote repo connect via SSH to your application gear and execute the same command against it by changing to the remote repo directory - your repo should be in
-
-[code]~/git/your_app_name.git[/code]
-
-(if not, do find -size +1M to find it), then do
-
-[code]
-cd
-cd mysql/data/yourdatabase
-myisamchk -r -v -f*.MYI[/code]
-
-and hopefully your database tables are now okay.
-You can now start the MySQL service on OpenShift by locally doing
-
-[code]rhc cartridge start mysql-5.5 -a your_app_name[/code]
-[/list]
-
-[b]Notes[/b]
-[list]
-[*] definitely DO turn off feeds and discovery by default and limit delivery reports from 30 days to 3 days if you are on the Free or Bronze plan on OpenShift with a single 1Gb gear by visiting [observer.baseurl]/admin/site when logged in as administrator of your Hubzilla site.
-[*] The above defaults have been added into the deploy script.
-[*] DO add git gc to the deploy script
-[*] MAYBE DO add myisamchk - only checking? to the end of the deploy script.
-[*] mysqlcheck is similar in function to myisamchk, but works differently. The main operational difference is that mysqlcheck must be used when the mysqld server is running, whereas myisamchk should be used when it is not. The benefit of using mysqlcheck is that you do not have to stop the server to perform table maintenance - this means this documenation should be fixed.
-[/list]
diff --git a/doc/en/Plugins.md b/doc/en/Plugins.md
deleted file mode 100644
index 88b42185b..000000000
--- a/doc/en/Plugins.md
+++ /dev/null
@@ -1,263 +0,0 @@
-
-Creating Plugins/Addons for $Projectname
-==========================================
-
-
-So you want to make $Projectname do something it doesn't already do. There are lots of ways. But let's learn how to write a plugin or addon.
-
-
-In your $Projectname folder/directory, you will probably see a sub-directory called 'addon'. If you don't have one already, go ahead and create it.
-
-
- mkdir addon
-
-Then figure out a name for your addon. You probably have at least a vague idea of what you want it to do. For our example I'm going to create a plugin called 'randplace' that provides a somewhat random location for each of your posts. The name of your plugin is used to find the functions we need to access and is part of the function names, so to be safe, use only simple text characters.
-
-Once you've chosen a name, create a directory beneath 'addon' to hold your working file or files.
-
- mkdir addon/randplace
-
-Now create your plugin file. It needs to have the same name, and it's a PHP script, so using your favourite editor, create the file
-
- addon/randplace/randplace.php
-
-The very first line of this file needs to be
-
- <?php
-
-Then we're going to create a comment block to describe the plugin. There's a special format for this. We use /* ... */ comment-style and some tagged lines consisting of
-
- /**
- *
- * Name: Random Place (here you can use better descriptions than you could in the filename)
- * Description: Sample $Projectname plugin, Sets a random place when posting.
- * Version: 1.0
- * Author: Mike Macgirvin <mike@zothub.com>
- *
- */
-
-These tags will be seen by the site administrator when he/she installs or manages plugins from the admin panel. There can be more than one author. Just add another line starting with 'Author:'.
-
-The typical plugin will have at least the following functions:
-
-* pluginname_load()
-* pluginname_unload()
-
-In our case, we'll call them randplace_load() and randplace_unload(), as that is the name of our plugin. These functions are called whenever we wish to either initialise the plugin or remove it from the current webpage. Also if your plugin requires things like altering the database schema before it can run for the very first time, you would likely place these instructions in the functions named
-
-* pluginname_install()
-* pluginname_uninstall()
-
-
-Next we'll talk about **hooks**. Hooks are places in $Projectname code where we allow plugins to do stuff. There are a [lot of these](help/Hooks), and they each have a name. What we normally do is use the pluginname_load() function to register a "handler function" for any hooks you are interested in. Then when any of these hooks are triggered, your code will be called.
-
-We register hook handlers with the 'register_hook()' function. It takes 3 arguments. The first is the hook we wish to catch, the second is the filename of the file to find our handler function (relative to the base of your $Projectname installation), and the third is the function name of your handler function. So let's create our randplace_load() function right now.
-
-
- function randplace_load() {
- register_hook('post_local', 'addon/randplace/randplace.php', 'randplace_post_hook');
-
- register_hook('feature_settings', 'addon/randplace/randplace.php', 'randplace_settings');
- register_hook('feature_settings_post', 'addon/randplace/randplace.php', 'randplace_settings_post');
-
- }
-
-
-So we're going to catch three events, 'post_local' which is triggered when a post is made on the local system, 'feature_settings' to set some preferences for our plugin, and 'feature_settings_post' to store those settings.
-
-Next we'll create an unload function. This is easy, as it just unregisters our hooks. It takes exactly the same arguments.
-
- function randplace_unload() {
- unregister_hook('post_local', 'addon/randplace/randplace.php', 'randplace_post_hook');
-
- unregister_hook('feature_settings', 'addon/randplace/randplace.php', 'randplace_settings');
- unregister_hook('feature_settings_post', 'addon/randplace/randplace.php', 'randplace_settings_post');
-
- }
-
-
-Hooks are called with two arguments. The first is always $a, which is our global App structure and contains a huge amount of information about the state of the web request we are processing; as well as who the viewer is, and what our login state is, and the current contents of the web page we're probably constructing.
-
-The second argument is specific to the hook you're calling. It contains information relevant to that particular place in the program, and often allows you to look at, and even change it. In order to change it, you need to add '&' to the variable name so it is passed to your function by reference. Otherwise it will create a copy and any changes you make will be lost when the hook process returns. Usually (but not always) the second argument is a named array of data structures. Please see the "hook reference" (not yet written as of this date) for details on any specific hook. Occasionally you may need to view the program source to see precisely how a given hook is called and how the results are processed.
-
-Let's go ahead and add some code to implement our post_local hook handler.
-
- function randplace_post_hook($a, &$item) {
-
- /**
- *
- * An item was posted on the local system.
- * We are going to look for specific items:
- * - A status post by a profile owner
- * - The profile owner must have allowed our plugin
- *
- */
-
- logger('randplace invoked');
-
- if(! local_channel()) /* non-zero if this is a logged in user of this system */
- return;
-
- if(local_channel() != $item['uid']) /* Does this person own the post? */
- return;
-
- if(($item['parent']) || (! is_item_normal($item))) {
- /* If the item has a parent, or isn't "normal", this is a comment or something else, not a status post. */
- return;
- }
-
- /* Retrieve our personal config setting */
-
- $active = get_pconfig(local_channel(), 'randplace', 'enable');
-
- if(! $active)
- return;
- /**
- *
- * OK, we're allowed to do our stuff.
- * Here's what we are going to do:
- * load the list of timezone names, and use that to generate a list of world cities.
- * Then we'll pick one of those at random and put it in the "location" field for the post.
- *
- */
-
- $cities = array();
- $zones = timezone_identifiers_list();
- foreach($zones as $zone) {
- if((strpos($zone,'/')) && (! stristr($zone,'US/')) && (! stristr($zone,'Etc/')))
- $cities[] = str_replace('_', ' ',substr($zone,strpos($zone,'/') + 1));
- }
-
- if(! count($cities))
- return;
- $city = array_rand($cities,1);
- $item['location'] = $cities[$city];
-
- return;
- }
-
-
-Now let's add our functions to create and store preference settings.
-
- /**
- *
- * Callback from the settings post function.
- * $post contains the global $_POST array.
- * We will make sure we've got a valid user account
- * and that only our own submit button was clicked
- * and if so set our configuration setting for this person.
- *
- */
-
- function randplace_settings_post($a,$post) {
- if(! local_channel())
- return;
- if($_POST['randplace-submit'])
- set_pconfig(local_channel(),'randplace','enable',intval($_POST['randplace']));
- }
-
-
-
- /**
- *
- * Called from the Feature Setting form.
- * The second argument is a string in this case, the HTML content region of the page.
- * Add our own settings info to the string.
- *
- * For uniformity of settings pages, we use the following convention
- * <div class="settings-block">
- * <h3>title</h3>
- * .... settings html - many elements will be floated...
- * <div class="clear"></div> <!-- generic class which clears all floats -->
- * <input type="submit" name="pluginnname-submit" class="settings-submit" ..... />
- * </div>
- */
-
-
-
- function randplace_settings(&$a,&$s) {
-
- if(! local_channel())
- return;
-
- /* Add our stylesheet to the page so we can make our settings look nice */
-
- head_add_css('/addon/randplace/randplace.css');
-
- /* Get the current state of our config variable */
-
- $enabled = get_pconfig(local_channel(),'randplace','enable');
-
- $checked = (($enabled) ? ' checked="checked" ' : '');
-
- /* Add some HTML to the existing form */
-
- $s .= '<div class="settings-block">';
- $s .= '<h3>' . t('Randplace Settings') . '</h3>';
- $s .= '<div id="randplace-enable-wrapper">';
- $s .= '<label id="randplace-enable-label" for="randplace-checkbox">' . t('Enable Randplace Plugin') . '</label>';
- $s .= '<input id="randplace-checkbox" type="checkbox" name="randplace" value="1" ' . $checked . '/>';
- $s .= '</div><div class="clear"></div>';
-
- /* provide a submit button */
-
- $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="randplace-submit" class="settings-submit" value="' . t('Submit') . '" /></div></div>';
-
- }
-
-
-
-
-
-***Advanced Plugins***
-
-Sometimes your plugins want to provide a range of new functionality which isn't provided at all or is clumsy to provide using hooks. In this case your plugin can also act as a 'module'. A module in our case refers to a structured webpage handler which responds to a given URL. Then anything which accesses that URL will be handled completely by your plugin.
-
-The key to this is to create a simple function named pluginname_module() which does nothing.
-
- function randplace_module() { return; }
-
-Once this function exists, the URL https://yoursite/randplace will access your plugin as a module. Then you can define functions which are called at various points to build a webpage just like the modules in the mod/ directory. The typical functions and the order which they are called is
-
- modulename_init($a) // (e.g. randplace_init($a);) called first - if you wish to emit json or xml,
- // you should do it here, followed by killme() which will avoid the default action of building a webpage
- modulename_aside($a) // Often used to create sidebar content
- modulename_post($a) // Called whenever the page is accessed via the "post" method
- modulename_content($a) // called to generate the central page content. This function should return a string
- // consisting of the central page content.
-
-Your module functions have access to the URL path as if they were standalone programs in the Unix operating system. For instance if you visit the page
-
- https://yoursite/randplace/something/somewhere/whatever
-
-we will create an argc/argv list for use by your module functions
-
- $x = argc(); $x will be 4, the number of path arguments after the sitename
-
- for($x = 0; $x < argc(); $x ++)
- echo $x . ' ' . argv($x);
-
-
- 0 randplace
- 1 something
- 2 somewhere
- 3 whatever
-
-
-***Porting Friendica Plugins***
-
-$Projectname uses a similar plugin architecture to the Friendica project. The authentication, identity, and permissions systems are completely different. Many Friendica plugins can be ported reasonably easily by renaming a few functions - and then ensuring that the permissions model is adhered to. The functions which need to be renamed are:
-
-* Friendica's pluginname_install() is pluginname_load()
-
-* Friendica's pluginname_uninstall() is pluginname_unload()
-
-$Projectname has _install and _uninstall functions but these are used differently.
-
-* Friendica's "plugin_settings" hook is called "feature_settings"
-
-* Friendica's "plugin_settings_post" hook is called "feature_settings_post"
-
-Changing these will often allow your plugin to function, but please double check all your permission and identity code because the concepts behind it are completely different in $Projectname. Many structured data names (especially DB schema columns) are also quite different.
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/Primary-Directory.md b/doc/en/Primary-Directory.md
deleted file mode 100644
index 92460c346..000000000
--- a/doc/en/Primary-Directory.md
+++ /dev/null
@@ -1,47 +0,0 @@
-#Primary Directory#
-
-By default, $Projectname will use available Directories on the web, which show you channels available around the world.
-
-There are certain scenarios where you might want your own directory-server that you can connect multiple hubs to. This will limit the channels that appear in all of your hubs to only channels on hubs connected to your directory-server.
-
-
-
-##Instuctions on how to set up one hub as the Primary Directory for a series of private hubs.##
-***
-
-
-* On the hub that will be the Directory Server, open the .htconfig.php file and set:
-
- `App::$config['system']['directory_mode'] = DIRECTORY_MODE_PRIMARY;`
-
-
- By default it should already be set as **DIRECTORY_MODE_NORMAL**, so just edit that line to say **DIRECTORY_MODE_PRIMARY**
-
-* Next, for each hub (including the Directory Server), from a terminal, cd into the folder where it is installed and run this :
-
- `util/config system directory_realm YOURREALMNAME`
-
- (**YOURREALMNAME** can be whatever you want your realm-name to be)
-
- then:
-
- `util/config system realm_token THEPASSWORD`
-
- (**THEPASSWORD** is whatever password you want for your realm)
-
- **NOTE:** Use the same realm-name and password for each hub
-
-* Lastly, for each "client" hub, (from a terminal) run:
-
- `util/config system directory_server https://theaddressofyourdirectoryserver.com`
-
-***
-Now when you view the directory of each hub, it should only show the channels that exist on the hubs in your realm. I have tested with two hubs so far, and it seems to be working fine.
-Channels created in each hub are reflected in the Primary Directory, and subsequently in the directory of all client hubs
-
-##Issues##
-***
-
-When I created the first hub,it was up and running for an hour or so before I changed it to PRIMARY_MODE, and after changing it, there were a few channels from across the matrix still present in the directory. I deleted them from the xchan table and that seems to have fixed the issue.
-
-
diff --git a/doc/en/Remove-Account.md b/doc/en/Remove-Account.md
deleted file mode 100644
index a8ef733a6..000000000
--- a/doc/en/Remove-Account.md
+++ /dev/null
@@ -1,26 +0,0 @@
-// this page is out of date...
-Remove Account
-==============
-
-It is presently not possible to remove an account without asking your site administrator for assistance.
-We'll have better doco when somebody finishes that bit.
-
-
-Remove Channel
-==============
-
-Visit the URL
-
- https://yoursite/removeme
-
-(replace 'yoursite' with the domain name of your $Projectname site).
-
-You will need to confirm your password and the channel you are currently logged into will be removed.
-
-This is irreversible.
-
-If you have identity clones on other sites this only removes the channel instance which exists
-on this site.
-
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/Schema-development.md b/doc/en/Schema-development.md
deleted file mode 100644
index e811bb8c3..000000000
--- a/doc/en/Schema-development.md
+++ /dev/null
@@ -1,81 +0,0 @@
-Red development - a guide to the schema system
-==============================================
-
-
-A schema, in a nutshell, is a collection of settings for a bunch of variables to define
-certain elements of a theme. A schema is loaded as though it were part of config.php
-and has access to all the same information. Importantly, this means it is identity aware,
-and can be used to do some interesting things. One could, for example, restrict options
-by service class, or present different options to different members.
-
-By default, we filter only by whether or not expert mode is enabled. If expert mode is
-enabled, all options are presented to the member. If it is not, only scheme, background
-image, font face, and iconset are available as choices.
-
-A schema is loaded *after* the member's personal settings. Therefore, to allow a member
-to overwrite a particular aspect of a schema you would use the following syntax:
-
- if (! $foo)
- $foo = 'bar';
-
-However, there are circumstances - particularly with positional elements - where it
-may be desirable (or necessary) to override a member's settings. In this case, the syntax
-is even simpler:
-
- $foo = 'bar';
-
-Members will not thank you for this, however, so only use it when it is required.
-
-If no personal options are set, and no schema is selected, we will first try to load a schema
-with the file name "default.php". This file should never be included with a theme. If it
-is, merge conflicts will occur as people update their code. Rather, this should be defined
-by administrators on a site by site basis.
-default.php and default.css MUST be symlinks to existing scheme files.
-
-You schema does not need to - and should not - contain all of these values. Only the values
-that differ from the defaults should be listed. This gives you some very powerful options
-with very few lines of code.
-
-Note the options available differ with each theme. The options available with the Redbasic
-theme are as follows:
-
-* nav_colour
- The colour of the navigation bar. Options are red, black and silver. Alternatively,
- one can set $nav_bg_1, $nav_bg_2, $nav_bg_3 and $nav_bg_4 to provide gradient and
- hover effects.
-* banner_colour
- The font colour of the banner element. Accepts an RGB or Hex value.
-* bgcolour
- Set the body background colour. Accepts an RGB or Hex value.
-* background_image
- Sets a background image. Accepts a URL or path.
-* item_colour
- Set the background colour of items. Accepts an RGB or Hex value.
-* item_opacity
- Set the opacity of items. Accepts a value from 0.01 to 1
-* toolicon_colour
- Set the colour of tool icons. Accepts an RGB or Hex value.
-* toolicon_activecolour
- Set the colour of active or hovered icon tools.
-* font_size
- Set the size of fonts in items and posts. Accepts px or em.
-* body_font_size
- Sets the size of fonts at the body level. Accepts px or em.
-* font_colour
- Sets the font colour. Accepts an RGB or Hex value.
-* radius
- Set the radius of corners. Accepts a numeral, and is always in px.
-* shadow
- Set the size of shadows shown with inline images. Accepts a numerical
- value. Note shadows are not applied to smileys.
-* converse_width
- Set the maximum width of the content region in px.
-* nav_min_opacity
-* top_photo
-* reply_photo
-
-If a your_schema_name.css file is found, the content of this file will be attached to the end of style.css.
-This gives the schem developer the possiblity to override any style component.
-
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/TermsOfService.md b/doc/en/TermsOfService.md
deleted file mode 100644
index c44b1eb56..000000000
--- a/doc/en/TermsOfService.md
+++ /dev/null
@@ -1,11 +0,0 @@
-Privacy Policy
-==============
-
-#include doc/en/gdpr1.md;
-
-
-Terms of Service
-================
-
-#include doc/en/SiteTOS.md;
-
diff --git a/doc/en/Translations.md b/doc/en/Translations.md
deleted file mode 100644
index 6106e43b1..000000000
--- a/doc/en/Translations.md
+++ /dev/null
@@ -1,93 +0,0 @@
-Translating $Projectname
-==========================
-
-Translation Process
--------------------
-
-The strings used in the UI of $Projectname is translated at [Transifex][1] and then
-included in the git repository at github. If you want to help with translation
-for any language, be it correcting terms or translating $Projectname to a
-currently not supported language, please register an account at transifex.com
-and contact the Redmatrix translation team there.
-
-Translating $Projectname is simple. Just use the online tool at transifex. If you
-don't want to deal with git & co. that is fine, we check the status of the
-translations regularly and import them into the source tree at github so that
-others can use them.
-
-We do not include every translation from transifex in the source tree to avoid
-a scattered and disturbed overall experience. As an uneducated guess we have a
-lower limit of 50% translated strings before we include the language. This
-limit is judging only by the amount of translated strings under the assumption
-that the most prominent strings for the UI will be translated first by a
-translation team. If you feel your translation useable before this limit,
-please contact us and we will probably include your teams work in the source
-tree.
-
-If you want to get your work into the source tree yourself, feel free to do so
-and contact us with and question that arises. The process is simple and
-$Projectname ships with all the tools necessary.
-
-The location of the translated files in the source tree is
- /view/LNG-CODE/
-where LNG-CODE is the language code used, e.g. de for German or fr for French.
-For the email templates (the *.tpl files) just place them into the directory
-and you are done. The translated strings come as a "hmessages.po" file from
-transifex which needs to be translated into the PHP file $Projectname uses. To do
-so, place the file in the directory mentioned above and use the "po2php"
-utility from the util directory of your $Projectname installation.
-
-Assuming you want to convert the German localization which is placed in
-view/de/hmessages.po you would do the following.
-
-1. Navigate at the command prompt to the base directory of your
- $Projectname installation
-
-2. Execute the po2php script, which will place the translation
- in the hstrings.php file that is used by $Projectname.
-
- $> php util/po2php.php view/de/hmessages.po
-
- The output of the script will be placed at view/de/hstrings.php where
- froemdoca os expecting it, so you can test your translation mmediately.
-
-3. Visit your $Projectname page to check if it still works in the language you
- just translated. If not try to find the error, most likely PHP will give
- you a hint in the log/warnings.about the error.
-
- For debugging you can also try to "run" the file with PHP. This should
- not give any output if the file is ok but might give a hint for
- searching the bug in the file.
-
- $> php view/de/hstrings.php
-
-4. commit the two files with a meaningful commit message to your git
- repository, push it to your fork of the $Projectname repository at github and
- issue a pull request for that commit.
-
-Utilities
----------
-
-Additional to the po2php script there are some more utilities for translation
-in the "util" directory of the $Projectname source tree. If you only want to
-translate $Projectname into another language you wont need any of these tools most
-likely but it gives you an idea how the translation process of $Projectname
-works.
-
-For further information see the utils/README file.
-
-Known Problems
---------------
-
-* $Projectname uses the language setting of the visitors browser to determain the
- language for the UI. Most of the time this works, but there are some known
- quirks.
-* the early translations are based on the friendica translations, if you
- some rough translations please let us know or fix them at Transifex.
-
-Links
-------
-[1]: http://www.transifex.com/projects/p/hubzilla/
-
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/Widgets.md b/doc/en/Widgets.md
deleted file mode 100644
index 6779d7ffd..000000000
--- a/doc/en/Widgets.md
+++ /dev/null
@@ -1,174 +0,0 @@
-Core Widgets
-============
-
-Some/many of these widgets have restrictions which may restrict the type of page where they may appear or may require login
-
-
-* clock - displays the current time
- * args: military (1 or 0) - use 24 hour time as opposed to AM/PM
-<br />&nbsp;<br />
-
-* profile - displays a profile sidebar on pages which load profiles (pages with nickname in the URL)
-
-* tagcloud - display a tagcloud of webpage items
-
- * args: count - number of items to return (default 24)
-<br />&nbsp;<br />
-
-* collections - privacy group selector for the current logged in channel
-
- * args: mode - one of "conversation", "group", "abook" depending on module
-<br />&nbsp;<br />
-
-* suggestions - friend suggestions for the current logged on channel
-
-* follow - presents a text box for following another channel
-
-* notes - private notes area for the current logged in channel if private_notes feature is enabled
-
-* savedsearch - network/matrix search with save - must be logged in and savedsearch feature enabled
-
-* filer - select filed items from network/matrix stream - must be logged in
-
-* archive - date range selector for network and channel pages
- * args: 'wall' - 1 or 0, limit to wall posts or network/matrix posts (default)
-<br />&nbsp;<br />
-
-* fullprofile - same as profile currently
-
-* categories - categories filter (channel page)
-
-* tagcloud_wall - tagcloud for channel page only
- * args: 'limit' - number of tags to return (default 50)
-<br />&nbsp;<br />
-
-* catcloud_wall - tagcloud for channel page categories
- * args: 'limit' - number of categories to return (default 50)
-<br />&nbsp;<br />
-
-* affinity - affinity slider for network page - must be logged in
-
-* settings_menu - sidebar menu for settings page, must be logged in
-
-* mailmenu - sidebar menu for private message page - must be logged in
-
-* design_tools - design tools menu for webpage building pages, must be logged in
-
-* findpeople - tools to find other channels
-
-* photo_albums - list photo albums of the current page owner with a selector menu
-
-* vcard - mini profile sidebar for the person of interest (page owner, whatever)
-
-* dirsafemode - directory selection tool - only on directory pages
-
-* dirsort - directory selection tool - only on directory pages
-
-* dirtags - directory tool - only on directory pages
-
-* menu_preview - preview a menu - only on menu edit pages
-
-* chatroom_list - list of chatrooms for the page owner
-
-* bookmarkedchats - list of bookmarked chatrooms collected on this site for the current observer
-
-* suggestedchats - "interesting" chatrooms chosen for the current observer
-
-* item - displays a single webpage item by mid or page title
- * args:
- * channel_id - channel that owns the content, defualt is the profile_uid
- * mid - message_id of webpage to display (must be webpage, not a conversation item)
- * title - URL page title of webpage (must provide one of either title or mid)
-<br />&nbsp;<br />
-
-* photo - display a single photo
- * args:
- * src - URL of photo, must be http or https
- * zrl - use zid authenticated link
- * style - CSS style string
-<br />&nbsp;<br />
-
-* cover_photo - display the cover photo for the selected channel
- * args:
- * channel_id - channel to use, default is the profile_uid
- * style - CSS style string (default is dynamically resized to width of region)
-<br />&nbsp;<br />
-
-
-* photo_rand - display a random photo from one of your photo albums. Photo permissions are honoured
- * args:
- * album - album name (very strongly recommended if you have lots of photos)
- * scale - typically 0 (original size), 1 (1024px), 2, (640px), or 3 (320px)
- * style - CSS style string
- * channel_id - if not your own
-<br />&nbsp;<br />
-
-* random_block - display a random block element from your webpage design tools collection. Permissions are honoured.
- * args:
- * contains - only return blocks which include the contains string in the block name
- * channel_id - if not your own
-<br />&nbsp;<br />
-
-* tasklist - provide a task or to-do list for the currently logged-in channel.
- * args:
- * all - display completed tasks if all is non-zero.
-<br />&nbsp;<br />
-
-* forums - provide a list of connected public forums with unseen counts for the current logged-in channel.
-<br />&nbsp;<br />
-
-* activity - provide a list of authors of unread network content for the current logged-in channel.
-
-* album - provides a widget containing a complete photo album from albums belonging to the page owner; this may be too large to present in a sidebar region as is best implemented as a content region widget.
- * args:
- * album - album name
- * title - optional title, album name is used if not present
-<br />&nbsp;<br />
-
-
-Creating New Widgets
-====================
-
-### Class Widgets
-
-To create a class-based widget named 'slugfish' create a file with the following contents:
-
-````
-<?php
-
-namespace Zotlabs\Widget;
-
-
-class Slugfish {
-
- function widget($args) {
-
- ... widget code goes here.
- ... The function returns a string which is the HTML content of the widget.
- ... $args is a named array which is passed any [var] variables from the layout editor
- ... For instance [widget=slugfish][var=count]3[/var][/widget] will populate $args with
- ... [ 'count' => 3 ]
-
- }
-
-````
-
-The resultant file may be placed in widget/Slugfish/Slugfish.php , or Zotlabs/SiteWidgets/Slugfish.php . It also may be linked from a git repository using util/add_widget_repo.
-
-
-
-Traditional function based widget:
-
-If you want a widget named 'slugfish', create widget/widget_slugfish.php containing
-
-
- <?php
-
- function widget_slugfish($args) {
-
- .. widget code goes here. See above information for class-based widgets for details.
-
- }
-
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/Zot---A-High-Level-Overview.md b/doc/en/Zot---A-High-Level-Overview.md
deleted file mode 100644
index 990ea037b..000000000
--- a/doc/en/Zot---A-High-Level-Overview.md
+++ /dev/null
@@ -1,109 +0,0 @@
-# Zot - A High Level Overview
-
-Here's a high level description of how zot works.
-
-In this example, "Indigo" is going to send a public message from his website at "podunk.edu". "Nickordo" is a recipient on another site ("example.com").
-
-
-Indigo first posts his message at podunk.edu. podunk.edu looks up who should receive the message and finds Nickordo. Nickordo usually posts from example.com so we add that destination to our list of recipients. We may also add other destinations for nickordo and anybody else that is following Indigo's posts.
-
-In this example we find that we only have one known recipient at one known location.
-
-We send a packet to example.com:
-
- {
- "type":"notify",
- "sender":{
- "guid":"kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA",
- "guid_sig":"PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK-R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q",
- "url":"http:\/\/podunk.edu",
- "url_sig":"T8Bp7j5DHHhQDCFcAHXfuhUfGk2P3inPbImwaXXF1xJd3TGgluoXyyKDx6WDm07x0hqbupoAoZB1qBP3_WfvWiJVAK4N1FD77EOYttUEHZ7L43xy5PCpojJQmkppGbPJc2jnTIc_F1vvGvw5fv8gBWZvPqTdb6LWF6FLrzwesZpi7j2rsioZ3wyUkqb5TDZaNNeWQrIEYXrEnWkRI_qTSOzx0dRTsGO6SpU1fPWuOOYMZG8Nh18nay0kLpxReuHCiCdxjXRVvk5k9rkcMbDBJcBovhiSioPKv_yJxcZVBATw3z3TTE95kGi4wxCEenxwhSpvouwa5b0hT7NS4Ay70QaxoKiLb3ZjhZaUUn4igCyZM0h6fllR5I6J_sAQxiMYD0v5ouIlb0u8YVMni93j3zlqMWdDUZ4WgTI7NNbo8ug9NQDHd92TPmSE1TytPTgya3tsFMzwyq0LZ0b-g-zSXWIES__jKQ7vAtIs9EwlPxqJXEDDniZ2AJ6biXRYgE2Kd6W_nmI7w31igwQTms3ecXe5ENI3ckEPUAq__llNnND7mxp5ZrdXzd5HHU9slXwDShYcW3yDeQLEwAVomTGSFpBrCX8W77n9hF3JClkWaeS4QcZ3xUtsSS81yLrp__ifFfQqx9_Be89WVyIOoF4oydr08EkZ8zwlAsbZLG7eLXY"
- },
- "callback":"\/post",
- "version":1,
- "secret":"1eaa6613699be6ebb2adcefa5379c61a3678aa0df89025470fac871431b70467"
- }
-
-This packet says the following:
-
-I'm Indigo and here is proof. I'm posting from podunk.edu and here is proof. I've got a package for you. The tracking number is "1eaa6613....".
-
-Example.com accepts this packet and says "whoa, hold on - I don't know you. I want to prove who you are." So Example.com connects to podunk.edu through a "well-known URL" that we use for this purpose and looks up the "guid" mentioned above. It should return a bunch of information, one item of which is a public key. Example.com uses this key to verify the signatures in the message to verify that indeed there is a person named Indigo at podunk.edu. We only need to do this once. (Note that Indigo can post from any location. All we have to do is prove that it's Indigo and that Indigo can prove that he's posting from another site.)
-
-Then example.com disconnects and flags that there's a message waiting at podunk.edu. Either immediately, or whenever the urge hits (depending on how important Indigo is to anybody on this site), example.com "calls" podunk.edu. It says something like this:
-
- {
- "type":"pickup",
- "url":"http:\/\/example.com",
- "callback_sig":"teE1_fLIqfyeCuZY4iS7sNU8jUlUuqYOYBiHLarkC99I9K-uSr8DAwVW8ZPZRK-uYdxRMuKFb6cumF_Gt9XjecCPBM8HkoXHOi_VselzJkxPwor4ZPtWYWWaFtRfcAm794LrWjdz62zdESTQd2JJIZWbrli1sUhK801BF3n0Ye6-X1MWhy9EUTVlNimOeRipcuD_srMhUcAXOEbLlrugZ8ovy2YBe6YOXkS8jj0RSFjsOduXAoVhQmNpcobSYsDvaQS3e3MvE6-oXE602zGQhuNLr7DIMt9PCdAeQo-ZM-DHlZGCkGk4O2oQFCXFzGPqLUMWDACGJfTfIWGoh_EJqT_SD5b_Yi_Wk9S1lj7vb-lmxe5JuIf7ezWzHoBT8vswnZxPYlidH2i9wapdzij9il_qqcCWWHIp7q_XkY_Zj52Z4r4gdmiqM-8y1c_1SDX7hrJFRwqL_PKFbEvyi5nMWTEzqp55Tay5Woiv19STK_H_8ufFfD9AOkYnk6rIOMsk9dn3a5tAFpDRyRndXkBWAXwiJjiND2zjue7BFu7Ty40THXcfYRh1a5XrAXcaGeYuagg-8J9tAufu9_LY3qGazFg8kRBVMOn4M8DRKSIhKj7z4MnbYL0s09gREojy4jqWO3VkaOjP2jUGzoPuUDLasudE1ehWFq0K_MTQNavgmp8",
- "callback":"http:\/\/example.com\/post",
- "secret":"1eaa6613699be6ebb2adcefa5379c61a3678aa0df89025470fac871431b70467",
- "secret_sig":"O7nB4_UJHBXi28Suwl9LBZF9hI_9KGVTgehnUlWF1oYMNRnBbVHB9lzUfAoalvp3STbU3xJbtD_S58tv6MfV7J5j2V_S1W5ex3dulmDGB8Pt_7Fe5mbEPmjQFcfv3Eg5dUjYIuDl0TDScfrHyImj7RZIWHbwd7wWVoMzzDa_o33klpYmKZCBvObCh55bRrlFkXZs_dRuOiPwkfX0C6_XES4OyOIYl45V30rdhmf-STrf4L9dKYy_axQ12RIwRcKychvVLwlUJn3bn9lgNXRRU_HTne-09OPcJbUOdcD3DkFoKOxMULBNKPHzsCau0ICYug7S0EP6LpCom_mW78s08LyVA1vYeFZjevBCiGecj57yIAQDYi6_rpWJfihYaWHRN0oqtScUR4Bdf0bQbEHxMs4zAtrOAxfyJCbi6U1pfnGgzXzB9ulOYGnVGNTF7Ey4K7FOZIBtk0ILY2JfvBUaVvVs8ttagOOHmhWhnbCvrnOFlkNdlce7zoJCSUJENUOCYmTRfwB_Jno5fAzRnrsYU3_Z-l1mzniU_OmUPz8mPEh7PwhkqAiVlyaM-q15gn7l2lAIDk9kp2X_iCme7v4V0ADN_DbpaI_0-6mPw5HLbKrCsA-sxlSMB4DO4lDCHYkauj0l25sbfroRWB_hax1O4Q0oWyOlVJLUqEC5nuUJCCE"
- }
-
-
-What this message says is: This is example.com, I have proof, and I'm here to pick up a package. Here's the tracking number, and here's proof that this is the tracking number you presumably sent to example.com.
-
-Good enough. Podunk.edu checks out the story and indeed, it is example.com, and yes, there's a package waiting with that tracking number. Here's the package...
-
- {
- "success":1,
- "pickup":{
- "notify":{
- "type":"notify",
- "sender":{
- "guid":"kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA",
- "guid_sig":"PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK-R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q",
- "url":"http:\/\/z.podunk.edu",
- "url_sig":"T8Bp7j5DHHhQDCFcAHXfuhUfGk2P3inPbImwaXXF1xJd3TGgluoXyyKDx6WDm07x0hqbupoAoZB1qBP3_WfvWiJVAK4N1FD77EOYttUEHZ7L43xy5PCpojJQmkppGbPJc2jnTIc_F1vvGvw5fv8gBWZvPqTdb6LWF6FLrzwesZpi7j2rsioZ3wyUkqb5TDZaNNeWQrIEYXrEnWkRI_qTSOzx0dRTsGO6SpU1fPWuOOYMZG8Nh18nay0kLpxReuHCiCdxjXRVvk5k9rkcMbDBJcBovhiSioPKv_yJxcZVBATw3z3TTE95kGi4wxCEenxwhSpvouwa5b0hT7NS4Ay70QaxoKiLb3ZjhZaUUn4igCyZM0h6fllR5I6J_sAQxiMYD0v5ouIlb0u8YVMni93j3zlqMWdDUZ4WgTI7NNbo8ug9NQDHd92TPmSE1TytPTgya3tsFMzwyq0LZ0b-g-zSXWIES__jKQ7vAtIs9EwlPxqJXEDDniZ2AJ6biXRYgE2Kd6W_nmI7w31igwQTms3ecXe5ENI3ckEPUAq__llNnND7mxp5ZrdXzd5HHU9slXwDShYcW3yDeQLEwAVomTGSFpBrCX8W77n9hF3JClkWaeS4QcZ3xUtsSS81yLrp__ifFfQqx9_Be89WVyIOoF4oydr08EkZ8zwlAsbZLG7eLXY"
- },
- "callback":"\/post",
- "version":1,
- "secret":"1eaa6613699be6ebb2adcefa5379c61a3678aa0df89025470fac871431b70467"
- },
- "message":{
- "message_id":"10b049ce384cbb2da9467319bc98169ab36290b8bbb403aa0c0accd9cb072e76@podunk.edu",
- "message_top":"10b049ce384cbb2da9467319bc98169ab36290b8bbb403aa0c0accd9cb072e76@podunk.edu",
- "message_parent":"10b049ce384cbb2da9467319bc98169ab36290b8bbb403aa0c0accd9cb072e76@podunk.edu",
- "created":"2012-11-20 04:04:16",
- "edited":"2012-11-20 04:04:16",
- "title":"",
- "body":"Hi Nickordo",
- "app":"",
- "verb":"post",
- "object_type":"",
- "target_type":"",
- "permalink":"",
- "location":"",
- "longlat":"",
- "owner":{
- "name":"Indigo",
- "address":"indigo@podunk.edu",
- "url":"http:\/\/podunk.edu",
- "photo":{
- "mimetype":"image\/jpeg",
- "src":"http:\/\/podunk.edu\/photo\/profile\/m\/5"
- },
- "guid":"kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA",
- "guid_sig":"PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK-R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q"
- },
- "author":{
- "name":"Indigo",
- "address":"indigo@podunk.edu",
- "url":"http:\/\/podunk.edu",
- "photo":{
- "mimetype":"image\/jpeg",
- "src":"http:\/\/podunk.edu\/photo\/profile\/m\/5"
- },
- "guid":"kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA",
- "guid_sig":"PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK-R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q"
- }
- }
- }
- }
-
-
-
-And that's the package (the original message). Example.com converts this into a form suitable for viewing by Nickordo and notifies Nickordo that there's a new message. Podunk.edu **might** discover that there are other packages waiting for example.com. If this happens it may also send any and all other waiting packages at this time. Each has the original tracking number attached.
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/about.md b/doc/en/about.md
new file mode 100644
index 000000000..cf7ab820c
--- /dev/null
+++ b/doc/en/about.md
@@ -0,0 +1,45 @@
+# What is Hubzilla?
+
+Hubzilla is a decentralised communication network with the aim of providing communication options that circumvent censorship, respect privacy and are therefore free from the restrictions imposed by today's commercial communication giants. These primarily provide spy networks for paying customers of all kinds and monopolise and centralise the entire Internet - which was not originally among the revolutionary goals that led to the World Wide Web.
+
+Hubzilla is free, open source and free of charge. It was developed to run on a Raspberry Pi as well as on the largest AMD and Intel Xeon multiprocessor servers. It can be used for communication between a few individuals or connect many thousands of people and more.
+
+Another goal is to be independent of skills and resources. Hubzilla is as easy to use for the ordinary computer user as it is for system administrators and developers.
+
+How you use it depends on how you *want to* use it.
+
+Hubzilla is written in PHP, making it easy to install on any of today's hosting platforms, including self-hosting at home, on shared servers or on virtual and dedicated servers.
+
+In other words, Hubzilla can run on any platform that has a web server, a MySQL-compatible database and PHP.
+
+Hubzilla offers some unique goodies:
+
+**One-click identification:** you can access other servers in the Hubzilla network by simply clicking on a link to them. Authentication is easily done automatically behind the scenes. Forget all the different usernames for different sites and passwords - Hubzilla does it all for you.
+
+**Clone:** You can clone your online identity (or, as we say, a channel). It is no longer tied to a specific server, domain or IP address. Simply import it to another Hubzilla server (or Hubzilla hub, as we call it) - directly online or using a previously generated export. If your primary hub is suddenly no longer online, no problem, your contacts, posts* and messages* are still automatically available and accessible under your cloned identity. *(\*: only posts and messages created after the moment of cloning)*
+
+**Privacy:** Hubzilla identities (Nomad IDs) can be deleted, backed up/downloaded and cloned. You have full control over your data. If you decide to delete all your data and your Nomad ID, all you have to do is click on a link and it will be deleted from the server immediately. No questions asked, no fuss.
+
+----
+
+## Hubzilla Features
+
+### Built-in Social
+
+When you post announcements on your website, they are automatically broadcast to your followers on the Fediverse, Mastodon, and more. You can also interact with them via your website.
+
+### Groups & Forums
+
+Building a community is important for your business or nonprofit organization. You can add public, private, and moderated discussion groups & forums. These work across nearly all fediverse platforms.
+
+### Cloud Storage
+
+You can store documents directly on your website, and share them with others. Public, private, or restricted access. With the files being stored on your domain name, they will know it comes from you.
+
+### Federated Single Sign On
+
+You can use your Hubzilla online identity to log into other websites that support Magic Signon (OpenWebAuth). That way you don't have to create a new account on every website, forum, membership site, or app.
+
+### Nomadic Identity
+
+Clone your online identity and content to multiple sites using the Nomad protocol and mirror any changes in near real time. All your friends and all your content are available on any of your cloned instances - at any time.
diff --git a/doc/en/about/about.bb b/doc/en/about/about.bb
deleted file mode 100644
index e909f54cd..000000000
--- a/doc/en/about/about.bb
+++ /dev/null
@@ -1,204 +0,0 @@
-[h3]What is $Projectname?[/h3]
-$Projectname is a [b]free and open source[/b] set of web applications and services running on a special kind of web server, called a "hub", that can connect to other hubs in a decentralised network we like to call "the grid", providing sophisticated communications, identity, and access control services which work together seamlessly across domains and independent websites. It allows anybody to publicly or [b]privately[/b] publish content via "channels", which are the fundamental, cryptographically secured identities that provide authentication independently of the hubs which host them. This revolutionary liberation of online identity from individual servers and domains is called "nomadic identity", and it is powered by the Zot protocol, a new framework for decentralised access control with fine-grained, extensible permissions.
-
-[h3]Right... so what is $Projectname?[/h3]
-From the practical perspective of hub members who use the software, $Projectname offers a variety of familiar, integrated web apps and services, including:
-[ul]
-[li]social networking discussion threads[/li]
-[li]cloud file storage[/li]
-[li]calendar and contacts (with CalDAV and CardDAV support)[/li]
-[li]webpage hosting with a content management system[/li]
-[li]wiki[/li]
-[li]and more...[/li][/ul]
-While all of these apps and services can be found in other software packages, only $Projectname allows you to set permissions for groups and individuals who may not even have accounts on your hub! In typical web apps, if you want to share things privately on the internet, the people you share with must have accounts on the server hosting your data; otherwise, there is no robust way for your server to [i]authenticate[/i] visitors to the site to know whether to grant them access. $Projectname solves this problem with an advanced system of [i]remote authentication[/i] that validates the identity of visitors by employing techniques that include public key cryptography.
-
-[h3]Software Stack[/h3]
-The $Projectname software stack is a relatively standard webserver application written primarily in PHP/MySQL and [url=https://framagit.org/hubzilla/core/blob/master/install/INSTALL.txt]requiring little more than a web server, a MySQL-compatible database, and the PHP scripting language[/url]. It is designed to be easily installable by those with basic website administration skills on typical shared hosting platforms with a broad range of computing hardware. It is also easily extended via plugins and themes and other third-party tools.
-
-[h3]Glossary[/h3]
-[dl terms="b"]
-[*= hub] An instance of this software running on a standard web server
-
-[*= grid] The global network of hubs that exchange information with each other using the Zot protocol.
-
-[*= channel] The fundamental identity on the grid. A channel can represent a person, a blog, or a forum to name a few. Channels can make connections with other channels to share information with highly detailed permissions.
-
-[*= clone] Channels can have clones associated with separate and otherwise unrelated accounts on independent hubs. Communications shared with a channel are synchronized among the channel clones, allowing a channel to send and receive messages and access shared content from multiple hubs. This provides resilience against network and hardware failures, which can be a significant problem for self-hosted or limited-resource web servers. Cloning allows you to completely move a channel from one hub to another, taking your data and connections with you. See nomadic identity.
-
-[*= nomadic identity] The ability to authenticate and easily migrate an identity across independent hubs and web domains. Nomadic identity provides true ownership of an online identity, because the identities of the channels controlled by an account on a hub are not tied to the hub itself. A hub is more like a "host" for channels. With Hubzilla, you don't have an "account" on a server like you do on typical websites; you own an identity that you can take with you across the grid by using clones.
-
-[*= [url=[baseurl]/help/developer/zot_protocol]Zot[/url]] The novel JSON-based protocol for implementing secure decentralised communications and services. It differs from many other communication protocols by building communications on top of a decentralised identity and authentication framework. The authentication component is similar to OpenID conceptually but is insulated from DNS-based identities. Where possible remote authentication is silent and invisible. This provides a mechanism for internet-scale distributed access control which is unobtrusive.
-[/dl]
-
-[h3]Features[/h3]
-This page lists some of the core features of $Projectname that are bundled with the official release. $Projectname is a highly extensible platform, so more features and capabilities can be added via additional themes and plugins.
-
-[h4]Affinity Slider[/h4]
-
-When adding connnections in $Projectname, members have the option of assigning "affinity" levels (how close your friendship is) to the new connection. For example, when adding someone who happens to be a person whose blog you follow, you could assign their channel an affinity level of &quot;Acquaintances&quot;.
-
-On the other hand, when adding a friend's channel, they could be placed under the affinity level of &quot;Friends&quot;.
-
-At this point, $Projectname [i]Affinity Slider[/i] tool, which usually appears at the top of your &quot;Matrix&quot; page, adjusts the content on the page to include those within the desired affinity range. Channels outside that range will not be displayed, unless you adjust the slider to include them.
-
-The Affinity Slider allows instantaneous filtering of large amounts of content, grouped by levels of closeness.
-
-[h4]Connection Filtering[/h4]
-
-You have the ability to control precisely what appears in your stream using the optional "Connection Filter". When enabled, the Connection Editor provides inputs for selecting criteria which needs to be matched in order to include or exclude a specific post from a specific channel. Once a post has been allowed, all comments to that post are allowed regardless of whether they match the selection criteria. You may select words that if present block the post or ensure it is included in your stream. Regular expressions may be used for even finer control, as well as hashtags or even the detected language of the post.
-
-[h4]Access Control Lists[/h4]
-
-When sharing content, members have the option of restricting who sees the content. By clicking on the padlock underneath the sharing box, one may choose desired recipients of the post, by clicking on their names.
-
-Once sent, the message will be viewable only by the sender and the selected recipients. In other words, the message will not appear on any public walls.
-
-Access Control Lists may be applied to content and posts, photos, events, webpages, chatrooms and files.
-
-[h4]Single Sign-on[/h4]
-
-Access Control Lists work for all channels in the grid due to our unique single sign-on technology. Most internal links provide an identity token which can be verified on other $Projectname sites and used to control access to private resources. You login once to your home hub. After that, authentication to all $Projectname resources is "magic".
-
-
-[h4]WebDAV enabled File Storage[/h4]
-
-Files may be uploaded to your personal storage area using your operating system utilities (drag and drop in most cases). You may protect these files with Access Control Lists to any combination of $Projectname members (including some third party network members) or make them public.
-
-[h4]Photo Albums[/h4]
-
-Store photos in albums. All your photos may be protected by Access Control Lists.
-
-[h4]Events Calendar[/h4]
-
-Create and manage events and tasks, which may also be protected with Access Control Lists. Events can be imported/exported to other software using the industry standard vcalendar/iCal format and shared in posts with others. Birthday events are automatically added from your friends and converted to your correct timezone so that you will know precisely when the birthday occurs - no matter where you are located in the world in relation to the birthday person. Events are normally created with attendance counters so your friends and connections can RSVP instantly.
-
-[h4]Chatrooms[/h4]
-
-You may create any number of personal chatrooms and allow access via Access Control Lists. These are typically more secure than XMPP, IRC, and other Instant Messaging transports, though we also allow using these other services via plugins.
-
-[h4]Webpage Building[/h4]
-
-$Projectname has many "Content Management" creation tools for building webpages, including layout editing, menus, blocks, widgets, and page/content regions. All of these may be access controlled so that the resulting pages are private to their intended audience.
-
-[h4]Apps[/h4]
-
-Apps may be built and distributed by members. These are different from traditional "vendor lockin" apps because they are controlled completely by the author - who can provide access control on the destination app pages and charge accordingly for this access. Most apps in $Projectname are free and can be created easily by those with no programming skills.
-
-[h4]Layout[/h4]
-
-Page layout is based on a description language called Comanche. $Projectname is itself written in Comanche layouts which you can change. This allows a level of customisation you won't typically find in so-called "multi-user environments".
-
-[h4]Bookmarks[/h4]
-
-Share and save/manage bookmarks from links provided in conversations.
-
-
-[h4]Private Message Encryption and Privacy Concerns[/h4]
-
-Private mail is stored in an obscured format. While this is not bullet-proof it typically prevents casual snooping by the site administrator or ISP.
-
-Each $Projectname channel has it's own unique set of private and associated public RSA 4096-bit keys, generated when the channels is first created. This is used to protect private messages and posts in transit.
-
-Additionally, messages may be created utilising "end-to-end encryption" which cannot be read by $Projectname operators or ISPs or anybody who does not know the passcode.
-
-Public messages are generally not encrypted in transit or in storage.
-
-Private messages may be retracted (unsent) although there is no guarantee the recipient hasn't read it yet.
-
-Posts and messages may be created with an expiration date, at which time they will be deleted/removed on the recipient's site.
-
-
-[h4]Service Federation[/h4]
-
-In addition to addon "cross-post connectors" to a variety of alternate networks, there is native support for importation of content from RSS/Atom feeds and using this to create special channels. Plugins are also available to communicate with others using the Diaspora and GNU-Social (OStatus) protocols. These networks do not support nomadic identity or cross-domain access control; however basic communications are supported to/from Diaspora, Friendica, GNU-Social, Mastodon and other providers which use these protocols.
-
-There is also experimental support for OpenID authentication which may be used in Access Control Lists. This is a work in progress. Your $Projectname hub may be used as an OpenID provider to authenticate you to external services which use this technology.
-
-Channels may have permissions to become "derivative channels" where two or more existing channels combine to create a new topical channel.
-
-[h4]Privacy Groups[/h4]
-
-Our implementation of privacy groups is similar to Google "Circles" and Diaspora "Aspects". This allows you to filter your incoming stream by selected groups, and automatically set the outbound Access Control List to only those in that privacy group when you post. You may over-ride this at any time (prior to sending the post).
-
-
-[h4]Directory Services[/h4]
-
-We provide easy access to a directory of members and provide decentralised tools capable of providing friend "suggestions". The directories are normal $Projectname sites which have chosen to accept the directory server role. This requires more resources than most typical sites so is not the default. Directories are synchronised and mirrored so that they all contain up-to-date information on the entire network (subject to normal propagation delays).
-
-
-[h4]TLS/SSL[/h4]
-
-For $Projectname hubs that use TLS/SSL, client to server communications are encrypted via TLS/SSL. Given recent disclosures in the media regarding widespread, global surveillance and encryption circumvention by the NSA and GCHQ, it is reasonable to assume that HTTPS-protected communications may be compromised in various ways. Private communications are consequently encrypted at a higher level before sending offsite.
-
-[h4]Channel Settings[/h4]
-
-When a channel is created, a role is chosen which applies a number of pre-configured security and privacy settings. These are chosen for best practives to maintain privacy at the requested levels.
-
-If you choose a "custom" privacy role, each channel allows fine-grained permissions to be set for various aspects of communication. For example, under the &quot;Security and Privacy Settings&quot; heading, each aspect on the left side of the page, has six (6) possible viewing/access options, that can be selected by clicking on the dropdown menu. There are also a number of other privacy settings you may edit.
-
-The options are:
-
- - Nobody except yourself.
- - Only those you specifically allow.
- - Anybody in your address book.
- - Anybody on this website.
- - Anybody in this network.
- - Anybody authenticated.
- - Anybody on the Internet.
-
-
-[h4]Public and Private Forums[/h4]
-
-Forums are typically channels which may be open to participation from multiple authors. There are currently two mechanisms to post to forums: 1) "wall-to-wall" posts and 2) via forum @mention tags. Forums can be created by anybody and used for any purpose. The directory contains an option to search for public forums. Private forums can only be posted to and often only seen by members.
-
-
-[h4]Account Cloning[/h4]
-
-Accounts in $Projectname are referred to as [i]nomadic identities[/i], because a member's identity is not bound to the hub where the identity was originally created. For example, when you create a Facebook or Gmail account, it is tied to those services. They cannot function without Facebook.com or Gmail.com.
-
-By contrast, say you've created a $Projectname identity called [b]tina@$Projectnamehub.com[/b]. You can clone it to another $Projectname hub by choosing the same, or a different name: [b]liveForever@Some$ProjectnameHub.info[/b]
-
-Both channels are now synchronized, which means all your contacts and preferences will be duplicated on your clone. It doesn't matter whether you send a post from your original hub, or the new hub. Posts will be mirrored on both accounts.
-
-This is a rather revolutionary feature, if we consider some scenarios:
-
- - What happens if the hub where an identity is based suddenly goes offline? Without cloning, a member will not be able to communicate until that hub comes back online (no doubt many of you have seen and cursed the Twitter "Fail Whale"). With cloning, you just log into your cloned account, and life goes on happily ever after.
-
- - The administrator of your hub can no longer afford to pay for his free and public $Projectname hub. He announces that the hub will be shutting down in two weeks. This gives you ample time to clone your identity(ies) and preserve your$Projectname relationships, friends and content.
-
- - What if your identity is subject to government censorship? Your hub provider may be compelled to delete your account, along with any identities and associated data. With cloning, $Projectname offers [b]censorship resistance[/b]. You can have hundreds of clones, if you wanted to, all named different, and existing on many different hubs, strewn around the internet.
-
-$Projectname offers interesting new possibilities for privacy. You can read more at the &lt;&lt;Private Communications Best Practices&gt;&gt; page.
-
-Some caveats apply. For a full explanation of identity cloning, read the &lt;HOW TO CLONE MY IDENTITY&gt;.
-
-[h4]Multiple Profiles[/h4]
-
-Any number of profiles may be created containing different information and these may be made visible to certain of your connections/friends. A "default" profile can be seen by anybody and may contain limited information, with more information available to select groups or people. This means that the profile (and site content) your beer-drinking buddies see may be different than what your co-workers see, and also completely different from what is visible to the general public.
-
-[h4]Account Backup[/h4]
-
-$Projectname offers a simple, one-click account backup, where you can download a complete backup of your profile(s). Backups can then be used to clone or restore a profile.
-
-[h4]Account Deletion[/h4]
-Accounts can be immediately deleted by clicking on a link. That's it. All associated content is then deleted from the grid (this includes posts and any other content produced by the deleted profile). Depending on the number of connections you have, the process of deleting remote content could take some time but it is scheduled to happen as quickly as is practical.
-
-[h4]Deletion of content[/h4]
-Any content created in $Projectname remains under the control of the member (or channel) that originally created it. At any time, a member can delete a message, or a range of messages. The deletion process ensures that the content is deleted, regardless of whether it was posted on a channel's primary (home) hub, or on another hub, where the channel was remotely authenticated via Zot ($Projectname communication and authentication protocol).
-
-[h4]Media[/h4]
-Similar to any other modern blogging system, social network, or a micro-blogging service, $Projectname supports the uploading of files, embedding of videos, linking web pages.
-
-[h4]Previewing/Editing[/h4]
-Posts and comments can be previewed prior to sending and edited after sending.
-
-[h4]Voting/Consensus[/h4]
-Posts can be turned into "consensus" items which allows readers to offer feedback, which is collated into "agree", "disagree", and "abstain" counters. This lets you gauge interest for ideas and create informal surveys.
-
-[h4]Extending $Projectname[/h4]
-
-$Projectname can be extended in a number of ways, through site customisation, personal customisation, option setting, themes, and addons/plugins.
-
-[h4]API[/h4]
-
-An API is available for use by third-party services. A plugin also provides a basic implementation of the Twitter API (for which hundreds of third-party tools exist). Access may be provided by login/password or OAuth, and client registration of OAuth applications is provided.
diff --git a/doc/en/about/project.bb b/doc/en/about/project.bb
deleted file mode 100644
index fe90b4d36..000000000
--- a/doc/en/about/project.bb
+++ /dev/null
@@ -1,186 +0,0 @@
-[h3]$Projectname Governance[/h3]
-Governance relates to the management of a project and particularly how this relates to conflict resolution.
-
-[h4]Community Governance[/h4]
-The project is maintained and decisions made by the 'community'. The governance structure is still evolving. Until the structure is finalised, decisions are made in the following order:
-
-[ol]
-[*] Lazy Consensus
-
-If a project proposal is made to one of the community governance forums and there are no serious objections in a "reasonable" amount of time from date of proposal (we usually provide 2-3 days for all interested parties to weigh in), no vote needs to be taken and the proposal will be considered approved. Some concerns may be raised at this time, but if these are addressed during discussion and work-arounds provided, it will still be considered approved.
-
-
-[*] Veto
-
-Senior developers with a significant history of project commits may veto any decision. The decision may not proceed until the veto is removed or an alternative proposal is presented.
-
-
-[*] Community Vote
-
-A decision which does not have a clear mandate or clear consensus, but is not vetoed, can be taken to a community vote. At present this is a simple popular vote in one of the applicable community forums. At this time, popular vote decides the outcome. This may change in the future if the community adopts a 'council' governance model. This document will be updated at that time with the updated governance rules.
-[/ol]
-
-Community Voting does not always provide a pleasant outcome and can generate polarised factions in the community (hence the reason why other models are under consideration). If the proposal is 'down voted' there are still several things which can be done and the proposal re-submitted with slightly different parameters (convert to an addon, convert to an optional feature which is disabled by default, etc.). If interest in the feature is high and the vote is "close", it can generate lots of bad feelings amongst the losing voters. On such close votes, it is [b]strongly recommended[/b] that the proposer take steps to address any concerns that were raised and re-submit.
-
-
-
-[h4]Privacy Policy[/h4]
-
-Q: Who can see my content?
-
-A: By default ANYBODY on the internet, UNLESS you restrict it. $Projectname allows you to choose the privacy level you desire. Restricted content will NOT be visible to "spy networks" and advertisers. It will be protected against eavesdropping by outsiders - to the best of our ability. Hub administrators with sufficient skills and patience MAY be able to eavesdrop on some private communications but they must expend effort to do so. Privacy modes exist within $Projectname which are even resistant to eavesdropping by skilled and determined hub administrators.
-
-Q: Can my content be censored?
-
-A: $Projectname (the network) CANNOT censor your content. Server and hub administrators are subject to local laws and MAY remove objectionable content from their site/hub. Anybody MAY become a hub administrator, including you; and therefore publish content which might otherwise be censored. You still MAY be subject to local laws.
-
-
-[h5]Definitions[/h5]
-
-**$Projectname**
-
-Otherwise referred to as "the network", $Projectname is a collection of individual computers/servers (aka **hubs**) which connect together to form a larger cooperative network.
-
-**hub**
-
-An individual computer or server connected to $Projectname. These are provided by a **hub administrator** and may be public or private, paid or free.
-
-**hub administrator**
-
-The system operator of an individual hub.
-
-[h5]Policies[/h5]
-
-**Public Information**
-
-Any information or anything posted by you within $Projectname MAY be public or visible to anybody on the internet. To the extent possible, $Projectname allows you to protect content and restrict who can view it.
-
-Your profile photo, your channel name, and the location (URL or network address) of your channel are visible to anybody on the internet and privacy controls will not affect the display of these items.
-
-You MAY additionally provide other profile information. Any information which you provide in your "default" or **public profile** MAY be transmitted to other hubs in $Projectname and additionally MAY be displayed in the channel directory. You can restrict the viewing of this profile information. It may be restricted only to members of your hub, or only connections (friends), or other limited sets of viewers as you desire. If you wish for your profile to be restricted, you must set the appropriate privacy setting, or simply DO NOT provide additional information.
-
-**Content**
-
-Content you provide (status posts, photos, files, etc.) belongs to you. $Projectname default is to publish content openly and visible to anybody on the internet (PUBLIC). You MAY control this in your channel settings and restrict the default permissions or you MAY restrict the visibility of any single published item separately (PRIVATE). $Projectname developers will ensure that restricted content is ONLY visible to those in the restriction list - to the best of their ability.
-
-Content (especially status posts) that you share with other networks or that you have made visible to anybody on the internet (PUBLIC) cannot easily be taken back once it has been published. It MAY be shared with other networks and made available through RSS/Atom feeds. It may also be syndicated on other $Projectname sites. It MAY appear on other networks and websites and be visible in internet searches. If you do not wish this default behaviour please adjust your channel settings and restrict who can see your content.
-
-**Comments and Forum posts**
-
-Comments to posts that were created by others and posts which are designated as forum posts belong to you as the creator/author, but the distribution of these posts is not under your direct control, and you relinquish SOME rights to these items. These posts/comments MAY be re-distributed to others, and MAY be visible to anybody on the internet. In the case of comments, the creator of the "first message" in the thread (conversation) to which you are replying controls the distribution of all comments and replies to that message. They "own" and therefore have certain rights with regard to the entire conversation (including all comments contained within it). You can still edit or delete the comment, but the conversation owner also has rights to edit, delete, re-distribute, and backup/restore any or all the content from the conversation.
-
-**Private Information**
-
-$Projectname developers will ensure that any content you provide which is designated as PRIVATE will be protected against eavesdropping - to the best of their ability. Private channel content CAN be seen in the database of every involved hub administrator, but private messages are obscured in the database. The latter means that it is very difficult, but NOT impossible for this content to be seen by a hub administrator. Private channel content and private messages are also stripped from email notifications. End to end encryption is provided as an optional feature and this CANNOT be seen, even by a determined administrator.
-
-[h5]Identity Privacy[/h5]
-
-Privacy for your identity is another aspect. Because you have a decentralized identity in $Projectname, your privacy extends beyond your home hub. If you want to have complete control of your privacy and security you should run your own hub on a dedicated server. For many people, this is complicated and may stretch their technical abilities. So let's list a few precautions you can make to assure your privacy as much as possible.
-
-A decentralized identity has a lot of advantages and gives you al lot of interesting features, but you should be aware of the fact that your identity is known by other hubs in $Projectname network. One of those advantages is that other channels can serve you customized content and allow you to see private things (such as private photos which others wish to share with you). Because of this those channels need to know who you are. But we understand that sometimes those other channels know more from you than you might desire. For instance the plug-in Visage that can tell a channel owner the last time you visit their profile. You can easily OPT-OUT of this low level and we think, harmless tracking.
-
-* You can enable [Do Not Track (DNT)](http://donottrack.us/) in your web browser. We respect this new privacy policy proposal. All modern browsers support DNT. You will find it in the privacy settings of your browsers or else you can consult the web browser's manual. This will not affect the functionality of $Projectname. This setting is probably enough for most people.
-
-*You can [disable publication](settings) of your channel in our channel directory. If you want people to find your channel, you should give your channel address directly to them. We think this is a good indication that you prefer extra privacy and automatically enable "Do Not Track" if this is the case.
-
-* You can have a blocked hub. That means that all channels and content on that hub is not public, and not visible to the outside world. This is something only your hub administrator can do. We also respect this and automatically enable "Do Not Track" if it is set.
-
-[h5]Censorship[/h5]
-
-$Projectname is a global network which is inclusive of all religions and cultures. This does not imply that every member of the network feels the same way you do on contentious issues, and some people may be STRONGLY opposed to the content you post. In general, if you wish to post something that you know may nor be universally acceptable, the best approach is to restrict the audience using privacy controls to a small circle of friends.
-
-$Projectname as a network provider is unable to censor content. However, hub administrators MAY censor any content which appears on their hub to comply with local laws or even personal judgement. Their decision is final. If you have issues with any hub administrator, you may move your account and postings to another site which is more in line with your expectations. Please check (periodically) the [Terms of Service](help/TermsOfService) of your hub to learn about any rules or guidelines. If your content consists of material which is illegal or may cause issues, you are STRONGLY encouraged to host your own (become a hub administrator). You may still find that your content is blocked on some hubs, but $Projectname as a network cannot block it from being posted.
-
-$Projectname RECOMMENDS that hub administrators provide a grace period of 1-2 days between warning an account holder of content that needs to be removed and physically removing or disabling the account. This will give the content owner an opportunity to export their channel meta-data and import it to another site. In rare cases the content may be of such a nature to justify the immediate termination of the account. This is a hub decision, not a $Projectname decision.
-
-If you typically and regularly post content of an adult or offensive nature, you are STRONGLY encouraged to mark your account "NSFW" (Not Safe For Work). This will prevent the display of your profile photo in the directory except to viewers that have chosen to disable "safe mode". If your profile photo is found by directory administrators to be adult or offensive, the directory administrator MAY flag your profile photo as NSFW. There is currently no official mechanism to contest or reverse this decision, which is why you SHOULD mark your own account NSFW if it is likely to be inappropriate for general audiences.
-
-[h3]Credits[/h3]
-
-Thanks to all who have helped and contributed to the project and its predecessors over the years.
-It is possible we missed in your name but this is unintentional. We also thank the community and
-its members for providing valuable input and without whom this entire effort would be meaningless.
-
-It is also worth acknowledging the contributions and solutions to problems which arose from
-discussions amongst members and developers of other somewhat related and competing projects;
-even if we have had our occasional disagreements.
-
-[list]
-[li]Mike Macgirvin[/li]
-[li]Fabio Comuni[/li]
-[li]Simon L'nu[/li]
-[li]marijus[/li]
-[li]Tobias Diekershoff[/li]
-[li]fabrixxm[/li]
-[li]tommy tomson[/li]
-[li]Simon[/li]
-[li]zottel[/li]
-[li]Christian Vogeley[/li]
-[li]jeroenpraat[/li]
-[li]Michael Vogel[/li]
-[li]erik[/li]
-[li]Zach Prezkuta[/li]
-[li]Paolo T[/li]
-[li]Michael Meer[/li]
-[li]Michael[/li]
-[li]Abinoam P. Marques Jr[/li]
-[li]Tobias Hößl[/li]
-[li]Alexander Kampmann[/li]
-[li]Olaf Conradi[/li]
-[li]Paolo Tacconi[/li]
-[li]tobiasd[/li]
-[li]Devlon Duthie[/li]
-[li]Zvi ben Yaakov (a.k.a rdc)[/li]
-[li]Alexandre Hannud Abdo[/li]
-[li]Olivier Migeot[/li]
-[li]Chris Case[/li]
-[li]Klaus Weidenbach[/li]
-[li]Michael Johnston[/li]
-[li]olivierm[/li]
-[li]Vasudev Kamath[/li]
-[li]pixelroot[/li]
-[li]Max Weller[/li]
-[li]duthied[/li]
-[li]Martin Schmitt[/li]
-[li]Sebastian Egbers[/li]
-[li]Erkan Yilmaz[/li]
-[li]sasiflo[/li]
-[li]Stefan Parviainen[/li]
-[li]Haakon Meland Eriksen[/li]
-[li]Oliver Hartmann (23n)[/li]
-[li]Erik Lundin[/li]
-[li]habeascodice[/li]
-[li]sirius[/li]
-[li]Charles[/li]
-[li]Tony Baldwin[/li]
-[li]Hauke Zuehl[/li]
-[li]Keith Fernie[/li]
-[li]Anne Walk[/li]
-[li]toclimb[/li]
-[li]Daniel Frank[/li]
-[li]Matthew Exon[/li]
-[li]Michal Supler[/li]
-[li]Tobias Luther[/li]
-[li]U-SOUND\mike[/li]
-[li]mrjive[/li]
-[li]nostupidzone[/li]
-[li]tonnerkiller[/li]
-[li]Antoine G[/li]
-[li]Christian Drechsler[/li]
-[li]Ludovic Grossard[/li]
-[li]RedmatrixCanada[/li]
-[li]Stanislav Lechev [0xAF][/li]
-[li]aweiher[/li]
-[li]bufalo1973[/li]
-[li]dsp1986[/li]
-[li]felixgilles[/li]
-[li]ike[/li]
-[li]maase2[/li]
-[li]mycocham[/li]
-[li]ndurchx[/li]
-[li]pafcu[/li]
-[li]Simó Albert i Beltran[/li]
-[li]Manuel Reva[/li]
-[li]Manuel Jiménez Friaza[/li]
-[li]Gustav Wall aka "neue medienordnung plus"[/li]
-[/list]
diff --git a/doc/en/accounts_profiles_channels_basics.bb b/doc/en/accounts_profiles_channels_basics.bb
deleted file mode 100644
index 63b13f036..000000000
--- a/doc/en/accounts_profiles_channels_basics.bb
+++ /dev/null
@@ -1,19 +0,0 @@
-[size=large][b]Accounts, Profiles and Channels[/b][/size]
-
-Once you have registered an [i]account[/i] at the matrix you have also created a [i]profile[/i] and a [i]channel[/i].
-
-[b]Account[/b]
-You have [i]one[/i] account. This consists of your email account and your password. With your account you access your profile and your channel.
-[i]Think of your account as the way you authenticate at one $Projectname site. It lets you do things, such as creating profiles and channels with which you can connect to other people.[/i]
-
-[b]Profile[/b]
-You have surely registered with some other internet services, such as forums or online communities. For all of them you provided some information about yourself, such as date of birth, country, age and the likes. [observer=1]If you like you can see your profile here: [baseurl]/profile/[observer.webname] and edit it by clicking on the pencil icon next to your avatar image. [/observer]
-Unlike other services $Projectname offers you the advantage of creating [i]many more profiles[/i]. That way you are able to distinguish between profiles targeted specially at everyone (your public profile), your work mates, your family and your partner.
-[i]Think of your profile as the basic information about yourself you tell other people.[/i]
-
-[b]Channel[/b]
-During the registration you created your first [i]channel[/i]. Yes, besides several profiles you are able to have several channels. This might be a bit confusing in the beginning, but let's clear things up. You already have created one channel. You can use this one for the public, to communicate with people about every day life. But perhaps you are an avid book reader and many people are bored by that. So you open a [i]second channel[/i] just for the book lovers, where you all can talk about books as much as you like. Obviously this is a new stream of posts, with a new profile (... or new profile[i]s[/i] ...) and completely different contacts. Some connections might exist in both channels, but there will be some that are exclusive to only one of both. You yourself just switch between both of them just like you would in real life switch when talking to people you meet on the street or people you meet specially to talk about books. You can even connect to yourself, or better: to your other channel. :)
-[i]Think of a channel as different spaces dedicated to different topics where you meet with different people.[/i]
-
-#include doc/macros/main_footer.bb;
-
diff --git a/doc/en/acl_dialog_post.html b/doc/en/acl_dialog_post.html
deleted file mode 100644
index 80b2c68b6..000000000
--- a/doc/en/acl_dialog_post.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<!-- Network and channel posts cannot change their permissions after being sent, this help
- file is for items of that nature. Files and photos etc should use a different help file. -->
-
-<h2>Post Permissions</h2>
-
-<p>The permissions dialog lets you select which channels and/or privacy groups can see the post. You can also select who is explicitly denied access. For example, say you are planning a surprise party for a friend. You can send an invitation post to everyone in your <b>Friends</b> group <i>except</i> the friend you are surprising. In this case you "Show" the <b>Friends</b> group but "Don't show" that one person.</p>
-
-<dl class="text-info dl-terms-large dl-horizontal">
-<dt style="width: 3em;">Tip!</td>
-<dd style="margin-left: 4em;">The border color of each channel indicates whether that channel &mdash; or one of the groups it's a member of &mdash; will have access to the post. The border color will also indicate when a channel, or group it belongs to, has been explicitly set to "Don't show".</dd>
-</dl>
-
-<h3>Why can't I edit a post's permissions after I saved it?</h3>
-
-<p>You are able to change permissons to your files, photos and the likes, but not to posts after you have saved them. The main reason is: Once you have saved a post it is being distributed either to the public channel and from there to other Hubzilla servers or to those you intended it to go. Just like you cannot reclaim something you gave to another person, you cannot change permissions to Hubzilla posts. We would need to track everywhere your posting goes, keep track of everyone you allowed to see it and then keep track of from whom to delete it.</p>
-<p>If a posting is public this is even harder, as the Hubzilla is a global network and there is no way to follow a post, let alone reclaim it reliably. Other networks that may receive your post have no reliable way to delete or reclaim the post.</p> \ No newline at end of file
diff --git a/doc/en/addons.bb b/doc/en/addons.bb
deleted file mode 100644
index 4bfa7a9fd..000000000
--- a/doc/en/addons.bb
+++ /dev/null
@@ -1,117 +0,0 @@
-[h3]Plugins/Addons[/h3]
-[list=1]
-[*] abcjsplugin - Create musical scores in your posts
-[*] adultphotoflag - prevents nsfw photos from being displayed in public albums
-[*] authchoose - only send identity assertions to sites of friends
-[*] b2tbtn - provide button to go directly to top of page if you are scrolled a long way down
-[*] bbmath - use complex math expressions in your posts
-[*] bookmarker - replace #^ with bookmark link in posts
-[*] buglink - provide a bug reporting icon in the lower-left corner of every page
-[*] calc - a scientific calculator
-[*] chess - cross domain identity aware interactive chess games
-[*] chords - generate fingering charts and alternatives for every known guitar chord
-[*] custom_home - set a custom page as the hub start page
-[*] diaspora - Diaspora protocol emulator
-[*] dirstats - show some interesting statistics generated by the directory server
-[*] docs - alternate documentation pages
-[*] donate - provides a project donation page
-[*] dreamhost - provide a more reliable service on Dreamhost shared hosting
-[*] dwpost - crosspost to Dreamwidth
-[*] emojione - allow emojis as emoticons
-[*] extcron - use an external cron service to run your hub's scheduled tasks
-[*] firefox - provide a link to install the Firefox Sharing API
-[*] flattrwidget - provides a "Flattr Us" button
-[*] flip - create upside down text
-[*] fortunate - displays random quote (fortune cookie). Requires setting up a fortune server.
-[*] friendica - Friendica (DFRN) protocol. Under development.
-[*] frphotos - import photo albums from Friendica
-[*] gnusoc - GNU-Social (OStatus) protocol. Under development.
-[*] hexit - hexadecimal conversion tool
-[*] hilite - allow language-specific highlighted code blocks in posts
-[*] hubwall - send an admin email to all hub accounts
-[*] ijpost - crosspost to Insanejournal
-[*] irc - connect to IRC chatrooms
-[*] jappixmini - XMPP chat
-[*] js_upload - upload multiple photos to photo albums at once.
-[*] keepout - prevents nearly all use of site when not logged in, more restrictive than 'block public' setting
-[*] ldapauth - login via account on LDAP or Windows Active Directory domain
-[*] libertree - crosspost to Libertree
-[*] likebanner - create a "like us on red#matrix" banner image
-[*] ljpost - crosspost to LiveJournal
-[*] logrot - logfile rotation utility
-[*] mahjongg - Chinese puzzle game
-[*] mailhost - when using multiple channel clones, select one to receive email notifications
-[*] mailtest - interface for testing mail delivery system
-[*] metatag - provide SEO friendly pages
-[*] mayan_places - set location field to a random city in the Mayan world
-[*] morechoice - additional gender/sexual-preference choices for profiles (not safe for work)
-[*] moremoods - Additional mood options
-[*] morepokes - additional poke options (not safe for work)
-[*] msgfooter - provide legal or other text on each outgoing post
-[*] noembed - use noembed.com as an addition to $Projectname native oembed functionality (currently broken)
-[*] nofed - prevent "federation" of channel posts, maintains all interaction on your site
-[*] nsabait - add random terrorism related hashtags to your posts
-[*] nsfw - Highly recommended plugin to collpase posts with inappropriate content
-[*] openclipatar - choose a profile photo from hundreds of royalty free images
-[*] openid - OpenID authentication and OpenID server. Your OpenID URL is [observer.baseurl]/id/[observer.webname]
-[*] opensearch - allow your site to become a browser search provider
-[*] openstreetmap - render locations and maps using OpenStreetMap
-[*] pageheader - display text at the top of every page on the site
-[*] phpmailer - alternate mail delivery system with more configurability
-[*] piwik - open source website analytics
-[*] planets - set location field to a random planet from Star Wars
-[*] pong - classic pong game
-[*] pubcrawl - ActivityPub protocol emulator
-[*] pubsubhubbub - PuSH protocol for optimised delivery to feed subscribers (required by GNU-Social protocol)
-[*] pumpio - crosspost to Pump.io
-[*] qrator - generate QR code images
-[*] rainbowtag - display your tag and category clouds in colours
-[*] randpost - post/reply bot based on and requires fortunate
-[*] redfiles - import file storage from redmatrix
-[*] redphotos - import photo albums from redmatrix
-[*] redred - Crosspost to another Red Matrix or Hubzilla channel
-[*] rendezvous - group location tracking
-[*] rtof - Crosspost to Friendica
-[*] sendzid - add 'zid' auth parmaters to all outbound links, not just in-network links
-[*] skeleton - sample addon/plugin to demonstrate plugin development
-[*] smiley_pack - extend the built-in smilie (emoticon) support
-[*] smileybutton - provides a smiley selector on the post window
-[*] startpage - set a personal preferred page to redirect after logging in.
-[*] statistics - Diaspora statistics generator
-[*] statusnet - GNU-social and StatusNet crosspost [zrl=[baseurl]/help/addons_gnusocial]Posting To Gnu Social[/zrl]
-[*] std_embeds - allow unfiltered embeds for popular providers like youtube, vimeo and soundcloud
-[*] superblock - Highly recommended - completely block an offensive channel from your stream
-[*] testdrive - Turns your hub into a test drive site with accounts that expire after a trail period.
-[*] tictac - 3D tic-tac-toe
-[*] torch - flashlight app
-[*] tour - feature tour for new members
-[*] tripleaes - demo plugin for providing custom encryption algorithms
-[*] twitter - crosspost to Twitter
-[*] twitter_api - Twitter/Statusnet compatible API
-[*] upload_limits - discover what server setting (there are a few) may be causing large photo uploads to fail
-[*] visage - show visitors to your channel
-[*] webmention - process webmentions
-[*] wholikesme - provides a page to display what connections have 'liked' your posts the most
-[*] webRTC - use an external server (mayfirst.org) to negotiate webRTC hookups
-[*] wppost - crosspost to WordPress (or other wordpress XMLRPC service)
-[*] xmpp - XMPP chat based on converse.js
-[/list]
-
-[h3]Addon Repositories[/h3]
-
-We [b]strongly recommend[/b] that authors of addons publish/submit them to the project addon repository. This has several advantages. Project developers can easily fix security flaws and make changes to comply with recent changes in core code. Addons provided in third-party repositories are considered untrusted. If the project core code changes in an incompatible way, there may be no alternative but to physically remove or rename the addon files in order to get your site working again. Often only the plugin/addon author can help you regain control of your website, and project developers are unable to assist you; because by definition your site configuration has been modified in ways that we cannot easily test or verify.
-
-For these reasons we [b]strongly recommend[/b] that you do NOT install addons from third-party repositories.
-
-We also recognise that some developers prefer working on their own and do not wish their code to be mingled with the project repository for a variety of reasons. These developers can ease troubleshooting and debugging by providing a README file in their respective code repository outlining the process for submitting patches and bug fixes. It is also recommended that these projects provide both a 'dev' (development) and 'master' (production) branch which tracks the current project branches of those names. This is because dev and master are often not compatible from the viewpoint of library interfaces. It is also highly recommended that your repository versions are tagged and moved forward within 24 hours of project releases. This is a major inconvenience for everybdy involved, and can present downtime for production sites while this process is being carried out; which is one more reason why we [b]strongly recommend[/b] that addons be submitted to the project addon repository and that you do NOT install such third-party addons.
-
-
-[url=https://framagit.org/hubzilla/addons]https://framagit.org/hubzilla/addons[/url] Main project addon repository
-
-[url=https://github.com/23n/red-addons]https://github.com/23n/red-addons[/url] Oliver's repository (mayan_places and flip)
-
-
-
-#include doc/macros/main_footer.bb;
-
-
diff --git a/doc/en/addons_gnusocial.bb b/doc/en/addons_gnusocial.bb
deleted file mode 100644
index fbb387476..000000000
--- a/doc/en/addons_gnusocial.bb
+++ /dev/null
@@ -1,65 +0,0 @@
-[b]How to cross-post to a GNUsocial instance[/b]
-
-Start on the GNUSocial instance where you have your account.
-
-In the GNUSocial instance, go to Settings > Connections. In the right column under "Developers," click the link to "Register an OAuth client application to use with this instance of StatusNet." This link may be found at your instance here:
-
-https://yourgnusocialinstance.org/settings/oauthapps
-
-Next, click the link to Register a new application. That brings up the new application form. Here's what to do on each field.
-
-Icon. I uploaded $Projectname icon located at this link, after saving it to my computer:
-
-https://framagit.org/hubzilla/core/blob/master/images/rm-32.png
-
-Name. Give the application an appropriate name. I called mine hubzilla. You might prefer r2g.
-
-Description. Use this field to describe the purpose of the application. I put something to the effect of use for crossposting from $Projectname to GNUsocial.
-
-
-Source URL. Put the main domain name of the Red site you're using. Don't forget to put the "s" in https://yourhubzillasite.com. If your Red installation is a subdomain, that would probably be called for.
-
-Organization. Since $Projectname is unorganized, I put that. If you use your installation for a group or business, that might be a good option.
-
-Homepage. If your group is using a subdomain, you probably want to put your main domain URI here. Since I'm on a hosted site, I put redmatrix.me.
-
-Callback URL. Leave blank.
-
-Type of application: select "desktop."
-
-Default access: select "Read-write."
-
-All fields except the callback URL must be filled in.
-
-Click on the save button.
-
-Then click on the icon or the name of the application for the information you'll need to insert over on $Projectname.
-
-*****
-
-Now open up a new tab or window and go to your $Projectname account, to Settings > Feature settings. Find the StatusNet Posting Settings.
-
-Insert the strings of numbers given on the GNUsocial site into $Projectname fields for Consumer Key and Consumer Secret.
-
-The Base API Path (remember the trailing /) will be your instance domain, plus the /api/ following. It will probably look like this:
-
-https://yourgnusocialinstance.org/api/
-
-In case of doubt check on your GNUsocial instance site in order to find the domain URLs of the Request token, Access token, and Authorization. It will be the first part of the domains, minus the /oauth/....
-
-StatusNet application name: Insert the name you gave to the application over on the GNUsocial site.
-
-Click Submit.
-
-A button will appear for you to "Sign in to StatusNet." Click it and that will open a tab or window on the GNUsocial site for you to click "Allow." Once clicked and successfully authorized, a security code number will appear. Copy it and go back to $Projectname app you just left and insert it in the field: "Copy the security code from StatusNet here." Click Submit.
-
-If successful, your information from the GNUsocial instance should appear in $Projectname app.
-
-You now have several options to choose, if you desire, and those will need to be confirmed by clicking "Submit" also. The most interesting is "Send public postings to StatusNet by default." This option automatically sends any post of yours made in your $Projectname account to your GNUsocial instance.
-
-If you don't choose this option, you will have an option to send a post to your GNUsocial instance by first opening the post (by clicking in the post text area) and clicking on the lock icon next to the Share button. Select the GNUsocial icon made up of three colored dialog baloons. Close that window, then make your post.
-
-If all goes well, you have just cross-posted your $Projectname post to your account on a GNUsocial instance.
-
-#include doc/macros/addons_footer.bb;
-
diff --git a/doc/en/admin/administrator_guide.md b/doc/en/admin/administrator_guide.md
deleted file mode 100644
index bf4dc7355..000000000
--- a/doc/en/admin/administrator_guide.md
+++ /dev/null
@@ -1,464 +0,0 @@
-### Overview
-
-$Projectname is more than a simple web application. It is a
-complex communications system which more closely resembles an email server
-than a web server. For reliability and performance, messages are delivered in
-the background and are queued for later delivery when sites are down. This
-kind of functionality requires a bit more of the host system than the typical
-blog. Not every PHP/MySQL hosting provider will be able to support
-$Projectname. Many will but please review the requirements and confirm these
-with your hosting provider prior to installation.
-
-We've tried very hard to ensure that $Projectname will run on commodity
-hosting platforms such as those used to host Wordpress blogs and Drupal
-websites. It will run on most any Linux VPS system. Windows LAMP platforms
-such as XAMPP and WAMP are not officially supported at this time however
-we welcome patches if you manage to get it working.
-
-### Where to find more help
-If you encounter problems or have issues not addressed in this documentation,
-please let us know via the [Github issue
-tracker](https://framagit.org/hubzilla/core/issues). Please be as clear as you
-can about your operating environment and provide as much detail as possible
-about any error messages you may see, so that we can prevent it from happening
-in the future. Due to the large variety of operating systems and PHP platforms
-in existence we may have only limited ability to debug your PHP installation or
-acquire any missing modules, but we will do our best to solve any general code
-issues.
-
-### Before you begin
-
-#### Choose a domain name or subdomain name for your server
-
-$Projectname can only be installed into the root of a domain or sub-domain, and can
-not be installed using alternate TCP ports.
-
-#### Decide if you will use SSL and obtain an SSL certificate before software installation
-
-You SHOULD use SSL. If you use SSL, you MUST use a "browser-valid" certificate.
-*You MUST NOT use self-signed certificates!*
-
-Please test your certificate prior to installation. A web tool for testing your
-certificate is available at "http://www.digicert.com/help/". When visiting your
-site for the first time, please use the SSL ("https://") URL if SSL is available.
-This will avoid problems later. The installation routine will not allow you to
-use a non browser-valid certificate.
-
-This restriction is incorporated because public posts from you may contain
-references to images on your own hub. Other members viewing their stream on
-other hubs will get warnings if your certificate is not trusted by their web
-browser. This will confuse many people because this is a decentralised network
-and they will get the warning about your hub while viewing their own hub and may
-think their own hub has an issue. These warnings are very technical and scary to
-some folks, many of whom will not know how to proceed except to follow the browser
-advice. This is disruptive to the community. That said, we recognise the issues
-surrounding the current certificate infrastructure and agree there are many
-problems, but that doesn't change the requirement.
-
-Free "browser-valid" certificates are available from providers such as StartSSL
-and LetsEncrypt.
-
-If you do NOT use SSL, there may be a delay of up to a minute for the initial
-install script - while we check the SSL port to see if anything responds there.
-When communicating with new sites, $Projectname always attempts connection on the
-SSL port first, before falling back to a less secure connection. If you do not
-use SSL, your webserver MUST NOT listen on port 443 at all.
-
-If you use LetsEncrypt to provide certificates and create a file under
-.well-known/acme-challenge so that LetsEncrypt can verify your domain ownership,
-please remove or rename the .well-known directory as soon as the certificate is
-generated. $Projectname will provide its own handler for ".well-known" services when
-it is installed, and an existing directory in this location may prevent some of
-these services from working correctly. This should not be a problem with Apache,
-but may be an issue with nginx or other web server platforms.
-
-### Deployment
-There are several ways to deploy a new hub.
-
-* Manual installation on an existing server
-* Automated installation on an existing server using a shell script
-* Automated deployment using an OpenShift virtual private server (VPS)
-
-### Requirements
-* Apache with mod-rewrite enabled and "AllowOverride All" so you can use a
- local .htaccess file. Some folks have successfully used nginx and lighttpd.
- Example config scripts are available for these platforms in doc/install.
- Apache and nginx have the most support.
-
-* PHP 8.1 or later.
- Note that on some shared hosting environments, the _command line_
- version of PHP might differ from the _webserver_ version
-
-* PHP *command line* access with register_argc_argv set to true in the
- php.ini file, and with no hosting provider restrictions on the use of
- exec() and proc_open().
-
-* curl, gd (with at least jpeg and png support), pdo-mysql (or pdo-postgres), mbstring, zip,
- and openssl extensions. The imagick extension is not required, but recommended.
-
-* xml extension is required if you want webdav to work.
-
-* some form of email server or email gateway such that PHP mail() works.
-
-* A supported database server. The supported databases are:
- - Mysql version 8.0.22 or later
- - MariaDB version 10.4 or later
- - PostgreSQL version 12 or later
-
-* ability to schedule jobs with cron.
-
-* Installation into a top-level domain or sub-domain (without a
- directory/path component in the URL) is REQUIRED.
-
-### Manual Installation
-
-#### Unpack the $Projectname files into the root of your web server document area
-If you copy the directory tree to your webserver, make sure that you include the
-hidden files like .htaccess.
-
-If you are able to do so, we recommend using git to clone the source
-repository rather than to use a packaged tar or zip file. This makes the
-software much easier to update. The Linux command to clone the repository
-into a directory "mywebsite" would be:
-
- git clone https://framagit.org/hubzilla/core.git mywebsite
-
-and then you can pick up the latest changes at any time with:
-
- git pull
-
-make sure folders ``store/[data]/smarty3`` and ``store`` exist and are
-writable by the webserver:
-
- mkdir -p "store/[data]/smarty3"
- chmod -R 777 store
-
- This permission (777) is very dangerous and if you have sufficient
- privilege and knowledge you should make these directories writeable
- only by the webserver and, if different, the user that will run the
- cron job (see below). In many shared hosting environments this may be
- difficult without opening a trouble ticket with your provider. The
- above permissions will allow the software to work, but are not
- optimal.
-
-The following directories also need to be writable by the webserver in order for certain
-web-based administrative tools to function:
-
-* `addon`
-* `extend`
-* `view/theme`
-* `widget`
-
-#### Official addons
-##### Installation
-Navigate to your website. Then you should clone the addon repository (separately). We'll give this repository a nickname of 'hzaddons'. You can pull in other hubzilla addon repositories by giving them different nicknames:
-
- cd mywebsite
- util/add_addon_repo https://framagit.org/hubzilla/addons.git hzaddons
-
-##### Updating
-For keeping the addon tree updated, you should be on your top level website directory and issue an update command for that repository::
-
- cd mywebsite
- util/update_addon_repo hzaddons
-
-Create searchable representations of the online documentation. You may do this
-any time that the documentation is updated :
-
- cd mywebsite
- util/importdoc
-
-### Automated installation via the .homeinstall shell script
-There is a shell script in (``.homeinstall/hubzilla-setup.sh``) that will install $Projectname and its dependencies on a fresh installation of Debian 9 stable (Stetch). It should work on similar Linux systems but your results may vary.
-
-#### Requirements
-The installation script was originally designed for a small hardware server behind your home router. However, it has been tested on several systems running Debian 9:
-
-* Home-PC (Debian-9.2-amd64) and Rapberry-Pi 3 (Rasbian = Debian 9.3)
-
- * Internet connection and router at home
- * Mini-PC / Raspi connected to your router
- * USB drive for backups
- * Fresh installation of Debian on your mini-pc
- * Router with open ports 80 and 443 for your Debian
-
-#### Overview of installation steps
-1. `apt-get install git`
-1. `mkdir -p /var/www/html`
-1. `cd /var/www/html`
-1. `git clone https://framagit.org/hubzilla/core.git .`
-1. `nano .homeinstall/hubzilla-config.txt`
-1. `cd .homeinstall/`
-1. `./hubzilla-setup.sh`
-1. `service apache2 reload`
-1. Open your domain with a browser and step throught the initial configuration of $Projectname.
-
-### Recommended Addons
-
-We recommend the following addons be installed on all public sites:
-
- nsfw - hide inappropriate posts/comments
- superblock - block content from offensive channels
-
-### Federation Addons
-
-Several web communities have begun to converge using common protocols. The protocols involved are somewhat limited in their abilities. The GNU-Social protocol for instance offers no privacy modes, and the Diaspora protocol is somewhat restrictive in what kinds of communications are allowed. All comments must be signed in a very unique manner by the original author. The ActivityPub protocol is also being considered and may be supported at a future date. No other existing protocol supports nomadic location as used by this project. This presents some support challenges as some features work with some networks and don't work with others. Nevertheless the federation protocols allow connections to be made to a much larger community of people worldwide. They are provided as addons.
-
-* diaspora - The Diaspora Protocol used by Diaspora and Friendica. You should enable 'Diaspora Statistics' (statistics) first to enable all the available features.
-
-* gnusoc - The GNU-Social Protocol, used by GNU-Social, Mastodon and several other communities. This addon requires you first install the 'pubsubhubbub' service (also an addon).
-
-Each member of your site must choose whether or not to allow these protocols individually as they may conflict with several desirable core features and abilities of this software (such as channel migration and cloning). They do this from their 'Settings -> Feature/Addon Settings' page. The administrator may also set the following:
-
- util/config system.diaspora_allowed 1
- util/config system.gnusoc_allowed 1
-
-and enable these protocols automatically for all newly created channels.
-
-### Service Classes
-
-Service classes allow you to set limits on system resources by limiting what individual
-accounts can do, including file storage and top-level post limits. Define custom service
-classes according to your needs in the `.htconfig.php` file. For example, create
-a _standard_ and _premium_ class using the following lines:
-
- // Service classes
-
- App::$config['system']['default_service_class']='standard'; // this is the default service class that is attached to every new account
-
- // configuration for standard service class
- App::$config['service_class']['standard'] =
- array('photo_upload_limit'=>2097152, // total photo storage limit per channel (here 2MB)
- 'total_identities' =>1, // number of channels an account can create
- 'total_items' =>0, // number of top level posts a channel can create. Applies only to top level posts of the channel user, other posts and comments are unaffected
- 'total_pages' =>100, // number of pages a channel can create
- 'total_channels' =>100, // number of channels the user can add, other users can still add this channel, even if the limit is reached
- 'attach_upload_limit' =>2097152, // total attachment storage limit per channel (here 2MB)
- 'chatters_inroom' =>20);
-
- // configuration for premium service class
- App::$config['service_class']['premium'] =
- array('photo_upload_limit'=>20000000000, // total photo storage limit per channel (here 20GB)
- 'total_identities' =>20, // number of channels an account can create
- 'total_items' =>20000, // number of top level posts a channel can create. Applies only to top level posts of the channel user, other posts and comments are unaffected
- 'total_pages' =>400, // number of pages a channel can create
- 'total_channels' =>2000, // number of channels the user can add, other users can still add this channel, even if the limit is reached
- 'attach_upload_limit' =>20000000000, // total attachment storage limit per channel (here 20GB)
- 'chatters_inroom' =>100);
-
-To apply a service class to an existing account, use the command line utility from the
-web root:
-
-`util/service_class`
-list service classes
-
-`util/config system default_service_class firstclass`
-set the default service class to 'firstclass'
-
-`util/service_class firstclass`
-list the services that are part of 'firstclass' service class
-
-`util/service_class firstclass photo_upload_limit 10000000`
-set firstclass total photo disk usage to 10 million bytes
-
-`util/service_class --account=5 firstclass`
-set account id 5 to service class 'firstclass' (with confirmation)
-
-`util/service_class --channel=blogchan firstclass`
-set the account that owns channel 'blogchan' to service class 'firstclass' (with confirmation)
-
-**Service class limit options**
-
-* photo_upload_limit - maximum total bytes for photos
-* total_items - maximum total toplevel posts
-* total_pages - maximum comanche pages
-* total_identities - maximum number of channels owned by account
-* total_channels - maximum number of connections
-* total_feeds - maximum number of rss feed connections
-* attach_upload_limit - maximum file upload storage (bytes)
-* minimum_feedcheck_minutes - lowest setting allowed for polling rss feeds
-* chatrooms - maximum chatrooms
-* chatters_inroom - maximum chatters per room
-* access_tokens - maximum number of Guest Access Tokens per channel
-
-### Theme management
-#### Repo management example
-1. Navigate to your hub web root
-
- ```
- root@hub:/root# cd /var/www
- ```
-2. Add the theme repo and give it a name
-
- ```
- root@hub:/var/www# util/add_theme_repo https://github.com/DeadSuperHero/redmatrix-themes.git DeadSuperHero
- ```
-3. Update the repo by using
-
- ```
- root@hub:/var/www# util/update_theme_repo DeadSuperHero
- ```
-
-### Channel Directory
-
-#### Keywords
-
-There is a "tag cloud" of keywords that can appear on the channel directory page.
-If you wish to hide these keywords, which are drawn from the directory server, you
-can use the *config* tool:
-
- util/config system disable_directory_keywords 1
-
-If your hub is in the standalone mode because you do not wish to connect to the
-global grid, you may instead ensure the the _directory_server_ system option is
-empty:
-
- util/config system directory_server ""
-
-### Administration
-
-#### Site Administration
-
-Administration of the website is commonly done through the admin webpage located at /admin on your website. In order to access this page you must have administration rights to the server. Administration rights are granted to the first account to register on your site, **provided** the email address of that account exactly matches the email address you provided as the administrator's email address during setup.
-
-There are several ways that this can fail and leave the system without an administrator account, for instance if the first account that was created provided a different email address than the administrator email address that was supplied during setup.
-
-For security reasons there is no web page or interface on the system which will give you administrator access. If you need to correct a situation where a system has no administrator account it **must** be done by editing the account table in the database. There is no other way. To do this, you will need to locate the entry in the account table which belongs to the desired administrator, and set 'account_roles' for that entry to 4096. You will then be able to access the admin page from your system's profile menu or directly via /admin .
-
-A hub can have multiple admins and there is no limit to how administrators you can have. Repeat the above process for every account you wish to provide with administration rights.
-
-### Troubleshooting
-
-#### Log files
-
-The system logfile is an extremely useful resource for tracking down
-things that go wrong. This can be enabled in the admin/log
-configuration page. A loglevel setting of `LOGGER_DEBUG` is preferred
-for stable production sites. Most things that go wrong with
-communications or storage are listed here. A setting of LOGGER_DATA
-provides *much* more detail, but may fill your disk. In either
-case we recommend the use of logrotate on your operating system to
-cycle logs and discard older entries.
-
-At the bottom of your .htconfig.php file are several lines (commented
-out) which enable PHP error logging. This reports issues with code
-syntax and executing the code and is the first place you should look
-for issues which result in a "white screen" or blank page. This is
-typically the result of code/syntax problems. Database errors are
-reported to the system logfile, but we've found it useful to have a
-file in your top-level directory called dbfail.out which *only*
-collects database related issues. If the file exists and is writable,
-database errors will be logged to it as well as to the system logfile.
-
-In the case of "500" errors, the issues may often be logged in your
-webserver logs, often /var/log/apache2/error.log or something
-similar. Consult your operating system documentation.
-
-There are three different log facilities.
-
-**The first is the database failure log**. This is only used if you
- create a file called specifically `dbfail.out` in the root folder of
- your website and make it write-able by the web server. If we have
- any database failed queries, they are all reported here. They
- generally indicate typos in our queries, but also occur if the
- database server disconnects or tables get corrupted. On rare
- occasions we'll see race conditions in here where two processes
- tried to create an xchan or cache entry with the same ID. Any other
- errors (especially persistent errors) should be investigated.
-
-**The second is the PHP error log**. This is created by the language
- processor and only reports issues in the language environment. Again
- these can be syntax errors or programming errors, but these
- generally are fatal and result in a "white screen of death";
- e.g. PHP terminates. You should probably look at this file if
- something goes wrong that doesn't result in a white screen of death,
- but it isn't uncommon for this file to be empty for days on end.
-
- There are some lines at the bottom of the supplied `.htconfig.php`
- file; which if uncommented will enable a PHP error log (*extremely*
- useful for finding the source of white screen failures). This isn't
- done by default due to potential issues with logfile ownership and
- write permissions and the fact that there is no logfile rotation by
- default.
-
-**The third is the "application log"**. This is used by $Projectname
- to report what is going on in the program and usually reports any
- difficulties or unexpected data we received. It also occasionally
- reports "heartbeat" status messages to indicate that we reached a
- certain point in a script. **This** is the most important log file
- to us, as we create it ourself for the sole purpose of reporting the
- status of background tasks and anything that seems weird or out of
- place. It may not be fatal, but maybe just unexpected. If you're
- performing a task and there's a problem, let us know what is in this
- file when the problem occurred. (Please don't send me 100M dumps
- you'll only piss me off). Just a few relevant lines so I can rule
- out a few hundred thousand lines of code and concentrate on where
- the problem starts showing up.
-
-These are your site logs, not mine. We report serious issues at any
-log level. I highly recommend `DEBUG` log level for most sites - which
-provides a bit of additional info and doesn't create huge
-logfiles. When there's a problem which defies all attempts to track,
-you might wish to use `DATA` log level for a short period of time to
-capture all the detail of what structures we were dealing with at the
-time. This log level will use a lot of space so is recommended only
-for brief periods or for developer test sites.
-
-I recommend configuring logrotate for both the php log and the
-application log. I usually have a look at dbfail.out every week or
-two, fix any issues reported and then starting over with a fresh
-file. Likewise with the PHP logfile. I refer to it once in a while to
-see if there's something that needs fixing.
-
-If something goes wrong, and it's not a fatal error, I look at the
-application logfile. Often I will
-
-```
-tail -f logfile.out
-```
-
-While repeating an operation that has problems. Often I'll insert
-extra logging statements in the code if there isn't any hint what's
-going wrong. Even something as simple as "got here" or printing out
-the value of a variable that might be suspect. You can do this too -
-in fact I encourage you to do so. Once you've found what you need to
-find, you can
-
-```
-git checkout file.php
-```
-
-To immediately clear out all the extra logging stuff you added. Use
-the information from this log and any detail you can provide from your
-investigation of the problem to file your bug report - unless your
-analysis points to the source of the problem. In that case, just fix
-it.
-
-##### Rotating log files
-
-1. Enable the **logrot** addon in the official [hubzilla-addons](https://framagit.org/hubzilla/addons) repo
-1. Create a directory in your web root called `log` with webserver write permissions
-1. Go to the **logrot** admin settings and enter this folder name as well as the max size and number of retained log files.
-
-#### Reporting issues
-
-When reporting issues, please try to provide as much detail as may be
-necessary for developers to reproduce the issue and provide the
-complete text of all error messages.
-
-We encourage you to try to the best of your abilities to use these
-logs combined with the source code in your possession to troubleshoot
-issues and find their cause. The community is often able to help, but
-only you have access to your site logfiles and it is considered a
-security risk to share them.
-
-If a code issue has been uncovered, please report it on the project
-bugtracker (https://framagit.org/hubzilla/core/issues). Again provide
-as much detail as possible to avoid us going back and forth asking
-questions about your configuration or how to duplicate the problem, so
-that we can get right to the problem and figure out what to do about
-it. You are also welcome to offer your own solutions and submit
-patches. In fact we encourage this as we are all volunteers and have
-little spare time available. The more people that help, the easier the
-workload for everybody. It's OK if your solution isn't perfect. Every
-little bit helps and perhaps we can improve on it.
diff --git a/doc/en/admin/hub_snapshots.md b/doc/en/admin/hub_snapshots.md
deleted file mode 100644
index ab0948aa8..000000000
--- a/doc/en/admin/hub_snapshots.md
+++ /dev/null
@@ -1,127 +0,0 @@
-### Hub Snapshot Tools
-
-Hubzilla developers frequently need to switch between branches that might have
-incompatible database schemas or content. The following two scripts create and
-restore complete snapshots of a Hubzilla instance, including both the hub web
-root and the entire database state. Each script requires a config file called
-`hub-snapshot.conf` residing in the same folder and containing the specific
-directories and database details of your hub.
-
-### Config
-
-The format of the config file is very strict. There must be no spaces between the
-variable name and the value. Replace only the content inside the quotes with your
-configuration. Save this file as `hub-snapshot.conf` alongside the scripts.
-
- # Location of hub root. Typically this is the location of the Hubzilla repo clone.
- HUBROOT="/var/www/"
- # MySQL database name
- DBNAME="hubzilla"
- # MySQL database user
- DBUSER="hubzilla"
- # MySQL database password
- DBPWD="akeufajeuwfb"
- # The target snapshot folder where the git repo will be initialized
- SNAPSHOTROOT="/root/snapshots/hubzilla/"
-
-### Snapshot
-
-Example usage:
-
- sh hub-snapshot.sh my-hub.conf "Commit message for the snapshot"
-
-**hub-snapshot.sh**:
-
- #!/bin/bash
-
- if ! [ -f "$1" ]; then
- echo "$1 is not a valid file. Aborting..."
- exit 1
- fi
- source "$1"
- #echo "$DBNAME"
- #echo "$DBUSER"
- #echo "$DBPWD"
- #echo "$HUBROOT"
- #echo "$SNAPSHOTROOT"
- MESSAGE="snapshot: $2"
-
- if [ "$DBPWD" == "" -o "$SNAPSHOTROOT" == "" -o "$DBNAME" == "" -o "$DBUSER" == "" -o "$HUBROOT" == "" ]; then
- echo "Required variable is not set. Aborting..."
- exit 1
- fi
-
- if [ ! -d "$SNAPSHOTROOT"/db/ ]; then
- mkdir -p "$SNAPSHOTROOT"/db/
- fi
- if [ ! -d "$SNAPSHOTROOT"/www/ ]; then
- mkdir -p "$SNAPSHOTROOT"/www/
- fi
-
- if [ ! -d "$SNAPSHOTROOT"/www/ ] || [ ! -d "$SNAPSHOTROOT"/db/ ]; then
- echo "Error creating snapshot directories. Aborting..."
- exit 1
- fi
-
- echo "Export database..."
- mysqldump -u "$DBUSER" -p"$DBPWD" "$DBNAME" > "$SNAPSHOTROOT"/db/"$DBNAME".sql
- echo "Copy hub root files..."
- rsync -va --delete --exclude=.git* "$HUBROOT"/ "$SNAPSHOTROOT"/www/
-
- cd "$SNAPSHOTROOT"
-
- if [ ! -d ".git" ]; then
- git init
- fi
- if [ ! -d ".git" ]; then
- echo "Cannot initialize git repo. Aborting..."
- exit 1
- fi
-
- git add -A
- echo "Commit hub snapshot..."
- git commit -a -m "$MESSAGE"
-
- exit 0
-
-### Restore
-
- #!/bin/bash
- # Restore hub to a previous state. Input hub config and commit hash
-
- if ! [ -f "$1" ]; then
- echo "$1 is not a valid file. Aborting..."
- exit 1
- fi
- source "$1"
- COMMIT=$2
-
- if [ "$DBPWD" == "" -o "$SNAPSHOTROOT" == "" -o "$DBNAME" == "" -o "$DBUSER" == "" -o "$HUBROOT" == "" ]; then
- echo "Required variable is not set. Aborting..."
- exit 1
- fi
- RESTOREDIR="$(mktemp -d)/"
-
- if [ ! -d "$RESTOREDIR" ]; then
- echo "Cannot create restore directory. Aborting..."
- exit 1
- fi
- echo "Cloning the snapshot repo..."
- git clone "$SNAPSHOTROOT" "$RESTOREDIR"
- cd "$RESTOREDIR"
- echo "Checkout requested snapshot..."
- git checkout "$COMMIT"
- echo "Restore hub root files..."
- rsync -a --delete --exclude=.git* "$RESTOREDIR"/www/ "$HUBROOT"/
- echo "Restore hub database..."
- mysql -u "$DBUSER" -p"$DBPWD" "$DBNAME" < "$RESTOREDIR"/db/"$DBNAME".sql
-
- chown -R www-data:www-data "$HUBROOT"/{store,extend,addon,.htlog,.htconfig.php}
-
- echo "Restored hub to snapshot $COMMIT"
- echo "Removing temporary files..."
-
- rm -rf "$RESTOREDIR"
-
- exit 0
-
diff --git a/doc/en/admin/zarlog_msgs.md b/doc/en/admin/zarlog_msgs.md
deleted file mode 100644
index 0fe16a1e2..000000000
--- a/doc/en/admin/zarlog_msgs.md
+++ /dev/null
@@ -1,112 +0,0 @@
-~~~
-L regate ZAR1131E dId2 mistaken
-L ZAR1132E Identity unknown
- ZAR1133A Sorry for any inconvience. Thank you for your response.
-L ZAR1134S email verfication denied {did2}
-L ZAR1135E not awaited url parameter received
-
-L regate ZAR1230S Unexpected registration verification request for
-L ZAR1231E dId2 mistaken
-L ZAR1232E Identity unknown
-L ZAR1234W Request not inside time frame
-L ZAR1235E Token verification failed
- ZAR1236I Verify successfull
-L ZAR1236E Verify failed
-L ZAR1237D unexpected
- (reason may be caused by new account flags implemented still not known)
- ZAR1238I Email resent
- ZAR1238E Resent failed
-L ZAR1239I Account successfull created
-L ZAR1239E Account creation error
-
- register ZAR0130E Registration on this hub is disabled.
- ZAR0131I Registration on this hub is by approval only.
- Register at another affiliated hub in case when prefered
- ZAR0132I Registration on this hub is by invitation only.
- Register at another affiliated hub
- ZAR0133I If the registation was already submitted with your data once ago,
- enter your identity (like email) here and submit
- ZAR0134I I have an invite code
- ZAR0135I This site requires verification. After completing this form,
- please check the notice or your email for further instructions.
- ZAR0136I Your email address (or leave blank to register without email)
-
-L register ZAR0230S Unexpected registration request
- ZAR0231E Email address mistake
- ZAR0231E Passwords do not match.
- ZAR0231E Please indicate acceptance of the Terms of Service. Registration failed.
- ZAR0232E Invitations are not available
-L ZAR0233E Registration on this hub is by invitation only
-L ZAR0234S Invitation code failed
-L ZAR0235S Invitation email failed
- ZAR0236E Invitation not in time or too late
- ZAR0237I Invitation code succesfully applied
-L ZAR0238E Email address already in use
-L ZAR0239D Error creating dId A
- ZAR0239I Your didital id is {did2} and your pin for is {pin}
- Keep these infos and your entered password safe
- Valid from ... nd expire ...
-L ZAR0239S Exceeding same ip register request of
-
- ui:admin:site
- ZAR0810C Register text
- Will be displayed prominently on the registration page.
- ZAR0820C register_policy
- Does this site allow new member registration?
- ZAR0830C Registration office on duty
- The weekdays and hours the register office is open for registrations
- ZAR0831I Testmode duties
- (interactive)
- ZAR0840C Account registrations max per day
- How many registration requests the site accepts during one day. Unlimited if zero or no value.
- ZAR0850C Account registrations from same ip
- How many pending registration requests the site accepts from a same ip address.
- ZAR0860C Account registration delay
- How long a registration request has to wait before validation can perform
- ZAR0862C Account registration expiration
- How long a registration to confirm remains valid. Not expire if zero or no value
- ZAR0870C Auto channel create
- Auto create a channel when register a new account. When On, the register form will show
- additional fields for the channel-name and the nickname.
- ZAR0880C Invitation only
- Only allow new member registrations with an invitation code.
- Above register policy must be set to Yes.
- ZAR0881C Invitation also
- Also allow new member registrations with an invitation code.
- Above register policy must be set to Yes.
- ZAR0890C Verify Email Addresses
- Check to verify email addresses used in account registration (recommended).
-
- invite ZAI0100E All users invitation limit exceeded
- ZAI0101E Permission denied.
- ZAI0102E Invite App (Not Installed)
- ZAI0103E Invites not proposed by configuration. Contact the site admin
- ZAI0104E Invites by users not enabled
- ZAI0105W You have no more invitations available
- ZAI0106I Invitations I am using
- ZAI0107I Invitations we are using
- ZAI0109E Not on xchan
- ZAI0110I § Note, the email(s) sent will be recorded in the system logs
- (see ZAI0208I @ L)
- ZAI0111I Enter email addresses, one per line
- ZAI0112I Your message
- Here you may enter personal notes to the recipient(s)
- ZAI0113I Invite template
- ZAI0114I Note, the invitation code is valid up to ...
-
- invite ZAI0201E Permission denied.
- ZAI0202E Invite App (Not Installed)
- ZAI0203E Not a valid email address
- ZAI0204E Not a real email address
- ZAI0205E Not allowed email address
- ZAI0206E mail address already in use
- ZAI0207I Note, the invitation code is valid up to
- ZAI0208E Message delivery failed.
- ZAI0208I Message delivery success.
-L ZAI0208I to {email} Message delivery success. ({account#}, {channel#}, from:{email}})
- ZAI0209I Accepted email address
- ZAI0210E Too many recipients for one invitation (max n)
- ZAI0211E No recipients for this invitation
- ZAI0212I n mail(s) sent, n mail error(s)
- ZAI0213E Register is closed
-~~~
diff --git a/doc/en/adminmanual/CLI_tools.md b/doc/en/adminmanual/CLI_tools.md
new file mode 100644
index 000000000..7c1ec2e40
--- /dev/null
+++ b/doc/en/adminmanual/CLI_tools.md
@@ -0,0 +1,112 @@
+### CLI Tools (utils)
+
+If you have access to the shell as an administrator, you can use other small CLI tools, which can be found in the ‘utils’ directory.
+
+#### addons
+
+You can use the addons script to display which addons are installed and which are available. You can also install and uninstall add-ons and reinstall all add-ons.
+
+- `util/addons list`
+ Display of all installed add-ons
+- `util/addons list all`
+ Display of all add-ons that are installed (*) and those that are deactivated due to incompatibility (!)
+- `util/addons install foo`
+ Install the addon with the name ‘foo’
+- `util/addons uninstall foo`
+ Uninstall the addon with the name ‘foo’
+- `util/addons reinstall`
+ Reinstall all addons
+
+#### admins
+
+The admins script allows you to display all admins of the hub, add further admins and remove existing admins.
+
+- `util/admins`
+- `util/admins list`
+- `util/admins add <account_id>`
+- `util/admins remove <account_id>`
+
+#### config / pconfig
+
+See: [Advanced configurations for administrators](/help/adminmanualadvanced_configurations.md)
+
+#### connect
+
+You can use connect to establish a connection between one channel of your hub and another channel.
+
+- `util/connect <channel_id> <channel@hub>`
+- `util/connect <channel_address> <channel@hub>`
+
+#### dcp
+
+You can use dcp to copy files to the store area of a channel on your hub.
+
+- `util/dcp <source> <destination directory>` where destination directory must be `store/$nickname/path` or `$nickname/path`.
+
+#### dmkdir
+
+You can use dmkdir to create a subdirectory in the store area of a channel on your hub.
+
+- `util/dmkdir <directory>` where directory must ba `store/$nickname/path/<directory>` or `$nickname/path/<directory>`.
+
+#### fresh (The Freaking REd Shell)
+
+This only works under Unix/Linux. If the readline module is installed, it uses this for the input, otherwise it only reads from stdin and writes to stdout.
+The commands are processed in sequence until the command ‘exit’, ‘quit’ or the end of the file is reached.
+
+Commands:
+
+- `version`
+ Displays the current Fresh version.
+
+- `login <email address>`
+
+- Asks for a password and authenticates `<email address>` as the current user.
+
+ user.
+
+- `finger <channel address>`
+ Performs a lookup of `<channel address>` and reports the result.
+
+- `channel <channel nbickname>`
+ Switches the current channel to the channel with the specified nickname.
+
+- `conn [<id1> <id2> ...]`
+
+ Without arguments, this lists all connections of the current channel with an ID.
+ If IDs are specified, the details of the individual connections are displayed.
+
+#### hz
+
+Simple, minimalist command line tool to send the status to hubzilla via the API. Requires curl.
+
+```
+hz [<configuration file>]
+```
+
+hz requires a configuration file. You can either use a `.hubzilla` file and omit the `<configuration file>` parameter or create a configuration file under any name in the hubzilla directory, the name of which you then specify when calling hz.
+
+Format of the configuration file:
+
+```
+USER=<your username>
+PASS=<your password>
+HUB=<domain of the hub>
+```
+
+You can then enter your posting and finalise the entry with Ctrl-D.
+
+#### storageconf
+
+You can use storageconf to specify the storage location for thumbnails (file system or database) and query the current configuration.
+
+- `util/storageconv stats`
+ Shows the currently set status
+- `util/storageconv fs`
+ Moves the thumbnails from the database to the file system
+- `util/storageconv db`
+ Moves the thumbnails from the file system to the database
+
+#### thumbrepair
+
+thumbrepair recreates the local thumbnails.
diff --git a/doc/en/adminmanual/Creating-Templates.md b/doc/en/adminmanual/Creating-Templates.md
new file mode 100644
index 000000000..371c69b6b
--- /dev/null
+++ b/doc/en/adminmanual/Creating-Templates.md
@@ -0,0 +1,86 @@
+### Creating Page Templates
+
+
+A page template for use with Comanche requires two files - a PHP template and a CSS file. Page templates will need to be installed by the system administrator of your site.
+
+
+First choose a name. Here we'll create a template and call it "demo".
+
+You will need to create the files "view/php/demo.php" and "view/css/demo.css" to hold the PHP template and CSS respectively.
+
+To get a better idea of this process, let's look at an existing template - the "default" template. This is used by default throughout the application.
+
+#### view/php/default.php
+
+ <!DOCTYPE html >
+ <html>
+ <head>
+ <title><?php if(x($page,'title')) echo $page['title'] ?></title>
+ <script>var baseurl="<?php echo z_root() ?>";</script>
+ <?php if(x($page,'htmlhead')) echo $page['htmlhead'] ?>
+ </head>
+ <body>
+ <?php if(x($page,'nav')) echo $page['nav']; ?>
+ <aside id="region_1"><?php if(x($page,'aside')) echo $page['aside']; ?></aside>
+ <section id="region_2"><?php if(x($page,'content')) echo $page['content']; ?>
+ <div id="page-footer"></div>
+ <div id="pause"></div>
+ </section>
+ <aside id="region_3"><?php if(x($page,'right_aside')) echo $page['right_aside']; ?></aside>
+ <footer><?php if(x($page,'footer')) echo $page['footer']; ?></footer>
+ </body>
+ </html>
+
+
+Here's is the corresponding CSS file
+
+#### view/php/default.css
+
+
+ aside#region_1 {
+ display: block;
+ width: 210px;
+ position: absolute;
+ top: 65px;
+ left: 0;
+ margin-left: 10px;
+ }
+
+ aside input[type='text'] {
+ width: 174px;
+ }
+
+
+ section {
+ position: absolute;
+ top: 65px;
+ left: 250px;
+ display: block;
+ right: 15px;
+ padding-bottom: 350px;
+ }
+
+
+Some things you may notice when looking at these definitions:
+
+* We have not specified any CSS for the "nav", "right_aside", or "footer" regions. In this template "nav" and "footer" will be the full page width and we will let the size and placement of these elements be controlled by the theme. "right_aside" is not currently used.
+
+* There are elements on the page such as "page-footer" and "pause" for which there is no apparent content. This content will come from Javascript elements.
+
+* Our default template uses absolute positioning. Modern web design often uses "float" div containers so that scrollbars aren't typically needed when viewing on small-screen devices.
+
+To design a new template, it is best to start with an existing template, and modify it as desired. That is what we will do here.
+
+The way that Comanche provides content inside a specific region is by using a region tag.
+
+ [region=aside][widget=profile][/widget][/region]
+
+This example will place a "profile" widget in the "aside" region. But what it actually does is place the HTML for the widget into a code variable **$page['aside']**. Our default page template defines a region on the page (the CSS positions this as an absolute sidebar) and then inserts the contents of $page['aside'] (if it exists).
+
+So if you wanted to create a template with a region named "foo", you would provide a place for it on the page, then include the contents of $page['foo'] wherever you wanted to use it, and then using Comanche, you could specify
+
+ [region=foo][widget=profile][/widget][/region]
+
+and this would place a profile widget into the "foo" region you created.
+
+Use the CSS file to position the region on the page where desired and optionally control its size.
diff --git a/doc/en/adminmanual/Installation_using_docker.md b/doc/en/adminmanual/Installation_using_docker.md
new file mode 100644
index 000000000..5c787e850
--- /dev/null
+++ b/doc/en/adminmanual/Installation_using_docker.md
@@ -0,0 +1,56 @@
+### Installation using Docker
+
+It is possible to install Hubzilla comfortably and conveniently as a Docker container. Saiwal ([sk@hub.utsukta.org](https://hub.utsukta.org/channel/sk)) offers a preconfigured environment for a Hubzilla container for this purpose.
+
+The key features are:
+
+- Use Docker Compose to set up a fully functional [Hubzilla](https://hubzilla.org/page/info/discover) instance with just a few commands.
+- Prebuilt images available via dockerhub for amd64, arm/v7, arm64.
+- Continuous Updates: The Docker image is built to allow for easy updates whenever new changes are made to the Hubzilla core or its dependencies.
+- SMTP Integration: Built-in support for sending emails using ssmtp, making it easy to configure email notifications for your Hubzilla instance.
+
+The repository for the container is located here: [**skprg/hubzilla-docker**](https://github.com/skprg/hubzilla-docker)
+
+#### Building the image from scratch
+
+- Clone the Repository:
+
+```
+git clone https://github.com/skprg/hubzilla-docker.git
+cd hubzilla-docker
+```
+
+- Configure Your Environment: Update the `docker-compose.yml` file with your SMTP and other settings.
+- Build and Run the Container:
+
+```
+docker-compose up --build -d
+```
+
+#### Using prebuilt image
+
+- Replace the following lines in `docker-compose.yml`
+
+```
+ build:
+ context: .
+ dockerfile: Dockerfile
+```
+
+with
+
+```
+ image: ghcr.io/skprg/hubzilla-docker:latest
+```
+
+- Run the container:
+
+```
+docker compose up -d
+```
+
+Access Your Hubzilla Instance: Navigate to http://localhost (or the appropriate URL) to view your Hubzilla instance.
+
+#### **Further notes / update / tips**
+
+Further notes, upgrade instructions and tips can be found in the repository linked above. \ No newline at end of file
diff --git a/doc/en/adminmanual/Nomad---A-High-Level-Overview.md b/doc/en/adminmanual/Nomad---A-High-Level-Overview.md
new file mode 100644
index 000000000..5eae733da
--- /dev/null
+++ b/doc/en/adminmanual/Nomad---A-High-Level-Overview.md
@@ -0,0 +1,107 @@
+### Nomad - A High Level Overview
+
+Here's a high level description of how Nomad works.
+
+In this example, "Indigo" is going to send a public message from his website at "podunk.edu". "Nickordo" is a recipient on another site ("example.com").
+
+
+Indigo first posts his message at podunk.edu. podunk.edu looks up who should receive the message and finds Nickordo. Nickordo usually posts from example.com so we add that destination to our list of recipients. We may also add other destinations for nickordo and anybody else that is following Indigo's posts.
+
+In this example we find that we only have one known recipient at one known location.
+
+We send a packet to example.com:
+
+ {
+ "type":"notify",
+ "sender":{
+ "guid":"kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA",
+ "guid_sig":"PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK-R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q",
+ "url":"http:\/\/podunk.edu",
+ "url_sig":"T8Bp7j5DHHhQDCFcAHXfuhUfGk2P3inPbImwaXXF1xJd3TGgluoXyyKDx6WDm07x0hqbupoAoZB1qBP3_WfvWiJVAK4N1FD77EOYttUEHZ7L43xy5PCpojJQmkppGbPJc2jnTIc_F1vvGvw5fv8gBWZvPqTdb6LWF6FLrzwesZpi7j2rsioZ3wyUkqb5TDZaNNeWQrIEYXrEnWkRI_qTSOzx0dRTsGO6SpU1fPWuOOYMZG8Nh18nay0kLpxReuHCiCdxjXRVvk5k9rkcMbDBJcBovhiSioPKv_yJxcZVBATw3z3TTE95kGi4wxCEenxwhSpvouwa5b0hT7NS4Ay70QaxoKiLb3ZjhZaUUn4igCyZM0h6fllR5I6J_sAQxiMYD0v5ouIlb0u8YVMni93j3zlqMWdDUZ4WgTI7NNbo8ug9NQDHd92TPmSE1TytPTgya3tsFMzwyq0LZ0b-g-zSXWIES__jKQ7vAtIs9EwlPxqJXEDDniZ2AJ6biXRYgE2Kd6W_nmI7w31igwQTms3ecXe5ENI3ckEPUAq__llNnND7mxp5ZrdXzd5HHU9slXwDShYcW3yDeQLEwAVomTGSFpBrCX8W77n9hF3JClkWaeS4QcZ3xUtsSS81yLrp__ifFfQqx9_Be89WVyIOoF4oydr08EkZ8zwlAsbZLG7eLXY"
+ },
+ "callback":"\/post",
+ "version":1,
+ "secret":"1eaa6613699be6ebb2adcefa5379c61a3678aa0df89025470fac871431b70467"
+ }
+
+This packet says the following:
+
+I'm Indigo and here is proof. I'm posting from podunk.edu and here is proof. I've got a package for you. The tracking number is "1eaa6613....".
+
+Example.com accepts this packet and says "whoa, hold on - I don't know you. I want to prove who you are." So Example.com connects to podunk.edu through a "well-known URL" that we use for this purpose and looks up the "guid" mentioned above. It should return a bunch of information, one item of which is a public key. Example.com uses this key to verify the signatures in the message to verify that indeed there is a person named Indigo at podunk.edu. We only need to do this once. (Note that Indigo can post from any location. All we have to do is prove that it's Indigo and that Indigo can prove that he's posting from another site.)
+
+Then example.com disconnects and flags that there's a message waiting at podunk.edu. Either immediately, or whenever the urge hits (depending on how important Indigo is to anybody on this site), example.com "calls" podunk.edu. It says something like this:
+
+ {
+ "type":"pickup",
+ "url":"http:\/\/example.com",
+ "callback_sig":"teE1_fLIqfyeCuZY4iS7sNU8jUlUuqYOYBiHLarkC99I9K-uSr8DAwVW8ZPZRK-uYdxRMuKFb6cumF_Gt9XjecCPBM8HkoXHOi_VselzJkxPwor4ZPtWYWWaFtRfcAm794LrWjdz62zdESTQd2JJIZWbrli1sUhK801BF3n0Ye6-X1MWhy9EUTVlNimOeRipcuD_srMhUcAXOEbLlrugZ8ovy2YBe6YOXkS8jj0RSFjsOduXAoVhQmNpcobSYsDvaQS3e3MvE6-oXE602zGQhuNLr7DIMt9PCdAeQo-ZM-DHlZGCkGk4O2oQFCXFzGPqLUMWDACGJfTfIWGoh_EJqT_SD5b_Yi_Wk9S1lj7vb-lmxe5JuIf7ezWzHoBT8vswnZxPYlidH2i9wapdzij9il_qqcCWWHIp7q_XkY_Zj52Z4r4gdmiqM-8y1c_1SDX7hrJFRwqL_PKFbEvyi5nMWTEzqp55Tay5Woiv19STK_H_8ufFfD9AOkYnk6rIOMsk9dn3a5tAFpDRyRndXkBWAXwiJjiND2zjue7BFu7Ty40THXcfYRh1a5XrAXcaGeYuagg-8J9tAufu9_LY3qGazFg8kRBVMOn4M8DRKSIhKj7z4MnbYL0s09gREojy4jqWO3VkaOjP2jUGzoPuUDLasudE1ehWFq0K_MTQNavgmp8",
+ "callback":"http:\/\/example.com\/post",
+ "secret":"1eaa6613699be6ebb2adcefa5379c61a3678aa0df89025470fac871431b70467",
+ "secret_sig":"O7nB4_UJHBXi28Suwl9LBZF9hI_9KGVTgehnUlWF1oYMNRnBbVHB9lzUfAoalvp3STbU3xJbtD_S58tv6MfV7J5j2V_S1W5ex3dulmDGB8Pt_7Fe5mbEPmjQFcfv3Eg5dUjYIuDl0TDScfrHyImj7RZIWHbwd7wWVoMzzDa_o33klpYmKZCBvObCh55bRrlFkXZs_dRuOiPwkfX0C6_XES4OyOIYl45V30rdhmf-STrf4L9dKYy_axQ12RIwRcKychvVLwlUJn3bn9lgNXRRU_HTne-09OPcJbUOdcD3DkFoKOxMULBNKPHzsCau0ICYug7S0EP6LpCom_mW78s08LyVA1vYeFZjevBCiGecj57yIAQDYi6_rpWJfihYaWHRN0oqtScUR4Bdf0bQbEHxMs4zAtrOAxfyJCbi6U1pfnGgzXzB9ulOYGnVGNTF7Ey4K7FOZIBtk0ILY2JfvBUaVvVs8ttagOOHmhWhnbCvrnOFlkNdlce7zoJCSUJENUOCYmTRfwB_Jno5fAzRnrsYU3_Z-l1mzniU_OmUPz8mPEh7PwhkqAiVlyaM-q15gn7l2lAIDk9kp2X_iCme7v4V0ADN_DbpaI_0-6mPw5HLbKrCsA-sxlSMB4DO4lDCHYkauj0l25sbfroRWB_hax1O4Q0oWyOlVJLUqEC5nuUJCCE"
+ }
+
+
+What this message says is: This is example.com, I have proof, and I'm here to pick up a package. Here's the tracking number, and here's proof that this is the tracking number you presumably sent to example.com.
+
+Good enough. Podunk.edu checks out the story and indeed, it is example.com, and yes, there's a package waiting with that tracking number. Here's the package...
+
+ {
+ "success":1,
+ "pickup":{
+ "notify":{
+ "type":"notify",
+ "sender":{
+ "guid":"kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA",
+ "guid_sig":"PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK-R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q",
+ "url":"http:\/\/z.podunk.edu",
+ "url_sig":"T8Bp7j5DHHhQDCFcAHXfuhUfGk2P3inPbImwaXXF1xJd3TGgluoXyyKDx6WDm07x0hqbupoAoZB1qBP3_WfvWiJVAK4N1FD77EOYttUEHZ7L43xy5PCpojJQmkppGbPJc2jnTIc_F1vvGvw5fv8gBWZvPqTdb6LWF6FLrzwesZpi7j2rsioZ3wyUkqb5TDZaNNeWQrIEYXrEnWkRI_qTSOzx0dRTsGO6SpU1fPWuOOYMZG8Nh18nay0kLpxReuHCiCdxjXRVvk5k9rkcMbDBJcBovhiSioPKv_yJxcZVBATw3z3TTE95kGi4wxCEenxwhSpvouwa5b0hT7NS4Ay70QaxoKiLb3ZjhZaUUn4igCyZM0h6fllR5I6J_sAQxiMYD0v5ouIlb0u8YVMni93j3zlqMWdDUZ4WgTI7NNbo8ug9NQDHd92TPmSE1TytPTgya3tsFMzwyq0LZ0b-g-zSXWIES__jKQ7vAtIs9EwlPxqJXEDDniZ2AJ6biXRYgE2Kd6W_nmI7w31igwQTms3ecXe5ENI3ckEPUAq__llNnND7mxp5ZrdXzd5HHU9slXwDShYcW3yDeQLEwAVomTGSFpBrCX8W77n9hF3JClkWaeS4QcZ3xUtsSS81yLrp__ifFfQqx9_Be89WVyIOoF4oydr08EkZ8zwlAsbZLG7eLXY"
+ },
+ "callback":"\/post",
+ "version":1,
+ "secret":"1eaa6613699be6ebb2adcefa5379c61a3678aa0df89025470fac871431b70467"
+ },
+ "message":{
+ "message_id":"10b049ce384cbb2da9467319bc98169ab36290b8bbb403aa0c0accd9cb072e76@podunk.edu",
+ "message_top":"10b049ce384cbb2da9467319bc98169ab36290b8bbb403aa0c0accd9cb072e76@podunk.edu",
+ "message_parent":"10b049ce384cbb2da9467319bc98169ab36290b8bbb403aa0c0accd9cb072e76@podunk.edu",
+ "created":"2012-11-20 04:04:16",
+ "edited":"2012-11-20 04:04:16",
+ "title":"",
+ "body":"Hi Nickordo",
+ "app":"",
+ "verb":"post",
+ "object_type":"",
+ "target_type":"",
+ "permalink":"",
+ "location":"",
+ "longlat":"",
+ "owner":{
+ "name":"Indigo",
+ "address":"indigo@podunk.edu",
+ "url":"http:\/\/podunk.edu",
+ "photo":{
+ "mimetype":"image\/jpeg",
+ "src":"http:\/\/podunk.edu\/photo\/profile\/m\/5"
+ },
+ "guid":"kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA",
+ "guid_sig":"PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK-R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q"
+ },
+ "author":{
+ "name":"Indigo",
+ "address":"indigo@podunk.edu",
+ "url":"http:\/\/podunk.edu",
+ "photo":{
+ "mimetype":"image\/jpeg",
+ "src":"http:\/\/podunk.edu\/photo\/profile\/m\/5"
+ },
+ "guid":"kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA",
+ "guid_sig":"PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK-R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q"
+ }
+ }
+ }
+ }
+
+
+
+And that's the package (the original message). Example.com converts this into a form suitable for viewing by Nickordo and notifies Nickordo that there's a new message. Podunk.edu **might** discover that there are other packages waiting for example.com. If this happens it may also send any and all other waiting packages at this time. Each has the original tracking number attached.
diff --git a/doc/en/adminmanual/Primary-Directory.md b/doc/en/adminmanual/Primary-Directory.md
new file mode 100644
index 000000000..55a45a872
--- /dev/null
+++ b/doc/en/adminmanual/Primary-Directory.md
@@ -0,0 +1,46 @@
+### Primary Directory
+
+By default, hubzilla will use available Directories on the web, which show you channels available around the world.
+
+There are certain scenarios where you might want your own directory-server that you can connect multiple hubs to. This will limit the channels that appear in all of your hubs to only channels on hubs connected to your directory-server.
+
+#### Instuctions on how to set up one hub as the Primary Directory for a series of private hubs.
+
+***
+
+
+* On the hub that will be the Directory Server, open the .htconfig.php file and set:
+
+ `App::$config['system']['directory_mode'] = DIRECTORY_MODE_PRIMARY;`
+
+
+ By default it should already be set as **DIRECTORY_MODE_NORMAL**, so just edit that line to say **DIRECTORY_MODE_PRIMARY**
+
+* Next, for each hub (including the Directory Server), from a terminal, cd into the folder where it is installed and run this :
+
+ `util/config system directory_realm YOURREALMNAME`
+
+ (**YOURREALMNAME** can be whatever you want your realm-name to be)
+
+ then:
+
+ `util/config system realm_token THEPASSWORD`
+
+ (**THEPASSWORD** is whatever password you want for your realm)
+
+ **NOTE:** Use the same realm-name and password for each hub
+
+* Lastly, for each "client" hub, (from a terminal) run:
+
+ `util/config system directory_server https://theaddressofyourdirectoryserver.com`
+
+***
+Now when you view the directory of each hub, it should only show the channels that exist on the hubs in your realm. I have tested with two hubs so far, and it seems to be working fine.
+Channels created in each hub are reflected in the Primary Directory, and subsequently in the directory of all client hubs
+
+#### Issues
+
+***
+
+When I created the first hub,it was up and running for an hour or so before I changed it to PRIMARY_MODE, and after changing it, there were a few channels from across the matrix still present in the directory. I deleted them from the xchan table and that seems to have fixed the issue.
+
diff --git a/doc/en/adminmanual/Schema-development.md b/doc/en/adminmanual/Schema-development.md
new file mode 100644
index 000000000..979c5d130
--- /dev/null
+++ b/doc/en/adminmanual/Schema-development.md
@@ -0,0 +1,76 @@
+### Hubzilla development - a guide to the schema system
+
+A schema, in a nutshell, is a collection of settings for a bunch of variables to define
+certain elements of a theme. A schema is loaded as though it were part of config.php
+and has access to all the same information. Importantly, this means it is identity aware,
+and can be used to do some interesting things. One could, for example, restrict options
+by service class, or present different options to different members.
+
+By default, we filter only by whether or not expert mode is enabled. If expert mode is
+enabled, all options are presented to the member. If it is not, only scheme, background
+image, font face, and iconset are available as choices.
+
+A schema is loaded *after* the member's personal settings. Therefore, to allow a member
+to overwrite a particular aspect of a schema you would use the following syntax:
+
+ if (! $foo)
+ $foo = 'bar';
+
+However, there are circumstances - particularly with positional elements - where it
+may be desirable (or necessary) to override a member's settings. In this case, the syntax
+is even simpler:
+
+ $foo = 'bar';
+
+Members will not thank you for this, however, so only use it when it is required.
+
+If no personal options are set, and no schema is selected, we will first try to load a schema
+with the file name "default.php". This file should never be included with a theme. If it
+is, merge conflicts will occur as people update their code. Rather, this should be defined
+by administrators on a site by site basis.
+default.php and default.css MUST be symlinks to existing scheme files.
+
+You schema does not need to - and should not - contain all of these values. Only the values
+that differ from the defaults should be listed. This gives you some very powerful options
+with very few lines of code.
+
+Note the options available differ with each theme. The options available with the Redbasic
+theme are as follows:
+
+* nav_colour
+ The colour of the navigation bar. Options are red, black and silver. Alternatively,
+ one can set $nav_bg_1, $nav_bg_2, $nav_bg_3 and $nav_bg_4 to provide gradient and
+ hover effects.
+* banner_colour
+ The font colour of the banner element. Accepts an RGB or Hex value.
+* bgcolour
+ Set the body background colour. Accepts an RGB or Hex value.
+* background_image
+ Sets a background image. Accepts a URL or path.
+* item_colour
+ Set the background colour of items. Accepts an RGB or Hex value.
+* item_opacity
+ Set the opacity of items. Accepts a value from 0.01 to 1
+* toolicon_colour
+ Set the colour of tool icons. Accepts an RGB or Hex value.
+* toolicon_activecolour
+ Set the colour of active or hovered icon tools.
+* font_size
+ Set the size of fonts in items and posts. Accepts px or em.
+* body_font_size
+ Sets the size of fonts at the body level. Accepts px or em.
+* font_colour
+ Sets the font colour. Accepts an RGB or Hex value.
+* radius
+ Set the radius of corners. Accepts a numeral, and is always in px.
+* shadow
+ Set the size of shadows shown with inline images. Accepts a numerical
+ value. Note shadows are not applied to smileys.
+* converse_width
+ Set the maximum width of the content region in px.
+* nav_min_opacity
+* top_photo
+* reply_photo
+
+If a your_schema_name.css file is found, the content of this file will be attached to the end of style.css.
+This gives the schem developer the possiblity to override any style component.
diff --git a/doc/en/adminmanual/Widgets.md b/doc/en/adminmanual/Widgets.md
new file mode 100644
index 000000000..a3e853c64
--- /dev/null
+++ b/doc/en/adminmanual/Widgets.md
@@ -0,0 +1,157 @@
+### Core Widgets
+
+Some/many of these widgets have restrictions which may restrict the type of page where they may appear or may require login
+
+
+* clock - displays the current time
+ * args: military (1 or 0) - use 24 hour time as opposed to AM/PM
+
+* profile - displays a profile sidebar on pages which load profiles (pages with nickname in the URL)
+
+* tagcloud - display a tagcloud of webpage items
+
+ * args: count - number of items to return (default 24)
+
+* collections - privacy group selector for the current logged in channel
+
+ * args: mode - one of "conversation", "group", "abook" depending on module
+
+* suggestions - friend suggestions for the current logged on channel
+
+* follow - presents a text box for following another channel
+
+* notes - private notes area for the current logged in channel if private_notes feature is enabled
+
+* savedsearch - network/matrix search with save - must be logged in and savedsearch feature enabled
+
+* filer - select filed items from network/matrix stream - must be logged in
+
+* archive - date range selector for network and channel pages
+ * args: 'wall' - 1 or 0, limit to wall posts or network/matrix posts (default)
+
+* fullprofile - same as profile currently
+
+* categories - categories filter (channel page)
+
+* tagcloud_wall - tagcloud for channel page only
+ * args: 'limit' - number of tags to return (default 50)
+
+* catcloud_wall - tagcloud for channel page categories
+ * args: 'limit' - number of categories to return (default 50)
+
+* affinity - affinity slider for network page - must be logged in
+
+* settings_menu - sidebar menu for settings page, must be logged in
+
+* mailmenu - sidebar menu for private message page - must be logged in
+
+* design_tools - design tools menu for webpage building pages, must be logged in
+
+* findpeople - tools to find other channels
+
+* photo_albums - list photo albums of the current page owner with a selector menu
+
+* vcard - mini profile sidebar for the person of interest (page owner, whatever)
+
+* dirsafemode - directory selection tool - only on directory pages
+
+* dirsort - directory selection tool - only on directory pages
+
+* dirtags - directory tool - only on directory pages
+
+* menu_preview - preview a menu - only on menu edit pages
+
+* chatroom_list - list of chatrooms for the page owner
+
+* bookmarkedchats - list of bookmarked chatrooms collected on this site for the current observer
+
+* suggestedchats - "interesting" chatrooms chosen for the current observer
+
+* item - displays a single webpage item by mid or page title
+ * args:
+ * channel_id - channel that owns the content, defualt is the profile_uid
+ * mid - message_id of webpage to display (must be webpage, not a conversation item)
+ * title - URL page title of webpage (must provide one of either title or mid)
+
+* photo - display a single photo
+ * args:
+ * src - URL of photo, must be http or https
+ * zrl - use zid authenticated link
+ * style - CSS style string
+
+* cover_photo - display the cover photo for the selected channel
+ * args:
+ * channel_id - channel to use, default is the profile_uid
+ * style - CSS style string (default is dynamically resized to width of region)
+
+
+* photo_rand - display a random photo from one of your photo albums. Photo permissions are honoured
+ * args:
+ * album - album name (very strongly recommended if you have lots of photos)
+ * scale - typically 0 (original size), 1 (1024px), 2, (640px), or 3 (320px)
+ * style - CSS style string
+ * channel_id - if not your own
+
+* random_block - display a random block element from your webpage design tools collection. Permissions are honoured.
+ * args:
+ * contains - only return blocks which include the contains string in the block name
+ * channel_id - if not your own
+
+* tasklist - provide a task or to-do list for the currently logged-in channel.
+ * args:
+ * all - display completed tasks if all is non-zero.
+
+* forums - provide a list of connected public forums with unseen counts for the current logged-in channel.
+
+* activity - provide a list of authors of unread network content for the current logged-in channel.
+
+* album - provides a widget containing a complete photo album from albums belonging to the page owner; this may be too large to present in a sidebar region as is best implemented as a content region widget.
+ * args:
+ * album - album name
+ * title - optional title, album name is used if not present
+
+
+#### Creating New Widgets
+
+#### Class Widgets
+
+To create a class-based widget named 'slugfish' create a file with the following contents:
+
+````
+<?php
+
+namespace Zotlabs\Widget;
+
+
+class Slugfish {
+
+ function widget($args) {
+
+ ... widget code goes here.
+ ... The function returns a string which is the HTML content of the widget.
+ ... $args is a named array which is passed any [var] variables from the layout editor
+ ... For instance [widget=slugfish][var=count]3[/var][/widget] will populate $args with
+ ... [ 'count' => 3 ]
+
+ }
+
+````
+
+The resultant file may be placed in widget/Slugfish/Slugfish.php , or Zotlabs/SiteWidgets/Slugfish.php . It also may be linked from a git repository using util/add_widget_repo.
+
+
+
+Traditional function based widget:
+
+If you want a widget named 'slugfish', create widget/widget_slugfish.php containing
+
+
+ <?php
+
+ function widget_slugfish($args) {
+
+ .. widget code goes here. See above information for class-based widgets for details.
+
+ }
+
+
diff --git a/doc/en/adminmanual/Zot---A-High-Level-Overview.md b/doc/en/adminmanual/Zot---A-High-Level-Overview.md
new file mode 100644
index 000000000..5eae733da
--- /dev/null
+++ b/doc/en/adminmanual/Zot---A-High-Level-Overview.md
@@ -0,0 +1,107 @@
+### Nomad - A High Level Overview
+
+Here's a high level description of how Nomad works.
+
+In this example, "Indigo" is going to send a public message from his website at "podunk.edu". "Nickordo" is a recipient on another site ("example.com").
+
+
+Indigo first posts his message at podunk.edu. podunk.edu looks up who should receive the message and finds Nickordo. Nickordo usually posts from example.com so we add that destination to our list of recipients. We may also add other destinations for nickordo and anybody else that is following Indigo's posts.
+
+In this example we find that we only have one known recipient at one known location.
+
+We send a packet to example.com:
+
+ {
+ "type":"notify",
+ "sender":{
+ "guid":"kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA",
+ "guid_sig":"PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK-R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q",
+ "url":"http:\/\/podunk.edu",
+ "url_sig":"T8Bp7j5DHHhQDCFcAHXfuhUfGk2P3inPbImwaXXF1xJd3TGgluoXyyKDx6WDm07x0hqbupoAoZB1qBP3_WfvWiJVAK4N1FD77EOYttUEHZ7L43xy5PCpojJQmkppGbPJc2jnTIc_F1vvGvw5fv8gBWZvPqTdb6LWF6FLrzwesZpi7j2rsioZ3wyUkqb5TDZaNNeWQrIEYXrEnWkRI_qTSOzx0dRTsGO6SpU1fPWuOOYMZG8Nh18nay0kLpxReuHCiCdxjXRVvk5k9rkcMbDBJcBovhiSioPKv_yJxcZVBATw3z3TTE95kGi4wxCEenxwhSpvouwa5b0hT7NS4Ay70QaxoKiLb3ZjhZaUUn4igCyZM0h6fllR5I6J_sAQxiMYD0v5ouIlb0u8YVMni93j3zlqMWdDUZ4WgTI7NNbo8ug9NQDHd92TPmSE1TytPTgya3tsFMzwyq0LZ0b-g-zSXWIES__jKQ7vAtIs9EwlPxqJXEDDniZ2AJ6biXRYgE2Kd6W_nmI7w31igwQTms3ecXe5ENI3ckEPUAq__llNnND7mxp5ZrdXzd5HHU9slXwDShYcW3yDeQLEwAVomTGSFpBrCX8W77n9hF3JClkWaeS4QcZ3xUtsSS81yLrp__ifFfQqx9_Be89WVyIOoF4oydr08EkZ8zwlAsbZLG7eLXY"
+ },
+ "callback":"\/post",
+ "version":1,
+ "secret":"1eaa6613699be6ebb2adcefa5379c61a3678aa0df89025470fac871431b70467"
+ }
+
+This packet says the following:
+
+I'm Indigo and here is proof. I'm posting from podunk.edu and here is proof. I've got a package for you. The tracking number is "1eaa6613....".
+
+Example.com accepts this packet and says "whoa, hold on - I don't know you. I want to prove who you are." So Example.com connects to podunk.edu through a "well-known URL" that we use for this purpose and looks up the "guid" mentioned above. It should return a bunch of information, one item of which is a public key. Example.com uses this key to verify the signatures in the message to verify that indeed there is a person named Indigo at podunk.edu. We only need to do this once. (Note that Indigo can post from any location. All we have to do is prove that it's Indigo and that Indigo can prove that he's posting from another site.)
+
+Then example.com disconnects and flags that there's a message waiting at podunk.edu. Either immediately, or whenever the urge hits (depending on how important Indigo is to anybody on this site), example.com "calls" podunk.edu. It says something like this:
+
+ {
+ "type":"pickup",
+ "url":"http:\/\/example.com",
+ "callback_sig":"teE1_fLIqfyeCuZY4iS7sNU8jUlUuqYOYBiHLarkC99I9K-uSr8DAwVW8ZPZRK-uYdxRMuKFb6cumF_Gt9XjecCPBM8HkoXHOi_VselzJkxPwor4ZPtWYWWaFtRfcAm794LrWjdz62zdESTQd2JJIZWbrli1sUhK801BF3n0Ye6-X1MWhy9EUTVlNimOeRipcuD_srMhUcAXOEbLlrugZ8ovy2YBe6YOXkS8jj0RSFjsOduXAoVhQmNpcobSYsDvaQS3e3MvE6-oXE602zGQhuNLr7DIMt9PCdAeQo-ZM-DHlZGCkGk4O2oQFCXFzGPqLUMWDACGJfTfIWGoh_EJqT_SD5b_Yi_Wk9S1lj7vb-lmxe5JuIf7ezWzHoBT8vswnZxPYlidH2i9wapdzij9il_qqcCWWHIp7q_XkY_Zj52Z4r4gdmiqM-8y1c_1SDX7hrJFRwqL_PKFbEvyi5nMWTEzqp55Tay5Woiv19STK_H_8ufFfD9AOkYnk6rIOMsk9dn3a5tAFpDRyRndXkBWAXwiJjiND2zjue7BFu7Ty40THXcfYRh1a5XrAXcaGeYuagg-8J9tAufu9_LY3qGazFg8kRBVMOn4M8DRKSIhKj7z4MnbYL0s09gREojy4jqWO3VkaOjP2jUGzoPuUDLasudE1ehWFq0K_MTQNavgmp8",
+ "callback":"http:\/\/example.com\/post",
+ "secret":"1eaa6613699be6ebb2adcefa5379c61a3678aa0df89025470fac871431b70467",
+ "secret_sig":"O7nB4_UJHBXi28Suwl9LBZF9hI_9KGVTgehnUlWF1oYMNRnBbVHB9lzUfAoalvp3STbU3xJbtD_S58tv6MfV7J5j2V_S1W5ex3dulmDGB8Pt_7Fe5mbEPmjQFcfv3Eg5dUjYIuDl0TDScfrHyImj7RZIWHbwd7wWVoMzzDa_o33klpYmKZCBvObCh55bRrlFkXZs_dRuOiPwkfX0C6_XES4OyOIYl45V30rdhmf-STrf4L9dKYy_axQ12RIwRcKychvVLwlUJn3bn9lgNXRRU_HTne-09OPcJbUOdcD3DkFoKOxMULBNKPHzsCau0ICYug7S0EP6LpCom_mW78s08LyVA1vYeFZjevBCiGecj57yIAQDYi6_rpWJfihYaWHRN0oqtScUR4Bdf0bQbEHxMs4zAtrOAxfyJCbi6U1pfnGgzXzB9ulOYGnVGNTF7Ey4K7FOZIBtk0ILY2JfvBUaVvVs8ttagOOHmhWhnbCvrnOFlkNdlce7zoJCSUJENUOCYmTRfwB_Jno5fAzRnrsYU3_Z-l1mzniU_OmUPz8mPEh7PwhkqAiVlyaM-q15gn7l2lAIDk9kp2X_iCme7v4V0ADN_DbpaI_0-6mPw5HLbKrCsA-sxlSMB4DO4lDCHYkauj0l25sbfroRWB_hax1O4Q0oWyOlVJLUqEC5nuUJCCE"
+ }
+
+
+What this message says is: This is example.com, I have proof, and I'm here to pick up a package. Here's the tracking number, and here's proof that this is the tracking number you presumably sent to example.com.
+
+Good enough. Podunk.edu checks out the story and indeed, it is example.com, and yes, there's a package waiting with that tracking number. Here's the package...
+
+ {
+ "success":1,
+ "pickup":{
+ "notify":{
+ "type":"notify",
+ "sender":{
+ "guid":"kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA",
+ "guid_sig":"PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK-R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q",
+ "url":"http:\/\/z.podunk.edu",
+ "url_sig":"T8Bp7j5DHHhQDCFcAHXfuhUfGk2P3inPbImwaXXF1xJd3TGgluoXyyKDx6WDm07x0hqbupoAoZB1qBP3_WfvWiJVAK4N1FD77EOYttUEHZ7L43xy5PCpojJQmkppGbPJc2jnTIc_F1vvGvw5fv8gBWZvPqTdb6LWF6FLrzwesZpi7j2rsioZ3wyUkqb5TDZaNNeWQrIEYXrEnWkRI_qTSOzx0dRTsGO6SpU1fPWuOOYMZG8Nh18nay0kLpxReuHCiCdxjXRVvk5k9rkcMbDBJcBovhiSioPKv_yJxcZVBATw3z3TTE95kGi4wxCEenxwhSpvouwa5b0hT7NS4Ay70QaxoKiLb3ZjhZaUUn4igCyZM0h6fllR5I6J_sAQxiMYD0v5ouIlb0u8YVMni93j3zlqMWdDUZ4WgTI7NNbo8ug9NQDHd92TPmSE1TytPTgya3tsFMzwyq0LZ0b-g-zSXWIES__jKQ7vAtIs9EwlPxqJXEDDniZ2AJ6biXRYgE2Kd6W_nmI7w31igwQTms3ecXe5ENI3ckEPUAq__llNnND7mxp5ZrdXzd5HHU9slXwDShYcW3yDeQLEwAVomTGSFpBrCX8W77n9hF3JClkWaeS4QcZ3xUtsSS81yLrp__ifFfQqx9_Be89WVyIOoF4oydr08EkZ8zwlAsbZLG7eLXY"
+ },
+ "callback":"\/post",
+ "version":1,
+ "secret":"1eaa6613699be6ebb2adcefa5379c61a3678aa0df89025470fac871431b70467"
+ },
+ "message":{
+ "message_id":"10b049ce384cbb2da9467319bc98169ab36290b8bbb403aa0c0accd9cb072e76@podunk.edu",
+ "message_top":"10b049ce384cbb2da9467319bc98169ab36290b8bbb403aa0c0accd9cb072e76@podunk.edu",
+ "message_parent":"10b049ce384cbb2da9467319bc98169ab36290b8bbb403aa0c0accd9cb072e76@podunk.edu",
+ "created":"2012-11-20 04:04:16",
+ "edited":"2012-11-20 04:04:16",
+ "title":"",
+ "body":"Hi Nickordo",
+ "app":"",
+ "verb":"post",
+ "object_type":"",
+ "target_type":"",
+ "permalink":"",
+ "location":"",
+ "longlat":"",
+ "owner":{
+ "name":"Indigo",
+ "address":"indigo@podunk.edu",
+ "url":"http:\/\/podunk.edu",
+ "photo":{
+ "mimetype":"image\/jpeg",
+ "src":"http:\/\/podunk.edu\/photo\/profile\/m\/5"
+ },
+ "guid":"kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA",
+ "guid_sig":"PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK-R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q"
+ },
+ "author":{
+ "name":"Indigo",
+ "address":"indigo@podunk.edu",
+ "url":"http:\/\/podunk.edu",
+ "photo":{
+ "mimetype":"image\/jpeg",
+ "src":"http:\/\/podunk.edu\/photo\/profile\/m\/5"
+ },
+ "guid":"kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA",
+ "guid_sig":"PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK-R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q"
+ }
+ }
+ }
+ }
+
+
+
+And that's the package (the original message). Example.com converts this into a form suitable for viewing by Nickordo and notifies Nickordo that there's a new message. Podunk.edu **might** discover that there are other packages waiting for example.com. If this happens it may also send any and all other waiting packages at this time. Each has the original tracking number attached.
diff --git a/doc/en/adminmanual/administration.md b/doc/en/adminmanual/administration.md
new file mode 100644
index 000000000..caf7c42ff
--- /dev/null
+++ b/doc/en/adminmanual/administration.md
@@ -0,0 +1,11 @@
+### Administration
+
+#### Website administration
+
+The website is usually managed via the administration page, which is located under /admin on your website. To access this page, you must have administration rights for the server. Administration rights are granted to the first account that registers on your website, **provided that** the email address of this account exactly matches the email address that you specified as the administrator's email address during setup.
+There are several ways in which this can fail and the system can be left without an administrator account, for example if the first account created has a different email address to the administrator email address specified during setup.
+
+![adm01](/help/en/adminmanual/pic/adm01.png)
+
+For security reasons, there is no web page or interface on the system that gives you administrator rights. If you need to correct a situation where a system does not have an administrator account, this **must** be done by editing the account table in the database. There is no other option. To do this, you must find the entry in the account table that belongs to the desired administrator and set ‘account_roles’ for this entry to 4096. You can then call up the administrator page via the profile menu of your system or directly via /admin.
+A hub can have several administrators and the number of administrators is not limited. Repeat the above procedure for each account that you want to give administration rights.
diff --git a/doc/en/adminmanual/advanced_configurations.md b/doc/en/adminmanual/advanced_configurations.md
new file mode 100644
index 000000000..a7820d3e8
--- /dev/null
+++ b/doc/en/adminmanual/advanced_configurations.md
@@ -0,0 +1,182 @@
+### Advanced configurations for administrators
+
+*This document assumes that you are an administrator.*
+Hubzilla contains many configuration options that are hidden in the main administration area. Generally, these are options that are considered too niche, advanced, or confusing.
+These settings can be changed via the shell from the top level web directory using the following syntax
+
+`util/config cat key value`
+
+for a website configuration, or
+
+`util/pconfig channel_id cat key value`
+
+for a member configuration.
+
+For a site configuration, another option is to insert a line in .htconfig.php with the syntax:
+
+App::$config[‘cat’][‘key’] = ‘value’;`
+
+#### Member configuration (pconfig)
+
+- system.always_my_theme
+ Always use your own theme when watching channels in the same hub. This leads to some pretty imaginative problems when viewing channels with themed comanches.
+- system.blocked
+ An array of Xchans that are blocked by this channel. Technically this is a hidden configuration and belongs here, but addons (notably Superblock) have made this available in the UI.
+- system.default_cipher
+ Sets the default cipher used for E2EE elements.
+- system.display_friend_count
+ Defines the number of connections to be displayed in the connection profile widget.
+- system.do_not_track
+ Like the browser header. This will break many identity-based functions. You should really only set permissions that make sense.
+- system.forcepublicuploads
+ Forces uploaded photos to be public when uploaded as wall elements. It makes much more sense to set the permissions correctly in the first place. Do this instead.
+- system.network_page_default
+ Sets the default parameters for displaying the network page. This should contain the same query string as the manual filtering.
+- system.paranoia
+ Defines the security level of the IP check. If the IP address of a logged on session changes, this level is applied to determine if the account should be logged off as a security breach. The options are: 0 - no IP check 1 - check 3 octets 2 - check 2 octets 3 - check for all differences
+- system.prevent_tag_hijacking
+ Prevents external networks from hijacking hashtags in your posts and redirecting them to their own resources.
+- system.startpage
+ Another one of those technically hidden configurations provided by addons. Defines the default page that is displayed when you log in. This is made available to the user interface by the startpage add-on.
+- system.taganyone
+ Requires that the configuration of the same name is activated. Allows @mention tagging of everyone, regardless of whether you are connected or not. This does not scale.
+- system.anonymous_comments
+ By default or if set to 1, custom permissions can be set to allow anonymous (moderated) comments like WordPress, moderated by the channel owner. If set to 0, no member of your site can select or enable this.
+- system.user_scalable
+ Determines whether the app is scalable to touchscreens. Set to on by default, set to zero to deactivate - real zero, not just false.
+
+#### Configuration of the website
+
+- randprofile.check
+ When requesting a random profile, first check whether it actually exists
+- randprofile.retry
+ Number of attempts to obtain a random profile
+- system.admin_email
+ Specifies the e-mail address of the administrator for this site. This is defined during installation.
+- system.authlog
+ Log file used for logging authentication errors. Used to connect to server-side software such as fail2ban. Auth errors are also logged in the main logs.
+- system.auto_channel_create
+ Adds the necessary form elements to create the first channel on the account registration page and creates it (possibly after email validation or administrator approval). This excludes the ability to import a channel from another site as the first channel created on this site for a new account. Use with system.default_permissions_role to streamline registration.
+- system.auto_follow
+ The first channel of an account automatically follows the channels listed here - comma separated list of webbies (member@hub addresses).
+- system.blacklisted_sites
+ An array of specific hubs that are to be completely blocked by this hub.
+- system.block_public_search
+ Similar to block_public, with the difference that only public access to the search functions is blocked. Useful for pages that want to be public but are overrun by search engines.
+- system.cron_hour
+ Specify an hour in which cron_daily should be executed. By default, without configuration, this is executed at midnight UTC.
+- system.default_permissions_role
+ If this value is set to a valid name for a permission role, this role will be used for the first channel created by a new account and will not prompt for the ‘channel type’ on the channel creation form. Examples of valid names are: ‘social’, ‘social_restricted’, ‘social_private’, ‘forum’, ‘forum_restricted’ and ‘forum_private’. Read more about authorisation roles [here](https://hub.hubzilla.hu/help/roles).
+- system.default_profile_photo
+ Defines the profile photo with which new channels start. This should contain the name of a directory located under images/default_profile_photos/ or it should not be set. If it is not set, ‘rainbow_man’ is assumed.
+- system.directorytags
+ Specifies the number of keyword tags to display on the directory page. The default is 50 if it is not set to a positive integer.
+- system.disable_directory_keywords
+ If ‘1’, no directory keywords are displayed. If the hub is a directory server, prevent keywords from being returned to all directory clients. Please do not set this for directory servers in the RED_GLOBAL area.
+- system.disable_discover_tab
+ Allows you to completely disable the ability to discover public content from external sites.
+- system.disable_dreport
+ If ‘1’, no delivery reports are saved or linked.
+- system.dlogfile
+ Log file used for logging development errors. Exactly the same as logger otherwise. This is not magic and requires your own logging instructions. Developer tool.
+- system.email_notify_icon_url
+ URL of the image (32x32) to be displayed in email notifications (HTML bodies).
+- system.expire_delivery_reports
+ Expiry date in days for delivery reports - default value 10
+- system.expire_limit
+ Do not expire more than this number of reports per channel per expiry run to avoid exhausting the memory. Default value 5000.
+- system.photo_storage_type
+ If ‘1’, the file system is used instead of the SQL database to store the thumbnails. Default setting is ‘0’. Introduced in 4.2
+- system.hidden_version_siteinfo
+ If ‘true’, the software version is not displayed on the siteinfo pages (system.hide_version hides the version on these pages as well, this setting *only* hides the version on the siteinfo pages).
+- system.hide_help
+ Do not show the link to the help documentation in the navigation bar
+- system.hide_in_statistics
+ Instructs the red statistics servers to completely hide this hub in hub lists.
+- system.hide_version
+ If true, the software version is not displayed on websites and tools. (*) Must be set in .htconfig.php.
+- system.ignore_imagick
+ Ignores imagick and uses GD, even if imagick is installed on the server. Prevents some problems with PNG files in older versions of imagick.
+- system.max_daily_registrations
+ Defines the maximum number of new registrations allowed in a day. Useful to prevent oversubscription when publicising the project.
+- system.max_import_size
+ If configured, the maximum length of an imported text message. This is usually left at 200 KBytes or more to accommodate private Friendica photos that are embedded.
+- system.max_tagged_forums
+ Spam protection. Limits the number of tagged forums recognised in each post. Default is 2, only the first ‘n’ tags are delivered as forums, the others cause no delivery.
+- system.minimum_feedcheck_minutes
+ The minimum interval between checking RSS feeds. If this interval is less than the cron interval, the feeds are checked with every cron job. The default value is 60 if it has not been set. The site setting can also be overridden for each individual channel by a service class setting aptly named ‘minimum_feedcheck_minutes’.
+- system.no_age_restriction
+ Do not restrict registration to persons over the age of 13. In many countries it is required by law that age must be specified and that all personal data of minors must be blocked.
+- system.object_cache_days
+ Defines how long cached embedded content can be used without being retrieved again. The default setting is 30 days.
+- system.openssl_conf_file
+ Specify a file that contains the OpenSSL configuration. Required in some Windows installations to find the openssl configuration file on the system. Read the code first. If you cannot read the code, do not play with it.
+- system.openssl_encrypt
+ Use encryption engine from openssl, default is false (uses mcrypt for AES encryption)
+- system.optimize_items
+ Executes optimise_table during some tasks to keep your database clean and defragmented. This comes at the expense of performance while the operations are running, but also makes things run a bit faster when they are not. There are also CLI utilities to perform this operation that you may prefer, especially if you have a large site.
+- system.override_poll_lockfile
+ Ignores the lock file in the poller process to allow more than one process to run at a time.
+- system.paranoia
+ Like pconfig, but on a site-wide basis. Can be overridden by member settings.
+- system.pin_types
+ Array of allowed element types for pinning. The default values depend on the module, but can be changed here.
+- system.photo_cache_time
+ How long the photos should be cached, in seconds. The default value is 86400 (1 day). A longer time increases performance, but also means that it takes longer for changed authorisations to apply.
+- system.platform_name
+ What should be displayed as the platform name on websites and in statistics. (*) Must be set in .htconfig.php.
+- system.rating_enabled
+ Distributed reputation reports and data collection. This function is currently being revised.
+- system.poke_basic
+ Reduce the number of poke verbs to exactly 1 (‘poke’). Deactivate other verbs.
+- system.proc_run_use_exec
+ If 1, the system call exec in proc_run is used to execute background tasks. By default we use proc_open and proc_close. On some (currently rare) systems this does not work well.
+- system.projecthome
+ Displays the project page on your homepage for logged out viewers.
+- system.projecthome
+ Defines the project home page as the start page of the hub. (Obsolete)
+- system.pubstream_order
+ Defines the pubstream order. Possible values ‘commented’ (default), ‘created’ and ‘edited’.
+- system.register_link
+ Path to which the ‘register’ link in the registration form should point. For closed sites, this will point to ‘pubsites’. For open sites, it will normally redirect to ‘register’, but you can change this to a custom site page that offers subscriptions or similar.
+- system.reserved_channels
+ Do not allow members to register channels with this comma-separated list of names (no spaces).
+- system.sellpage
+ A URL to display in the list of public sites to sell your hub - show classes of service, etc.
+- system.startpage
+ Defines the default page that is displayed for all channels on this website after a login. Can be overridden by user settings.
+- system.sys_expire_days
+ How many days should discovered public content from other websites be kept?
+- system.taganyone
+ Allows @mention tagging of everyone, whether you are connected or not.
+- system.tempdir
+ Location where temporary files are stored (currently unused), default is defined in PHP configuration.
+- system.tos_url
+ Sets an alternative link for the ToS storage location.
+- system.transport_security_header
+ if non-zero and SSL is used, a strict-transport-security header is inserted on the web pages
+- system.uploaddir
+ Storage location for uploading files (default is system.tempdir, currently only used by the js_upload plugin)
+- system.workflow_channel_next
+ The page to which new members should be redirected immediately after creating a channel.
+- system.workflow_register_next
+ The page to which members are redirected immediately after creating an account (only if auto_channel_create or UNO is activated).
+
+#### Directory configuration
+
+##### Default values for the directory search
+
+- directory.globaldir
+ 0 or 1. default value 0. If you visit the directory on a site, you will only see the members of this site by default. You have to take an extra step to see the people in the rest of the network; and by doing this, there is a clear delineation that these people are *not* members of this site, but of a larger network.
+- directory.pubforums
+ 0 or 1. public forums *should* be 0 by default.
+- directory.safemode
+ 0 or 1.
+
+##### Configuration of the directory server
+
+- system.directory_mode
+- system.directory_primary
+- system.directory_realm
+- system.directory_server
+- system.realm_token \ No newline at end of file
diff --git a/doc/en/adminmanual/automated_installation.md b/doc/en/adminmanual/automated_installation.md
new file mode 100644
index 000000000..089866b13
--- /dev/null
+++ b/doc/en/adminmanual/automated_installation.md
@@ -0,0 +1,26 @@
+### Automated installation via the shell script .homeinstall
+
+There is a shell script in (`.homeinstall/hubzilla-setup.sh`) that will install Hubzilla and its dependencies on a fresh installation of Debian stable. It should work on similar Linux systems, but your results may vary.
+
+#### Requirements
+
+The installation script was originally developed for a small hardware server behind your home router. However, it has been tested on several systems running Debian 9:
+
+- Home-PC (Debian-9.2-amd64) and Rapberry-Pi 3 (Rasbian = Debian 9.3)
+ - Internet connection and router at home
+ - Mini-PC / Raspi connected to the router
+ - USB drive for backups
+ - Fresh installation of Debian on your mini PC
+ - Router with open ports 80 and 443 for your Debian
+
+#### Overview of the installation steps
+
+1. `apt-get install git`
+2. `mkdir -p /var/www/html`
+3. `cd /var/www/html`
+4. `git clone https://framagit.org/hubzilla/core.git .`
+5. `nano .homeeinstall/hubzilla-config.txt`
+6. `cd .homeeinstall/`
+7. `./hubzilla-setup.sh`
+8. `Reload apache2 service`
+9. Open your domain with a browser and go through the initial configuration of Hubzilla.
diff --git a/doc/en/adminmanual/before_you_start.md b/doc/en/adminmanual/before_you_start.md
new file mode 100644
index 000000000..c849c267b
--- /dev/null
+++ b/doc/en/adminmanual/before_you_start.md
@@ -0,0 +1,15 @@
+### Before you start
+
+Select a domain name or a subdomain name for your server.
+
+The software can only be installed in the root directory of a domain or subdomain and cannot be installed via alternative TCP ports. These restrictions may be relaxed in the future, but they are inconvenient, so we STRONGLY recommend that you continue to adhere to them.
+
+Decide if you want to use SSL and obtain an SSL certificate before installing the software. You SHOULD use SSL. If you use SSL, you MUST use a ‘browser valid’ certificate. You CANNOT use self-signed certificates!
+Please test your certificate before installation. You can find a web tool for testing your certificate at [‘http://www.digicert.com/help/'](http://www.digicert.com/help/). When you visit your website for the first time, please use the SSL URL (‘https://’) if SSL is available. This will avoid problems later on. The installation routine does not allow you to use a non-browser valid certificate.
+
+This restriction is made because public posts by you may contain links to images in your own hub. Other members viewing your stream on other hubs will receive warnings if your certificate is not trusted by their web browser. This will confuse many people as it is a decentralised network and they will receive the warning about your hub while viewing their own hub and may think their own hub has a problem. These warnings are very technical and scary for some people, many of whom don't know how to proceed other than to follow the browser's advice. This is disruptive to the community. That said, we recognise the problems associated with the current certificate infrastructure and agree that there are many issues, but that doesn't change the requirement.
+
+Free ‘browser valid’ certificates are available from providers such as ZeroSSL, LetsEncrypt and a few others.
+If you are NOT using SSL, there may be a delay of up to a minute on the first install script - while we check the SSL port to see if anything is responding there. When communicating with new sites, Hubzilla will always try to connect via the SSL port first before falling back to a less secure connection. If you are not using SSL, your web server does not HAVE to listen on port 443 at all.
+
+If you are using LetsEncrypt to provide certificates and create a file under .well-known/acme-challenge so that LetsEncrypt can verify your domain ownership, please remove the .well-known directory or rename it once the certificate has been generated. The software provides its own handler for ‘.well-known’ services during installation, and an existing directory in this location may prevent some of these services from working correctly. This should not be a problem with Apache, but can be a problem with nginx or other web server platforms. \ No newline at end of file
diff --git a/doc/en/adminmanual/channel_dirctory.md b/doc/en/adminmanual/channel_dirctory.md
new file mode 100644
index 000000000..8e7a33582
--- /dev/null
+++ b/doc/en/adminmanual/channel_dirctory.md
@@ -0,0 +1,15 @@
+### Channel directory
+
+#### Keywords
+
+There is a ‘keyword cloud’ with keywords that can be shown on the channel directory page. If you want to hide these keywords, which are obtained from the directory server, you can use the *configuration tool*:
+
+```
+util/config system disable_directory_keywords 1
+```
+
+If your hub is in standalone mode because you do not want to connect to the global network, you can instead ensure that the *directory_server* system option is empty:
+
+```
+util/config system directory_server ‘’
+``` \ No newline at end of file
diff --git a/doc/en/adminmanual/database.md b/doc/en/adminmanual/database.md
new file mode 100644
index 000000000..289bf97cd
--- /dev/null
+++ b/doc/en/adminmanual/database.md
@@ -0,0 +1,72 @@
+### Database
+
+#### Database updates
+
+On the https://hub.hubzilla.hu/admin/dbsync page, the administrator can check whether an update has failed and retry it if necessary.
+If an update has failed, but for some reason is not registered as failed, the administrator can try to run the update again. For example, for DB update #1999, by visiting the web page:
+[https://hubzilla.com.bradmin/dbsync/1999](https://hubzilla.com.bradmin/dbsync/1999?f=&zid=pepecyb@hub.hubzilla.hu)
+
+#### Database tables
+
+| Table | Description |
+| ------------------------------------------------------------ | ------------------------------------------------------------ |
+| [abconfig](https://hub.hubzilla.hu/help/database/db_abconfig) | any storage space for connections from local channels |
+| [abook](https://hub.hubzilla.hu/help/database/db_abook) | Connections of the local channels |
+| [account](https://hub.hubzilla.hu/help/database/db_account) | Service provider account |
+| [addon](https://hub.hubzilla.hu/help/database/db_addon) | registered plugins |
+| [app](https://hub.hubzilla.hu/help/database/db_app) | personal app data |
+| [attach](https://hub.hubzilla.hu/help/database/db_attach) | File attachments |
+| [auth_codes](https://hub.hubzilla.hu/help/database/db_auth_codes) | OAuth usage |
+| [cache](https://hub.hubzilla.hu/help/database/db_cache) | OEmbed cache |
+| [cal](https://hub.hubzilla.hu/help/database/db_cal) | CalDAV container for events |
+| [channel](https://hub.hubzilla.hu/help/database/db_channel) | local channels |
+| [chat](https://hub.hubzilla.hu/help/database/db_chat) | Chat room content |
+| [chatpresence](https://hub.hubzilla.hu/help/database/db_chatpresence) | Channel presence information for the chat |
+| [chatroom](https://hub.hubzilla.hu/help/database/db_chatroom) | Data for the actual chat room |
+| [clients](https://hub.hubzilla.hu/help/database/db_clients) | OAuth usage |
+| [config](https://hub.hubzilla.hu/help/database/db_config) | Main configuration storage |
+| [conv](https://hub.hubzilla.hu/help/database/db_conv) | Meta conversation structure for private messages in Diaspora |
+| [event](https://hub.hubzilla.hu/help/database/db_event) | Events |
+| [pgrp_member](https://hub.hubzilla.hu/help/database/db_pgrp_member) | Data protection groups (collections), group information |
+| [pgrp](https://hub.hubzilla.hu/help/database/db_pgrp) | Data protection groups (collections), member information |
+| [hook](https://hub.hubzilla.hu/help/database/db_hook) | Plugin hook register |
+| [hubloc](https://hub.hubzilla.hu/help/database/db_hubloc) | xchan location storage, links a hub location with a xchan |
+| [iconfig](https://hub.hubzilla.hu/help/database/db_iconfig) | Expandable, arbitrary memory for elements |
+| [issue](https://hub.hubzilla.hu/help/database/db_issue) | Upcoming error/problem database |
+| [item](https://hub.hubzilla.hu/help/database/db_item) | all articles and websites |
+| [item_id](https://hub.hubzilla.hu/help/database/db_item_id) | (deprecated by iconfig) other identifiers in other services for contributions |
+| [likes](https://hub.hubzilla.hu/help/database/db_likes) | ‘Like‘ things |
+| [mail](https://hub.hubzilla.hu/help/database/db_mail) | private messages |
+| [menu](https://hub.hubzilla.hu/help/database/db_menu) | Website menu data |
+| [menu_item](https://hub.hubzilla.hu/help/database/db_menu_item) | Entries for menus on websites |
+| [notify](https://hub.hubzilla.hu/help/database/db_notify) | Notifications |
+| [obj](https://hub.hubzilla.hu/help/database/db_obj) | Object data for things (x has y) |
+| [outq](https://hub.hubzilla.hu/help/database/db_outq) | Outgoing queue |
+| [pconfig](https://hub.hubzilla.hu/help/database/db_pconfig) | Personal (per channel) configuration memory |
+| [photo](https://hub.hubzilla.hu/help/database/db_photo) | Photo store |
+| [poll](https://hub.hubzilla.hu/help/database/db_poll) | Data for polls |
+| [poll_elm](https://hub.hubzilla.hu/help/database/db_poll_elm) | Data for poll elements |
+| [profdef](https://hub.hubzilla.hu/help/database/db_profdef) | Definitions for user-defined profile fields |
+| [profext](https://hub.hubzilla.hu/help/database/db_profext) | User-defined profile field data |
+| [profile](https://hub.hubzilla.hu/help/database/db_profile) | Channel profiles |
+| [profile_check](https://hub.hubzilla.hu/help/database/db_profile_check) | DFRN remote authorisation, may be outdated |
+| [register](https://hub.hubzilla.hu/help/database/db_register) | Registrations that require an administrative authorisation |
+| [session](https://hub.hubzilla.hu/help/database/db_session) | Storage of web sessions |
+| [shares](https://hub.hubzilla.hu/help/database/db_shares) | Information on common elements |
+| [sign](https://hub.hubzilla.hu/help/database/db_sign) | Diaspora signatures. Is gradually being phased out. |
+| [site](https://hub.hubzilla.hu/help/database/db_site) | Location table for finding directory peers |
+| [source](https://hub.hubzilla.hu/help/database/db_source) | Data from channel sources |
+| [sys_perms](https://hub.hubzilla.hu/help/database/db_sys_perms) | Extensible authorisations for OAuth |
+| [term](https://hub.hubzilla.hu/help/database/db_term) | Article taxonomy table (categories, tags, etc.) |
+| [tokens](https://hub.hubzilla.hu/help/database/db_tokens) | OAuth usage |
+| [updates](https://hub.hubzilla.hu/help/database/db_updates) | Directory synchronisation updates |
+| [verify](https://hub.hubzilla.hu/help/database/db_verify) | General verification structure |
+| [vote](https://hub.hubzilla.hu/help/database/db_vote) | Voting data for polls |
+| [xchan](https://hub.hubzilla.hu/help/database/db_xchan) | List of known channels in the universe |
+| [xchat](https://hub.hubzilla.hu/help/database/db_xchat) | Chat rooms with bookmarks |
+| [xconfig](https://hub.hubzilla.hu/help/database/db_xconfig) | like pconfig, but for channels without a local account |
+| [xign](https://hub.hubzilla.hu/help/database/db_xign) | Channels ignored by friend suggestions |
+| [xlink](https://hub.hubzilla.hu/help/database/db_xlink) | ‘Friends of friends’ links derived from poco, also storage of ratings |
+| [xperm](https://hub.hubzilla.hu/help/database/db_xperm) | OAuth/OpenID-Connect extendable authorisations Authorisation memory |
+| [xprof](https://hub.hubzilla.hu/help/database/db_xprof) | if this node is a directory server, it contains basic public profile information about everyone on the network |
+| [xtag](https://hub.hubzilla.hu/help/database/db_xtag) | if this hub is a directory server, it contains tags or interests from everyone on the network | \ No newline at end of file
diff --git a/doc/en/adminmanual/directories.md b/doc/en/adminmanual/directories.md
new file mode 100644
index 000000000..84b35706d
--- /dev/null
+++ b/doc/en/adminmanual/directories.md
@@ -0,0 +1,89 @@
+### Directory Configuration
+
+Directories in Hubzilla serve the purpose of searching and locating members anywhere in the network. They are also used to store and query "ratings" of members and sites. The directory services are distributed and mirrored so that a failure of one will not take down or disrupt the entire network.
+
+#### Standard Configuration
+
+New sites operating as directory clients will automatically select from a hard-coded list of directory servers during their first directory access. You may examine or over-ride this decision using
+
+```
+util/config system directory_server
+```
+
+To set a different server,
+
+```
+util/config system directory_server https://newdirectory.something
+```
+
+#### Standalone configuration
+
+Some sites may wish to operate in 'standalone' mode and not connect to any external directory services. This is useful for isolated sites ("off the gird") and test sites, but can also be useful for small organisations who do not wish to connect with other sites in the network.
+
+To configure this, please look in your .htconfig.php file for the following text and set the configuration accordingly.
+
+```
+// Configure how we communicate with directory servers.
+// DIRECTORY_MODE_NORMAL = directory client, we will find a directory
+// DIRECTORY_MODE_SECONDARY = caching directory or mirror
+// DIRECTORY_MODE_PRIMARY = main directory server
+// DIRECTORY_MODE_STANDALONE = "off the grid" or private directory services
+
+App::$config['system']['directory_mode'] = DIRECTORY_MODE_STANDALONE;
+```
+
+#### Secondary server configuration
+
+You may also configure your site as a secondary server. This operates as a mirror of the primary directory and allows disitribution of the load amongst available servers. There is very little functional difference between a primary and secondary sever, however there may only be *one* primary directory server per realm (realms are discussed later in this document).
+
+Before choosing to be a directory server, please be advised that you should be an active member of the network and have the resources and time available to manage these services. They don't typically require management, but the requirement is more for stability as losing a directory server can cause issues to directory clients which are reliant on it.
+
+#### Changing the directory server
+
+If a directory server indicates that it is no longer a directory server, this should be detected by the software and the configuration for that server will be removed (blanked). If it goes offline permanently without warning, you will only know if site members report that directory services are unavailable. Currently this can only be repaired manually by the site administrator by selecting a new directory and performing:
+
+```
+util/config system directory_server https://newdirectory.something
+```
+
+Eventually we hope to make this a selectable box from the site admin panel.
+
+
+
+### Directory realms
+
+Large organisations may wish to use directory 'realms' rather than a single standalone directory. The standard and default realm is known as RED_GLOBAL. By creating a new realm, your organisation has the ability to create its own hierarchy of primary and secondary servers and clients.
+
+```
+util/config system directory_realm MY_REALM
+```
+
+Your realm *must* have a primary directory. Create this first. Then set the realm the same on all sites within your directory realm (servers and clients).
+
+You may also provide a "sub-realm" that operates indepently from the RED_GLOBAL realm (or any other realm) but allows cross membership and some ability to lookup members of the entire directory space. This has only undergone light testing so be prepared to help out and fix any issues that may arise. A sub-realm contains its parent realm within the realm name.
+
+```
+util/config system directory_realm RED_GLOBAL:MY_REALM
+```
+
+#### Realm access
+
+You may wish that your directory servers and services are only used by members of your realm. To do this a token or password must be supplied to access the realm directory services. This token is not encrypted during transit, but is sufficient to prevent casual access to your directory servers. The following must be configured for all sites (clients and directory servers) within the realm:
+
+```
+util/config system realm_token my-secret-realm-password
+```
+
+
+
+### Directory mirrors
+
+Mirroring occurs with a daily transaction log of activities which are shared between directory servers. In the case of directory and profile updates, the channel address performing the update is transmitted, and the other directory servers probe that channel at its source for changes. We do not and should not trust any information given us by other directory servers. We always check the information at the source.
+
+Ratings are handled slightly differently - an encrypted packet (signed by the channel that created the rating) is passed between the servers. This signature needs to be verified before the rating is accepted. Ratings are always published to the primary directory server and propagated to all other directory servers from there. For this reason there can only be one primary server in a realm. If a misconfigured site claims to be a primary directory, it is ignored in the RED_GLOBAL realm. For other realms there is currently no such protection. Be aware of this when working with alternate realms.
+
+Newly created directory servers are not provided a "full dump", but for performance reasons and minimal disruption to the other servers in the network, they are brought online slowly. It may take up to a month for a new secondary directory server to provide a full view of the network. Please do not add any secondary servers to the hard-coded list of fallback directory servers until it has been operating as a directory for at least a month.
+
+All channels are configured to "ping" their directory server once a month, at somewhat random times during the month. This gives the ability for the directory to discover dead channels and sites (they stop pinging). Subsequently they are marked dead or unreachable and over time will be removed from the directory results.
+
+Channels may be configured to be "hidden" from the directory. These channels may still exist in the directory but will be un-searchable and some "sensitive" personal information will not be stored at all. \ No newline at end of file
diff --git a/doc/en/adminmanual/faq_admins.md b/doc/en/adminmanual/faq_admins.md
new file mode 100644
index 000000000..16ffcea7b
--- /dev/null
+++ b/doc/en/adminmanual/faq_admins.md
@@ -0,0 +1,87 @@
+### Hubzilla FAQ
+
+#### Is there a way to change the Admin account?
+
+#### Is there a way to have multiple administrators?
+
+Yes, but it's a bit messy at the moment as it is not yet exposed in the UI. To make an account an administrative account, one needs to add 4096 to the account_roles entry in the account table of the database. Likewise, to remove administrative permissions, one must subtract 4096 from the account roles.
+
+#### I can log in, but there are no posts or webpages
+
+Most likely, your item table has crashed. Run the MySQL command
+```
+repair table item;
+```
+
+#### Login doesn't work, immediately after login, the page reloads and I'm logged out
+
+Most likely, your session table has crashed. Run the MySQL command
+```
+repair table session;
+```
+
+#### When I switch theme, I sometimes get elements of one theme superimposed on top of the other
+
+a) store/[data]/smarty3 isn't writeable by the webserver. Make it so.
+
+b) You're using Midori, or with certain themes, Konqueror in KHTML mode.
+
+#### My network tab won't load, it appears to be caused by a photo or video
+
+Your PHP memory limit is too low. Increase the size of the memory_limit directive in your php.ini
+
+Contrary to popular belief, the number of users on a hub doesn't make any difference to the required memory limit, rather, the content of an individuals matrix counts. Streams with lots of photos and video require more memory than streams with lots of text.
+
+#### I have no communication with anybody
+
+You're listening on port 443, but do not have a valid SSL certificate. Note this applies even if your baseurl is http.
+
+Don't listen on port 443 if you cannot use it. It is strongly recommended to solve this problem by installing a browser valid SSL certificate rather than disabling port 443.
+
+#### How do I update a non-Git install?
+
+1) Backup .htconfig.php
+2) Backup everything in store/
+3) Backup any custom changes in mod/site/ and view/site
+3) Delete your existing installation
+4) Upload the new version.
+5) Upload the new version of themes and addons.
+6) Restore everything backed up earlier.
+
+#### What do I need to do when moving my hub to a different server
+
+1) Git clone on the new server. Repeat the process for any custom themes, and addons.
+2) Rsync .htconfig.php
+3) Rsync everything in store/
+4) Rsync everything in mod/site/ and view/site (these will only exist if you have custom modules)
+5) Dump and restore DB.
+
+#### How do I reinstall an existing hub on the same server?
+
+1)
+```
+git reset --hard HEAD
+```
+will reset all files to their upstream defaults. This will not reset any local files that do not also exist upstream. Eg, if you have local changes to mod/channel.php, this will reset them - but will not reset any changes in mod/site/channel.php
+2) If you absolutely must reinstall - for example, if you need to upgrade operating system - follow the steps for moving to a different server, but instead of using rsync, backup and restore some other way.
+
+Do not reinstall a hub with a fresh database and fresh .htconfig.php unless as a very last resort. Creating a temporary account and ask for help via a support channel for non-trivial reinstalls is preferable to reinstalling fresh.
+
+#### How do I set the default homepage for logged out viewers?
+
+Use the custom_home addon available in the main addons repository.
+
+#### What do the different directory mode settings mean?
+
+```
+// Configure how we communicate with directory servers.
+// DIRECTORY_MODE_NORMAL = directory client, we will find a directory (all of your member's queries will be directed elsewhere)
+// DIRECTORY_MODE_SECONDARY = caching directory or mirror (keeps in sync with realm primary [adds significant cron execution time])
+// DIRECTORY_MODE_PRIMARY = main directory server (you do not want this unless you are operating your own realm. one per realm.)
+// DIRECTORY_MODE_STANDALONE = "off the grid" or private directory services (only local site members in directory)
+```
+
+- The default is NORMAL. This off-loads most directory services to a different server. The server used is the config:system/directory_server. This setting MAY be updated by the code to one of the project secondaries if the current server is unreachable. You should either be in control of this other server, or should trust it to use this setting.
+- SECONDARY. This allows your local site to act as a directory server without exposing your member's queries to another server. It requires extra processing time during the cron polling, and is not recommended to be run on a shared web host.
+- PRIMARY. This allows you to run a completely separate 'Network' of directory servers with your own Realm. By default, all servers are on the RED_GLOBAL realm unless the config:system/directory_realm setting is overridden. *Do not use this unless you have your own directory_realm.*
+- STANDALONE. This is like primary, except it's a 'Network' all on it's own without talking to any other servers. Use this if you have only one server and want to be segregated from the Red#Matrix directory listings.
diff --git a/doc/en/adminmanual/federation_addons.md b/doc/en/adminmanual/federation_addons.md
new file mode 100644
index 000000000..1546a9105
--- /dev/null
+++ b/doc/en/adminmanual/federation_addons.md
@@ -0,0 +1,8 @@
+### Federation Addons
+
+Various web communities have started to join together using common protocols. The protocols involved are somewhat limited in their possibilities. The Diaspora protocol is somewhat restrictive in terms of the types of communication allowed. All comments must be signed by the original author in a very unique way. The ActivityPub protocol is also supported. No other existing protocol supports nomadic locations (there is now support for nomadic identities in ActivityPub) as used in this project. This poses some challenges for support, as some features work with some networks and not with others. Nonetheless, federation protocols make it possible to connect to a much larger community of people worldwide. They are provided as add-ons.
+
+- **Diaspora Protocol** - The Diaspora Protocol is used by Diaspora and Friendica. You should first enable ‘Diaspora Statistics’ to utilise all available features.
+- **PubCrawl** - The ActivityPub protocol supported by Mastodon, MIsskey, Friendica and all Fediverse services.
+
+Each member of your site must decide for themselves whether or not to allow these protocols, as they may conflict with some desirable core features and capabilities of this software (such as channel migration and cloning). You do this on your ‘Settings -> Feature/Addon Settings’ page. \ No newline at end of file
diff --git a/doc/en/adminmanual/filesync.md b/doc/en/adminmanual/filesync.md
new file mode 100644
index 000000000..587b20207
--- /dev/null
+++ b/doc/en/adminmanual/filesync.md
@@ -0,0 +1,53 @@
+### File Sync and Clone
+
+File cloning across multiple instances of a channel is a very hard problem, due to the nature of PHP memory allocation. This needs to be handled dramatically differently than cloning or syncing of other information. (Processing one large video file or 40-50 photos could exhaust memory). Therefore we can't easily just dump all the data to a dump file and sequentially process it. Loading the dump file itself is likely to exhaust memory.
+
+There are also two primary operations we are considering. The first is the hardest - saving and then importing all your channel information into a new channel clone. The second is synchronising file changes as they occur across two or more "active" clones.
+
+For the first cut at this tool we will concentrate on the second case, while trying to maintain some measure of compatibility with the first case so that we can re-use the same tools.
+
+#### Meta Data
+
+
+First we need the metadata for the file in order to precisely re-construct its structure on another site. This requires the following information:
+
+'attach' structure (without file contents - which is the default) for the file itself **and** its parent directories so that we can re-create its precise place in the file system, since we do not know if the parent directory has been imported previously or ever.
+
+'photo' structure for any photo elements which were created as a result of uploading this file into the system. This typically contains several different 'scales' or thumbnail images, some of which may be cropped for profile photo use or cover photo use. We need to retain the cropping information which is not present in the metadata, but only in the stored data. The actual thumbnail image data may or may not be included in the metadata. A cover photo of large scale (scale #7) could potentially cause memory issues. Not as bad as a 100M video, but if you have several of these they could add up.
+
+'item' entries which are linked to this file. These can be file share activities, the "parent item" linked to photos, and any attached conversation items (photo likes, comments, etc.)
+
+All of these items will require URL replacement and re-signing of the item as they are relocated to another site.
+
+
+#### File Data
+
+Then we have the actual file data we need to reconstruct the file. This needs to be stored separately from the meta-data to avoid memory exhaustion when processing. The actual file data can be used to reconstruct the attach structure and the first four photo scales. If this is a photo, we need access to the "#4 scale" (profile photo) and the #7 scale (cover photo) as they were originally cropped. All other thumbnails can be generated from these.
+
+
+
+#### File Sync
+
+
+We will consider this operation first because it is probably the most straightforward to implement. When a photo is added to or removed or changed from the source system, we will send a clone sync packet to all known clones containing the metadata - but **no file data** . We can only send one sync packet per file operation that needs to be synced.
+
+The receiving end will create and perform URL translation on all the metadata structures and store them. Then it will need to fetch the actual data. Assuming CURL supports streaming, an authenticated request is sent to the original site and the original file is requested and streamed directly to disk (bypassing all processing). If photo scale #4 or scale #7 is required, these are requested and stored into their respective structures. We're assuming in this case that the cover photo large scale will not exhaust memory. If CURL cannot be made to support streaming, request packets need to be queued and sent to the origination site to obtain "chunks" of the file and re-assembled once all chunks have been retrieved.
+
+The authenticated request depends on the mechanism. For CURL streaming, some signed secret with a timestamp will probably need to be generated and posted to the file origination site. Then the data can be retrieved with minimal internal processing and dumped directly to disk using stdio buffering. In the case of a Nomad request, the Nomad request packet will be validated, however scheduling chunk batches and re-assembling them could be tricky.
+
+
+#### File Backup/Restore
+
+This is much more complicated as we do not have an authenticate web server to request data from. The metadata can be mostly the same, but we need some form of signalling that we will not be fetching the file via the web. This will likely require a client side process to parse each metadata file and locate a file on disk which it is associated with. Then the data would need to be streamed to the destination server with a special endpoint designed for this task. A java app might be the best option here to retain platform neutrality.
+
+Another option would be to use WebDAV for this step. The metadata files would be uploaded first, and then the data files. If a data file corresponded to an existing metadata file, the metadata would be processed; the file stored appropriately, and the metadata file then removed. In this case, photos of scales 4 and 7 would need to be provided in the metadata.
+
+
+Optionally, this step could also be performed with a filesystem local to the destination server. This would be the highest performance, and a suite of shell-based tools (in the case of Linux) could perform the "client-side" of the task.
+
+The complexity of this task mandates careful planning into how the data is organised and stored and if necessary backed up remotely or transmitted for backup by the source website.
+
+
+#### Backward Compatibility
+
+There are some obvious issues with making data available for backup or cloning which existed on the system prior to the existence of restore/sync tools. To keep the tools themselves relatively uncomplicated (to the extent possible given the constraints) backward compatibility may have to be preformed by dedicated plugin or addon. \ No newline at end of file
diff --git a/doc/en/adminmanual/further_help.md b/doc/en/adminmanual/further_help.md
new file mode 100644
index 000000000..053c31817
--- /dev/null
+++ b/doc/en/adminmanual/further_help.md
@@ -0,0 +1,3 @@
+### Where to find further help
+
+If you run into problems or have questions that are not covered in this documentation, please let us know via the [Github Issue Tracker](‘https://framagit.org/hubzilla/core/issues’). Please describe your operating environment as accurately as possible and provide as much detail as possible about the error messages you see so that we can avoid them in the future. Due to the wide variety of operating systems and PHP platforms, we have limited ability to debug your PHP installation or obtain missing modules, but we will do our best to resolve common code issues. \ No newline at end of file
diff --git a/doc/en/adminmanual/hub_snapshot_tools.md b/doc/en/adminmanual/hub_snapshot_tools.md
new file mode 100644
index 000000000..4f6086679
--- /dev/null
+++ b/doc/en/adminmanual/hub_snapshot_tools.md
@@ -0,0 +1,127 @@
+### Hub snapshot tools
+
+Hubzilla developers often need to switch between branches that may have incompatible database schemas or content. The following two scripts create and restore complete snapshots of a Hubzilla instance, including the Hub web root and the entire database state. Each script requires a configuration file called `hub-snapshot.conf`, which is located in the same folder and contains the specific directories and database details of your Hub.
+
+#### Configuration file
+
+The format of the configuration file is very strict. There must be no spaces between the variable name and the value. Replace only the content inside the inverted commas with your configuration. Save this file as `hub-snapshot.conf` together with the scripts.
+
+```
+# Location of the hub root. Normally this is the location of the Hubzilla repo clone.
+HUBROOT=‘/var/www/’
+# Name of the MySQL database
+DBNAME=‘hubzilla’
+# MySQL database user
+DBUSER=‘hubzilla’
+# MySQL database password
+DBPWD=‘akeufajeuwfb’
+# The target snapshot folder in which the Git repository is to be initialised
+SNAPSHOTROOT=‘/root/snapshots/hubzilla/’
+```
+
+#### Snapshot
+
+Example of use:
+
+```
+sh hub-snapshot.sh my-hub.conf ‘Commit message for the snapshot’
+```
+
+**hub-snapshot.sh**:
+
+```
+#!/bin/bash
+
+if ! [ -f ‘$1’ ]; then
+ echo ‘$1 is not a valid file. Aborting..."
+ exit 1
+fi
+source ‘$1’
+#echo ‘$DBNAME’
+#echo ‘$DBUSER’
+#echo ‘$DBPWD’
+#echo ‘$HUBROOT’
+#echo ‘$SNAPSHOTROOT’
+MESSAGE=‘snapshot: $2’
+
+if [ ‘$DBPWD’ == ‘’ -o ‘$SNAPSHOTROOT’ == ‘’ -o ‘$DBNAME’ == ‘’ -o ‘$DBUSER’ == ‘’ -o ‘$HUBROOT’ == ‘’ ]; then
+ echo ‘Required variable is not set. Aborting..."
+ exit 1
+fi
+
+if [ ! -d ‘$SNAPSHOTROOT’/db/ ]; then
+ mkdir -p ‘$SNAPSHOTROOT’/db/
+fi
+if [ ! -d ‘$SNAPSHOTROOT’/www/ ]; then
+ mkdir -p ‘$SNAPSHOTROOT’/www/
+fi
+
+if [ ! -d ‘$SNAPSHOTROOT’/www/ ] || [ ! -d ‘$SNAPSHOTROOT’/db/ ]; then
+ echo ‘Error creating snapshot directories. Aborting..."
+ exit 1
+fi
+
+echo ‘Export database...’
+mysqldump -u ‘$DBUSER’ -p‘$DBPWD’ ‘$DBNAME’ > ‘$SNAPSHOTROOT’/db/‘$DBNAME’.sql
+echo ‘Copy hub root files...’
+rsync -va --delete --exclude=.git* ‘$HUBROOT’/ ‘$SNAPSHOTROOT’/www/
+
+cd ‘$SNAPSHOTROOT’
+
+if [ ! -d ‘.git’ ]; then
+ git init
+fi
+if [ ! -d ‘.git’ ]; then
+ echo ‘Cannot initialise git repo. Aborting..."
+ exit 1
+fi
+
+git add -A
+echo ‘Commit hub snapshot...’
+git commit -a -m ‘$MESSAGE’
+
+exit 0
+```
+
+#### Restore
+
+```
+/bin/bash
+# Restore hub to a previous state. Input hub config and commit hash
+
+if ! [ -f ‘$1’ ]; then
+ echo ‘$1 is not a valid file. Aborting..."
+ exit 1
+fi
+source ‘$1’
+COMMIT=$2
+
+if [ ‘$DBPWD’ == ‘’ -o ‘$SNAPSHOTROOT’ == ‘’ -o ‘$DBNAME’ == ‘’ -o ‘$DBUSER’ == ‘’ -o ‘$HUBROOT’ == ‘’ ]; then
+ echo ‘Required variable is not set. Aborting..."
+ exit 1
+fi
+RESTOREDIR=‘$(mktemp -d)/’
+
+if [ ! -d ‘$RESTOREDIR’ ]; then
+ echo ‘Cannot create restore directory. Aborting..."
+ exit 1
+fi
+echo ‘Cloning the snapshot repo...’
+git clone ‘$SNAPSHOTROOT’ ‘$RESTOREDIR’
+cd ‘$RESTOREDIR’
+echo ‘Checkout requested snapshot...’
+git checkout ‘$COMMIT’
+echo ‘Restore hub root files...’
+rsync -a --delete --exclude=.git* ‘$RESTOREDIR’/www/ ‘$HUBROOT’/
+echo ‘Restore hub database...’
+mysql -u ‘$DBUSER’ -p‘$DBPWD’ ‘$DBNAME’ < ‘$RESTOREDIR’/db/‘$DBNAME’.sql
+
+chown -R www-data:www-data ‘$HUBROOT’/{store,extend,addon,.htlog,.htconfig.php}
+
+echo ‘Restored hub to snapshot $COMMIT’
+echo ‘Removing temporary files...’
+
+rm -rf ‘$RESTOREDIR’
+
+exit 0
+``` \ No newline at end of file
diff --git a/doc/en/adminmanual/installation.md b/doc/en/adminmanual/installation.md
new file mode 100644
index 000000000..06d10fb40
--- /dev/null
+++ b/doc/en/adminmanual/installation.md
@@ -0,0 +1,8 @@
+### Installation
+
+There are several ways to install a new hub.
+
+- Manual installation on an existing server
+- Automated installation on an existing server with a shell script
+- Automated deployment via an OpenShift Virtual Private Server (VPS)
+- Installation as Docker-Container \ No newline at end of file
diff --git a/doc/en/adminmanual/installation_requirements.md b/doc/en/adminmanual/installation_requirements.md
new file mode 100644
index 000000000..72b9c6c29
--- /dev/null
+++ b/doc/en/adminmanual/installation_requirements.md
@@ -0,0 +1,14 @@
+### Installation requirements
+
+- Apache with mod-rewrite enabled and ‘AllowOverride All’ so you can use a local .htaccess file. Some people have successfully used nginx and lighttpd. Example configuration scripts are available for these platforms in doc/install. Apache and nginx have the most support.
+- PHP 8.1 or higher. Note that in some shared hosting environments, the *command line version* of PHP may differ from the *web server version*
+- *PHP command line access* if register_argc_argv is set to true in the php.ini file and the hosting provider has no restrictions on the use of exec() and proc_open().
+- curl, gd (with at least jpeg and png support), pdo-mysql (or pdo-postgres), mbstring, zip and openssl extensions. The imagick extension is not required, but is recommended.
+- The xml extension is required if you want to use webdav.
+- Some kind of email server or email gateway so that PHP mail() works.
+- A supported database server. The supported databases are:
+ - Mysql version 8.0.22 or higher
+ - MariaDB version 10.4 or higher
+ - PostgreSQL version 12 or higher
+- Ability to schedule jobs with cron.
+- Installation in a top-level domain or sub-domain (without directory/path component in the URL) is REQUIRED. \ No newline at end of file
diff --git a/doc/en/adminmanual/keywords.md b/doc/en/adminmanual/keywords.md
new file mode 100644
index 000000000..7764c3be1
--- /dev/null
+++ b/doc/en/adminmanual/keywords.md
@@ -0,0 +1 @@
+# Channel directory
diff --git a/doc/en/adminmanual/manual_for_administrators.md b/doc/en/adminmanual/manual_for_administrators.md
new file mode 100644
index 000000000..5defc07e2
--- /dev/null
+++ b/doc/en/adminmanual/manual_for_administrators.md
@@ -0,0 +1,29 @@
+## Manual for administrators
+
+Hubzilla is more than a simple web application. It is a complex communication system that is more like an email server than a web server. For reliability and performance reasons, messages are delivered in the background and queued for later delivery when websites are down. This type of functionality requires a little more from the host system than the typical blog. Not every PHP/MySQL hosting provider is able to support Hubzilla. Many can, but please check the requirements and confirm with your hosting provider prior to installation.
+We have gone to great lengths to ensure that Hubzilla runs on standard hosting platforms such as those used for WordPress blogs and Drupal websites. It runs on most Linux systems.
+
+#include doc/en/adminmanual/further_help.md;
+#include doc/en/adminmanual/before_you_start.md;
+#include doc/en/adminmanual/installation.md;
+#include doc/en/adminmanual/automated_installation.md;
+#include doc/en/adminmanual/Installation_using_docker.md;
+#include doc/en/adminmanual/recommendec_addons.md;
+#include doc/en/adminmanual/problems-following-an-update.md;
+#include doc/en/adminmanual/federation_addons.md;
+#include doc/en/adminmanual/service_classes.md;
+#include doc/en/adminmanual/theme_management.md;
+#include doc/en/adminmanual/Creating-Templates.md;
+#include doc/en/adminmanual/Schema-development.md;
+#include doc/en/adminmanual/Widgets.md;
+#include doc/en/adminmanual/channel_directory.md;
+#include doc/en/adminmanual/Primary-Directory.md;
+#include doc/en/adminmanual/administration.md;
+#include doc/en/adminmanual/troubleshooting.md;
+#include doc/en/adminmanual/hub_snapshot_tools.md;
+#include doc/en/adminmanual/database.md;
+#include doc/en/adminmanual/advanced_configurations.md;
+#include doc/en/adminmanual/CLI_tools.md;
+#include doc/en/adminmanual/filesync.md;
+#include doc/en/adminmanual/Nomad---A-High-Level-Overview.md;
+#include doc/en/adminmanual/faq_admins.md;
diff --git a/doc/en/adminmanual/manual_installation.md b/doc/en/adminmanual/manual_installation.md
new file mode 100644
index 000000000..41e07dfef
--- /dev/null
+++ b/doc/en/adminmanual/manual_installation.md
@@ -0,0 +1,60 @@
+### Manual installation
+
+#### Unzip the Hubzilla files into the root directory of your web server document area
+
+When copying the directory tree to your web server, make sure to include the hidden files such as .htaccess.
+
+If you are able to, we recommend cloning the source code repository with git instead of using a packed tar or zip file. This makes it much easier to update the software. The Linux command to clone the repository into a ‘mywebsite’ directory is as follows:
+
+```
+git clone https://framagit.org/hubzilla/core.git mywebsite
+```
+
+and then you can pull the latest changes at any time with:
+
+```
+git pull
+```
+
+Make sure that the folders `store/[data]/smarty3` and `store` exist and are writable by the web server:
+
+```
+mkdir -p ‘store/[data]/smarty3’
+chmod -R 777 store
+```
+
+This authorisation (777) is very dangerous and if you have sufficient privileges and knowledge, you should only change these directories for the web server and, if different, for the user running the cron job (see below). In many shared hosting environments, this can be difficult without opening a trouble ticket with your provider. The above authorisations allow the software to function, but are not optimal.
+
+The following directories must also be writable by the web server for certain web-based management tools to work:
+
+- `addon`
+- `extend`
+- `view/theme`
+- `widget`
+
+#### **Official addons**
+
+##### **Installation**
+
+Navigate to your website. Then you should clone the addon repository (separately). We nickname this repository ‘hzaddons’. You can include other Hubzilla addon repositories by giving them different nicknames:
+
+```
+cd mywebsite
+util/add_addon_repo https://framagit.org/hubzilla/addons.git hzaddons
+```
+
+##### **Refresh**
+
+To keep the addon tree up to date, you should be at the top level of the website directory and enter an update command for this repository::
+
+```
+cd mywebsite
+util/update_addon_repo hzaddons
+```
+
+Create searchable representations of the online documentation. You can do this every time the documentation is updated:
+
+```
+cd mywebsite
+util/importdoc
+``` \ No newline at end of file
diff --git a/doc/en/adminmanual/pic/adm01.png b/doc/en/adminmanual/pic/adm01.png
new file mode 100644
index 000000000..c4fa01b90
--- /dev/null
+++ b/doc/en/adminmanual/pic/adm01.png
Binary files differ
diff --git a/doc/en/adminmanual/problems-following-an-update.md b/doc/en/adminmanual/problems-following-an-update.md
new file mode 100644
index 000000000..159e2d633
--- /dev/null
+++ b/doc/en/adminmanual/problems-following-an-update.md
@@ -0,0 +1,39 @@
+### Problems Following An Update
+
+A good 90% of all bugs encountered immediately after updating the code to the latest version are simple cache errors of one sort or another. If you update and find something very obvious is broken - like your matrix page doesn't load, notifications are missing, or comment boxes are missing - the chances are it's not a bug at all. Breaking basic functionality is the kind of thing developers tend to notice.
+
+If this happens to you, there are a few simple steps to take before resorting to the support forums:
+
+**Browser Cache**
+
+Symptoms: Menus do not expand, ACL selector does not open, progress indicator does not display (or loops forever), Matrix and channel pages do not load.
+
+Force reload the page. Shift reload, or ctrl+f5. Occasionally, but very, very rarely, you will also need to clear the session data - which is achieved by restarting the browser.
+
+**FastCGI**
+
+Symptoms: Incorrect variables. The basic UI mostly works, but displays incorrect content or is missing content entirely.
+
+If you're using php-fpm, this problem is usually resolved with
+```
+service php-fpm restart
+```
+
+
+**Smarty Cache**
+
+Symptoms:
+
+1) White Screen Of Death. This is most prevalent on the settings and admin pages.
+
+2) Missing icons, tabs, menus or features.
+
+We use the Smarty3 template engine to generate pages. These templates are compiled before they are displayed. Occasionally, a new or modified template will fail to overwrite the old compiled version. To clear the Smarty cache, delete all the files in store/[data]/smarty3/compiled **but do not delete the directory itself**. Templates will then be recompiled on their next access.
+
+**Theme Issues**
+
+There are many themes for Hubzilla. Only Redbasic is officialy supported by the core developers. This applies *even if a core developer happens to support an additional theme*. This means new features are only guaranteed to work in Redbasic.
+
+Redbasic uses a few javascript libraries that are done differently, or entirely absent in other themes. This means new features may only work properly in Redbasic. Before reporting an issue, therefore, you should switch to Redbasic to see if it exists there. If the issue goes away, this is not a bug - it's a theme that isn't up to date.
+
+Should you report an issue with the theme developers then? No. Theme developers use their themes. Chances are, they know. Give them two or three days to catch up and *then* report the issue if it's still not fixed. There are two workarounds for this situation. Firstly, you can temporarily use Redbasic. Secondly, most themes are open source too - open a pull request and make yourself a friend.
diff --git a/doc/en/adminmanual/recommended_addons.md b/doc/en/adminmanual/recommended_addons.md
new file mode 100644
index 000000000..c1deea873
--- /dev/null
+++ b/doc/en/adminmanual/recommended_addons.md
@@ -0,0 +1,7 @@
+### Recommended addons
+
+We recommend installing the following addons on all public pages:
+
+**nsfw** - hide inappropriate posts/comments
+
+**superblock** - blocks content from inappropriate channels \ No newline at end of file
diff --git a/doc/en/adminmanual/service_classes.md b/doc/en/adminmanual/service_classes.md
new file mode 100644
index 000000000..6f6e880bd
--- /dev/null
+++ b/doc/en/adminmanual/service_classes.md
@@ -0,0 +1,57 @@
+### Service classes
+
+You can use service classes to limit system resources by restricting the options of individual accounts, e.g. the storage of files and the limitation of posts at the top level. Define customised service classes according to your requirements in the `.htconfig.php` file. For example, create a *standard* and a *premium class* with the following lines:
+
+```
+// Service classes
+
+App::$config[‘system’][‘default_service_class’]=‘standard’; // this is the default service class that will be associated with each new account
+
+// Configuration for the default service class
+App::$config[‘service_class’][‘default’] =
+array(‘photo_upload_limit’=>2097152, // total storage limit for photos per channel (here 2MB)
+‘total_identities’ =>1, // Number of channels an account can create
+‘total_items’ =>0, // Number of top-level items a channel can create. Applies only to top-level posts of the channel user, other posts and comments are not affected
+‘total_pages’ =>100, // Number of pages a channel can create
+‘total_channels’ =>100, // Number of channels the user can add, other users can still add this channel even if the limit is reached
+‘attach_upload_limit’ =>2097152, // total storage space for attachments per channel (here 2MB)
+‘chatters_inroom’ =>20);
+
+// Configuration for premium service class
+App::$config[‘service_class’][‘premium’] =
+array(‘photo_upload_limit’=>20000000000, // total storage limit for photos per channel (here 20GB)
+‘total_identities’ =>20, // Number of channels an account can create
+‘total_items’ =>20000, // Number of top-level items a channel can create. Only applies to top-level posts of the channel user, other posts and comments are not affected
+‘total_pages’ =>400, // Number of pages that a channel can create
+‘total_channels’ =>2000, // Number of channels the user can add, other users can still add this channel even if the limit is reached
+‘attach_upload_limit’ =>20000000000, // Total storage space for attachments per channel (here 20GB)
+‘chatters_inroom’ =>100);
+```
+
+To apply a service class to an existing account, use the command line utility from the web root:
+
+`util/service_class` list service classes
+
+`util/config system default_service_class firstclass` sets the default service class to ‘firstclass’
+
+`util/service_class firstclass` lists the services that are part of the ‘firstclass’ service class
+
+`util/service_class firstclass photo_upload_limit 10000000` sets the total photo disc usage of the firstclass to 10 million bytes
+
+`util/service_class --account=5 firstclass` sets the account ID 5 to the service class ‘firstclass’ (with confirmation)
+
+`util/service_class --channel=blogchan firstclass` sets the account to which the channel ‘blogchan’ belongs to the service class ‘firstclass’ (with confirmation)
+
+**Options for limiting the service class**
+
+- photo_upload_limit - maximum total number of bytes for photos
+- total_items - maximum total number of top-level posts
+- total_pages - maximum number of pages for Comanche
+- total_identities - maximum number of channels owned by the account
+- total_channels - maximum number of connections
+- total_feeds - maximum number of RSS feed connections
+- attach_upload_limit - maximum file upload space (bytes)
+- minimum_feedcheck_minutes - lowest permissible setting for querying RSS feeds
+- chatrooms - maximum number of chatrooms
+- chatters_inroom - maximum number of chatters per room
+- access_tokens - maximum number of guest access tokens per channel \ No newline at end of file
diff --git a/doc/en/adminmanual/theme_management.md b/doc/en/adminmanual/theme_management.md
new file mode 100644
index 000000000..a769bb57d
--- /dev/null
+++ b/doc/en/adminmanual/theme_management.md
@@ -0,0 +1,7 @@
+### Theme management
+
+#### Example for repo management
+
+1. Navigate to your hub web rootroot@hub`:/root# cd /var/www`
+
+1. Add the theme repo and give it a nameroot@hub`:/var/www# util/add_theme_repo https://github.com/DeadSuperHero/redmatrix-themes.git DeadSuperHero` \ No newline at end of file
diff --git a/doc/en/adminmanual/troubleshooting.md b/doc/en/adminmanual/troubleshooting.md
new file mode 100644
index 000000000..1b6a3d0ba
--- /dev/null
+++ b/doc/en/adminmanual/troubleshooting.md
@@ -0,0 +1,55 @@
+### Troubleshooting
+
+#### Log files
+
+The system log file is an extremely useful resource for tracking down errors. This can be activated on the admin/log configuration page. A log level setting of `LOGGER_DEBUG` is preferred for stable production sites. Most problems with communication or storage are listed here. A setting of LOGGER_DATA provides *much* more detail, but can fill up your hard drive. In any case, we recommend using logrotate on your operating system to check the logs and delete older entries.
+
+At the end of the .htconfig.php file there are several (commented out) lines that enable PHP error logging. This reports problems with the code syntax and execution of the code and is the first place you should look for problems that result in a ‘white screen’ or blank page. This is usually the result of code/syntax problems. Database errors are reported in the system log file, but we have found it useful to create a file called dbfail.out in your root directory where *only* database related problems are collected. If the file exists and is writable, database errors will be logged in this file as well as in the system log file.
+
+In the case of ‘500’ errors, the problems are often logged in the logs of your web server, often in /var/log/apache2/error.log or similar. Consult the documentation of your operating system.
+There are three different logging options.
+
+**The first is the database error log**.
+
+This is only used if you create a file with the name `dbfail.out` in the root directory of your website and make it writable for the web server. If database queries have failed, they are reported here. They generally indicate typing errors in our queries, but also occur when the database server interrupts the connection or tables become corrupted. In rare cases, race conditions are reported here when two processes try to create an xchan or cache entry with the same ID. All other errors (especially persistent errors) should be investigated.
+
+**The second area is the PHP error log**.
+
+This is generated by the language processor and only reports problems in the language environment. Again, these can be syntax or programming errors, but they are usually fatal and result in a ‘white screen of death’, i.e. PHP is terminated. You should look at this file if something goes wrong that does not result in a white screen, but it is not uncommon for this file to be blank for days.
+
+At the end of the supplied `.htconfig.php` file are some lines that, if not commented out, will enable a PHP error log (*extremely* useful for finding the cause of white screen errors). This is not the case by default as there may be issues with log file ownership and write permissions and the log file does not rotate by default.
+
+**The third file is the ‘application log’**.
+
+This is used by Hubzilla to report what is going on in the programme and usually reports any difficulties or unexpected data we have received. Occasionally, ‘heartbeat’ status messages are also output to indicate that we have reached a certain point in a script. **This** is the most important log file for us, as we create it ourselves for the sole purpose of reporting the status of background tasks and anything that seems strange or out of place. It doesn't necessarily have to be something bad, but maybe just unexpected. If you are running a task and a problem occurs, let us know what is in this file when the problem occurs. (Please don't send me 100M dumps, that would just annoy me). Just a few relevant lines so I can eliminate a few hundred thousand lines of code and focus on where the problem is occurring.
+
+These are your website logs, not mine. We report serious issues at every log level. For most websites, I recommend the `DEBUG` log level - it provides a little extra information and doesn't generate huge log files. If a problem occurs that defies all attempts to track it, you should use the `DATA` log level for a short period of time to capture all the details of the structures we were dealing with at the time. This log level consumes a lot of memory and is therefore only recommended for short periods of time or for developer test sites.
+I recommend configuring logrotate for both the php log and the application log. I usually take a look at dbfail.out every week or two, fix any reported issues and then start over with a new file. The same goes for the PHP log file. I check it from time to time to see if there is anything that needs to be fixed.
+
+If something goes wrong and it's not a serious error, I look at the application log file. Often I will run
+
+```
+tail -f logfile.out
+```
+
+while repeating a process where there are problems. Often I'll add additional logging statements to the code if there's no indication of what's going wrong. Even something as simple as ‘got here’ or expressing the value of a variable that might be suspicious. You can do this too - in fact, I would encourage you to do so. Once you've found what you need to look for, you can run
+
+```
+git checkout file.php
+```
+
+to immediately delete any extra logging data you've added. Use the information from this log and any details you can provide from your investigation of the problem to file your bug report - unless your analysis points to the source of the problem. In this case, simply fix it.
+
+##### **Rotating log files**
+
+1. Activate the **logrot** addon in the official [hubzilla-addons](https://framagit.org/hubzilla/addons) repo
+2. Create a directory in your web root called `log` with write permissions for the web server
+3. Go to the **logrot admin settings** and enter this folder name and the maximum size and number of log files kept.
+
+#### Report problems
+
+When reporting problems, please try to provide as much detail as necessary to allow the developers to reproduce the problem and include the full text of any error messages.
+
+We encourage you to use these logs, along with the source code you have, to the best of your ability to fix problems and find their cause. The community can often help, but only you have access to your site's log files, and it is considered a security risk to share them.
+
+If a code issue has been uncovered, please report it in the project's bug tracker [(https://framagit.org/hubzilla/core/issues)](https://framagit.org/hubzilla/core/issues). Again, provide as much detail as possible so we don't have to keep asking questions about your configuration or duplicating the issue so we can address the problem directly and figure out what to do. You are also welcome to offer your own solutions and submit patches. In fact, we encourage you to do so, as we are all volunteers and have limited time available. The more people that help out, the easier the work will be for everyone. It's okay if your solution isn't perfect. Every little bit helps, and maybe we can improve it. \ No newline at end of file
diff --git a/doc/en/admins.bb b/doc/en/admins.bb
deleted file mode 100644
index 6c78ce999..000000000
--- a/doc/en/admins.bb
+++ /dev/null
@@ -1,15 +0,0 @@
-[h2]Documentation for Hub Administrators[/h2]
-[h3]Deploying your hub[/h3]
-[zrl=[baseurl]/help/install]Install[/zrl]
-[zrl=[baseurl]/help/red2pi]Installing $Projectname on the Raspberry Pi[/zrl]
-[zrl=[baseurl]/help/Hubzilla_on_OpenShift]$Projectname on OpenShift[/zrl]
-[h3]Taking care of your hub[/h3]
-[zrl=[baseurl]/help/troubleshooting]Troubleshooting Tips[/zrl]
-[zrl=[baseurl]/help/theme_management]Theme Management[/zrl]
-[zrl=[baseurl]/help/hidden_configs]Tweaking $Projectname's Hidden Configurations[/zrl]
-[zrl=[baseurl]/help/service_classes]Service Classes[/zrl]
-[zrl=[baseurl]/help/directories]Working with and configuring Directories[/zrl]
-[h3]Frequently asked questions[/h3]
-[zrl=[baseurl]/help/faq_admins]FAQ For Admins[/zrl]
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/api/api_albums.md b/doc/en/api/api_albums.md
deleted file mode 100644
index 230daae3c..000000000
--- a/doc/en/api/api_albums.md
+++ /dev/null
@@ -1,66 +0,0 @@
-API albums
-==========
-
-Description: list photo albums
-
-GET /api/z/1.0/albums
-
-
-Output:
-
- text - textual name
-
- total - number of photos in this album
-
- url - web URL
-
- urlencode - textual name, urlencoded
-
- bin2hex - textual name using bin2hex (which is used in the web URL link)
-
-
-Example:
-
-
- {
-
- "success": true,
- "albums": [
- {
- "text": "/",
- "total": "2",
- "url": "https://xyz.macgirvin.com/photos/hubzilla/album/",
- "urlencode": "",
- "bin2hex": ""
- },
- {
- "text": "2016-01",
- "total": "6",
- "url": "https://xyz.macgirvin.com/photos/hubzilla/album/323031362d3031",
- "urlencode": "2016-01",
- "bin2hex": "323031362d3031"
- },
- {
- "text": "2016-02",
- "total": "7",
- "url": "https://xyz.macgirvin.com/photos/hubzilla/album/323031362d3032",
- "urlencode": "2016-02",
- "bin2hex": "323031362d3032"
- },
- {
- "text": "Cover Photos",
- "total": "5",
- "url": "https://xyz.macgirvin.com/photos/hubzilla/album/436f7665722050686f746f73",
- "urlencode": "Cover+Photos",
- "bin2hex": "436f7665722050686f746f73"
- },
- {
- "text": "Profile Photos",
- "total": "26",
- "url": "https://xyz.macgirvin.com/photos/hubzilla/album/50726f66696c652050686f746f73",
- "urlencode": "Profile+Photos",
- "bin2hex": "50726f66696c652050686f746f73"
- }
- ]
-
- }
diff --git a/doc/en/api/api_filedata.md b/doc/en/api/api_filedata.md
deleted file mode 100644
index 1d46a495c..000000000
--- a/doc/en/api/api_filedata.md
+++ /dev/null
@@ -1,66 +0,0 @@
-API filedata
-=============
-
-Provides the ability to download a file from cloud storage in chunks
-
-GET /api/z/1.0/filedata
-
-
-Required:
-
- - file_id
- attach.hash of desired file ('begins with' match)
-
-
-Optional:
-
- - start
- starting byte of returned data in file (counting from 0)
-
- - length
- length (prior to base64 encoding) of chunk to download
-
-
-Returns:
-
- attach (DB) structure with base64 encoded 'content' comprised of the desired chunk
-
-
-
-Example:
-
- https://xyz.macgirvin.com/api/z/1.0/filedata?f=&file_id=9f5217770fd&start=0&length=48
-
-Returns:
-
- {
-
- "attach": {
- "id": "107",
- "aid": "1",
- "uid": "2",
- "hash": "9f5217770fd55d563bd77f84d534d8e119a187514bbd391714626cd9c0e60207",
- "creator": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
- "filename": "pcxtopbm.c",
- "filetype": "application/octet-stream",
- "filesize": "3934",
- "revision": "0",
- "folder": "",
- "flags": "0",
- "is_dir": "0",
- "is_photo": "0",
- "os_storage": "1",
- "os_path": "",
- "display_path": "",
- "content": "LyogcGN4dG9wYm0uYyAtIGNvbnZlcnQgUEMgcGFpbnRicnVzaCAoLnBjeCkgZmls",
- "created": "2016-07-24 23:13:01",
- "edited": "2016-07-24 23:13:01",
- "allow_cid": "",
- "allow_gid": "",
- "deny_cid": "",
- "deny_gid": "",
- "start": 0,
- "length": 48
- }
-
- } \ No newline at end of file
diff --git a/doc/en/api/api_files.md b/doc/en/api/api_files.md
deleted file mode 100644
index c2a10fce5..000000000
--- a/doc/en/api/api_files.md
+++ /dev/null
@@ -1,103 +0,0 @@
-API files
-=========
-
-List file storage (attach DB)
-
-GET /api/z/1.0/files
-
-
-Options:
-
- - hash
- return only entries matching hash (exactly)
-
- - filename
- return only entries matching filename (substring)
-
- - filetype
- return only entries matching filetype/mimetype (substring)
-
- - start
- start at record (default 0)
-
- - records
- number of records to return or 0 for unlimited
-
-
-
-Example:
-
-curl -u mychannel:mypassword https://xyz.macgirvin.com/api/z/1.0/files -d filetype=multipart/mixed
-
-
-Returns:
-
- {
-
- "success": true,
- "results": [
- {
- "id": "1",
- "aid": "1",
- "uid": "2",
- "hash": "44ee8b2a1a7f36dea07b93b7747a2383a1bc0fdd08339e8928bfcbe45f65d939",
- "filename": "Profile Photos",
- "filetype": "multipart/mixed",
- "filesize": "0",
- "revision": "0",
- "folder": "",
- "os_storage": "1",
- "is_dir": "1",
- "is_photo": "0",
- "flags": "0",
- "created": "2016-01-02 21:51:17",
- "edited": "2016-01-02 21:51:17",
- "allow_cid": "",
- "allow_gid": "",
- "deny_cid": "",
- "deny_gid": ""
- },
- {
- "id": "12",
- "aid": "1",
- "uid": "2",
- "hash": "71883f1fc64af33889229cbc79c5a056deeec5fc277d765f182f19073e1b2998",
- "filename": "Cover Photos",
- "filetype": "multipart/mixed",
- "filesize": "0",
- "revision": "0",
- "folder": "",
- "os_storage": "1",
- "is_dir": "1",
- "is_photo": "0",
- "flags": "0",
- "created": "2016-01-15 00:24:33",
- "edited": "2016-01-15 00:24:33",
- "allow_cid": "",
- "allow_gid": "",
- "deny_cid": "",
- "deny_gid": ""
- },
- {
- "id": "16",
- "aid": "1",
- "uid": "2",
- "hash": "f48f7ec3278499d1dd86b72c3207beaaf4717b07df5cc9b373f14d7aad2e1bcd",
- "filename": "2016-01",
- "filetype": "multipart/mixed",
- "filesize": "0",
- "revision": "0",
- "folder": "",
- "os_storage": "1",
- "is_dir": "1",
- "is_photo": "0",
- "flags": "0",
- "created": "2016-01-22 03:24:55",
- "edited": "2016-01-22 03:26:57",
- "allow_cid": "",
- "allow_gid": "",
- "deny_cid": "",
- "deny_gid": ""
- }
- ]
- }
diff --git a/doc/en/api/api_functions.bb b/doc/en/api/api_functions.bb
deleted file mode 100644
index fe7cb11ba..000000000
--- a/doc/en/api/api_functions.bb
+++ /dev/null
@@ -1,133 +0,0 @@
-[b]Red Twitter API[/b]
-
-The &quot;basic&quot; Red web API is based on the Twitter API, as this provides instant compatibility with a huge number of third-party clients and applications without requiring any code changes on their part. It is also a super-set of the StatusNet version of the Twitter API, as this also has existing wide support.
-
-Red has a lot more capability that isn't exposed in the Twitter interfaces or where we are forced to &quot;dumb-down&quot; the API functions to work with the primitive Twitter/StatusNet communications and privacy model. So we plan to extend the Twitter API in ways that will allow Red-specific clients to make full use of Red features without being crippled.
-
-A dedicated Red API is also being developed to work with native data structures and permissions and which do not require translating to different privacy and permission models and storage formats. This will be described in other documents. The prefix for all of the native endpoints is 'api/red'.
-
-Red provides multiple channels accesible via the same login account. With Red, any API function which requires authentication will accept a parameter &amp;channel={channel_nickname} - and will select that channel and make it current before executing the API command. By default, the default channel associated with an account is selected.
-
-Red also provides an extended permission model. In the absence of any Red specific API calls to set permissions, they will be set to the default permissions settings which are associated with the current channel.
-
-Red will probably never be able to support the Twitter 'api/friendships' functions fully because Red is not a social network and has no concept of &quot;friendships&quot; - it only recognises permissions to do stuff (or not do stuff as the case may be).
-
-Legend: T= Twitter, S= StatusNet, F= Friendica, R= Red, ()=Not yet working, J= JSON only (XML formats deprecated)
-
-Twitter API compatible functions:
-
- api/account/verify_credentials T,S,F,R
- api/statuses/update T,S,F,R
- api/users/show T,S,F,R
- api/statuses/home_timeline T,S,F,R
- api/statuses/friends_timeline T,S,F,R
- api/statuses/public_timeline T,S,F,R
- api/statuses/show T,S,F,R
- api/statuses/retweet T,S,F,R
- api/statuses/destroy T,S,F,(R)
- api/statuses/mentions T,S,F,(R)
- api/statuses/replies T,S,F,(R)
- api/statuses/user_timeline T,S,F,(R)
- api/favorites T,S,F,R
- api/account/rate_limit_status T,S,F,R
- api/help/test T,S,F,R
- api/statuses/friends T,S,F,R
- api/statuses/followers T,S,F,R
- api/friends/ids T,S,F,R
- api/followers/ids T,S,F,R
- api/direct_messages/new T,S,F,R
- api/direct_messages/conversation T,S,F,R
- api/direct_messages/all T,S,F,R
- api/direct_messages/sent T,S,F,R
- api/direct_messages T,S,F,R
- api/oauth/request_token T,S,F,R
- api/oauth/access_token T,S,F,R
- api/favorites T,S,R
- api/favorites/create T,S,R
- api/favorites/destroy T,S,R
-
-Twitter API functions supported by StatusNet but not currently by Friendica or Red
-
- api/statuses/retweets_of_me T,S
- api/friendships/create T,S
- api/friendships/destroy T,S
- api/friendships/exists T,S
- api/friendships/show T,S
- api/account/update_location T,S
- api/account/update_profile_background_image T,S
- api/account/update_profile_image T,S
- api/blocks/create T,S
- api/blocks/destroy T,S
-
-Twitter API functions not currently supported by StatusNet
-
- api/statuses/retweeted_to_me T
- api/statuses/retweeted_by_me T
- api/direct_messages/destroy T
- api/account/end_session T,(R)
- api/account/update_delivery_device T
- api/notifications/follow T
- api/notifications/leave T
- api/blocks/exists T
- api/blocks/blocking T
- api/lists T
-
-Statusnet compatible extensions to the Twitter API supported in both Friendica and Red
-
- api/statusnet/version S,F,R
- api/statusnet/config S,F,R
-
-Friendica API extensions to the Twitter API supported in both Friendica and Red
-
- api/statuses/mediap F,R
-
-Red specific API extensions to the Twitter API not supported in Friendica
-
- api/account/logout R
- api/export/basic R,J
- api/friendica/config R
- api/red/config R
- api/friendica/version R
-
- api/red/version R
-
- api/red/channel/export/basic R,J
- api/red/channel/stream R,J (currently post only)
- api/red/albums R,J
- api/red/photos R,J (option album=xxxx)
-
-Red proposed API extensions to the Twitter API
-
- api/statuses/edit (R),J
- api/statuses/permissions (R),J
- api/statuses/permissions/update (R),J
- api/statuses/ids (R),J # search for existing message_id before importing a foreign post
- api/files/show (R),J
- api/files/destroy (R),J
- api/files/update (R),J
- api/files/permissions (R),J
- api/files/permissions/update (R),J
- api/pages/show (R),J
- api/pages/destroy (R),J
- api/pages/update (R),J
- api/pages/permissions (R),J
- api/pages/permissions/update (R),J
- api/events/show (R),J
- api/events/update (R),J
- api/events/permissions (R),J
- api/events/permissions/update (R),J
- api/events/destroy (R),J
- api/photos/show (R),J
- api/photos/update (R),J
- api/photos/permissions (R),J
- api/photos/permissions/update (R),J
- api/albums/destroy (R),J
- api/albums/show (R),J
- api/albums/update (R),J
- api/albums/permissions (R),J
- api/albums/permissions/update (R),J
- api/albums/destroy (R),J
- api/friends/permissions (R),J
-
-#include doc/macros/main_footer.bb;
-
diff --git a/doc/en/api/api_group_members.md b/doc/en/api/api_group_members.md
deleted file mode 100644
index 497e0aac6..000000000
--- a/doc/en/api/api_group_members.md
+++ /dev/null
@@ -1,133 +0,0 @@
-API group_members
-=================
-
-GET /api/z/1.0/group_members
-
-
-
-Required:
-
- group_id or group_name
-
-
-Returns:
-
- group_member+abook+xchan (DB join) for each member of the privacy group
-
-
- [
-
- {
- "id": "1",
- "uid": "2",
- "gid": "1",
- "xchan": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
- "abook_id": "2",
- "abook_account": "1",
- "abook_channel": "2",
- "abook_xchan": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
- "abook_my_perms": "218555",
- "abook_their_perms": "0",
- "abook_closeness": "0",
- "abook_created": "2016-01-02 21:16:26",
- "abook_updated": "2016-01-02 21:16:26",
- "abook_connected": "0000-00-00 00:00:00",
- "abook_dob": "0000-00-00 00:00:00",
- "abook_flags": "0",
- "abook_blocked": "0",
- "abook_ignored": "0",
- "abook_hidden": "0",
- "abook_archived": "0",
- "abook_pending": "0",
- "abook_unconnected": "0",
- "abook_self": "1",
- "abook_feed": "0",
- "abook_profile": "",
- "abook_incl": "",
- "abook_excl": "",
- "abook_instance": "",
- "xchan_hash": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
- "xchan_guid": "lql-1VnxtiO4-WF0h72wLX1Fu8szzHDOXgQaTbELwXW77k8AKFfh-hYr70vqMrc3SSvWN-Flrc5HFhRTWB7ICw",
- "xchan_guid_sig": "PafvEL0VpKfxATxlCqDjfOeSIMdmpr3iU7X-Sysa1h5LzDpjSXsjO37tYZL-accb1M5itLlfnW5epkTa5I4flsW21zSY1A2jCuBQUTLLGV7rNyyBy7lgqJUFvAMRx0TfXzP9lcaPqlM9T1tA6jfWOsOmkdzwofGeXBnsjGfjsO2xdGYe6vwjOU0DSavukvzDMnOayB9DekpvDnaNBTxeGLM45Skzr7ZEMcNF7TeXMbnvpfLaALYEKeQs9bGH-UgAG8fBWgzVAzeBfx_XSR1rdixjyiZGP0kq0h35SlmMPcEjliodOBFwMXqpXFB7Ibp4F6o6te2p2ErViJccQVG8VNKB6SbKNXY6bhP5zVcVsJ-vR-p4xXoYJJvzTN7yTDsGAXHOLF4ZrXbo5yi5gFAlIrTLAF2EdWQwxSGyLRWKxG8PrDkzEzX6cJJ0VRcLh5z6OI5QqQNdeghPZbshMFMJSc_ApCPi9_hI4ZfctCIOi3T6bdgTNKryLm5fhy_eqjwLAZTGP-aUBgLZpb1mf2UojBn6Ey9cCyq-0T2RWyk-FcIcbV4qJ-p_8oODqw13Qs5FYkjLr1bGBq82SuolkYrXEwQClxnrfKa4KYc2_eHAXPL01iS9zVnI1ySOCNJshB97Odpooc4wk7Nb2Fo-Q6THU9zuu0uK_-JbK7IIl6go2qA",
- "xchan_pubkey": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA18JB76lyP4zzL/y7BCej\neJnfZIWZNtM3MZvI1zEVMWmmwOS+u/yH8oPwyaDk4Y/tnj8GzMPj1lCGVRcd8EJa\nNrCMd50HODA5EsJtxpsOzRcILYjOcTtIAG1K4LtKqELi9ICAaFp0fNfa+Jf0eCek\nvPusx2/ORhy+o23hFoSMhL86o2gmaiRnmnA3Vz4ZMG92ieJEDMXt9IA1EkIqS4y5\nBPZfVPLD1pv8iivj+dtN1XjwplgjUbtxmU0/Ej808nHppscRIqx/XJ0XZU90oNGw\n/wYoK2EzJlPbRsAkwNqoFrAYlr5HPpn4BJ2ebFYQgWBUraD7HwS5atsQEaxGfO21\nlUP0+lDg9t3CXvudDj0UG1jiEKbVIGA+4aG0GN2DSC5AyRq/GRxqyay5W2vQbAZH\nyvxPGrZFO24I65g3pjhpjEsLqZ4ilTLQoLMs0drCIcRm5RxMUo4s/LMg16lT4cEk\n1qRtk2X0Sb1AMQQ2uRXiVtWz77QHMONEYkf6OW4SHbwcv5umvlv69NYEGfCcbgq0\nAV7U4/BWztUz/SWj4r194CG43I9I8dmaEx9CFA/XMePIAXQUuABfe1QMOR6IxLpq\nTHG1peZgHQKeGz4aSGrhQkZNNoOVNaZoIfcvopxcHDTZLigseEIaPPha4WFYoKPi\nUPbZ5o8gTLc750uzrnb2jwcCAwEAAQ==\n-----END PUBLIC KEY-----\n",
- "xchan_photo_mimetype": "image/png",
- "xchan_photo_l": "https://xyz.macgirvin.com/photo/profile/l/2",
- "xchan_photo_m": "https://xyz.macgirvin.com/photo/profile/m/2",
- "xchan_photo_s": "https://xyz.macgirvin.com/photo/profile/s/2",
- "xchan_addr": "teller@xyz.macgirvin.com",
- "xchan_url": "https://xyz.macgirvin.com/channel/teller",
- "xchan_connurl": "https://xyz.macgirvin.com/poco/teller",
- "xchan_follow": "https://xyz.macgirvin.com/follow?f=&url=%s",
- "xchan_connpage": "",
- "xchan_name": "Teller",
- "xchan_network": "zot",
- "xchan_instance_url": "",
- "xchan_flags": "0",
- "xchan_photo_date": "2016-10-19 01:26:50",
- "xchan_name_date": "2016-01-02 21:16:26",
- "xchan_hidden": "0",
- "xchan_orphan": "0",
- "xchan_censored": "0",
- "xchan_selfcensored": "0",
- "xchan_system": "0",
- "xchan_pubforum": "0",
- "xchan_deleted": "0"
- },
- {
- "id": "12",
- "uid": "2",
- "gid": "1",
- "xchan": "xuSMUYxw1djBB97qXsbrBN1nzJH_gFwQL6pS4zIy8fuusOfBxNlMiVb4h_q5tOEvpE7tYf1EsryjNciMuPIj5w",
- "abook_id": "24",
- "abook_account": "1",
- "abook_channel": "2",
- "abook_xchan": "xuSMUYxw1djBB97qXsbrBN1nzJH_gFwQL6pS4zIy8fuusOfBxNlMiVb4h_q5tOEvpE7tYf1EsryjNciMuPIj5w",
- "abook_my_perms": "218555",
- "abook_their_perms": "218555",
- "abook_closeness": "80",
- "abook_created": "2016-01-27 00:48:43",
- "abook_updated": "2016-12-04 17:16:58",
- "abook_connected": "2016-12-04 17:16:58",
- "abook_dob": "0001-01-01 00:00:00",
- "abook_flags": "0",
- "abook_blocked": "0",
- "abook_ignored": "0",
- "abook_hidden": "0",
- "abook_archived": "0",
- "abook_pending": "0",
- "abook_unconnected": "0",
- "abook_self": "0",
- "abook_feed": "0",
- "abook_profile": "debb5236efb1626cfbad33ccb49892801e5f844aa04bf81f580cfa7d13204819",
- "abook_incl": "",
- "abook_excl": "",
- "abook_instance": "",
- "xchan_hash": "xuSMUYxw1djBB97qXsbrBN1nzJH_gFwQL6pS4zIy8fuusOfBxNlMiVb4h_q5tOEvpE7tYf1EsryjNciMuPIj5w",
- "xchan_guid": "d5EMLlt1tHHZ0dANoA7B5Wq9UgXoWcFS9-gXOkL_AAejcPApoQRyxfHTuu8DoTbUaO-bYmX5HPuWuK9PHyqNmA",
- "xchan_guid_sig": "CVWEMRPtzI1YcHfnnWHTuv3H964OAmSElgUfxMoX6RdQdxNpqb_POirpVuyP8s3W17mVCfO5V9IAjkg5iKcqCk6YcvOD_egmMy-AnM9TC1kKndQHw55CunD82Q8K_xBNSXkSROizcNkKh9DVLjJPFjW1AqtI4njkZ3EMgrWqnbFRM1qPToUoCY9zM3tEMHoAD9YX1zP90wl40LzfN-dtcNWpSBbiz9owou62uzLbN7mrCwKOMlXLjwwGswRnxIsEnb3O-FXOs8hs0mArKe9snq1-BKeD16LyzxgwlpVLElzIJZGEZGtMdIJgeRzKuBvPjsOIpQ1yAkuOpFJ3nGCM-IPOIIjAmyVl5zD3xPVcxxpZlJRn5fG1Y-gnqTgsrEQCA7M6XPWQdrdHU4akZfyUyFJDhv3uM-jon9VzrYTBw68R0WA-1Z8WafEHA4qh5OWAj85lUarwhr7iTiEckH51ypPCPs6VbT6Pw7yMaxfjFOcipashQagx0tfOlDhE5dQANOXKASFtH1J9-CZY2MQdLPQ6u54d5whuHKMGaJ0V68pnmZ2rOn7g344Ah2WCJrm17jj60QsRMorqRFj7GMdPIA1XB8Wrk88MuYOe3Dhyuu6ZWKI7YTWJS690ZVkKUqAiNHqj0W86DtaiPUc_mmGR0fHl4Gksnko3WmCFv9q2X2E",
- "xchan_pubkey": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoj2xCJktBA8Ww7Hp+ZNL\nrNuQpo8UB/bfvRkIy+yua3xpF1TuXcnAH61kyRz8vXgOu/l2CyxQbIoaGslCV5Sy\n8JKeNXe+IilUdSSEjMIwCPfSPsYnMHsSnHWmPmclvJwEtQUKOZmW5mMuVBvXy7D2\njomFwc69AYphdyys6eQ7Dcn6+FRBiQbyMprZ5lxyVW+O4DuXVNa3ej2ebx0gCJZ4\ntTIlBoKwEey91dY+FyKVFjdwfNczpmL7LgmZXqcVx+MG3mYgibwdVMiXVj5X06cs\nV9hJ5Xi+Aklsv/UWJtjw9FVt7y9TLptnhh4Ra6T/MDmnBBIAkOR7P/X8cRv078MT\nl0IMsP0RJcDEtTLtwHFVtDs6p52KDFqclKWbqmxmxqV3OTPVYtArRGIzgnJi/5ur\nHRr5G6Cif7QY3UowsIOf78Qvy28LwSbdymgBAWwPPKIviXWxGO+9kMWdmPSUQrWy\nK0+7YA9P9fBUFfn9Hc+p8SJQmQ6OAqLwrDGiPSOlGaNrbEqwqLGgIpXwK+lEFcFJ\n3SPOjJRWdR2whlMxvpwX+39+H7dWN3vSa3Al4/Sq7qW8yW2rYwf+eGyp4Z0lRR+8\nJxFMCwZkSw5g14YdlikAPojv5V1c6KuA5ieg8G1hwyONV7A4JHPyEdPt0W0TZi6C\nCOVkPaC3xGrguETZpJfVpwUCAwEAAQ==\n-----END PUBLIC KEY-----\n",
- "xchan_photo_mimetype": "image/png",
- "xchan_photo_l": "https://xyz.macgirvin.com/photo/9da63aa910ea14e1501ee1a749d181a6-4",
- "xchan_photo_m": "https://xyz.macgirvin.com/photo/9da63aa910ea14e1501ee1a749d181a6-5",
- "xchan_photo_s": "https://xyz.macgirvin.com/photo/9da63aa910ea14e1501ee1a749d181a6-6",
- "xchan_addr": "cloner@xyz.macgirvin.com",
- "xchan_url": "http://abc.macgirvin.com/channel/cloner",
- "xchan_connurl": "http://abc.macgirvin.com/poco/cloner",
- "xchan_follow": "https://xyz.macgirvin.com/follow?f=&url=%s",
- "xchan_connpage": "",
- "xchan_name": "Karen",
- "xchan_network": "zot",
- "xchan_instance_url": "",
- "xchan_flags": "0",
- "xchan_photo_date": "2016-03-31 19:59:20",
- "xchan_name_date": "2016-01-26 23:23:42",
- "xchan_hidden": "0",
- "xchan_orphan": "0",
- "xchan_censored": "0",
- "xchan_selfcensored": "0",
- "xchan_system": "0",
- "xchan_pubforum": "0",
- "xchan_deleted": "0"
- }
-
- ] \ No newline at end of file
diff --git a/doc/en/api/api_item_update.md b/doc/en/api/api_item_update.md
deleted file mode 100644
index cf1a28044..000000000
--- a/doc/en/api/api_item_update.md
+++ /dev/null
@@ -1,225 +0,0 @@
-API item/update
-===============
-
-
-Usage: POST /api/z/1.0/item/update
-
-Description: item/update posts an item (typically a conversation item or post, but can be any item) using form input.
-
-
-Required:
-
-- body
-
- text/bbcode contents by default.
-
-
-Optional:
-
-- $_FILES['media']
-
- uploaded media file to include with post
-
-- title
-
- title of post/item
-
-- contact_allow
-
- array of xchan.xchan_hash allowed to view this item
-
-- group_allow
-
- array of group.hash allowed to view this item
-
-- contact_deny
-
- array of xchan.xchan_hash not allowed to view this item
-
-- group_deny
-
- array of group.hash not allowed to view this item
-
-- coord
-
- geographic coordinates
-
-- location
-
- freefrom location
-
-- expire
-
- datetime this post will expire or be removed
-
-- mimetype
-
- mimetype if not text/bbcode
-
-- parent
-
- item.id of parent to this post (makes it a comment)
-
-- parent_mid
-
- alternate form of parent using message_id
-
-- remote_xchan
-
- xchan.xchan_hash of this message author if not the channel owner
-
-- consensus
-
- boolean set to true if this is a consensus or voting item (default false)
-
-- nocomment
-
- boolean set to true if comments are to be disabled (default false)
-
-- origin
-
- do not use this without reading the code
-
-- namespace
-
- persistent identity for a remote network or service
-
-- remote_id
-
- message_id of this resource on a remote network or service
-
-- message_id
-
- message_id of this item (leave unset to generate one)
-
-- created
-
- datetime of message creation
-
-- post_id
-
- existing item.id if this is an edit operation
-
-- app
-
- application or network name to display with item
-
-- categories
-
- comma separated categories for this item
-
-- webpage
-
- item.page_type if not 0
-
-- pagetitle
-
- for webpage and design elements, the 'page name'
-
-- layout_mid
-
- item.mid of layout for this design element
-
-- plink
-
- permalink for this item if different than the default
-
-- verb
-
- activitystream verb for this item/activity
-
-- obj_type
-
- activitystream object type for this item/activity
-
-
-
-Example:
-
-curl -u mychannel:mypassword https://xyz.macgirvin.com/api/z/1.0/item/update -d body="hello world"
-
-
-Returns:
-
-
- {
-
- "success": true,
- "item_id": "2245",
- "item": {
- "id": "2245",
- "mid": "14135cdecf6b8e3891224e4391748722114da6668eebbcb56fe4667b60b88249@xyz.macgirvin.com",
- "aid": "1",
- "uid": "2",
- "parent": "2245",
- "parent_mid": "14135cdecf6b8e3891224e4391748722114da6668eebbcb56fe4667b60b88249@xyz.macgirvin.com",
- "thr_parent": "14135cdecf6b8e3891224e4391748722114da6668eebbcb56fe4667b60b88249@xyz.macgirvin.com",
- "created": "2016-12-03 20:00:12",
- "edited": "2016-12-03 20:00:12",
- "expires": "0001-01-01 00:00:00",
- "commented": "2016-12-03 20:00:12",
- "received": "2016-12-03 20:00:12",
- "changed": "2016-12-03 20:00:12",
- "comments_closed": "0001-01-01 00:00:00",
- "owner_xchan": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
- "author_xchan": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
- "source_xchan": "",
- "mimetype": "text/bbcode",
- "title": "",
- "body": "hello world",
- "html": "",
- "app": "",
- "lang": "",
- "revision": "0",
- "verb": "http://activitystrea.ms/schema/1.0/post",
- "obj_type": "http://activitystrea.ms/schema/1.0/note",
- "obj": "",
- "tgt_type": "",
- "target": "",
- "layout_mid": "",
- "postopts": "",
- "route": "",
- "llink": "https://xyz.macgirvin.com/display/14135cdecf6b8e3891224e4391748722114da6668eebbcb56fe4667b60b88249@xyz.macgirvin.com",
- "plink": "https://xyz.macgirvin.com/channel/mychannel/?f=&mid=14135cdecf6b8e3891224e4391748722114da6668eebbcb56fe4667b60b88249@xyz.macgirvin.com",
- "resource_id": "",
- "resource_type": "",
- "attach": "",
- "sig": "sa4TOQNfHtV13HDZ1tuQGWNBpZp-nWhT2GMrZEmelXxa_IvEepD2SEsCTWOBqM8OKPJLfNy8_i-ORXjrOIIgAa_aT8cw5vka7Q0C8L9eEb_LegwQ_BtH0CXO5uT30e_8uowkwzh6kmlVg1ntD8QqrGgD5jTET_fMQOIw4gQUBh40GDG9RB4QnPp_MKsgemGrADnRk2vHO7-bR32yQ0JI-8G-eyeqGaaJmIwkHoi0vXsfjZtU7ijSLuKEBWboNjKEDU89-vQ1c5Kh1r0pmjiDk-a5JzZTYShpuhVA-vQgEcADA7wkf4lJZCYNwu3FRwHTvhSMdF0nmyv3aPFglQDky38-SAXZyQSvd7qlABHGCVVDmYrYaiq7Dh4rRENbAUf-UJFHPCVB7NRg34R8HIqmOKq1Su99bIWaoI2zuAQEVma9wLqMoFsluFhxX58KeVtlCZlro7tZ6z619-dthS_fwt0cL_2dZ3QwjG1P36Q4Y4KrCTpntn9ot5osh-HjVQ01h1I9yNCj6XPgYJ8Im3KT_G4hmMDFM7H9RUrYLl2o9XYyiS2nRrf4aJHa0UweBlAY4zcQG34bw2AMGCY53mwsSArf4Hs3rKu5GrGphuwYX0lHa7XEKMglwBWPWHI49q7-oNWr7aWwn1FnfaMfl4cQppCMtKESMNRKm_nb9Dsh5e0",
- "diaspora_meta": "",
- "location": "",
- "coord": "",
- "public_policy": "",
- "comment_policy": "contacts",
- "allow_cid": "",
- "allow_gid": "",
- "deny_cid": "",
- "deny_gid": "",
- "item_restrict": "0",
- "item_flags": "0",
- "item_private": "0",
- "item_origin": "1",
- "item_unseen": "0",
- "item_starred": "0",
- "item_uplink": "0",
- "item_consensus": "0",
- "item_wall": "1",
- "item_thread_top": "1",
- "item_notshown": "0",
- "item_nsfw": "0",
- "item_relay": "0",
- "item_mentionsme": "0",
- "item_nocomment": "0",
- "item_obscured": "0",
- "item_verified": "1",
- "item_retained": "0",
- "item_rss": "0",
- "item_deleted": "0",
- "item_type": "0",
- "item_hidden": "0",
- "item_unpublished": "0",
- "item_delayed": "0",
- "item_pending_remove": "0",
- "item_blocked": "0"
- }
-
- } \ No newline at end of file
diff --git a/doc/en/api/api_posting.bb b/doc/en/api/api_posting.bb
deleted file mode 100644
index c708ad143..000000000
--- a/doc/en/api/api_posting.bb
+++ /dev/null
@@ -1,24 +0,0 @@
-[b][size=xx-large]Posting to the Matrix via the API[/size][/b]
-
-The API allows you to post to the red# by HTTP POST request. Below you see an example using the command line tool cURL:
-
-[code]curl -ssl -u [color=blue]$E-Mail[/color]:[color=blue]$Password[/color] -d "[color=blue]$Parameters[/color]" [url][observer=1][observer.baseurl][/observer][observer=0]example.com[/observer]/api/statuses/update
-[/url][/code]
-[table][tr][td]$E-Mail:[/td][td]The E-Mail Address you use to login, or the channel nickname (without the hostname)[/td][/tr]
-[tr][td]$Password:[/td][td]The Password you use to login[/td][/tr]
-[tr][td]$Parameters:[/td][td]That's the interesting part, here you insert the content you want to send using the following parameters:[/td][/tr][/table]
-
-[ul]
-[*]title: the title of the posting
-[*]channel: the channel you want to post to (do not use this parameter with HTTP Basic auth)
-[*]category: a comma-seperated list of categories for the posting
-[*]status: the content of the posting, formatted with BBCode
- OR
-[*]htmlstatus:the content of the posting, formatted in HTML.
-[/ul]
-
-To post to a specific channel, replace the email address with the channel nickname. If you supply the channel parameter, it has to match the "email", but is superfluous anyway.
-
-Instead of calling [observer=1][observer.baseurl][/observer][observer=0]example.com[/observer]/api/statuses/update which returns a json (you could also add .json on the end to clarify) output, you can use [observer.baseurl]/api/statuses/update.xml to get an xml formatted return.
-
-Instead of Basic HTTP Authentification you could also use oAuth.
diff --git a/doc/en/api/api_xchan.md b/doc/en/api/api_xchan.md
deleted file mode 100644
index d2b15e04c..000000000
--- a/doc/en/api/api_xchan.md
+++ /dev/null
@@ -1,44 +0,0 @@
-API xchan
-=========
-
-An xchan is a global location independent channel and is the primary record for a network
-identity. It may refer to channels on other websites, networks, or services.
-
-GET /api/z/1.0/xchan
-
-Required: one of [ address, hash, guid ] as GET parameters
-
-Returns a portable xchan structure
-
-Example: https://xyz.macgirvin.com/api/z/1.0/xchan?f=&address=mike@macgirvin.com
-
-Returns:
-
- {
- "hash": "jr54M_y2l5NgHX5wBvP0KqWcAHuW23p1ld-6Vn63_pGTZklrI36LF8vUHMSKJMD8xzzkz7s2xxCx4-BOLNPaVA",
- "guid": "sebQ-IC4rmFn9d9iu17m4BXO-kHuNutWo2ySjeV2SIW1LzksUkss12xVo3m3fykYxN5HMcc7gUZVYv26asx-Pg",
- "guid_sig": "Llenlbl4zHo6-g4sa63MlQmTP5dRCrsPmXHHFmoCHG63BLq5CUZJRLS1vRrrr_MNxr7zob_Ykt_m5xPKe5H0_i4pDj-UdP8dPZqH2fqhhx00kuYL4YUMJ8gRr5eO17vsZQ3XxTcyKewtgeW0j7ytwMp6-hFVUx_Cq08MrXas429ZrjzaEwgTfxGnbgeQYQ0R5EXpHpEmoERnZx77VaEahftmdjAUx9R4YKAp13pGYadJOX5xnLfqofHQD8DyRHWeMJ4G1OfWPSOlXfRayrV_jhnFlZjMU7vOdQwHoCMoR5TFsRsHuzd-qepbvo3pzvQZRWnTNu6oPucgbf94p13QbalYRpBXKOxdTXJrGdESNhGvhtaZnpT9c1QVqC46jdfP0LOX2xrVdbvvG2JMWFv7XJUVjLSk_yjzY6or2VD4V6ztYcjpCi9d_WoNHruoxro_br1YO3KatySxJs-LQ7SOkQI60FpysfbphNyvYMkotwUFI59G08IGKTMu3-GPnV1wp7NOQD1yzJbGGEGSEEysmEP0SO9vnN45kp3MiqbffBGc1r4_YM4e7DPmqOGM94qksOcLOJk1HNESw2dQYWxWQTBXPfOJT6jW9_crGLMEOsZ3Jcss0XS9KzBUA2p_9osvvhUKuKXbNztqH0oZIWlg37FEVsDs_hUwUJpv2Ar09k4",
- "pubkey": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7QCwvuEIwCHjhjbpz3Oc\ntyei/Pz9nDksNbsc44Cm8jxYGMXsTPFXDZYCcCB5rcAhPPdZSlzaPkv4vPVcMIrw\n5cdX0tvbwa3rNTng6uFE7qkt15D3YCTkwF0Y9FVZiZ2Ko+G23QeBt9wqb9dlDN1d\nuPmu9BLYXIT/JXoBwf0vjIPFM9WBi5W/EHGaiuqw7lt0qI7zDGw77yO5yehKE4cu\n7dt3SakrXphL70LGiZh2XGoLg9Gmpz98t+gvPAUEotAJxIUqnoiTA8jlxoiQjeRK\nHlJkwMOGmRNPS33awPos0kcSxAywuBbh2X3aSqUMjcbE4cGJ++/13zoa6RUZRObC\nZnaLYJxqYBh13/N8SfH7d005hecDxWnoYXeYuuMeT3a2hV0J84ztkJX5OoxIwk7S\nWmvBq4+m66usn6LNL+p5IAcs93KbvOxxrjtQrzohBXc6+elfLVSQ1Rr9g5xbgpub\npSc+hvzbB6p0tleDRzwAy9X16NI4DYiTj4nkmVjigNo9v2VPnAle5zSam86eiYLO\nt2u9YRqysMLPKevNdj3CIvst+BaGGQONlQalRdIcq8Lin+BhuX+1TBgqyav4XD9K\nd+JHMb1aBk/rFLI9/f2S3BJ1XqpbjXz7AbYlaCwKiJ836+HS8PmLKxwVOnpLMbfH\nPYM8k83Lip4bEKIyAuf02qkCAwEAAQ==\n-----END PUBLIC KEY-----\n",
- "photo_mimetype": "image/jpeg",
- "photo_l": "https://xyz.macgirvin.com/photo/350b74555c04429148f2e12775f6c403-4",
- "photo_m": "https://xyz.macgirvin.com/photo/350b74555c04429148f2e12775f6c403-5",
- "photo_s": "https://xyz.macgirvin.com/photo/350b74555c04429148f2e12775f6c403-6",
- "address": "mike@macgirvin.com",
- "url": "https://macgirvin.com/channel/mike",
- "connurl": "https://macgirvin.com/poco/mike",
- "follow": "https://macgirvin.com/follow?f=&url=%s",
- "connpage": "https://macgirvin.com/connect/mike",
- "name": "Mike Macgirvin",
- "network": "zot",
- "instance_url": "",
- "flags": "0",
- "photo_date": "2012-12-06 05:06:11",
- "name_date": "2012-12-06 04:59:13",
- "hidden": "1",
- "orphan": "0",
- "censored": "0",
- "selfcensored": "0",
- "system": "0",
- "pubforum": "0",
- "deleted": "0"
- } \ No newline at end of file
diff --git a/doc/en/api/group.md b/doc/en/api/group.md
deleted file mode 100644
index 8829ff416..000000000
--- a/doc/en/api/group.md
+++ /dev/null
@@ -1,41 +0,0 @@
-API group
-=========
-
-GET /api/z/1.0/group
-
-
-Description: list privacy groups
-
-
-Returns: DB tables of all privacy groups.
-
-To use with API group_members, provide either 'group_id' from the id element returned in this call, or 'group_name' from the gname returned in this call.
-
-
- [
-
- {
- "id": "1",
- "hash": "966c946394f3e2627bbb8a55026b5725e582407098415c02f85232de3f3fde76Friends",
- "uid": "2",
- "visible": "0",
- "deleted": "0",
- "gname": "Friends"
- },
- {
- "id": "2",
- "hash": "852ebc17f8c3ed4866f2162e384ded0f9b9d1048f93822c0c84196745f6eec66Family",
- "uid": "2",
- "visible": "1",
- "deleted": "0",
- "gname": "Family"
- },
- {
- "id": "3",
- "hash": "cc3cb5a7f9818effd7c7c80a58b09a189b62efa698a74319117babe33ee30ab9Co-workers",
- "uid": "2",
- "visible": "0",
- "deleted": "0",
- "gname": "Co-workers"
- }
- ] \ No newline at end of file
diff --git a/doc/en/api/statuses_update.bb b/doc/en/api/statuses_update.bb
deleted file mode 100644
index acad440de..000000000
--- a/doc/en/api/statuses_update.bb
+++ /dev/null
@@ -1,23 +0,0 @@
-[h2]statuses/update[/h2]
-Parameters
-
- title: Title of the status
- status: Status in text [or bbcode] format
- htmlstatus: Status in HTML format
- in_reply_to_status_id
- lat: latitude
- long: longitude
- media: image data
- source: Application name
- group_allow
- contact_allow
- group_deny
- contact_deny
-
-
-Example
-
-[code]
-curl -u theUsername:thePassword http://mywebsite/api/statuses/update.xml -d status='Hello world'
-[/code]
-
diff --git a/doc/en/campaign.bb b/doc/en/campaign.bb
deleted file mode 100644
index dddc614f9..000000000
--- a/doc/en/campaign.bb
+++ /dev/null
@@ -1,237 +0,0 @@
-[b]Initial Indiegg pitch[/b]
-
-[b][color= grey][size=20]What have we done, and what we hope to achieve[/size][/color][/b]
-
-[b][color= grey][size=18]Single-click sign on, nomadic identity, censorship-resistance, privacy, self-hosting[/size][/color][/b]
-
-We started $Projectname project by asking ourselves a few questions:
-
-- Imagine if it was possible to just access the content of different web sites, without the need to enter usernames and passwords for every site. Such a feature would permit Single-Click user identification: the ability to access sites simply by clicking on links to remote sites.
-Authentication just happens automagically behind the scenes. Forget about remembering multiple user names with multiple passwords when accessing different sites online.
-
-We liked this idea and went ahead with coding it immediately. Today, single-click sign is in alpha state. It needs more love, which means a solid three months of full-time development efforts.
-
-- Think of your Facebook, Twitter, WordPress, or any other website where you currently have an account. Now imagine being able to clone your account, to make an exact duplicate of it (with all of your friends, posts and settings), then export your cloned account into another server that is part of this communication network. After you're done, both of your accounts are synced from the time they were cloned. It doesn't matter where you log in (at your original location, or where you imported your clone). You see the same content, the same friends, posts, and account settings.
-At that point, it is more appropriate to call your account an identity that is nomadic (it is not tied to one home, unless you choose to do so!).
-It's 2013, our online presence no longer has to be tied to a single server, domain name or IP address. We should be able to clone and import our identities to other servers. In such a network, it should only matter who you are, not where you are.
-
-We're very intrigued by the possibilities nomadic identities open up for freedom, censorship-resistance, and identity resilience. Consider the following scenarios:
-
- -- Should a repressive government or corporation decide to delete your account, your cloned identity lives on, because it is located on another server, across the world, which is part of the same communication network. You can't be silenced!
-
- -- What if there is a server meltdown, and your identity goes off line. No problem, you log into your clone and all is good.
-
- -- Your server administrator can no longer afford to keep paying to support a free service (a labor love and principle, which all of us have participating in as system administrators of Friendica sites!). She notifies you that you must clone your account before the shutoff date. Rather than loose all your friends, and start from scratch by creating a new identity somewhere, you clone and move to another server.
-We feel this is especially helpful for the free web, where administrators of FOSS community sites are often faced with difficult financial decisions. Since many of them rely on donations, sometimes servers have to be taken offline, when costs become prohibitive for the brave DIY souls running those server. Nomadic identities should relieve some of the pressures associated with such situations.
-
-At the same time, we are also thinking of solutions that would make it possible for people running Red hubs to be financially sustainable. To that end, we're starting to implement service classes in our code, which would allow administrators to structure paid levels of service, if they choose to do so.
-
-Today, nomadic identity is currently in alpha state. It also needs more love, which means a solid three months of full-time development efforts.
-
-- Imagine a social network that is censorship-resistant, and privacy-respecting by design. It is not controlled by one mega-corporation, and where users cannot be easily censored by oppressive governments. So, in addition to nomadic identities, we are talking about decentralization, open source, freely software, that can run on any hardware that supports a database and a modern web browser. And we mean &quot;any hardware&quot;, from a self-hosted $35 Raspberry Pi, to the very latest Intel Xeon and AMD Bulldozer-powered server behemoths.
-
-We've realized that privacy requires full control over content. We should be able to delete, backup and download all of our content, as well as associated account/identity information. To this end, we have already implemented the initial version of account export and backup.
-
-Concerned about pages and pages of posts from months and years past? The solution should be simple: visit your settings page, specify that all content older than 7 days, with the exception of starred posts, should be automatically deleted. Done, the clutter is gone! (Consider also the privacy and anti-mass surveillance implications of this feature. PRISM disclosures have hinted that three-letter spying agencies around the world are recording all internet traffic and storing it for a few days at a time. We feel that automatic post expiration becomes a rather useful feature in this context, and implementing it is one of our near future priorities.)
-
-[b][color= grey][size=18]The Affinity Slider and Access Control Lists[/size][/color][/b]
-
-- What if the permissions and access control lists that help secure modern operating systems were extended into a communication network that lived on the internet? This means somebody could log into this network from their home site, and with the simple click of a few buttons dynamically sort who can have access to their online content on a very fine level: from restricting others from seeing your latest blog post, to sharing your bookmarks with the world.
-
-We've coded the initial version of such a new feature. It is called the &quot;Affinity Slider&quot;, and in our very-alpha user interface it looks like this.
-[img]https://friendicared.net/photo/b07b0262e3146325508b81a9d1ae4a1e-0.png[/img]
-
-{INSERT SCREENSHOT OF A MATRIX PAGE}
-
-Think of it as an easy way to filter content that you see, based on the degree of &quot;closeness&quot; to you. Move the slider to Friends, and only content coming from contacts you've tagged as friends is displayed on your home page. Uncluttering thousands of contacts, friends, RSS feeds, and other content should be a basic feature of modern communication on the web, but not at the expense of ease of use.
-
-In addition to the Affinity Slider, we also have the ACL (Access Control List). Say you want to share something with only 5 of your contacts (a blog, two friends from college, and two forums). You click on the padlock, choose the recipients, and that's it. Only those identities will recieve their posts. Furthermore, the post will be encrypted via PKI (pulic key encryption) to help maintain privacy. In the age of PRISM, we don't know all the details on what's safe out there, but we still think that privacy by design should be automatically present, invisible to the user, and easy to use.
-Attaching permissions to any data that lives on this network, potentially solves a great many headaches, while achieving simplicity in communication.
-
-Think of it this way: the internet is nothing, but a bunch of permissions and a folder of data. You, the user controls the permissions and thus the data that is relevant to you.
-
-[b][color= grey][size=20]The Matrix is Born![/size][/color][/b]
-
-After asking and striving to answer a number of such questions, we realized that we were imagining a general purpose communication network with a number of unique, and potentially game-changing, features. We called it $Projectname and started thinking of it as an over-lay on top of the internet as it exists today; an operating system re-invented as a communication network, with its own permissions, access control lists, protocol, connectors to others services, and open-ended possibilities via its API. The sum of the matrix is greater than it's parts. We're not building website, but a way for websites to link together and grow into something that is unique and ever-changing, with autonomy and privacy.
-
-It's a lot of work, for anyone. So far, we've got a team of a handful of volunteers, code geeks, brave early adopters, system administrators and other good people, willing to give the project a shot. We're motivated by our commitment to a free web, where privacy is built-in, and corporations don't have a stranglehold on our daily communication.
-
-We need your help to finish it and release it to the world!
-
-[b][color= grey][size=20]What have we written so far[/size][/color][/b]
-
-As of the today, $Projectname is in developer preview (alpha) state. It is not ready for everyday use, but some of the initial set of core features are implemented (again, in alpha state). These include:
-
-- Zot, the protocol powering the matrix
-- Single-signon logins.
-- Nomadic identities
-- Basic content manipulation: creation, deletion, rudimentary handling of photos, and media files
-- A bare-bones outline of the API and user documentation.
-
-
-[b][color= grey][size=20]Our TO-DO List[/size][/color][/b]
-
-However, in addition to finishing and polishing the above, there are a number of features that have to implemented to make $Projectname ready for daily use. If we meet our fundraising goal, we hope to dive into the following road map, by order of priority:
-
-- A professionally designed user interface (UI), interface that is adaptive to any user level, from end users who want to use the Matrix as a social network, to tinkerers who will put together a customized blog using Comanche, to hackers who will develop and extend the matrix using a built-in code editor, that hooks to the API and the git.
-
-- Comanche, our new markup language, similar to BBCode, with which to create elaborate and complex web pages by assembling them from a series of components - some of which are pre-built and others which can be defined on the fly. You can read more about it on our github wiki: https://github.com/friendica/red/wiki/Comanche
-
-- A unique help system that lives in the matrix, but is not based on the principles of a search engine. We have some interesting ideas about decentralizing help documentation, without going down the road of distributed search engines. Here's a hint: We shouldn't be searching at all, we should just be filtering what's already there in new, and cunning ways.
-
-- An appropriate logo, along with professionally done documentation system, both for our API, as well as users.
-
-- WordPress-like single button software upgrades
-
-- A built-in development environment, using an integrated web-based code editor such as Ace9
-
-[b][color= grey][size=20]What will the money be used for[/size][/color][/b]
-
-If we raise our targeted amount of funds, we plan to use it as follows:
-
-1) Fund 6 months {OR WHATEVER} of full time work for our current core developers, Mike, Thomas, and Tobias {ANYONE ELSE?]
-
-2) Pay a professional web developer to design an kick ass reference theme, along with a project logo.
-
-3) {WHAT ELSE?}
-
-[b][color= grey][size=20]Deadlines[/size][/color][/b]
-
-[b]March, 2014: $Projectname Beta with the following features[/b]
-
-- {LIST FEATURES}
-
-[b][color= grey][size=20]Who We Are[/size][/color][/b]
-
-Mike: {FILL IN BIO, reference Friendica, etc.}
-
-Thomas: {bio blurb}
-
-Tobias: {bio blurb}
-
-Arto: {documentation, etc.}
-
-{WHO ELSE? WE NEED A TEAM, AT LEAST 3-4 PEOPLE}
-
-[b][color= grey][size=20]What Do I Get as a Supporter?[/size][/color][/b]
-
-Our ability to reach 1.0 stable release depends on your generosity and support. We appreciate your help, regardless of the amount! Here's what we're thinking as far as different contribution levels go:
-
-[b]$1: {CATCHY TAGLINE}[/b]
-
-We'll list your name on our initial supporters list, a Hall of Fame of the matrix!
-
-[b]$5:[/b]
-
-[b]$10: [/b]
-
-[b]$16: [/b]
-
-You get one of your $Projectname t-shirts, as well as our undying gratitude.
-
-[b]$32: [/b]
-
-[b]$64 [/b]
-
-[b]128 [/b]
-
-[b]$256: [/b]
-
-[b]$512: [/b]
-
-[b]$1024 [/b]
-
-[b]$2048[/b]
-
-Each contributor at this level gets their own $Projectname virtual private server, installed, hosted and supported by us for a period of 1 year.
-
-[b][color= grey][size=20]Why are we so excited about $Projectname?[/size][/color][/b]
-
-{SOMETHING ABOUT THE POTENTIAL IMPACT OF RED, ITS INNOVATIONS, ETC&gt;
-
-[b][color= grey][size=20]Other Ways to Help[/size][/color][/b]
-
-We're a handful of volunteers, and we understand that not everyone can contribute by donating money. There are many other ways you can in getting the Matrix to version 1.0!
-
-First, you can checkout our source code on github: https://framagit.org/hubzilla/core/
-
-Maybe you can dive in and help us out with some development.
-
-Second, you can install the current developer preview on a server and start compiling bug reports.
-
-Third, register at one of the public alpha Red hubs, and get a feel for what Red is trying to do!
-
-Perhaps you're good at writing and documenting stuff. Grab an account at one of the public alphas and give us a hand.
-
-[b][color= grey][size=20]Frequently Asked Questions[/size][/color][/b]
-
-[b]1. Is Red a social network?[/b]
-
-$Projectname is not a social network. We're thinking of it as a general purpose communication network, with sharing, and public/private communications built into the matrix.
-
-[b]2. What is the difference between Red and Friendica?[/b]
-
-What's the difference between a passport, and a postcard?
-
-Friendica is really, really good at sending postcards. It can do all sorts of things with postcards. It can send them to your friends. It can send them to people you don't know. It can put them in an envelope and send them privately. It can run them through a photocopier and plaster them all over the internet. It can even take postcards in one language and convert them to many others so your friends who speak a different language can read them.
-
-What Friendica can't do, is wave a postcard at somebody and expect them to believe that holding this postcard prove you are who you say you are. Sure, if you've been sending somebody postcards, they might accept that it is you in the picture, but somebody who has never heard of you will not accept ownership of a postcard as proof of identity.
-
-$Projectname offers a passport.
-
-You can still use it to send postcards. At the same time, when you wave your passport at somebody, they do accept it as proof of identity. No longer do you need to register at every single site you use. You already have an account - it's just not necessarily at our site - so we'll ask to see your passport instead.
-
-Once you've proven your identity, a Red hub lets you use our services, as though you'd registered with directly, and we'd verified your credentials as would have happened in the olden days. These resources can, of course, be anything at all.
-
-[b]2. Why did you choose PHP, instead of Ruby or Python?[/b]
-
-The reference implementation is in PHP. We chose PHP, because it is available everywhere, and is easily configurable. We understand the debates between proponents and opponents of PHP, Ruby and Python. Nothing prevents implementations of Zot and the matrix in those languages. In fact, people on the matrix have already started developing a version of Red in Python [SOURCE?], and there is talk about future implementations in C (aiming for blazing native performance) and Java. It's free and open source, so we feel it's only a matter of time, once Red is initially completed.
-
-[b]4. Other than PHP, what other technology does Red use?[/b]
-
-We use MySQL as our database (this include any forks such as, MariaDB or Percona), and any modern webserver (Apache, nginx, etc.).
-
-[b]5. How is the Affinity Slider different from Mozilla's Persona?[/b]
-{COMPLETE}
-
-[b]6. Does $Projectname use encryption? Details please![/b]
-
-Yes, we do our best to use free and open source encryption libraries to help achieve privacy from general, mass surveillance.
-
-Communication between web browsers and Red hubs is encrypted using SSL certificates.
-
-Private communication on the matrix is protected by AES symmetric encryption, which is itself protected by RSA PKI (public key encryption). By default, we use AES-256-CBC, and our RSA keys are set to 4096-bits.
-
-For more info on our initial implementation of encrypted communication, check out our source code at Github: https://github.com/friendica/red/blob/master/include/crypto.php
-
-[b]7. What do you mean by decentralization? [/b]
-
-
-[b]8. Can I build my own website with in $Projectname?[/b]
-
-Yes. The short explanation: We've got this spiffy idea we're calling &quot;Comanche&quot;, which will allow non-programmers to build complete custom websites, and any such website will be able to connect to any other website or channel in the matrix. The goal of Comanche is to hide the technical complexities of communicating in the matrix, while encouraging people to use their creativity and put together their own unique presence on the matrix.
-
-The longer explanation: Comanche is a markup language, similar to bbcode, with which to create elaborate and complex web pages by assembling them from a series of components - some of which are pre-built and others which can be defined on the fly. Comanche uses a Page Description Language file (&quot;.pdl&quot;, pronounced &quot;puddle&quot;) to create these pages. Bbcode is not a requirement; an XML PDL file could also be used. The tag delimiters would be different. Usage is the same.
-
-Additional information is available on our Github project wiki: https://github.com/friendica/red/wiki/Comanche
-
-Comanche is another one of our priorities for the next six months.
-
-[b]9. Where can I see some technical description of Zot?[/b]
-
-Our github wiki contains a number of high-level and technical descriptions of Zot, Comanche, and Red in general: https://github.com/friendica/red/wiki
-
-[b]10. What happens if you raise more than {TARGETED NUMBER}?[/b]
-
-Raising more than our initial goal of funds, will speed up our development efforts. More developers will be able to take time off from other jobs, and concentrate efforts on finishing Red.
-
-[b]11 Can I make a contribution via Bitcoin?[/b]
-
-{YES/NO}
-
-[b]12. I have additional Questions[/]
-
-Awesome. We'd be more than happy to chat. You can find us {HERE}
-
-#include doc/macros/main_footer.bb;
-
diff --git a/doc/en/checking_account_quota_usage.bb b/doc/en/checking_account_quota_usage.bb
deleted file mode 100644
index 7612d03d8..000000000
--- a/doc/en/checking_account_quota_usage.bb
+++ /dev/null
@@ -1,20 +0,0 @@
-[b]Checking your account quota usage (service limits usage)[/b]
-
-Your hub might implement service class limits, assigning limits to the total size of file, photo, channels, top-level posts, etc., that can be created by an account holder for a specific service level.
-
-Here's how you can quickly check how much of your assigned quota you're currently using:
-
-[b]Check file storage quota levels[/b]
-Visit the following URL in your browser:
-[observer=1][observer.baseurl]/filestorage/[observer.webname][/observer]
-[observer=0]example.com/filestorage/username[/observer]
-
-[b]Check uploaded photos storage quota levels[/b]
-[observer=1][observer.baseurl]/photos/[observer.webname][/observer]
-[observer=0]example.com/photos/username[/observer]
-
-Example:
-[observer=1][observer.baseurl]/filestorage/[observer.webname][/observer]
-[observer=0]example.com/filestorage/username[/observer]
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/comanche.bb b/doc/en/comanche.bb
deleted file mode 100644
index faf7e695e..000000000
--- a/doc/en/comanche.bb
+++ /dev/null
@@ -1,261 +0,0 @@
-[b]Comanche Page Description Language[/b]
-
-Comanche is a markup language similar to bbcode with which to create elaborate and complex web pages by assembling them from a series of components - some of which are pre-built and others which can be defined on the fly. Comanche uses a Page Decription Language to create these pages.
-
-Comanche primarily chooses what content will appear in various regions of the page. The various regions have names and these names can change depending on what layout template you choose.
-
-[b]Page Templates[/b]
-Currently there are five layout templates, unless your site provides additional layouts.
-
-[code]
- [b]default[/b]
- The default template defines a &quot;nav&quot; region across the top, &quot;aside&quot; as a fixed width sidebar,
- &quot;content&quot; for the main content region, and &quot;footer&quot; for a page footer.
-
-
- [b]full[/b]
- The full template defines the same as the default template with the exception that there is no &quot;aside&quot; region.
-
-
- [b]choklet[/b]
- The choklet template provides a number of fluid layout styles which can be specified by flavour:
-
- (default flavour) - a two column layout similar to the "default" template, but more fluid
- bannertwo - a two column layout with a banner region, compatible with the "default" template on small displays
- three - three column layout (adds a "right_aside" region to the default template)
- edgestwo - two column layout with fixed side margins
- edgesthree - three column layout with fixed side margins
- full - three column layout with fixed side margins and adds a "header" region beneath the navigation bar
-
- [b]redable[/b] (sic)
- A template for reading longer texts full screen (so without navigation bar). Three columns: aside, content and right_aside.
- For maximum readability it is advised to only use the middle content column.
-
- [b]zen[/b]
- Gives you the freedom to do everything yourself. Just a blank page with a content region.
-
-[/code]
-
-To choose a layout template, use the 'template' tag.
-
-[code]
- [template]full[/template]
-
-[/code]
-
-To choose the "choklet" template with the "three" flavour:
-
-[code]
- [template=three]choklet[/template]
-
-[/code]
-
-The default template will be used if no other template is specified. The template can use any names it desires for content regions. You will be using 'region' tags to decide what content to place in the respective regions.
-
-Three &quot;macros&quot; have been defined for your use.
-[code]
- $htmlhead - replaced with the site head content.
- $nav - replaced with the site navigation bar content.
- $content - replaced with the main page content.
-
-[/code]
-
-By default, $nav is placed in the &quot;nav&quot; page region and $content is placed in the &quot;content&quot; region. You only need to use these macros if you wish to re-arrange where these items appear, either to change the order or to move them to other regions.
-
-
-To select a theme for your page, use the 'theme' tag.
-[code]
- [theme]suckerberg[/theme]
-
-[/code]
-This will select the theme named &quot;suckerberg&quot;. By default your channel's preferred theme will be used.
-
-[code]
- [theme=passion]suckerberg[/theme]
-
-[/code]
-This will select the theme named &quot;suckerberg&quot; and select the &quot;passion&quot; schema (theme variant). Alternatively it may be possible to use a condensed theme notation for this.
-
-[code]
- [theme]suckerberg:passion[/theme]
-
-[/code]
-
-The condensed notation isn't part of Comanche itself but is recognised by $Projectname platform as a theme specifier.
-
-[b]Regions[/b]
-Each region has a name, as noted above. You will specify the region of interest using a 'region' tag, which includes the name. Any content you wish placed in this region should be placed between the opening region tag and the closing tag.
-
-[code]
- [region=htmlhead]....content goes here....[/region]
- [region=aside]....content goes here....[/region]
- [region=nav]....content goes here....[/region]
- [region=content]....content goes here....[/region]
-
-[/code]
-
-[b]CSS and Javascript[/b]
-We have the possibility to include javascript and css libraries in the htmlhead region. At present we make use of jquery (js), bootstrap (css/js) and foundation (css/js).
-This will overwrite the selected themes htmlhead.
-
-[code]
- [region=htmlhead]
- [css]bootstrap[/css]
- [js]jquery[/js]
- [js]bootstrap[/js]
- [/region]
-
-[/code]
-
-[b]Menus and Blocks[/b]
-Your webpage creation tools allow you to create menus and blocks, in addition to page content. These provide a chunk of existing content to be placed in whatever regions and whatever order you specify. Each of these has a name which you define when the menu or block is created.
-
-[code]
- [menu]mymenu[/menu]
-
-[/code]
-This places the menu called &quot;mymenu&quot; at this location on the page, which must be inside a region.
-
-[code]
- [menu=horizontal]mymenu[/menu]
-
-[/code]
-This places the menu called &quot;mymenu&quot; at this location on the page, which must be inside a region. Additionally it applies the "horizontal" class to the menu. "horizontal" is defined in the redbasic theme. It may or may not be available in other themes.
-
-[code]
- [menu][var=wrap]none[/var]mymenu[/menu]
-
-[/code]
-The variable [var=wrap]none[/var] in a block removes the wrapping div element from the menu.
-
-[code]
- [block]contributors[/block]
-[/code]
-This places a block named &quot;contributors&quot; in this region.
-
-[code]
- [block=someclass]contributors[/block]
-
-[/code]
-This places a block named &quot;contributors&quot; in this region. Additionally it applies the &quot;someclass&quot; class to the block. This replaces the default block classes &quot;bblock widget&quot;.
-
-[code]
- [block][var=wrap]none[/var]contributors[/block]
-
-[/code]
-The variable [var=wrap]none[/var] in a block removes the wrapping div element from the block.
-
-[b]Widgets[/b]
-Widgets are executable apps provided by the system which you can place on your page. Some widgets take arguments which allows you to tailor the widget to your purpose. (TODO: list available widgets and arguments). The base system provides
-
-[code]
- profile - widget which duplicates the profile sidebar of your channel page. This widget takes no arguments
- tagcloud - provides a tag cloud of categories
- count - maximum number of category tags to list
-
-[/code]
-
-Widgets and arguments are specified with the 'widget' and 'var' tags.
-[code]
- [widget=recent_visitors][var=count]24[/var][/widget]
-
-[/code]
-
-This loads the &quot;recent_visitors&quot; widget and supplies it with the argument &quot;count&quot; set to &quot;24&quot;.
-
-[b]Comments[/b]
-The 'comment' tag is used to delimit comments. These comments will not appear on the rendered page.
-
-[code]
- [comment]This is a comment[/comment]
-
-[/code]
-
-[b]Conditional Execution[/b]
-You can use an 'if' construct to make decisions. These are currently based on system configuration variable or the current observer.
-
-[code]
- [if $config.system.foo]
- ... the configuration variable system.foo evaluates to 'true'.
- [else]
- ... the configuration variable system.foo evaluates to 'false'.
- [/if]
-
- [if $observer]
- ... this content will only be show to authenticated viewers
- [/if]
-
-[/code]
-
- The 'else' clause is optional.
-
- Several tests are supported besides boolean evaluation.
-
-[code]
- [if $config.system.foo == bar]
- ... the configuration variable system.foo is equal to the string 'bar'
- [/if]
- [if $config.system.foo != bar]
- ... the configuration variable system.foo is not equal to the string 'bar'
- [/if]
- [if $config.system.foo {} bar ]
- ... the configuration variable system.foo is a simple array containing a value 'bar'
- [/if]
- [if $config.system.foo {*} bar]
- ... the configuration variable system.foo is a simple array containing a key named 'bar'
- [/if]
-[/code]
-
-[b]Complex Example[/b]
-[code]
- [comment]use an existing page template which provides a banner region plus 3 columns beneath it[/comment]
-
- [template]3-column-with-header[/template]
-
- [comment]Use the &quot;darknight&quot; theme[/comment]
-
- [theme]darkknight[/theme]
-
- [comment]Use the existing site navigation menu[/comment]
-
- [region=nav]$nav[/region]
-
- [region=side]
-
- [comment]Use my chosen menu and a couple of widgets[/comment]
-
- [menu]myfavouritemenu[/menu]
-
- [widget=recent_visitors]
- [var=count]24[/var]
- [var=names_only]1[/var]
- [/widget]
-
- [widget=tagcloud][/widget]
- [block]donate[/block]
-
- [/region]
-
-
-
- [region=middle]
-
- [comment]Show the normal page content[/comment]
-
- $content
-
- [/region]
-
-
-
- [region=right]
-
- [comment]Show my condensed channel &quot;wall&quot; feed and allow interaction if the observer is allowed to interact[/comment]
-
- [widget]channel[/widget]
-
- [/region]
-
-[/code]
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/context/de/admin/logs/help.html b/doc/en/context/de/admin/logs/help.html
deleted file mode 100644
index 1441d9075..000000000
--- a/doc/en/context/de/admin/logs/help.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Allgemein</dt>
- <dd>Auf dieser Seite können die Einstellungen zur Protokollierung angepasst und vorhandene Protokolle angezeigt werden.</dd>
- <dt>Protokoll Einstellungen</dt>
- <dd>Bei aktivierter Debugging-Option werden System-Log Informationen an die Datei in der <b>Protokolldatei</b> im EIngabefeld hinzugefügt. Der Pfad ist relativ zum Hub-Wurzelverzeichnis. Daauf achten, dass diese Datei vom Webserver geschrieben werden kann.</dd>
- <dt>Protokollstufe</dt>
- <dd>Die Protokollstufe bestimmt, wie viele Informationen an die Protokolldatei angefügt werden. Warnung: Eine hohe Protokollstufe kann sehr schnell die Größe der Protokolldatei anwachsen lassen, vor allem, wenn der Hub viele Mitglieder hat.</dd>
-</dl>
diff --git a/doc/en/context/de/admin/queue/help.html b/doc/en/context/de/admin/queue/help.html
deleted file mode 100644
index e59fdcaee..000000000
--- a/doc/en/context/de/admin/queue/help.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Allgemein</dt>
- <dd>Die Warteschlangenstatistik zeigt, wie viele Nachrichten zur Auslieferung an andere Hubs noch in der Warteschlange stehen. Die Priorität steht in Beziehung mit der Anzahl fehlgeschlagener Auslieferungsversuche.</dd>
-</dl>
diff --git a/doc/en/context/de/admin/security/help.html b/doc/en/context/de/admin/security/help.html
deleted file mode 100644
index 6465e8f0e..000000000
--- a/doc/en/context/de/admin/security/help.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Allgemein</dt>
- <dd>Auf dieser Seite können verschiedene Sicherheitseinstellungen vorgenommen werden. Um geänderte Einstellungen zu speichern, muss der Knopf "Bestätigen" gedrückt werden.</dd>
-</dl>
diff --git a/doc/en/context/de/appman/help.html b/doc/en/context/de/appman/help.html
deleted file mode 100644
index 888ee6206..000000000
--- a/doc/en/context/de/appman/help.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Allgemein</dt>
- <dd>Ändere die individuellen Eigenschaften der ausgewählten App. Kategorien erlauben dir das Einsortieren der Apps um sie in der Liste leichter zu finden. Kundenspezifische Apps, die du oder dein Administrator erstellt haben, können weitere Felder enthalten wie "Preis der App" oder "Ort (URL), um die App zu kaufen". Dies gilt nicht für Hubzilla Core Apps.</dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/de/channel/help.html b/doc/en/context/de/channel/help.html
deleted file mode 100644
index fbfcac3c1..000000000
--- a/doc/en/context/de/channel/help.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Allgemein</dt>
- <dd>Auf dieser Seite wird der eigene aktuelle Kanal angezeigt. Dies entspricht ungefähr den Profilseiten in anderen sozialen Netzwerken. Im Kanal veröffentliche Beiträge werden gemäß den Anzeige-Berechtigungen des Betrachters angezeigt.</dd>
- <dt>Beitrag anlegen</dt>
- <dd>Falls der Betrachter/Besucher die Berechtigung zum Anlegen von Beiträgen besitzt, wird im oberen Bereich der Beitragseditor angezeigt.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#tabs-collapse-1", 0); return false;' title="Click to highlight element...">Kanal-Inhalte-Tabs</a></dt>
- <dd>Die Tabs für unterschiedliche Kanal-Inhalte zeigen die unterschiedlichen veröffentlichten Inhalte des Kanals an. The <b>Über</b> Tab verweist auf das Profil des Kanals. Der <b>Fotos</b> Tab verweist auf die Foto-Gallerien des Kanals. Der <b>Dateien</b> Tab verweist auf allgemeine vom Kanal veröffentlichte und geteilte Dateien.</dd>
-</dl>
diff --git a/doc/en/context/de/chat/help.html b/doc/en/context/de/chat/help.html
deleted file mode 100644
index ba355365f..000000000
--- a/doc/en/context/de/chat/help.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Allgemein</dt>
- <dd>Chaträume können angelegt und für die Echtzeit-Kommunikation verwendet werden. Die Zugriffskontrolle verwendet dabei das Hubzilla-Berechtigungssystem.</dd>
- <dt>Chatraum anlegen</dt>
- <dd>Über den Knopf "Neu anlegen" wird ein neuer Chatraum eröffnet. Hierfür einen Namen und angeben, wie lange Nachrichten aufbewahrt werden sollen.</dd>
- <dt>Chatten</dt>
- <dd>Die Nachricht in der der Nachrichtenbox eingeben und "Bestätigen" drücken. Über den Menüknopf neben "Bestätigen" kann ein Anwesenheitsstatus gesetzt werden. Andere Teilnehmer am Chatraum werden im Seitenpanel unter "Chatmitglieder" angezeigt.</dd>
-</dl>
diff --git a/doc/en/context/de/cloud/help.html b/doc/en/context/de/cloud/help.html
deleted file mode 100644
index e72ad1743..000000000
--- a/doc/en/context/de/cloud/help.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Allgemein</dt>
- <dd>Diese Seite zeigt die veröffentlichten und geteilten Dateien des Kanals an. Welche Dateien ein Besucher sehen kann, wird über die individuellen Dateiberechtigungen bestimmt, die vom Kanal-Besitzer bestimmt werden. Wurde die Berechtigung zum Anlegen/Hochladen von Dateien vergeben, werden die entsprechenden Steuerungsknöpfe angezeigt.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#tabs-collapse-1", 0); return false;' title="Click to highlight element...">Kanal-Inhalte-Tabs</a></dt>
- <dd>Die Tabs für unterschiedliche Kanal-Inhalte zeigen die unterschiedlichen veröffentlichten Inhalte des Kanals an. The <b>Über</b> Tab verweist auf das Profil des Kanals. Der <b>Fotos</b> Tab verweist auf die Foto-Gallerien des Kanals. Der <b>Dateien</b> Tab verweist auf allgemeine vom Kanal veröffentlichte und geteilte Dateien.</dd>
-</dl>
diff --git a/doc/en/context/de/connections/help.html b/doc/en/context/de/connections/help.html
deleted file mode 100644
index 61789ea38..000000000
--- a/doc/en/context/de/connections/help.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Allgemein</dt>
- <dd>Auf dieser Seite werden die Verbindungen des Kanals angezeigt. Diese Liste kann <a href='#' onclick='contextualHelpFocus(".section-title-wrapper", 0); return false;' title="Click to highlight element...">sortiert und gefilter werden über den Menüknopf neben dem Suchknopf</a>.</dd>
- <dt>Verbindungsdetails</dt>
- <dd>Jeder Eintrag in der Liste zeigt die Details einer Verbindung. Ein durchsichtiges (ausgegrautes) Avatar-Bild zeigt archivierte Verbindungen an (diese konnten min. 30 Tage nicht erreicht werden).</dd>
- <dt>Verbindungsstatus</dt>
- <dd>Eine Verbindung kann sich in verschiedenen Stati befinden: <ul><li>Archiviert</li><li>Ignoriert</li><li>Blockiert</li><li>Versteckt</li></ul></dd>
-</dl>
diff --git a/doc/en/context/de/connections/ifpending/help.html b/doc/en/context/de/connections/ifpending/help.html
deleted file mode 100644
index 3e55b34c3..000000000
--- a/doc/en/context/de/connections/ifpending/help.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Allgemein</dt>
- <dd>Auf dieser Seite werden Verbindungswünsche von anderen Kanälen an diesen Kanal angezeigt. Diese Liste kann <a href='#' onclick='contextualHelpFocus(".section-title-wrapper", 0); return false;' title="Click to highlight element...">sortiert und gefilter werden über den Menüknopf neben dem Suchknopf</a>.</dd>
- <dt>Verbindungsdetails</dt>
- <dd>Jeder Eintrag in der Liste zeigt die Details einer Verbindung. Ein durchsichtiges (ausgegrautes) Avatar-Bild zeigt archivierte Verbindungen an (diese konnten min. 30 Tage nicht erreicht werden).</dd>
- <dt>Verbindungsstatus</dt>
- <dd>Eine Verbindung kann sich in verschiedenen Stati befinden: <ul><li>Archiviert</li><li>Ignoriert</li><li>Blockiert</li><li>Versteckt</li></ul></dd>
-</dl>
diff --git a/doc/en/context/de/events/help.html b/doc/en/context/de/events/help.html
deleted file mode 100644
index b0dc95b3e..000000000
--- a/doc/en/context/de/events/help.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Allgemein</dt>
- <dd>Auf dieser Seite wird ein Kalender mit eigenen und von anderen verbundenen Kanälen geteilte Ereignisse angezeigt.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#title", 0); return false;' title="Click to highlight element...">Kalenderanzeige</a></dt>
- <dd>Die Monats-, Wochen- und Tagesansicht des Kalenders kann im Panel ausgewählt werden.</dd>
- <dt>Export/Import</dt>
- <dd>Kalendereinträge können Eim Standard-Format iCalendar (.ics) exportiert oder importiert werden.</dd>
-</dl>
diff --git a/doc/en/context/de/mail/help.html b/doc/en/context/de/mail/help.html
deleted file mode 100644
index b89135d8c..000000000
--- a/doc/en/context/de/mail/help.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Allgemein</dt>
- <dd>Die Privaten Nachrichten sind nur für den Kanal-Besitzer und den Empfänger sichtbar.</dd>
- <dt>Konversationen</dt>
- <dd>Über <b>Konversationen</b> kann eine vollständige fortlaufene Ansicht einer Konversation angezeigt werden. Verfügbare Konversationen werden unter dem Punkt Konversationen im Panel angezeigt.</dd>
- <dt>Eingang/Ausgang</dt>
- <dd>Einzelne versandte Nachrichten kännen über den Punkt <b>Ausgang</b> und empfangene Nachrichten über <b>Eingang</b> gefiltert dargestellt werden.</dd>
- <dt>Neue Nachricht</dt>
- <dd>Über <b>Neue Nachricht</b> kann eine neue Nachricht erstellt werden.</dd>
- <dt>Sonstiges</dt>
- <dd>Über das Menü einzelner Nachrichten können besondere Funktionen ausgelöst werden: Zustellungsbericht kann abgerufen werden, Nachrichten (die noch nicht angesehen wurden!) können widerrufen und gelöscht werden.</dd>
-</dl>
diff --git a/doc/en/context/de/network/help.html b/doc/en/context/de/network/help.html
deleted file mode 100644
index 6590f597d..000000000
--- a/doc/en/context/de/network/help.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Allgemein</dt>
- <dd>Auf der Netzwerkseite wird der Strom von Beiträgen und Konversationen angezeigt. Typischerweise wird nach kürzlich aktualisierten Beiträgen sortiert. Diese Seite kann sehr weitgehend angepasst werden.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#profile-jot-wrapper", 0); return false;' title="Click to highlight element...">Beitrag anlegen</a></dt>
- <dd>Im oberen Bereich der Seite wird eine Textbox "Teilen" angezeigt. Nach Klick in diese Box wird der Beitragseditor angezeigt. Der Beitragseditor kann angepasst werden; in den Basiseinstellungen stehen Felder für Inhalt und einem optionalen Titel bereit. Knöpfe unterhalb des Inhaltes erleichtern die Eingabe von Formatierungen, Links, Bildern und anderen Daten im Beitrag. Über die Knöpfe im rechten Bereich kann eine Vorschau aktiviert, Berechtigungen gesetzt und der Beitrag abgesendet werden.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#group-sidebar", 1); return false;' title="Click to highlight element...">Gruppen</a></dt>
- <dd>Die erstellten Gruppen werden im Panel angezeigt. Über die Gruppen kann nach Beiträgen von Gruppenmitgliedern gefiltert werden.</dd>
- <dt><a href='#' onclick='$("#dbtn-acl").click(); return false;' title="Click to highlight element...">Beitragsberechtigungen</a></dt>
- <dd>Die Zugriffskontrollliste (Access Control List; ACL) steuert, wer den Beitrag sehen darf. Nach Klick auf das Schloß neben dem Knopf "Teilen" öffnet sich ein Dialog, in dem Kanäle oder Gruppen ausgewählt werdenkönnen, die diesen Beitrag sehen dürfen. s kann auch gezielt verboten werden, dass ein Kanal oder eine Gruppe den Artikel sehen darf (nützlich beispielsweise für die Planung einer Überraschungsparty in einer Gruppe, in der der Überraschte Mitglied ist).</dd>
-</dl>
diff --git a/doc/en/context/de/photos/help.html b/doc/en/context/de/photos/help.html
deleted file mode 100644
index 437d2c369..000000000
--- a/doc/en/context/de/photos/help.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Allgemein</dt>
- <dd>Diese Seite zeigt die veröffentlichten und geteilten Fotos des Kanals an. Welche Fotos ein Besucher sehen kann, wird über die individuellen Berechtigungen bestimmt, die vom Kanal-Besitzer bestimmt werden. Wurde die Berechtigung zum Anlegen/Hochladen von Bildern vergeben, werden die entsprechenden Steuerungsknöpfe angezeigt.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#tabs-collapse-1", 0); return false;' title="Click to highlight element...">Kanal-Inhalte-Tabs</a></dt>
- <dd>Die Tabs für unterschiedliche Kanal-Inhalte zeigen die unterschiedlichen veröffentlichten Inhalte des Kanals an. The <b>Über</b> Tab verweist auf das Profil des Kanals. Der <b>Fotos</b> Tab verweist auf die Foto-Gallerien des Kanals. Der <b>Dateien</b> Tab verweist auf allgemeine vom Kanal veröffentlichte und geteilte Dateien.</dd>
-</dl>
diff --git a/doc/en/context/de/profile/help.html b/doc/en/context/de/profile/help.html
deleted file mode 100644
index ece33457d..000000000
--- a/doc/en/context/de/profile/help.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Allgemein</dt>
- <dd>Dies ist die Profilseite eines Kanals. Hier werden typischerweise Informationen angezeigt, die den Kanal beschreiben. Wenn der Kanal beispielsweise eine Person in einem sozialen Netzwerk repräsentiert, könnten Kontaktinformationen und andere persönliche Details hier veröffentlicht werden. Kanäle können mehrere Profile besitzen, die abhängig vom Betrachter/Besucher angezeigt werden.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#tabs-collapse-1", 0); return false;' title="Click to highlight element...">Kanal-Inhalte-Tabs</a></dt>
- <dd>Die Tabs für unterschiedliche Kanal-Inhalte zeigen die unterschiedlichen veröffentlichten Inhalte des Kanals an. The <b>Über</b> Tab verweist auf das Profil des Kanals. Der <b>Fotos</b> Tab verweist auf die Foto-Gallerien des Kanals. Der <b>Dateien</b> Tab verweist auf allgemeine vom Kanal veröffentlichte und geteilte Dateien.</dd>
-</dl>
diff --git a/doc/en/context/de/register/help.html b/doc/en/context/de/register/help.html
deleted file mode 100644
index 9ee062ee8..000000000
--- a/doc/en/context/de/register/help.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Allgemein</dt>
- <dd>Auf dieser Seite können sich Besucher registrieren, um mit einer Anmeldungskennung Zugang zum Portal zu erhalten. Angemeldeten Benutzern sehen nicht nur die öffentlichen Inhalte, sondern können selber Inhalte veröffentlichen und Soziale Netzwerk Kommunikationen durchführen, und sehr viel mehr.</dd>
- <dt>Arten der Registrierung</dt>
- <dd>Eine Registrierung ist mit einer eMail Adresse möglich, oder auch anonym (dann das eMail Feld nicht ausfüllen). Vielleicht haben Sie auch einen Einladungscode erhalten, der dann mit der eMail Adresse angegeben werden kann. Der Link oberhalb des Feldes eMail ermöglich die Eingabe des Einladungscodes.</dd>
- <dt>Ablauf der Registrierung</dt>
- <dd>Für Anmeldungen nach einer erfolgreichen Registrierung ist ein eigenes Kennwort festzulegen. Es ist sicherheitshalber zweimal mit identischen Werten einzugeben, weil es nicht angezeigt wird. Das Kennwort ist geheim zu halten und nur für den eigenen Gebrauch bestimmt. Anonym registrierte Benutzer erhalten eine automatisch zugewiesene Kennung, und sollten das eigene Kennwort nicht vergessen, weil es im Gegensatz zur eMail Registrierung zunächst keine Rücksetzfunktion gibt. Je nach Konfiguration der Hub-Instanz kann eine Bestätigungsfunktion erforderlich sein. Benutzer der eMail Registrierung erhalten eine entsprechende Nachricht. Bei anonymen Registrierungen wird ein weiterer Dialog angezeigt, der die Zugangskennung und eine Pin zeigt. Jene Dialogseite sollte unbedingt sicher und langfristig aufbewahrt werden (z.B. durch Ausdruck, Screenshot, Foto), weil die Daten zu einem späten Zeitpunkt noch einmal zu bestätigen sind.</dd>
- <dt>Die Digitale Identität</dt>
- <dd>Je nach Konfiguration der Hub-Instanz kann bereits bei der Registrierung (alternativ auch bei der ersten Anmeldung) ein anzeigbarer Name und ein Spitzname ("nickname") eingegeben werden. Der Nickname hat eine sehr weitreichende Bedeutung und läßt sich nachträglich nicht mehr ändern. Es handelt sich dabei um eine eindeutige Digitale Identität (DID), die mit allen eigenen Aktivitäten verknüft ist, sein wird, und bleibt. Diese DID eignet sich nicht nur zur Anmeldung in dieser Hub-Instanz, sondern auch in allen verbundenen Instanzen des Federalen Netzwerkes. Im Sprachgebrauch der Federalen Netze ist diese digitale Identität ein "Kanal". Das ist vergleichbar etwa mit einer Rufnummer im Telefonnetz. Die DID hat das Format kanal@instanz.tld = nickname@instanz.tld und ist, wie gesagt, nachträglich nicht mehr änderbar. Obwohl sich das Format wie eine eMail Adresse darstellt, handelt es sich nicht um eine solche.</dd>
- <dt>Bevor die Registrierung begonnen wird ...</dt>
- <dd>sollte (oben rechts im Hamburger Menü <span class="fa fa-fw fa-bars"> </span>) die bevorzugte Sprache (Englisch, Spanisch, Deutsch z.B.) gewählt werden. Die aktuelle Sprache wird in den Folgeschritten und auch bei und nach der Anmeldung verwendet. Das läßt sich aber jederzeit und je nach Bedarf ändern. Auch sei darauf hingewiesen, dass diese Hub-Instanz nicht die einzige ist. Eine Übersicht über andere Hub-Instanzen ist <a href="./pubsites"> hier </a> zu finden.</dd>
-</dl>
diff --git a/doc/en/context/de/settings/account/help.html b/doc/en/context/de/settings/account/help.html
deleted file mode 100644
index 03fc0cc14..000000000
--- a/doc/en/context/de/settings/account/help.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Allgemein</dt>
- <dd>
- Wenn du dich registriert und dir damit einen <i>Zugang</i> zum Grid verschafft hast, hast du ein <i>Profil</i> und einen <i>Kanal</i> erzeugt.
- </dd>
- <dt>Zugang</dt>
- <dd>
- Du hast <i>einen</i> Zugang, der sich aus deiner Emailadresse mit deinem Passwort zusammen setzt. Damit greifst du auf dein Profil und deinen Kanal zu.
- <i>Du authetifizierst dich also einmal auf einem Hub von Hubzilla, und es erlaubt dir beliebig Profile und Kanäle anzulegen und dich mit anderen Leuten zu verbinden.</i>
- </dd>
- <dt>Profil</dt>
- <dd>
- Du bist sicherlich bei anderen Services im Internet registriert, wie irgendwelchen Foren oder anderen Online Communities. Überall hinterläßt du Informationen über dich wie deinen Geburtstag, dein Alter, dein Land und Ähnliches. Im Gegensatz zu anderen Services erlaubt dir Hubzilla das Anlegen <i>vieler verschiedener Profile</i>. So kannst du ein öffentliches Profil anlegen, das zur allgemeinen Verwendung gedacht ist, ein Profil für deine Arbeitskollegen, deine Familie oder deinen Lebenspartner. <i>Deine Profile sind die grundlegende Information, die du anderen Leuten je nach Beziehung zur Verfügung stellen willst</i>.
- </dd>
- <dt>Kanal</dt>
- <dd>
- Während der Registrierung hast du deinen ersten <i>Kanal</i> erzeugt. In der Tat, neben verschiedenen Profilen kannst du auch verschiedene Kanäle haben. Das könnte etwas verwirrend sein am Anfang, aber lass uns die Dinge besser verstehen. Du hast also bereits einen Kanal erstellt, den du zum Beispiel für die breite Öffentlichkeit und für den Alltag verwenden kannst. Aber vielleicht bist du ein Bücherwurm, und viele deiner Leser langweilt das. Also eröffnest Du einen <i>zweiten Kanal</i> für Bücherfreunde, in dem ihr euch ungezwungen über Bücher austauschen könnt. Offensichtlich ergibt sich daraus ein neuer Stream von Beiträgen, womöglich mit einem neuen Profil oder neuen Profilen und komplett eigenen Verbindungen oder Kontakten. Einige finden sich in beiden Kanälen, aber die meisten werden wohl nur in einem der Kanäle mitlesen. Du wechselst zwischen den Kanälen wie im richtigen Leben je nachdem, ob du dich auf der Straße mit Leuten triffst oder mit Bücherfreunden sprichst. Im Prinzip können sich deine Kanäle auch untereinander verbinden, du also mit dir selbst sprechen, wenn du das möchtest. <i>Deine Kanäle sind also wie Räume für unterschiedliche Themen, in denen du auf verschiedene Leute triffst</i>.
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/de/settings/channel/help.html b/doc/en/context/de/settings/channel/help.html
deleted file mode 100644
index 98a388f77..000000000
--- a/doc/en/context/de/settings/channel/help.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Allgemein</dt>
- <dd>
- Wenn du dich registriert und dir damit einen <i>Zugang</i> zum Grid verschafft hast, hast du ein <i>Profil</i> und einen <i>Kanal</i> erzeugt.
- </dd>
- <dt>Zugang</dt>
- <dd>
- Du hast <i>einen</i> Zugang, der sich aus deiner Emailadresse mit deinem Passwort zusammensetzt. Damit greifst du auf dein Profil und deinen Kanal zu.
- <i>Du authetifizierst dich also einmal auf einem Hub von Hubzilla, und es erlaubt dir beliebig Profile und Kanäle anzulegen und dich mit anderen Leuten zu verbinden.</i>
- </dd>
- <dt>Profil</dt>
- <dd>
- Du bist sicherlich bei anderen Services im Internet registriert, wie irgendwelchen Foren oder anderen Online Communities. Überall hinterläßt du Informationen über dich wie deinen Geburtstag, dein Alter, dein Land und Ähnliches. Im Gegensatz zu anderen Services erlaubt dir Hubzilla das Anlegen <i>vieler verschiedener Profile</i>. So kannst du ein öffentliches Profil anlegen, das zur allgemeinen Verwendung gedacht ist, ein Profil für deine Arbeitskollegen, deine Familie oder deinen Lebenspartner. <i>Deine Profile sind die grundlegende Information, die du anderen Leuten je nach Beziehung zur Verfügung stellen willst</i>.
- </dd>
- <dt>Kanal</dt>
- <dd>
- Während der Registrierung hast du deinen ersten <i>Kanal</i> erzeugt. In der Tat, neben verschiedenen Profilen kannst du auch verschiedene Kanäle haben. Das könnte etwas verwirrend sein am Anfang, aber lass uns die Dinge besser verstehen. Du hast also bereits einen Kanal erstellt, den du zum Beispiel für die breite Öffentlichkeit und für den Alltag verwenden kannst. Aber vielleicht bist du ein Bücherwurm und viele deiner Leser langweilt das. Also eröffnest Du einen <i>zweiten Kanal</i> für Bücherfreunde, in dem ihr euch ungezwungen über Bücher austauschen könnt. Offensichtlich ergibt sich daraus ein neuer Stream von Beiträgen, womöglich mit einem neuen Profil oder neuen Profilen und komplett eigenen Verbindungen oder Kontakten. Einige finden sich in beiden Kanälen, aber die meisten werden wohl nur in einem der Kanäle mitlesen. Du wechselst zwischen den Kanälen wie im richtigen Leben je nachdem, ob du dich auf der Straße mit Leuten triffst oder mit Bücherfreunden sprichst. Im Prinzip können sich deine Kanäle auch untereinander verbinden, du also mit dir selbst sprechen, wenn du das möchtest. <i>Deine Kanäle sind also wie Räume für unterschiedliche Themen, in denen du auf verschiedene Leute triffst</i>.
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/de/settings/features/help.html b/doc/en/context/de/settings/features/help.html
deleted file mode 100644
index a019d2d60..000000000
--- a/doc/en/context/de/settings/features/help.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Allgemein</dt>
- <dd>Diese Seite ermöglicht dir viele zusätzliche Funktionen von Hubzilla einzustellen.</dd>
- <dt><a href='#' onclick='$("#general-settings-title h3 a").click(); setTimeout((function() {contextualHelpFocus("#general-settings-title", 0)}), 1000); return false;' title="Klicken um das Element anzuzeigen...">Allgemeine Funktionen</a></dt>
- <dd>Die Einstellungen für allgemeine Funktionen beinhalten Optionen, die relevant sind für deinen Kanal, zum Beispiel bei Webseiten oder Wikis.</dd>
- <dt><a href='#' onclick='$("#composition-settings-title h3 a").click(); setTimeout((function() {contextualHelpFocus("#composition-settings-title", 0)}), 1000); return false;' title="Klicken um das Element anzuzeigen...">Funktionen für die Beitragserstellung</a></dt>
- <dd>Diese Funktionen bieten weitere Optionen und Möglichkeiten für die Erstellung neuer Beiträge.</dd>
- <dt><a href='#' onclick='$("#net_module-settings-title h3 a").click(); setTimeout((function() {contextualHelpFocus("#net_module-settings-title", 0)}), 1000); return false;' title="Klicken um das Element anzuzeigen...">Filterung von Streams</a></dt>
- <dd>Diese Einstellungen verändern Funktionen, die mit der Filterung und der Ansichtskontrolle enkommender Beiträge verbunden sind.</dd>
- <dt><a href='#' onclick='$("#tools-settings-title h3 a").click(); setTimeout((function() {contextualHelpFocus("#tools-settings-title", 0)}), 1000); return false;' title="Klicken um das Element anzuzeigen...">Werkzeuge für Beiträge und Kommentare</a></dt>
- <dd>Hier findest du zusätzliche Werkzeuge um Beiträge zu kategorisieren, und erweiterte Methoden der Kommentierung wie Emojis und Community Tagging.</dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/de/settings/tokens/help.html b/doc/en/context/de/settings/tokens/help.html
deleted file mode 100644
index 4a7776109..000000000
--- a/doc/en/context/de/settings/tokens/help.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<dl class="dl-horizontal">
- <dt><a href="/help/member/member_guide#Guest_Access_Tokens">Gastzugangs-Token</a></dt>
- <dd>
- Hubzilla verwendet befristete "Wegwerf"-Logins oder "Zot Access Tokens", um das Teilen privater Informationen mit Nichtmitgliedern oder Mitgliedern im Fediverse zu ermöglichen, deren Knoten nur eingeschränkte Möglichkeiten zur Identifizierung bieten. Diese Token können zur Authentifizierung an einem Hub für den Zweck verwendet werden privilegierte oder zugangskontrollierte Ressourcen (Dateien, Fotos, Beiträge, Webseiten, Chaträume, usw.) zugänglich zu machen.
- </dd>
- <dt>Einen Token erzeugen</dt>
- <dd>
- Das Formular zur Erzeugung von Token benötigt drei Parameter: einen lesbaren Zugangsnamen, ein Passwort oder Zugangs-Token, und optional ein Verfallsdatum. Ein verfallenes Token kann nicht länger verwendet werden und wird von der Liste befristeter Zugänge entfernt. <i>Hinweis</i> Das Passwort-Feld zeigt das Token oder Passwort im Klartext.
- </dd>
- <dt>Einen Token teilen</dt>
- <dd>
- Wir geben keine Mechanismen für das Teilen der Zugangs-Token vor, jede Kommunikationsmethode ist recht. Alle Token, die du erzeugt hast, werden zu den Listen für die Zugangskontrolle hinzugefügt und können von überall verwendet werden, wo anhand dieser Listen authentifiziert werden kann.
-
- <b>Ein Beispiel:</b> Ein Besucher trifft auf deine Seite. Er hat ein von dir zur Verfügung gestelltes Zugangs-Token für eines Deiner Fotoalben, das nur von Dir und eben diesem befristeten Zugangsnamen betrachtet werden darf. Der Besucher erhält zunächst keinen Zugang.
-
- Nun wählt er "Login" aus der Navigation und erhält die Loginseite angezeigt, in die er seine Zugangsdaten eingibt. Ab sofort erhält er Zugang zum Fotoalbum.
-
- Alternativ kannst du einen Link mit deinem Besucher teilen, dessen URL um den Parameter "&zat=abc123" erweitert ist, wobei "abc123" das Zugangs-Token oder das Passwort für den befristeten Login ist. Mit dieser Erweiterung ist kein Login erforderlich, der Besucher erhält sofort den Zugang.
- </dd>
-</dl>
diff --git a/doc/en/context/de/wiki/help.html b/doc/en/context/de/wiki/help.html
deleted file mode 100644
index e203ee221..000000000
--- a/doc/en/context/de/wiki/help.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Allgemein</dt>
- <dd>Jedes Wiki ist eine Sammlung aus mit Markdown formatierten Seiten.</dd>
- <dt><a href="#" onclick='contextualHelpFocus("#wiki_list", 1); return false;' title="Click to highlight element...">Wiki Liste</a></dt>
- <dd>Wikis die dem eigenen Kanal gehören und <i>mit der Berechtigung zum Anschauen</i>, sind in der Seitenleiste gelistet.</dd>
- <dt><a href="#" onclick='contextualHelpFocus("#wiki-get-history", 0); return false;' title="Click to highlight element...">Seiten Versionen</a></dt>
- <dd>Jede Änderung einer Seite wird gespeichert, um eine schnelle Berichtigung zu ermöglichen. Klick auf das <b>Versionsgeschichte</b> Tab um den Verlauf der Seitenbearbeitung zu sehen, einschließlich Datum und Autor. Der Zurück-Knopf lädt die ausgewählte Änderung, aber ohne die Seite automatisch zu spreichern.</dd>
- <dt><a href="#" onclick='contextualHelpFocus("#wiki_page_list", 1); return false;' title="Click to highlight element...">Seiten</a></dt>
- <dd>Die Seiten des Wikis werden in <b>Wiki Seiten</b> gelistet. Vor dem Speichern einer Seite über das <b>Seiten</b> Dropdown Menu, hast Du die Möglichkeit eine <a href="#" onclick='contextualHelpFocus("#id_commitMsg", 0); return false;' title="Click to highlight element...">Zusammenfassung der Änderungen</a> einzutragen. Dieser Text wird anschließend unter <a href="#" onclick='contextualHelpFocus("#wiki-get-history", 0); return false;' title="Click to highlight element..."><b>Seiten Versionen</b></a> in der Versionsgeschichte angezeigt.</dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/en/admin/addons/assets/addon_repo_gui_1.png b/doc/en/context/en/admin/addons/assets/addon_repo_gui_1.png
deleted file mode 100644
index 37139b345..000000000
--- a/doc/en/context/en/admin/addons/assets/addon_repo_gui_1.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/context/en/admin/addons/help.html b/doc/en/context/en/admin/addons/help.html
deleted file mode 100644
index bfb5e416a..000000000
--- a/doc/en/context/en/admin/addons/help.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>This page manages which addons (also known as <i>plugins</i>) are installed.</dd>
- <dt>Manage Repos</dt>
- <dd>If your webserver has the necessary write permissions, you will see a button labeled <b>Manage Repos</b>,
- which opens a control panel for managing what plugin <i>repositories</i> are installed. These repos are
- stored in <span style="font-family: monospace;">extend/addon/[repo name]/</span>. The official Hubzilla
- addon repo can be added by entering the repo URL
- <span style="font-family: monospace;">https://framagit.org/hubzilla/addons.git</span>
- and choosing a name for the repo such as <b>official</b>. You should see this repo in the list similar
- to the following:
- <br>
- <img class="img-responsive" src="doc/context/en/admin/addons/assets/addon_repo_gui_1.png"></dd>
-</dl>
diff --git a/doc/en/context/en/admin/logs/help.html b/doc/en/context/en/admin/logs/help.html
deleted file mode 100644
index 708ec9bde..000000000
--- a/doc/en/context/en/admin/logs/help.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>This page allows you to adjust log settings and to view and existing log.</dd>
- <dt>Log Settings</dt>
- <dd>When you enable the Debugging option, system log information will begin appending to the file specified in the "Log File" box (path is relative to the hub root, for example /var/www). Note that this file must be writable by the web server.</dd>
- <dt>Log Level</dt>
- <dd>The log level option allows you to set how much information is appended to the log file. Warning: Increasing this level can quickly inflate the size of the log file to >100MB, especially on hubs with more than a few members.</dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/en/admin/queue/help.html b/doc/en/context/en/admin/queue/help.html
deleted file mode 100644
index 28885a154..000000000
--- a/doc/en/context/en/admin/queue/help.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>The queue statistics display how many posts are in the queue for delivery to other hubs. The priority is related to how many times the delivery has been unsuccessfully attempted.</dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/en/admin/security/help.html b/doc/en/context/en/admin/security/help.html
deleted file mode 100644
index bfe81b132..000000000
--- a/doc/en/context/en/admin/security/help.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>This page contains various administrator settings related to security. To save any changes you make to these settings, you must press the Submit button.</dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/en/appman/help.html b/doc/en/context/en/appman/help.html
deleted file mode 100644
index 27cb03624..000000000
--- a/doc/en/context/en/appman/help.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>Edit individual properties of the app you selected. Categories allow you to sort your apps to help you find them in the list more easily. Support for custom apps you or your administrator may choose to create includes fields such as "Price of app" and "Location for purchase" that are not applicable to core Hubzilla apps.</dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/en/cards/help.html b/doc/en/context/en/cards/help.html
deleted file mode 100644
index 9dbed3f97..000000000
--- a/doc/en/context/en/cards/help.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>Cards represent a persistent area for collaboration that is separate from the social stream. They are somewhat more lightweight than webpages and wikis for quick organisation of information and have the advantage of allowing collaboration and commentary. They are well suited for helping to organise complex tasks where there are frequent updates and feedback.
- </dd>
- <dt>Add Card</dt>
- <dd>
- Creating a new card is very similar to composing a new post.<br><br>
- <ul>
- <li>
- <b>Page link name</b>: The page link name is the name of the card for the static URL
- </li>
- <li>
- <b>Title</b>: The title is displayed at the top of the card
- </li>
- <li>
- <b>Categories</b>: If you have the <a href="/settings/features">Post Categories feature</a> enabled for your channel, then you can add categories to the card. These categories populate the <b>Categories</b> list on the left panel and allow filtering your collection of cards.
- </li>
- </ul>
- </dd>
-</dl>
diff --git a/doc/en/context/en/channel/help.html b/doc/en/context/en/channel/help.html
deleted file mode 100644
index 0c5b99754..000000000
--- a/doc/en/context/en/channel/help.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>This is the home page of a channel. It is similar to someone's profile "wall" in a social network context. Posts created by the channel are displayed according to the observer's viewing permissions.</dd>
- <dt>Create a Post</dt>
- <dd>If you have permission to create posts on the channel page, then you will see the post editor at the top.</dd>
-</dl>
diff --git a/doc/en/context/en/chat/help.html b/doc/en/context/en/chat/help.html
deleted file mode 100644
index cc71686d8..000000000
--- a/doc/en/context/en/chat/help.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>Create and use chat rooms to communicate in real-time, using the standard Hubzilla permissions system for chat room access control.</dd>
- <dt>Create new chat room</dt>
- <dd>Use the "Create New" button to create a new chat room. Enter a name and how long messages should be retained.</dd>
- <dt>Chatting</dt>
- <dd>Enter your message in the message box and press Submit. You can set a status by selecting the chat room menu button beside the Submit button. Other people "in the room" are visible in the side panel in the "Chat Members" panel.</dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/en/cloud/help.html b/doc/en/context/en/cloud/help.html
deleted file mode 100644
index a8f193223..000000000
--- a/doc/en/context/en/cloud/help.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>This page displays a channel's "cloud" files. The files visible to the observer depend on the individual file permissions set by the channel owner. If you have permission to create/upload files you will see control buttons above the file list.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#tabs-collapse-1", 0); return false;' title="Click to highlight element...">Channel Content Tabs</a></dt>
- <dd>The channel content tabs are links to other content published by the channel. The <b>About</b> tab links to the channel profile. The <b>Photos</b> tab links to the channel photo galleries. The <b>Files</b> tab links to the general shared files published by the channel.</dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/en/connections/help.html b/doc/en/context/en/connections/help.html
deleted file mode 100644
index 0f95fde63..000000000
--- a/doc/en/context/en/connections/help.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>This page displays a list of all this channel's connections. The list can be <a href='#' onclick='contextualHelpFocus(".section-title-wrapper", 0); return false;' title="Click to highlight element...">sorted and filtered using the menu button beside the search button</a>. </dd>
- <dt>Connection Details</dt>
- <dd>Each list entry shows the details of a specific connection. A translucent avatar image indicates an archived connection.</dd>
- <dt>Connection Status</dt>
- <dd>A connection can be in different states: <ul><li>Archived</li><li>Ignored</li><li>Blocked</li><li>Hidden</li></ul></dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/en/connections/ifpending/help.html b/doc/en/context/en/connections/ifpending/help.html
deleted file mode 100644
index 0f95fde63..000000000
--- a/doc/en/context/en/connections/ifpending/help.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>This page displays a list of all this channel's connections. The list can be <a href='#' onclick='contextualHelpFocus(".section-title-wrapper", 0); return false;' title="Click to highlight element...">sorted and filtered using the menu button beside the search button</a>. </dd>
- <dt>Connection Details</dt>
- <dd>Each list entry shows the details of a specific connection. A translucent avatar image indicates an archived connection.</dd>
- <dt>Connection Status</dt>
- <dd>A connection can be in different states: <ul><li>Archived</li><li>Ignored</li><li>Blocked</li><li>Hidden</li></ul></dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/en/connedit/help.html b/doc/en/context/en/connedit/help.html
deleted file mode 100644
index 9eb62ecc7..000000000
--- a/doc/en/context/en/connedit/help.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>This page allows you to change or edit any individual settings for a particular connection or delete a connection completely. You may have arrived at this page after creating or approving a new connection. If so, you are not required to do anything. Your connection has already been established. You <strong>may</strong> wish to add them to a group or adjust special permissions, and this page is presented so that you may do this while the opportunity is fresh.</dd>
- <dt><a href='#' onclick='contextualHelpFocus(".section-title-wrapper", 0); return false;' title="Click to highlight element...">Connection Tools</a></dt>
- <dd>The <a href='#' onclick='contextualHelpFocus(".section-title-wrapper", 0); return false;' title="Click to highlight element...">Connection Tools</a> menu access several settings. View Profile, View Recent Activity, Refresh Permissions, set or reset flags (Block, Ignore, Archive, Hide) and Delete the connection.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#group-sidebar", 0); return false;' title="Click to highlight element...">Privacy Groups</a></dt>
- <dd>Each connection may be assigned to one or more Privacy Groups for grouping collections of friends with access to specific posts, media and other content. You may add them to an existing privacy group here, or create a new privacy group. When you add them to an existing group the action is immediate and you are not required to submit a form. </dd>
- <dt><a href='#' onclick='contextualHelpFocus("#perms-tool", 0); return false;' title="Click to highlight element...">Individual Permissions</a></dt>
- <dd>Granting of permissions is usually automatic and require no action on your part. However you may wish to adjust specific permsisions for this connection which are different than for others.</dd>
- <dt>Feature Specific Settings</dt>
- <dd>A number of individual settings are controlled through additional features which may or may not be activated on your hub or for your channel. Several optional features have settings for each connection, and those may be set on this page through additional form tabs which may be present.</dd>
-</dl>
diff --git a/doc/en/context/en/events/help.html b/doc/en/context/en/events/help.html
deleted file mode 100644
index 8c0b1d4ab..000000000
--- a/doc/en/context/en/events/help.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>This page displays a calendar of events both owned by you and shared with you from other channels.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#title", 0); return false;' title="Click to highlight element...">Calendar View</a></dt>
- <dd>The calendar can be displayed in month, week, or day mode using the options in the side panel.</dd>
- <dt>Export/Import</dt>
- <dd>Export or import calendar events using standard iCalendar format (.ics) files.</dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/en/mail/help.html b/doc/en/context/en/mail/help.html
deleted file mode 100644
index a2361a135..000000000
--- a/doc/en/context/en/mail/help.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>The messages displayed in private mail are visible only to you and the single recipient. </dd>
- <dt>Combined View</dt>
- <dd>Complete conversations can be viewed in a continuous thread by selecting <b>Combined View</b>. Available conversations are displayed beneath the menu in the side panel.</dd>
- <dt>Inbox/Outbox</dt>
- <dd>Individual sent messages are viewed by selecting <b>Outbox</b>, and incoming messages are viewed using the <b>Inbox</b> filter.</dd>
- <dt>New Message</dt>
- <dd>Individual messages have delivery reports that can be viewed using the drop-down menu. Messages can also be recalled from the same menu, which can prevent the recipient from viewing the message <i>if they have not already read it</i>.</dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/en/network/help.html b/doc/en/context/en/network/help.html
deleted file mode 100644
index 53e993b69..000000000
--- a/doc/en/context/en/network/help.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>The network page displays a stream of posts and conversations, typically ordered by the most recently updated. This page is highly customizable.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#profile-jot-wrapper", 0); return false;' title="Click to highlight element...">Create a Post</a></dt>
- <dd>At the top of the page there is a text box that says "Share". Clicking this box opens a new post editor. The post editor is customizable, but the basic editor provides fields for a post body and an optional post <b>Title</b>. Buttons below the text area to the left provide shortcuts to text formatting and inserting links, images, and other data into the post. The buttons to the right provide a post preview, the post permissions setting, and a <b>Submit</b> button to send the post.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#group-sidebar", 1); return false;' title="Click to highlight element...">Privacy Groups</a></dt>
- <dd>The privacy groups you have created are displayed in the side panel. Selecting them filters posts to those created by channels in the chosen group.</dd>
- <dt><a href='#' onclick='$("#dbtn-acl").click(); return false;' title="Click to highlight element...">Post Permissions</a></dt>
- <dd>The access control list (ACL) is what you use to set who can see your new post. Pressing the ACL button beside the Submit button will display a dialog in which you can select what channels and/or privacy groups can see the post. You can also select who is explicitly denied access. For example, say you are planning a surprise party for a friend. You can send an invitation post to everyone in your <b>Friends</b> group <i>except</i> the friend you are surprising. In this case you "show" the <b>Friends</b> group but "don't show" that one person.</dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/en/photos/help.html b/doc/en/context/en/photos/help.html
deleted file mode 100644
index 78b442bb4..000000000
--- a/doc/en/context/en/photos/help.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>This page displays a channel's photo albums. The images visible to the observer depend on the individual image permissions.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#tabs-collapse-1", 0); return false;' title="Click to highlight element...">Channel Content Tabs</a></dt>
- <dd>The channel content tabs are links to other content published by the channel. The <b>About</b> tab links to the channel profile. The <b>Photos</b> tab links to the channel photo galleries. The <b>Files</b> tab links to the general shared files published by the channel.</dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/en/profile/help.html b/doc/en/context/en/profile/help.html
deleted file mode 100644
index 563e0df99..000000000
--- a/doc/en/context/en/profile/help.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>This is the profile page of a channel. It typically displays information describing the channel. If the channel represents a person in a social network, for example, then the profile might provide contact information and other personal details about the person. Channels can have multiple profiles, where the displayed profile depends on the observer.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#tabs-collapse-1", 0); return false;' title="Click to highlight element...">Channel Content Tabs</a></dt>
- <dd>The channel content tabs are links to other content published by the channel. The <b>About</b> tab links to the channel profile. The <b>Photos</b> tab links to the channel photo galleries. The <b>Files</b> tab links to the general shared files published by the channel.</dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/en/profiles/help.html b/doc/en/context/en/profiles/help.html
deleted file mode 100644
index 41f00fe64..000000000
--- a/doc/en/context/en/profiles/help.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>
- Once you have registered an <i>account</i> at the matrix you have also created a <i>profile</i> and a <i>channel</i>.
- </dd>
- <dt>Account</dt>
- <dd>
- You have <i>one</i> account. This consists of your email account and your password. With your account you access your
- profile and your channel.<i>Think of your account as the way you authenticate at one Hubzilla site. It lets you
- do things, such as creating profiles and channels with which you can connect to other people.</i>
- </dd>
- <dt>Profile</dt>
- <dd>
- You have surely registered with some other internet services, such as forums or online communities. For all of them
- you provided some information about yourself, such as date of birth, country, age and the likes. Unlike other
- services Hubzilla offers you the advantage of creating
- <i>many more profiles</i>. That way you are able to distinguish between profiles targeted specially at everyone
- (your public profile), your work mates, your family and your partner.<i>Think of your profile as the basic
- information about yourself you tell other people.</i>
- </dd>
- <dt>Channel</dt>
- <dd>
- During the registration you created your first <i>channel</i>. Yes, besides several profiles you are able to have
- several channels. This might be a bit confusing in the beginning, but let's clear things up. You already have
- created one channel. You can use this one for the public, to communicate with people about every day life. But
- perhaps you are an avid book reader and many people are bored by that. So you open a <i>second channel</i> just
- for the book lovers, where you all can talk about books as much as you like. Obviously this is a new stream of
- posts, with a new profile (... or new profile<i>s</i> ...) and completely different contacts. Some connections
- might exist in both channels, but there will be some that are exclusive to only one of both. You yourself just
- switch between both of them just like you would in real life switch when talking to people you meet on the street
- or people you meet specially to talk about books. You can even connect to yourself, or better: to your other
- channel. :)<i>Think of a channel as different spaces dedicated to different topics where you meet with different
- people.</i>
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/en/register/help.html b/doc/en/context/en/register/help.html
deleted file mode 100644
index 9e94ab762..000000000
--- a/doc/en/context/en/register/help.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>On this page visitors can register to get access to the portal with a login ID. Logged in users not only see the public content, but can publish content themselves and perform social network communications, and much more.</dd>
- <dt>Modes of registration</dt>
- <dd>Registration is possible with an eMail address, or anonymously (then do not fill in the eMail field). You may also have received an invitation code, which can then be entered with the eMail address. The link above the eMail field allows you to enter the invitation code.</dd>
- <dt>Registration procedure</dt>
- <dd>For logins after a successful registration, a separate password must be set. To be on the safe side, enter it twice with identical values, because it will not be displayed. The password is to be kept secret and is only intended for the user's own use. Anonymously registered users receive an automatically assigned ID, and should not forget their own password because, unlike eMail registration, there is initially no reset function. Depending on the configuration of the hub instance, a confirmation function may be required. Users of the eMail registration will receive a corresponding message. For anonymous registrations, another dialog will be displayed showing the access ID and a pin. This dialog page should be stored securely and for a long time (e.g. by printout, screenshot, photo), because the data must be confirmed again at a later point in time.</dd>
- <dt>The Digital Identity</dt>
- <dd>Depending on the configuration of the hub instance, a displayable name and a nickname can already be entered during registration (alternatively also during the first login). The nickname has a very extensive meaning and cannot be changed later. It is a unique Digital Identity (DID) that is, will be, and remains linked to all of one's activities. This DID is not only suitable for logging into this hub instance, but also into all connected instances of the federal network. In federal network parlance, this digital identity is a "channel". This is comparable to a telephone number in the telephone network. The DID has the format channel@instance.tld = nickname@instance.tld and, as mentioned, cannot be changed afterwards. Although the format looks like an eMail address, it is not.</dd>
- <dt>Before starting the registration ...</dt>
- <dd>... the preferred language (English, Spanish, German, for example) should be selected (top right in the hamburger menu <span class="fa fa-fw fa-bars"> </span>). The current language is used in the subsequent steps and also during and after login. However, this can be changed at any time and as needed. It should also be noted that this Hub instance is not the only one. An overview of other Hub instances can be found <a href="./pubsites"> here </a>.</dd>
-</dl>
diff --git a/doc/en/context/en/settings/account/help.html b/doc/en/context/en/settings/account/help.html
deleted file mode 100644
index 41f00fe64..000000000
--- a/doc/en/context/en/settings/account/help.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>
- Once you have registered an <i>account</i> at the matrix you have also created a <i>profile</i> and a <i>channel</i>.
- </dd>
- <dt>Account</dt>
- <dd>
- You have <i>one</i> account. This consists of your email account and your password. With your account you access your
- profile and your channel.<i>Think of your account as the way you authenticate at one Hubzilla site. It lets you
- do things, such as creating profiles and channels with which you can connect to other people.</i>
- </dd>
- <dt>Profile</dt>
- <dd>
- You have surely registered with some other internet services, such as forums or online communities. For all of them
- you provided some information about yourself, such as date of birth, country, age and the likes. Unlike other
- services Hubzilla offers you the advantage of creating
- <i>many more profiles</i>. That way you are able to distinguish between profiles targeted specially at everyone
- (your public profile), your work mates, your family and your partner.<i>Think of your profile as the basic
- information about yourself you tell other people.</i>
- </dd>
- <dt>Channel</dt>
- <dd>
- During the registration you created your first <i>channel</i>. Yes, besides several profiles you are able to have
- several channels. This might be a bit confusing in the beginning, but let's clear things up. You already have
- created one channel. You can use this one for the public, to communicate with people about every day life. But
- perhaps you are an avid book reader and many people are bored by that. So you open a <i>second channel</i> just
- for the book lovers, where you all can talk about books as much as you like. Obviously this is a new stream of
- posts, with a new profile (... or new profile<i>s</i> ...) and completely different contacts. Some connections
- might exist in both channels, but there will be some that are exclusive to only one of both. You yourself just
- switch between both of them just like you would in real life switch when talking to people you meet on the street
- or people you meet specially to talk about books. You can even connect to yourself, or better: to your other
- channel. :)<i>Think of a channel as different spaces dedicated to different topics where you meet with different
- people.</i>
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/en/settings/channel/help.html b/doc/en/context/en/settings/channel/help.html
deleted file mode 100644
index 41f00fe64..000000000
--- a/doc/en/context/en/settings/channel/help.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>
- Once you have registered an <i>account</i> at the matrix you have also created a <i>profile</i> and a <i>channel</i>.
- </dd>
- <dt>Account</dt>
- <dd>
- You have <i>one</i> account. This consists of your email account and your password. With your account you access your
- profile and your channel.<i>Think of your account as the way you authenticate at one Hubzilla site. It lets you
- do things, such as creating profiles and channels with which you can connect to other people.</i>
- </dd>
- <dt>Profile</dt>
- <dd>
- You have surely registered with some other internet services, such as forums or online communities. For all of them
- you provided some information about yourself, such as date of birth, country, age and the likes. Unlike other
- services Hubzilla offers you the advantage of creating
- <i>many more profiles</i>. That way you are able to distinguish between profiles targeted specially at everyone
- (your public profile), your work mates, your family and your partner.<i>Think of your profile as the basic
- information about yourself you tell other people.</i>
- </dd>
- <dt>Channel</dt>
- <dd>
- During the registration you created your first <i>channel</i>. Yes, besides several profiles you are able to have
- several channels. This might be a bit confusing in the beginning, but let's clear things up. You already have
- created one channel. You can use this one for the public, to communicate with people about every day life. But
- perhaps you are an avid book reader and many people are bored by that. So you open a <i>second channel</i> just
- for the book lovers, where you all can talk about books as much as you like. Obviously this is a new stream of
- posts, with a new profile (... or new profile<i>s</i> ...) and completely different contacts. Some connections
- might exist in both channels, but there will be some that are exclusive to only one of both. You yourself just
- switch between both of them just like you would in real life switch when talking to people you meet on the street
- or people you meet specially to talk about books. You can even connect to yourself, or better: to your other
- channel. :)<i>Think of a channel as different spaces dedicated to different topics where you meet with different
- people.</i>
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/en/settings/features/help.html b/doc/en/context/en/settings/features/help.html
deleted file mode 100644
index 86e4f5dae..000000000
--- a/doc/en/context/en/settings/features/help.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>This page allows you to configure settings for the many additional features of Hubzilla.</dd>
- <dt><a href='#' onclick='$("#general-settings-title h3 a").click(); setTimeout((function() {contextualHelpFocus("#general-settings-title", 0)}), 1000); return false;' title="Click to highlight element...">General Features</a></dt>
- <dd>General feature settings include options relevant to your channel, such as webpage and wiki hosting.</dd>
- <dt><a href='#' onclick='$("#composition-settings-title h3 a").click(); setTimeout((function() {contextualHelpFocus("#composition-settings-title", 0)}), 1000); return false;' title="Click to highlight element...">Post Composition Features</a></dt>
- <dd>The post composition features provide extra options and capabilities when composing new posts.</dd>
- <dt><a href='#' onclick='$("#net_module-settings-title h3 a").click(); setTimeout((function() {contextualHelpFocus("#net_module-settings-title", 0)}), 1000); return false;' title="Click to highlight element...">Network and Stream Filtering</a></dt>
- <dd>These settings modify features associated with filtering and controlling your view of incoming posts.</dd>
- <dt><a href='#' onclick='$("#tools-settings-title h3 a").click(); setTimeout((function() {contextualHelpFocus("#tools-settings-title", 0)}), 1000); return false;' title="Click to highlight element...">Post/Comment Tools</a></dt>
- <dd>These provide additional tools for categorizing posts and allowing additional commenting methods such as emoji or community tagging.</dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/en/settings/tokens/help.html b/doc/en/context/en/settings/tokens/help.html
deleted file mode 100644
index 6d7e6b98d..000000000
--- a/doc/en/context/en/settings/tokens/help.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<dl class="dl-horizontal">
- <dt><a href="/help/member/member_guide#Guest_Access_Tokens">Guest Access Tokens</a></dt>
- <dd>
- In order to facilitate sharing of private resources with non-members or members of federation nodes with limited identification discovery, Hubzilla should provide members with a mechanism to create and manage temporary ("throwaway") logins, aka "Zot Access Tokens". These tokens/credentials may be used to authenticate to a hubzilla site for the sole purpose of accessing privileged or access controlled resources (files, photos, posts, webpages, chatrooms, etc.).
- </dd>
- <dt>Create a token</dt>
- <dd>
- The form to create/edit accepts three parameters, a human readable name, a password or access token, and an
- optional expiration. Once expired, the access token is no longer valid, may no longer be used, and will be
- automatically purged from the list of temporary accounts. The password field in the create/edit forms
- displays the text of the access token and not an obscured password.
- </dd>
- <dt>Share a token</dt>
- <dd>
- We do not specify mechanisms for sharing these tokens with others. Any communication method may be used. Any tokens you have created are added to the Access Control List selector and may be used anywhere that Access Control Lists are provided.
-
- <b>Example</b>: A visitor arrives at your site. She has an access token you have provided, and attempts to visit one of your photo albums (which is restricted to be viewed only by yourself and one temporary identity). Permission is denied.
-
- The visitor now selects "Login" from the menu navigation bar. This presents a login page. She enters the name and password you have provided her, and she can now view the restricted photo album.
-
- Alternatively, you may share a link to a protected file by adding a parameter "&zat=abc123" to the URL, where the string "abc123" is the access token or password for the temporary login. No further negotiation is required, and the file is presented.
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/en/webpages/help.html b/doc/en/context/en/webpages/help.html
deleted file mode 100644
index a4817e4bf..000000000
--- a/doc/en/context/en/webpages/help.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>You can create modular, identity-aware websites composed of shareable elements. </dd>
- <dt>Pages</dt>
- <dd>This page lists your "pages", which are assigned URLs where people can visit your site. The structure of pages are typically described by an associated <b>layout</b>, and their content is constructed from a collection of <b>blocks</b>.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#website-portation-tools", 1); return false;' title="Click to highlight element...">Website portation tools</a></dt>
- <dd>The website portation tools allows you import/export multiple webpage elements (pages, layouts, blocks). You can <b>import</b> either from an uploaded zip file or from an existing cloud files folder. You can <b>export</b> to either a zip file containing a select group of webpage elements in a form compatible with the import tool, or you can export directly to a cloud files folder. <a target="_blank" href="help/webpages">Read more...</a></dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/en/wiki/help.html b/doc/en/context/en/wiki/help.html
deleted file mode 100644
index 5dee85375..000000000
--- a/doc/en/context/en/wiki/help.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>Each wiki is a collection of pages, composed as Markdown-formatted text files.</dd>
- <dt>Wiki List</dt>
- <dd>Wikis owned by the channel <i>that you have permission to view</i> are listed in the side panel.</dd>
- <dt>Page History</dt>
- <dd>Every revision of a page is saved to allow quick reversion. Click the <b>History</b> tab to view a history of page revisions, including the date and author of each. The revert button will load the selected revision but will not automatically save the page.</dd>
- <dt>Pages</dt>
- <dd>The list of pages in the wiki are listed in the <b>Wiki Pages</b> panel. Prior to saving page edits using the <b>Page</b> control dropdown menu, you may <a href='#' onclick='contextualHelpFocus("#id_commitMsg", 0); return false;' title="Click to highlight element...">enter a custom message</a> to be displayed in the <a href='#' onclick='contextualHelpFocus("#wiki-get-history", 0); return false;' title="Click to highlight element..."><b>Page History</b></a> viewer along with the revision.</dd>
-</dl>
diff --git a/doc/en/context/es b/doc/en/context/es
deleted file mode 120000
index c8ba7666b..000000000
--- a/doc/en/context/es
+++ /dev/null
@@ -1 +0,0 @@
-es-es \ No newline at end of file
diff --git a/doc/en/context/es-es/admin/addons/assets/addon_repo_gui_1.png b/doc/en/context/es-es/admin/addons/assets/addon_repo_gui_1.png
deleted file mode 100644
index 37139b345..000000000
--- a/doc/en/context/es-es/admin/addons/assets/addon_repo_gui_1.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/context/es-es/admin/addons/help.html b/doc/en/context/es-es/admin/addons/help.html
deleted file mode 100644
index 49a047f5e..000000000
--- a/doc/en/context/es-es/admin/addons/help.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>Esta página gestiona qué addons (también llamados <i>plugins</i> o <i>complementos</i>) están instalados.</dd>
- <dt>Gestión de los repositorios</dt>
- <dd>Si su servidor web tiene los permisos de escritura necesarios, verá un botón etiquetado como <b>Gestión de repositorios</b>,
- que abre un panel de control para administrar qué <i>repositorios</i> de addons están instalados. Estos repositorios están
- almacenados en <span style="font-family: monospace;">extend/addon/[nombre del repositorio]/</span>. El repositorio de addons oficial de Hubzilla
- se puede añadir escribiendo la URL del repositorio
- <span style="font-family: monospace;">https://framagit.org/hubzilla/addons.git</span>
- y eligiendo un nombre para el repositorio como <b>oficial</b>. Debería ver este repositorio en una lista parecida
- a esta:
- <br>
- <img class="img-responsive" src="doc/context/es-es/admin/addons/assets/addon_repo_gui_1.png"></dd>
-</dl>
diff --git a/doc/en/context/es-es/admin/logs/help.html b/doc/en/context/es-es/admin/logs/help.html
deleted file mode 100644
index 3a2ec413d..000000000
--- a/doc/en/context/es-es/admin/logs/help.html
+++ /dev/null
@@ -1,9 +0,0 @@
- <dl class="dl-horizontal">
- <dt>General</dt>
- <dd>Esta página le permite ajustar los parámetros de los informes del sistema (logs) y para ver uno existente.</dd>
- <dt>Ajustes de los informes (logs)</dt>
- <dd>Cuando se habilita la opción de depuración, el sistema de información comenzará a añadir los informes (logs) en el archivo especificado en el cuadro "Fichero de informes"
- (la ruta es relativa al directorio raíz del servidor, por ejemplo, /var /www). Tenga en cuenta que este archivo tiene que ser modificable por el servidor web.</dd>
- <dt>Nivel de depuración</dt>
- <dd>La opción de nivel de depuración le permite establecer la cantidad de información que se anexa al fichero de informes (logs). Advertencia: El aumento de este nivel puede aumentar rápidamente el tamaño de este fichero hasta en más de 100 MB, especialmente en los hubs con más que unos pocos miembros.</dd>
- </dl>
diff --git a/doc/en/context/es-es/admin/queue/help.html b/doc/en/context/es-es/admin/queue/help.html
deleted file mode 100644
index de9e6cdbb..000000000
--- a/doc/en/context/es-es/admin/queue/help.html
+++ /dev/null
@@ -1,4 +0,0 @@
- <dl class="dl-horizontal">
- <dt>General</dt>
- <dd>Las estadísticas de la cola muestran cuántos mensajes están en la cola para su entrega a otros hubs. La prioridad está relacionada con la cantidad de veces que la entrega se ha intentado, sin éxito.</dd>
- </dl>
diff --git a/doc/en/context/es-es/admin/security/help.html b/doc/en/context/es-es/admin/security/help.html
deleted file mode 100644
index 229a91561..000000000
--- a/doc/en/context/es-es/admin/security/help.html
+++ /dev/null
@@ -1,4 +0,0 @@
- <dl class="dl-horizontal">
- <dt>General</dt>
- <dd>Esta página contiene varios ajustes para el administrador relacionados con la seguridad. Para guardar los cambios que realice en estos ajustes, debe pulsar el botón "Enviar".</dd>
- </dl>
diff --git a/doc/en/context/es-es/appman/help.html b/doc/en/context/es-es/appman/help.html
deleted file mode 100644
index 1b799a52b..000000000
--- a/doc/en/context/es-es/appman/help.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>Editar las propiedades individuales de la aplicación seleccionada. Las categorías le permiten ordenar sus aplicaciones para ayudarle a encontrarlas más fácilmente en la lista. El soporte para aplicaciones personalizadas que usted o su administrador puedan elegir para crear incluye campos como "Precio de la aplicación" y "Ubicación para la compra" que no son aplicables a las aplicaciones principales de Hubzilla.</dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/es-es/articles/help.html b/doc/en/context/es-es/articles/help.html
deleted file mode 100644
index 0bf176fdd..000000000
--- a/doc/en/context/es-es/articles/help.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>El artículo es un tipo de documento de una naturaleza un poco especial, a mitad de camino entre las fichas de organización de trabajo ("cards") y las entradas normales, pero que está separado del stream social. Son muy adecuados para ayudar a escribir y organizar textos, con un formateo básico, pero completo, sobre cualquier tema, especialmente sobre documentación seriada, de forma más amplia que un wiki pero menos que un página web. Al no formar parte del stream social, para acceder a él hace falta conocer su dirección: <b>[URL del sitio]/articles/[nombre del usuario, sin dominio]</b>. Ahí estarán disponibles los distintas artículos que escriba, por orden de publicación.</dd>
- <dt>Crear un artículo</dt>
- <dd>
- Crear un artículo nuevo es muy parecido a componer un nuevo post.<br><br>
- <ul>
- <li>
- <b>Nombre del enlace de la página</b>: Este nombre es el del nombre del artículo para una URL estática
- </li>
- <li>
- <b>Título</b>: El título se muestra en la parte de arriba del artículo
- </li>
- <li>
- <b>Temas o Categorías</b>: Si tiene activada la opción <a href="/settings/features">Temas de las entradas</a> en su canal, entonces puede añadirlos al artículo. Estas categorías o temas se despliegan en la lista <b>Temas</b> en el panel de la izquierda y permiten filtrar su colección de artículos.
- </li>
- <li> <b>Sumario</b>: Es el espacio, entre las etiquetas predefinidas [summary][/summary], reservado para redactar un resumen del artículo, que aparecerá en su encabezado. El espacio disponible a continuación del sumario, en la ventana de composición, es el adecuado para la redacción del artículo en sí.
- </ul>
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/es-es/cards/help.html b/doc/en/context/es-es/cards/help.html
deleted file mode 100644
index 34889cd25..000000000
--- a/doc/en/context/es-es/cards/help.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>Las fichas representan un área persistente para la colaboración que está separada del stream social. Son algo más ligeras que las páginas web y los wikis para una rápida organización de la información y tienen la ventaja de permitir la colaboración y los comentarios. Son muy adecuados para ayudar a organizar tareas complejas en las que hay actualizaciones y retroalimentación frecuentes.</dd>
- <dt>Añadir una ficha</dt>
- <dd>
- Crear una ficha nueva es muy parecido a componer un nuevo post.<br><br>
- <ul>
- <li>
- <b>Nombre del enlace de la página</b>: Este nombre es el de la ficha para una URL estática
- </li>
- <li>
- <b>Título</b>: El título se muestra en la parte de arriba de la ficha
- </li>
- <li>
- <b>Categorías</b>: Si tiene activada la opción <a href="/settings/features">Temas de las entradas</a> en su canal, entonces puede añadirlas a la ficha. Estas categorías o temas se despliegan en la lista <b>Temas</b> en el panel de la izquierda y permiten filtrar su colección de fichas.
- </li>
- </ul>
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/es-es/channel/help.html b/doc/en/context/es-es/channel/help.html
deleted file mode 100644
index 682125e27..000000000
--- a/doc/en/context/es-es/channel/help.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<dt>General</dt>
-<dd>Esta es la página principal de un canal. Es similar al "muro" del perfil de una persona en un contexto de red social. Las entradas creadas por el canal se muestran de acuerdo con los permisos de visualización
-del observador.</dd>
-<dt>Crear una entrada</dt>
-<dd>Si tiene permiso para crear entradas en la página del canal, a continuación, podrá ver el editor de entradas en la parte superior.</dd>
-<dt><a href="#" onclick="contextualHelpFocus(&quot;#tabs-collapse-1&quot;, 0); return false;">Pestañas de los contenidos del canal</a></dt>
-<dd>Las pestañas de los contenidos del canal son enlaces a otros contenidos publicados por el canal. La pestaña "<b>Mi perfil</b>" enlaza con el perfil del canal. La pestaña "<b>Fotos</b>" enlaza con las galerías
-de fotos. La pestaña "<b>Ficheros</b>" enlaza con los ficheros de cualquier tipo compartidos por el canal.</dd> \ No newline at end of file
diff --git a/doc/en/context/es-es/chat/help.html b/doc/en/context/es-es/chat/help.html
deleted file mode 100644
index 94df6a1d2..000000000
--- a/doc/en/context/es-es/chat/help.html
+++ /dev/null
@@ -1,8 +0,0 @@
- <dl class="dl-horizontal">
- <dt>General</dt>
- <dd>Creación y uso de salas de chat para comunicarse en tiempo real, utilizando el sistema de permisos estándar de Hubzilla para el control de acceso a la sala de chat.</dd>
- <dt>Crear una nueva sala de chat</dt>
- <dd>Utilice el botón "Crear" para crear una nueva sala de chat. Introduzca un nombre y cuánto tiempo se deben conservar los mensajes.</dd>
- <dt>Chatear</dt>
- <dd>Introduzca su mensaje en el cuadro de mensaje y pulse "Enviar". Se puede establecer un estado seleccionando el botón de menú sala de chat junto al botón "Enviar". Si hay otras personas en la sala, serán visibles en el panel lateral, en "Miembros del chat".</dd>
- </dl>
diff --git a/doc/en/context/es-es/cloud/help.html b/doc/en/context/es-es/cloud/help.html
deleted file mode 100644
index af891da17..000000000
--- a/doc/en/context/es-es/cloud/help.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>Esta página muestra los archivos "cloud" de un canal. Los archivos visibles para el observador dependen de los permisos de fichero individuales establecidos por el propietario del canal. Si tiene permiso para crear o cargar archivos verá botones de control encima de la lista de ficheros. </dd>
- <dt><a href='#' onclick='contextualHelpFocus("#tabs-collapse-1", 0); return false;' title="Pulsar en el elemento resaltado...">Pestañas de contenidos del canal</a></dt>
- <dd>Las pestañas de contenidos del canal son enlaces a otros contenidos publicados por el canal. La pestaña <b>Mi canal</b> enlaza con el perfil del canal. La pestaña <b>Fotos</b> enlaza con las galerías de fotos del canal. La pestaña <b>Ficheros</b> enlaza con los ficheros compartidos publicados por el canal.</dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/es-es/connections/help.html b/doc/en/context/es-es/connections/help.html
deleted file mode 100644
index a0aa9cf32..000000000
--- a/doc/en/context/es-es/connections/help.html
+++ /dev/null
@@ -1,15 +0,0 @@
- <dl class="dl-horizontal">
- <dt>General</dt>
- <dd>Esta página muestra una lista de todas las conexiones de este canal. La lista se puede <a href="#" onclick='contextualHelpFocus(".section-title-wrapper", 0); return false;' title="Pulsar sobre el elemento resaltado...">ordenar y filtrar usando el botón de menú al lado del botón de búsqueda</a>.</dd>
- <dt>Detalles de la conexión</dt>
- <dd>Cada entrada de la lista muestra los detalles de una conexión específica. Una imagen de avatar translúcida indica una conexión archivada.</dd>
- <dt>Estado de la conexión</dt>
- <dd>Una conexión puede estar en diferentes estados:
- <ul>
- <li>Archivada</li>
- <li>Ignorada</li>
- <li>Bloqueada</li>
- <li>Oculta</li>
- </ul>
- </dd>
- </dl>
diff --git a/doc/en/context/es-es/connections/ifpending/help.html b/doc/en/context/es-es/connections/ifpending/help.html
deleted file mode 100644
index 84e547851..000000000
--- a/doc/en/context/es-es/connections/ifpending/help.html
+++ /dev/null
@@ -1,15 +0,0 @@
- <dl class="dl-horizontal">
- <dt>General</dt>
- <dd>Esta página muestra una lista de todas las conexiones de este canal. La lista se puede&nbsp;<a href="#" onclick='contextualHelpFocus(".section-title-wrapper", 0); return false;' title="Pulsar sobre el elemento resaltado...">ordenar y filtrar usando el botón de menú al lado del botón de búsqueda</a>. </dd>
- <dt>Detalles de la conexión</dt>
- <dd>Cada entrada de la lista muestra los detalles de una conexión específica. Una imagen de avatar translúcida indica una conexión archivada.</dd>
- <dt>Estado de la conexión</dt>
- <dd>Una conexión puede estar en diferentes estados:
- <ul>
- <li>Archivada</li>
- <li>Ignorada</li>
- <li>Bloqueada</li>
- <li>Oculta</li>
- </ul>
- </dd>
- </dl>
diff --git a/doc/en/context/es-es/connedit/help.html b/doc/en/context/es-es/connedit/help.html
deleted file mode 100644
index e8c92ca28..000000000
--- a/doc/en/context/es-es/connedit/help.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>Esta página le permite cambiar o modificar cualquier ajuste individual para una conexión en concreto o eliminar una conexión completamente. Ha podido llegar a esta página tras crear o aprobar una conexión nueva. Si es así, no tiene por qué hacer nada. Su conexión ya se ha establecido. <strong>Es posible</strong> que desee añadirla a un grupo o ajustar para ella permisos especiales; si es así, esta página se presenta de modo que usted pueda hacerlo mientras todo el proceso es aún reciente.</dd>
- <dt><a href='#' onclick='contextualHelpFocus(".section-title-wrapper", 0); return false;' title="Pulsar para resaltar el elemento...">Conexiones</a></dt>
- <dd>El menú <a href='#' onclick='contextualHelpFocus(".section-title-wrapper", 0); return false;' title="Pulsar para resaltar el elemento...">Conexiones</a> le da acceso a varios ajustes: "Ver el perfil", "Ver la actividad reciente", "Recargar los permisos", añadir o quitar estados ("flags") ("Bloquear", "Ignorar", "Archivar", "Ocultar") y eliminar la conexión.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#group-sidebar", 0); return false;' title="Pulsar para resaltar el elemento...">Grupos de canales</a></dt>
- <dd>Todas las conexiones se pueden incluir en uno o varios grupos de canales para formar conjuntos de amigos con acceso a publicaciones concretas, ficheros multimedia y otros tipos de contenido. Puede añadirla aquí a un grupo de canales ya existente o crear un grupo nuevo. Cuando añade la conexión a un grupo que ya existe, el efecto es inmediato y no será requerido para confirmarlo.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#perms-tool", 0); return false;' title="Pulsar para resaltar el elemento...">Permisos individuales</a></dt>
- <dd>La concesión de permisos es, generalmente, automática y no requiere ninguna acción por su parte. Sin embargo, puede, si lo desea, ajustar permisos especiales, diferentes de los permisos concedidos a otros, para una conexión concreta.</dd>
- <dt>Ajustes de funcionalidades concretas</dt>
- <dd>Una serie de ajustes individuales se controlan mediante funcionalidades adicionales que pueden, o no, estar activadas en un hub o para su canal en concreto. Varias funcionalidades adicionales tienen ajustes para cada conexión, Estas funcionalidades se pueden configurar en esta página, de forma adicional, mediante pestañas que pueden estar presentes.</dd>
-</dl>
diff --git a/doc/en/context/es-es/events/help.html b/doc/en/context/es-es/events/help.html
deleted file mode 100644
index b0ac1f9ea..000000000
--- a/doc/en/context/es-es/events/help.html
+++ /dev/null
@@ -1,8 +0,0 @@
- <dl class="dl-horizontal">
- <dt>General</dt>
- <dd>Esta página muestra un calendario de eventos tanto de su propiedad como compartido con usted desde otros canales.</dd>
- <dt><a href="#" onclick='contextualHelpFocus("#title", 0); return false;' title="Pulsar en el elemento resaltado...">Vistas del calendario</a></dt>
- <dd>El calendario se puede mostrar en modo mensual, semanal o diario usando las opciones del panel lateral.</dd>
- <dt>Exportar/Importar</dt>
- <dd>Exportar o importar eventos del calendario usando el formato estándar de los ficheros de iCalendar (.ics).</dd>
- </dl>
diff --git a/doc/en/context/es-es/mail/help.html b/doc/en/context/es-es/mail/help.html
deleted file mode 100644
index 794af38a9..000000000
--- a/doc/en/context/es-es/mail/help.html
+++ /dev/null
@@ -1,10 +0,0 @@
- <dl class="dl-horizontal">
- <dt>General</dt>
- <dd>Los mensajes que aparecen en el correo privado son visibles sólo para usted y un único destinatario.</dd>
- <dt>Vista combinada</dt>
- <dd>Las conversaciones completas se pueden ver en un hilo continuo seleccionando "<b>Vista combinada</b>". Las conversaciones disponibles se muestran debajo del menú en el panel lateral.</dd>
- <dt>Bandeja de entrada/Bandeja de salida</dt>
- <dd>Los mensajes individuales enviados son visibles seleccionando la <b>Bandeja de salida</b> y los mensajes recibidos se pueden ver usando el filtro de la <b>Bandeja de entrada.</b></dd>
- <dt>Mensaje nuevo</dt>
- <dd>Los mensajes individuales tienen informes de entrega que se pueden ver usando el menú desplegable. Los mensajes también se pueden revocar desde el mismo menú, lo que puede evitar que el destinatario vea el mensaje, <i>si aún no lo ha leído</i>.</dd>
- </dl> \ No newline at end of file
diff --git a/doc/en/context/es-es/network/help.html b/doc/en/context/es-es/network/help.html
deleted file mode 100644
index 082f52a49..000000000
--- a/doc/en/context/es-es/network/help.html
+++ /dev/null
@@ -1,12 +0,0 @@
- <dl class="dl-horizontal">
- <dt>General</dt>
- <dd>La página de "Mi red" muestra un flujo de entradas y conversaciones, normalmente ordenadas según la actualización más reciente. Esta página es altamente personalizable.</dd>
- <dt><a href="#" onclick='contextualHelpFocus("#profile-jot-wrapper", 0); return false;' title="Pulsar sobre el elemento resaltado...">Crear una entrada</a></dt>
- <dd>En la parte superior de la página hay un cuadro de texto que dice "<b>Compartir</b>". Al hacer clic en esta casilla se abre un nuevo editor de entradas. El editor de entradas es personalizable, pero el editor básico proporciona campos para el cuerpo de la publicación y un título opcional. Los botones que hay debajo de la zona de texto, a la izquierda, proporcionan
- accesos directos para el <b>Formato</b> de texto y para&nbsp; insertar enlaces, imágenes y otros datos en la entrada. Los botones a la derecha proporcionan una vista previa del mensaje, los ajustes de permisos de la entrada, y un botón <b>Enviar</b> para publicarla.</dd>
- <dt><a href="#" onclick='contextualHelpFocus("#group-sidebar", 1); return false;' title="Pulsar sobre el elemento resaltado...">Grupos de canales</a></dt>
- <dd>Los grupos de canales que ha creado se muestran en el panel lateral. Seleccionándolos, se filtran las entradas creadas por los canales incluidos en el grupo elegido.</dd>
- <dt><a href="#" onclick='$("#dbtn-acl").click(); return false;' title="Pulsar sobre el elemento resaltado...">Permisos de una entrada</a></dt>
- <dd>La lista de control de acceso (ACL) es lo que se utiliza para establecer quién puede ver su nueva entrada. Al pulsar el botón ACL, al lado del botón Enviar, se mostrará un cuadro de diálogo en el que puede seleccionar qué canales y / o grupos de canales pueden ver el mensaje. También puede seleccionar a quién se le niega el acceso explícitamente. Por ejemplo,di gamos que usted está planeando una
- fiesta sorpresa para un amigo. Puede enviar un mensaje de invitación a todos los miembros de su grupo de <b>Amigos</b>, excepto el amigo al que quiere sorprender. En este caso, "se mostrará" al grupo de amigos, pero "no se mostrará" a esa única persona.</dd>
- </dl>
diff --git a/doc/en/context/es-es/photos/help.html b/doc/en/context/es-es/photos/help.html
deleted file mode 100644
index 6623456eb..000000000
--- a/doc/en/context/es-es/photos/help.html
+++ /dev/null
@@ -1,6 +0,0 @@
- <dl class="dl-horizontal">
- <dt>General</dt>
- <dd>Esta página muestra los álbumes de fotos de un canal. Las imágenes visibles para el observador dependen de los permisos individuales de cada imagen.</dd>
- <dt><a href="#" onclick='contextualHelpFocus("#tabs-collapse-1", 0); return false;' title="Pulsar sobre el elemento resaltado...">Pestañas de los contenidos del canal</a></dt>
-<dd>Las pestañas de los contenidos del canal son enlaces a otros contenidos publicados por el canal. La pestaña "<b>Mi perfil</b>" enlaza con el perfil del canal. La pestaña "<b>Fotos</b>" enlaza con las galerías de fotos. La pestaña "<b>Ficheros</b>" enlaza con los ficheros de cualquier tipo compartidos por el canal.</dd>
- </dl>
diff --git a/doc/en/context/es-es/profile/help.html b/doc/en/context/es-es/profile/help.html
deleted file mode 100644
index 28ff22499..000000000
--- a/doc/en/context/es-es/profile/help.html
+++ /dev/null
@@ -1,6 +0,0 @@
- <dl class="dl-horizontal">
- <dt>General</dt>
- <dd>Esta es la página de perfil de un canal. Por lo general muestra la información que describe el canal. Si el canal representa a una persona en una red social, por ejemplo, el perfil podría proporcionar información de contacto y otros datos personales. Los canales pueden tener varios perfiles, en cuyo caso el perfil que se muestra depende del observador.</dd>
- <dt><a href="#" onclick='contextualHelpFocus("#tabs-collapse-1", 0); return false;' title="Pulsar sobre el elemento resaltado...">Pestañas de los contenidos del canal</a></dt>
- <dd>Las pestañas de los contenidos del canal son enlaces a otros contenidos publicados por el canal. La pestaña "Mi perfil" enlaza con el perfil del canal. La pestaña "Fotos" nlaza con las galerías de fotos. La pestaña "Ficheros" enlaza con los ficheros de cualquier tipo compartidos por el canal.</dd>
- </dl>
diff --git a/doc/en/context/es-es/profiles/help.html b/doc/en/context/es-es/profiles/help.html
deleted file mode 100644
index 9303e049c..000000000
--- a/doc/en/context/es-es/profiles/help.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>
- Al registar <i>cuenta</i> en Hubzilla, también ha creado un <i>perfil</i> y un <i>canal</i>.
- </dd>
- <dt>Cuenta</dt>
- <dd>
- Tiene <i>una</i> cuenta. Esta consta de su correo electrónico y su contraseña. Mediante su cuenta, accede a su
- perfil y su canal. <i>Piense en su cuenta como la vía para identificarse en un sitio Hubzilla. Le permite
- hacer cosas, como crear perfiles y canales a través de los cuelas podrá conectar con otra gente.</i>
- </dd>
- <dt>Perfil</dt>
- <dd>
- Usted está registrado, seguramente, con algunos otros servicios de Internet, como foros o comunidades en línea. Para todos ellos
- usted proporcionó algún tipo de información sobre usted mismo, tal como su fecha de nacimiento, país, edad y gustos o preferencias. Frente a otros
- servicios, Hubzilla le ofrece la ventaja de crear
- <i>muchos más perfiles</i>. De esa manera usted puede distinguir entre los perfiles dirigidos especialmente a todo el mundo
- (su perfil público), Sus compañeros de trabajo, su familia y su pareja.<i>Piense en su perfil como la información básica
- básica que acerca de usted mismo muestra a otra gente,</i>
- </dd>
- <dt>Canal</dt>
- <dd>
- Durante el registro, creó su primer <i>canal</i>. Sí, además de los varios perfiles, usted puede tener
- varios canales. Esto podría ser un poco confuso al principio, pero vamos a aclarar las cosas. Usted ya ha creado
- un canal. Puede usarlo para el público en general, para informar a los demás sobre su vida cotidiana. Pero
- Tal vez usted es un ávido lector de libros y muchas personas se aburren con eso. Así que abre un <i>segundo canal</i> sólo
- para los amantes de los libros, en el que todos ustedes pueden hablar de libros tanto como quieran. Obviamente, este es un nuevo flujo de
- entradas, con un nuevo perfil (...o nuevos perfil<i>es</i>...) y contactos completamente diferentes. Algunas conexiones
- pueden existir en ambos canales, pero habrá algunas que estén exclusivamente en uno de los dos. Usted mismo simplemente
- cambie entre ambos como cambia de interlocutor en el mundo real cuando habla con varias personas en la calle
- o personas con las que se reúne especialmente para hablar de libros. Incluso puede conectarse a sí mismo, o mejor: a su otro
- canal. :)<i> Piense en un canal como en diferentes espacios dedicados a diferentes temas en los que puede hablar con diferentes
- personas.</i>
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/es-es/settings/account/help.html b/doc/en/context/es-es/settings/account/help.html
deleted file mode 100644
index 9303e049c..000000000
--- a/doc/en/context/es-es/settings/account/help.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>
- Al registar <i>cuenta</i> en Hubzilla, también ha creado un <i>perfil</i> y un <i>canal</i>.
- </dd>
- <dt>Cuenta</dt>
- <dd>
- Tiene <i>una</i> cuenta. Esta consta de su correo electrónico y su contraseña. Mediante su cuenta, accede a su
- perfil y su canal. <i>Piense en su cuenta como la vía para identificarse en un sitio Hubzilla. Le permite
- hacer cosas, como crear perfiles y canales a través de los cuelas podrá conectar con otra gente.</i>
- </dd>
- <dt>Perfil</dt>
- <dd>
- Usted está registrado, seguramente, con algunos otros servicios de Internet, como foros o comunidades en línea. Para todos ellos
- usted proporcionó algún tipo de información sobre usted mismo, tal como su fecha de nacimiento, país, edad y gustos o preferencias. Frente a otros
- servicios, Hubzilla le ofrece la ventaja de crear
- <i>muchos más perfiles</i>. De esa manera usted puede distinguir entre los perfiles dirigidos especialmente a todo el mundo
- (su perfil público), Sus compañeros de trabajo, su familia y su pareja.<i>Piense en su perfil como la información básica
- básica que acerca de usted mismo muestra a otra gente,</i>
- </dd>
- <dt>Canal</dt>
- <dd>
- Durante el registro, creó su primer <i>canal</i>. Sí, además de los varios perfiles, usted puede tener
- varios canales. Esto podría ser un poco confuso al principio, pero vamos a aclarar las cosas. Usted ya ha creado
- un canal. Puede usarlo para el público en general, para informar a los demás sobre su vida cotidiana. Pero
- Tal vez usted es un ávido lector de libros y muchas personas se aburren con eso. Así que abre un <i>segundo canal</i> sólo
- para los amantes de los libros, en el que todos ustedes pueden hablar de libros tanto como quieran. Obviamente, este es un nuevo flujo de
- entradas, con un nuevo perfil (...o nuevos perfil<i>es</i>...) y contactos completamente diferentes. Algunas conexiones
- pueden existir en ambos canales, pero habrá algunas que estén exclusivamente en uno de los dos. Usted mismo simplemente
- cambie entre ambos como cambia de interlocutor en el mundo real cuando habla con varias personas en la calle
- o personas con las que se reúne especialmente para hablar de libros. Incluso puede conectarse a sí mismo, o mejor: a su otro
- canal. :)<i> Piense en un canal como en diferentes espacios dedicados a diferentes temas en los que puede hablar con diferentes
- personas.</i>
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/es-es/settings/channel/help.html b/doc/en/context/es-es/settings/channel/help.html
deleted file mode 100644
index 9303e049c..000000000
--- a/doc/en/context/es-es/settings/channel/help.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>
- Al registar <i>cuenta</i> en Hubzilla, también ha creado un <i>perfil</i> y un <i>canal</i>.
- </dd>
- <dt>Cuenta</dt>
- <dd>
- Tiene <i>una</i> cuenta. Esta consta de su correo electrónico y su contraseña. Mediante su cuenta, accede a su
- perfil y su canal. <i>Piense en su cuenta como la vía para identificarse en un sitio Hubzilla. Le permite
- hacer cosas, como crear perfiles y canales a través de los cuelas podrá conectar con otra gente.</i>
- </dd>
- <dt>Perfil</dt>
- <dd>
- Usted está registrado, seguramente, con algunos otros servicios de Internet, como foros o comunidades en línea. Para todos ellos
- usted proporcionó algún tipo de información sobre usted mismo, tal como su fecha de nacimiento, país, edad y gustos o preferencias. Frente a otros
- servicios, Hubzilla le ofrece la ventaja de crear
- <i>muchos más perfiles</i>. De esa manera usted puede distinguir entre los perfiles dirigidos especialmente a todo el mundo
- (su perfil público), Sus compañeros de trabajo, su familia y su pareja.<i>Piense en su perfil como la información básica
- básica que acerca de usted mismo muestra a otra gente,</i>
- </dd>
- <dt>Canal</dt>
- <dd>
- Durante el registro, creó su primer <i>canal</i>. Sí, además de los varios perfiles, usted puede tener
- varios canales. Esto podría ser un poco confuso al principio, pero vamos a aclarar las cosas. Usted ya ha creado
- un canal. Puede usarlo para el público en general, para informar a los demás sobre su vida cotidiana. Pero
- Tal vez usted es un ávido lector de libros y muchas personas se aburren con eso. Así que abre un <i>segundo canal</i> sólo
- para los amantes de los libros, en el que todos ustedes pueden hablar de libros tanto como quieran. Obviamente, este es un nuevo flujo de
- entradas, con un nuevo perfil (...o nuevos perfil<i>es</i>...) y contactos completamente diferentes. Algunas conexiones
- pueden existir en ambos canales, pero habrá algunas que estén exclusivamente en uno de los dos. Usted mismo simplemente
- cambie entre ambos como cambia de interlocutor en el mundo real cuando habla con varias personas en la calle
- o personas con las que se reúne especialmente para hablar de libros. Incluso puede conectarse a sí mismo, o mejor: a su otro
- canal. :)<i> Piense en un canal como en diferentes espacios dedicados a diferentes temas en los que puede hablar con diferentes
- personas.</i>
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/es-es/settings/features/help.html b/doc/en/context/es-es/settings/features/help.html
deleted file mode 100644
index a9c3c2d6c..000000000
--- a/doc/en/context/es-es/settings/features/help.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>Esta página le permite configurar los ajustes para muchas funcionalidades adicionales de Hubzilla.</dd>
- <dt><a href='#' onclick='$("#general-settings-title h3 a").click(); setTimeout((function() {contextualHelpFocus("#general-settings-title", 0)}), 1000); return false;' title="Pulsar para resaltar el elemento...">Funcionalidades básicas</a></dt>
- <dd>Las ajustes de las funcionalidades básicas incluyen opciones importantes para su canal, tales como el hospedaje de páginas web y wikis.</dd>
- <dt><a href='#' onclick='$("#composition-settings-title h3 a").click(); setTimeout((function() {contextualHelpFocus("#composition-settings-title", 0)}), 1000); return false;' title="Pulsar para resaltar el elemento...">Opciones para la redacción de entradas</a></dt>
- <dd>Los ajustes de la redacción de entradas incluyen opciones adicionales para la composición de nuevas publicaciones.</dd>
- <dt><a href='#' onclick='$("#net_module-settings-title h3 a").click(); setTimeout((function() {contextualHelpFocus("#net_module-settings-title", 0)}), 1000); return false;' title="Pulsar para resaltar el elemento...">Filtrado del contenido</a></dt>
- <dd>Estos ajustes modifican funcionalidades asociadas al filtrado del contenido y a cómo ver las publicaciones nuevas.</dd>
- <dt><a href='#' onclick='$("#tools-settings-title h3 a").click(); setTimeout((function() {contextualHelpFocus("#tools-settings-title", 0)}), 1000); return false;' title="Pulsar para resaltar el elemento...">Gestión de entradas y comentarios</a></dt>
- <dd>Estos ajustes proporcionan herramientas adicionales para establecer el tema de las entradas y permiten métodos adicionales para los comentarios, tales como los emojis y el etiquetado de la comunidad.</dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/es-es/settings/tokens/help.html b/doc/en/context/es-es/settings/tokens/help.html
deleted file mode 100644
index 6d6a8d81f..000000000
--- a/doc/en/context/es-es/settings/tokens/help.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<dl class="dl-horizontal">
- <dt><a href="/help/member/member_guide#Guest_Access_Tokens">Guest Access Tokens</a></dt>
- <dd>
- Para facilitar el intercambio de recursos privados con no miembros o miembros de otros nodos federados con una identificación limitada, Hubzilla debe proporcionar a los miembros un mecanismo para crear y administrar inicios de sesión temporales ("desechables"), también conocidos como "Zot Access Tokens". Estos tokens/credenciales se pueden utilizar para autenticarse en un sitio de hubzilla con el único propósito de acceder a recursos privilegiados o de acceso controlado (archivos, fotos, publicaciones, páginas web, salas de chat, etc.).
- </dd>
- <dt>Crear un token</dt>
- <dd>
- El formulario para crear / editar acepta tres parámetros, un nombre legible por el usuario, una contraseña o un token de acceso, y un
- límite de caducidad opcional. Una vez que ha caducado, el acceso mediante el token deja de ser válido, ya no puede usarse, y será
- purgado automáticamente de la lista de cuentas temporales. El campo de la contraseña, en los formularios para crear/editar,
- muestra el texto del token de acceso y no una displays the text of the access token and not una contraseña oscurecida.
- </dd>
- <dt>Compartir un token</dt>
- <dd>
- No especificamos mecanismos para compartir estos tokens con otros. Se puede usar cualquier método de comunicación. Cualquier token que haya creado se añade al selector de la Lista de Control y puede ser usado por cualquiera que esté en esa lista.
-
- <b>Ejemplo</b>: Una visitante llega a su sitio. Tiene un token de acceso que usted le ha proporcionado e intenta ver uno de sus álbumes de fotos (que está restringido para que solo lo puedan ver usted mismo y una identidad temporal). El permiso es denegado.
-
- La visitante, ahora, selecciona "Iniciar sesión" del menú de la barra de navegación, que muestra la página de inicio de sesión. Ella escribe el nombre y contraseña que usted le ha proporcionado: ahora ya puede ver el álbum privado de fotos.
-
- De forma alternativa, puede compartir un enlace a un fichero protegido, añadiendo un parámetro del tipo "&zat=abc123" a la dirección URL, en el que la cadena "abc123" es el token de acceso o contraseña para el inicio de sesión temporal. No se requiere ninguna negociación posterior: el fichero será mostrado.
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/es-es/webpages/help.html b/doc/en/context/es-es/webpages/help.html
deleted file mode 100644
index 7cd7f7c11..000000000
--- a/doc/en/context/es-es/webpages/help.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>Puede crear sitios web modulares, con identidad propia, compuestos de elementos compartibles. </dd>
- <dt>Páginas</dt>
- <dd>Esta página enumera sus "páginas", a las que se asignan URLs en las que la gente pueden visitar su sitio. La estructura de las páginas se describe típicamente mediante una plantilla de diseño <b>asociada</b>, y su contenido se construye a partir de una colección de <b>bloques</b>.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#website-portation-tools", 1); return false;' title="Pulsar para resaltar el elmento...">Herramientas de portabilidad de sitios web</a></dt>
- <dd>Las herramientas de portabilidad de sitios web le permiten importar y exportar múltiples elementos de páginas web (páginas, plantillas, bloques). Puede <b>importar</b> tanto de un fichero zip como de una carpeta de ficheros existente en la nube. Puede <b>exportar</b> tanto a un fichero zip que contenga un grupo seleccionado de elementos de páginas web, o exportarlos directamente a una carpeta de ficheros en la nube. <a target="_blank" href="help/webpages">Leer más...</a></dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/es-es/wiki/help.html b/doc/en/context/es-es/wiki/help.html
deleted file mode 100644
index fdae1f095..000000000
--- a/doc/en/context/es-es/wiki/help.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>Cada wiki es una colección de páginas, compuestas como ficheros de texto formateados en Markdown.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#wiki_list", 1); return false;' title="Pulsar para resaltar el elemento...">Lista de wikis</a></dt>
- <dd>Las páginas wiki propiedad del canal <i>que esté autorizado a ver</i> aparecen en el panel lateral.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#wiki-get-history", 0); return false;' title="Pulsar para resaltar el elemento...">Historial de la página</a></dt>
- <dd>Cada revisión de una página se salva para permitir su rápida recuperación. Pulsar en la pestaña <b>Historial</b> para ver las revisiones de la página, incluyendo la fecha y el autor de cada una. El botón de reversión cargará la revisión seleccionada, pero no salvará automáticamente la página.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#wiki_page_list", 1); return false;' title="Pulsar para resaltar el elemento...">Páginas</a></dt>
- <dd>La lista de páginas en el wiki aparece en el panel <b>Páginas del wiki</b>. Antes de salvar las páginas editadas usando el control <b>Página</b> en el menú desplegable, puede <a href='#' onclick='contextualHelpFocus("#id_commitMsg", 0); return false;' title="Pulsar para resaltar el elemento...">escribir un mensaje personalizado</a> que se verá en el visor <a href='#' onclick='contextualHelpFocus("#wiki-get-history", 0); return false;' title="Pulsar para resaltar el elemento..."><b>Historial de la página</b></a> junto con la revisión.</dd>
-</dl>
diff --git a/doc/en/context/fr/admin/logs/help.html b/doc/en/context/fr/admin/logs/help.html
deleted file mode 100644
index 53f8e65c3..000000000
--- a/doc/en/context/fr/admin/logs/help.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Informations de base</dt>
- <dd>Cette page vous permet d'ajuster les paramètres du journal d'événements et d'afficher le journal existant.</dd>.
- <dt>Réglages du journal</dt>
- <dd>Lorsque vous activez l'option "Débogage", les informations du journal système commenceront à s'ajouter au fichier spécifié dans la boîte "Fichier du journal" (le chemin est relatif à la racine du hub, par exemple /var/www). Notez que ce fichier doit être inscriptible par le serveur web.</dd>.
- <dt>Niveau de journalisation</dt>
- <dd>L'option "Niveau de journalisation" vous permet de définir la quantité d'informations ajoutées au fichier journal. Attention : Augmenter ce niveau peut rapidement gonfler la taille du fichier journal à >100MB, en particulier sur les hubs avec plusieurs membres.</dd>.
-</dl>
diff --git a/doc/en/context/fr/admin/queue/help.html b/doc/en/context/fr/admin/queue/help.html
deleted file mode 100644
index 836d81b92..000000000
--- a/doc/en/context/fr/admin/queue/help.html
+++ /dev/null
@@ -1,4 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Informations de base</dt>
- <dd>Les statistiques de file d'attente affichent le nombre de messages dans la file d'attente pour livraison à d'autres centres. La priorité est liée au nombre de fois où la livraison a été tentée sans succès.</dd>
-</dl>
diff --git a/doc/en/context/fr/appman/help.html b/doc/en/context/fr/appman/help.html
deleted file mode 100644
index d65f78378..000000000
--- a/doc/en/context/fr/appman/help.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Information de base</dt>
- <dd>Modifiez les propriétés individuelles de l'application que vous avez sélectionnée. Les catégories vous permettent de trier vos applications pour vous aider à les trouver plus facilement dans la liste. La prise en charge des applications personnalisées que vous ou votre administrateur pouvez choisir de créer inclut des champs tels que &laquo;Prix de l'application&raquo; et &laquo;Lieu d'achat&raquo; qui ne sont pas applicables aux applications centrales Hubzilla.
- </dd>
-</dl>
diff --git a/doc/en/context/fr/cards/help.html b/doc/en/context/fr/cards/help.html
deleted file mode 100644
index a58a9a73e..000000000
--- a/doc/en/context/fr/cards/help.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Informations de base</dt>
- <dd>Les cartes représentent un domaine persistant de collaboration qui est distinct du volet social. Ils sont un peu plus légers que les pages web et les wikis pour une organisation rapide de l'information et ont l'avantage de permettre la collaboration et le commentaire. Ils sont bien adaptés pour aider à organiser des tâches complexes où il y a des mises à jour et des retours d'information fréquents.
- </dd>
- <dt>Ajouter une carte</dt>
- <dd>
- La création d'une nouvelle carte est très similaire à la composition d'un nouveau message.<br><br>
- <ul>
- <li>
- <b>Nom du lien de page :</b> Le nom du lien de page est le nom de la carte pour l'URL statique.
- </li>
- <li>
- <b>Titre :</b> Le titre est affiché en haut de la carte.
- </li>
- <li>
- <b>Catégories :</b> Si la <a href="/settings/features">fonction catégories de messages</a> est activée pour votre chaîne, vous pouvez ajouter des catégories à la carte. Ces catégories remplissent la liste des catégories sur le panneau de gauche et permettent de filtrer votre collection de cartes.
- </li>
- </ul>
- </dd>
-</dl>
diff --git a/doc/en/context/fr/channel/help.html b/doc/en/context/fr/channel/help.html
deleted file mode 100644
index b2cf958b9..000000000
--- a/doc/en/context/fr/channel/help.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Informations de base<dt>
- <dd>C'est la page d'accueil d'un canal. Il est similaire au "mur" de quelqu'un dans un contexte de réseau social. Les messages créés par le canal sont affichés en fonction des permissions de visualisation de l'observateur.</dd>
-<dt>Créer un message</dt>
- <dd>Si vous avez la permission de créer des messages sur la page du canal, alors vous verrez l'éditeur de messages en haut de la page.</dd>
-</dl>
diff --git a/doc/en/context/fr/chat/help.html b/doc/en/context/fr/chat/help.html
deleted file mode 100644
index 5f354c3c6..000000000
--- a/doc/en/context/fr/chat/help.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Informations de base</dt>
- <dd>Créez et utilisez des salons de discussion pour communiquer en temps réel, en utilisant le système standard de permissions Hubzilla pour le contrôle d'accès aux salons de discussion.</dd>
- <dt>Créer un nouveau salon de discussion</dt>
- <dd>Utilisez le bouton &laquo;Nouveau&raquo; pour créer un nouveau salon de discussion. Entrez un nom et la durée de conservation des messages.</dd>
- <dt>Discussion</dt>
- <dd>Entrez votre message dans la boîte de message et cliquez sur &laquo;Envoyer&raquo;. Vous pouvez définir un statut en sélectionnant le bouton du menu de la salle de discussion à côté du bouton &laquo;Envoyer&raquo;. D'autres personnes &laquo;dans la salon&raquo; sont visibles dans le panneau latéral du panneau &laquo;Membres du Salon&raquo;.</dd>
-</dl>
diff --git a/doc/en/context/fr/cloud/help.html b/doc/en/context/fr/cloud/help.html
deleted file mode 100644
index c72e2ca1b..000000000
--- a/doc/en/context/fr/cloud/help.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Informations de base</dt>
- <dd>Cette page affiche les fichiers cloud d'un canal. Les fichiers visibles par l'observateur dépendent des permissions individuelles définies par le propriétaire du canal. Si vous avez l'autorisation de créer/télécharger des fichiers, vous verrez des boutons de contrôle au-dessus de la liste des fichiers.
- </dd>
- <dt><a href='#' onclick='contextualHelpFocus("#tabs-collapse-1", 0); return false;' title="Cliquez pour mettre en évidence l'élément...">Onglets de contenu de la chaîne</a></dt>
- <dd>Les onglets de contenu du canal sont des liens vers d'autres contenus publiés par le canal. L'onglet <b>A propos</b> permet d'accéder au profil du canal. L'onglet <b>Photos</b> permet d'accéder aux galeries de photos de la chaîne. L'onglet <b>Fichiers</b> permet d'accéder aux fichiers généraux partagés publiés par le canal.</dd>
-</dl>
diff --git a/doc/en/context/fr/connections/help.html b/doc/en/context/fr/connections/help.html
deleted file mode 100644
index 38e581866..000000000
--- a/doc/en/context/fr/connections/help.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Informations de base</dt>
- <dd>Cette page affiche une liste de toutes les connexions de ce canal. La liste peut être <a href='#' onclick='contextualHelpFocus(".section-title-wrapper", 0); return false;' title="Cliquez pour mettre en évidence l'élément...">triée et filtrée à l'aide du bouton de menu à côté du bouton de recherche</a>. </dd>
- <dt>Détails de connexion</dt>
- <dd>Chaque entrée de liste affiche les détails d'une connexion spécifique. Une image d'avatar translucide indique une connexion archivée.</dd>
- <dt>État de la connexion</dt>
- <dd> Une connexion peut être dans différents états : <ul><li>Archivé</li><li>Ignoré</li><li>Bloqué</li><li>Caché</li></ul></dd>
-</dl>
diff --git a/doc/en/context/fr/connections/ifpending/help.html b/doc/en/context/fr/connections/ifpending/help.html
deleted file mode 100644
index 38e581866..000000000
--- a/doc/en/context/fr/connections/ifpending/help.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Informations de base</dt>
- <dd>Cette page affiche une liste de toutes les connexions de ce canal. La liste peut être <a href='#' onclick='contextualHelpFocus(".section-title-wrapper", 0); return false;' title="Cliquez pour mettre en évidence l'élément...">triée et filtrée à l'aide du bouton de menu à côté du bouton de recherche</a>. </dd>
- <dt>Détails de connexion</dt>
- <dd>Chaque entrée de liste affiche les détails d'une connexion spécifique. Une image d'avatar translucide indique une connexion archivée.</dd>
- <dt>État de la connexion</dt>
- <dd> Une connexion peut être dans différents états : <ul><li>Archivé</li><li>Ignoré</li><li>Bloqué</li><li>Caché</li></ul></dd>
-</dl>
diff --git a/doc/en/context/fr/connedit/help.html b/doc/en/context/fr/connedit/help.html
deleted file mode 100644
index c9b784d3e..000000000
--- a/doc/en/context/fr/connedit/help.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Informations de base</dt>
- <dd>Cette page vous permet de modifier ou d'éditer les paramètres individuels pour une connexion particulière ou de supprimer complètement une connexion. Vous êtes peut-être arrivé sur cette page après avoir créé ou approuvé une nouvelle connexion. Si c'est le cas, vous n'êtes pas tenu de faire quoi que ce soit. Votre connexion a déjà été établie. <strong>Vous pouvez</strong> les ajouter à un groupe ou ajuster des permissions spéciales, et cette page est présentée de façon à ce que vous puissiez le faire pendant que l'occasion se présente.
- </dd>
- <dt><a href='#' onclick='contextualHelpFocus(".section-title-wrapper", 0); return false;' title="Cliquez pour mettre en évidence l'élément...">Outils de connexion</a></dt>
- <dd>Le <a href='#' onclick='contextualHelpFocus(".section-title-wrapper", 0); return false;' title="Cliquez pour mettre en évidence l'élément...">menu Outils de connexion</a> permet d'accéder à plusieurs paramètres. Afficher le profil, Afficher l'activité récente, Rafraîchir les autorisations, définir ou réinitialiser les indicateurs (Bloquer, Ignorer, Archiver, Cacher) et supprimer la connexion.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#group-sidebar", 0); return false;' title="Cliquez pour mettre en évidence l'élément...">Groupes de protection de la vie privée</a></dt>
- <dd>Chaque connexion peut être assignée à un ou plusieurs groupes de confidentialité pour regrouper des collections d'amis ayant accès à des messages, médias et autres contenus spécifiques. Vous pouvez les ajouter à un groupe de confidentialité existant ici, ou créer un nouveau groupe de confidentialité. Lorsque vous les ajoutez à un groupe existant, l'action est immédiate et vous n'avez pas besoin de soumettre un formulaire. </dd>
- <dt><a href='#' onclick='contextualHelpFocus("#perms-tool", 0); return false;' title="Cliquez pour mettre en évidence l'élément...">Permissions individuelles</a></dt>
- <dd>L'octroi de permissions est généralement automatique et n'exige aucune action de votre part. Cependant, il se peut que vous souhaitiez ajuster des paramètres spécifiques pour cette connexion qui sont différents des autres.</dd>
- <dt>Paramètres spécifiques aux fonctions</dt>
- <dd>Un certain nombre de paramètres individuels sont contrôlés par des fonctions supplémentaires qui peuvent ou non être activées sur votre hub ou pour votre canal. Plusieurs fonctions optionnelles ont des paramètres pour chaque connexion, et ceux-ci peuvent être définis sur cette page par le biais d'onglets de formulaire supplémentaires qui peuvent être présents.</dd>
-</dl>
diff --git a/doc/en/context/fr/events/help.html b/doc/en/context/fr/events/help.html
deleted file mode 100644
index aafd49703..000000000
--- a/doc/en/context/fr/events/help.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Informations de base</dt>
- <dd>Cette page affiche un calendrier des événements qui vous appartiennent et que vous partagez avec d'autres canaux.</dd>
- <dt>Vue du calendrier</dt>
- <dd>Le calendrier peut être affiché en mode mois, semaine ou jour en utilisant les options du panneau latéral.</dd>
- <dt>Exportation/Importation</dt>
- <dd>Exporter ou importer des événements de calendrier en utilisant des fichiers au format standard iCalendar (.ics).</dd>
-</dl>
diff --git a/doc/en/context/fr/mail/help.html b/doc/en/context/fr/mail/help.html
deleted file mode 100644
index fcf4eff21..000000000
--- a/doc/en/context/fr/mail/help.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Informations de base</dt>
- <dd>Les messages affichés dans courrier privé ne sont visibles que pour vous et le destinataire unique.</dd>
- <dt>Vue combinée</dt>
- <dd>Les conversations complètes peuvent être visualisées dans un fil continu en sélectionnant <b>Vue combinée </b>. Les conversations disponibles sont affichées sous le menu dans le panneau latéral.</dd>
- <dt>Boîte de réception/Boîte d'envoi</dt>
- <dd>Les messages individuels envoyés sont visualisés en sélectionnant <b>Boîte d'envoi</b>, et les messages entrants sont visualisés en utilisant le filtre <b>Boîte de réception</b>.</dd>
- <dt>Nouveau message.</dt>
- <dd>Les messages individuels ont des rapports de livraison qui peuvent être visualisés à l'aide du menu déroulant. Les messages peuvent également être rappelés à partir du même menu, ce qui peut empêcher le destinataire de voir le message <i>s'il ne l'a pas déjà lu</i>.</dd>
-</dl>
diff --git a/doc/en/context/fr/photos/help.html b/doc/en/context/fr/photos/help.html
deleted file mode 100644
index 8609e9849..000000000
--- a/doc/en/context/fr/photos/help.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Informations de base</dt>
- <dd>Cette page affiche les albums photos du canal. Les images visibles pour l'observateur dépendent des permissions d'images individuelles.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#tabs-collapse-1", 0); return false;' title="Cliquez pour mettre en évidence l'élément...">Onglets de contenu du canal</a></dt>
- <dd>Les onglets de contenu du canal sont des liens vers d'autres contenus publiés par le canal. L'onglet <b>A propos</b> permet d'accéder au profil de canal. L'onglet <b>Photos</b> permet d'accéder aux galeries de photos des chaînes. L'onglet <b>Fichiers</b> permet d'accéder aux fichiers généraux partagés publiés par le canal.</dd>
-</dl>
diff --git a/doc/en/context/fr/profile/help.html b/doc/en/context/fr/profile/help.html
deleted file mode 100644
index 26559db6f..000000000
--- a/doc/en/context/fr/profile/help.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Informations de base</dt>
- <dd>Ceci est la page de profil d'un canal. Il affiche typiquement des informations décrivant le canal. Si le canal représente une personne dans un réseau social, par exemple, le profil peut fournir des informations de contact et d'autres détails personnels sur la personne. Les canaux peuvent avoir plusieurs profils, où le profil affiché dépend de l'observateur.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#tabs-collapse-1", 0); return false;' title="Cliquez pour mettre en évidence l'élément...">Onglets de contenu du canal</a></dt>
- <dd>Les onglets de contenu du canal sont des liens vers d'autres contenus publiés par le canal. L'onglet <b>A propos</b> permet d'accéder au profil de canal. L'onglet <b>Photos</b> permet d'accéder aux galeries de photos des chaînes. L'onglet <b>Fichiers</b> permet d'accéder aux fichiers généraux partagés publiés par le canal.</dd>
-</dl>
diff --git a/doc/en/context/fr/settings/account/help.html b/doc/en/context/fr/settings/account/help.html
deleted file mode 100644
index ed8e7f374..000000000
--- a/doc/en/context/fr/settings/account/help.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Informations de base</dt>
- <dd>
- Une fois que vous avez enregistré un <i>compte</i> dans la matrice, vous avez également créé un <i>profil</i> et un <i>canal</i>.
- </dd>
- <dt>Compte</dt>
- <dd>
- Vous avez <i>un compte</i>. Il contient votre compte de courriel et votre mot de passe. Avec votre compte, vous accédez à votre profil et votre canal. Pensez à votre compte comme la façon dont vous vous authentifiez sur un site Hubzilla. Il vous permet de faire des choses, comme créer des profils et des canaux avec lesquels vous pouvez vous connecter à d'autres personnes.
- </dd>
- <dt>Profile</dt>
- <dd>
- Vous vous êtes sûrement inscrit à d'autres services Internet, comme les forums ou les communautés en ligne. Pour chacun d'entre eux, vous avez fourni des informations sur vous-même, telles que la date de naissance, le pays, l'âge et autres. Contrairement à d'autres services, Hubzilla vous offre l'avantage de créer <i>plus de profils</i>. Vous pouvez ainsi faire la distinction entre les profils qui s'adressent à tous (votre profil public) et vos collègues de travail, votre famille et votre partenaire. Pensez à votre profil comme étant l'information de base sur vous-même que vous donnez aux autres.
- </dd>
- <dt>Channel</dt>
- <dd>
- Lors de l'enregistrement, vous avez créé votre premier <i>canal</i>. Oui, en plus de plusieurs profils, vous pouvez avoir plusieurs canaux. Cela pourrait être un peu confus au début, mais mettons les choses au clair. Vous avez déjà créé un canal. Vous pouvez utiliser celui-ci pour le public, pour communiquer avec les gens sur la vie de tous les jours. Mais vous êtes peut-être un lecteur de livres passionné et beaucoup de gens s'ennuient. Ainsi, vous ouvrez un <i>second canal </i> juste pour les amateurs de livres, où vous pouvez tous parler de livres autant que vous le souhaitez. Il s'agit évidemment d'un nouveau flux de messages, avec un nouveau profil (ou nouveaux profils) et des contacts complètement différents. Certaines connexions peuvent exister dans les deux canaux, mais il y en aura qui sont exclusives à l'un d'entre eux. Vous-même vous changez entre les deux comme vous le feriez dans la vie réelle lorsque vous parlez à des gens que vous rencontrez dans la rue ou à des gens que vous rencontrez pour parler de livres spécifiquement. Vous pouvez même vous connecter à vous-même, ou mieux : à votre autre canal <i class="fa fa-smile-o" aria-hidden="true"></i> <i>.<br/>Penser un canal comme différents espaces dédiés à différents sujets où vous rencontrez différentes personnes.</i>.
- </dd>
-</dl>
diff --git a/doc/en/context/fr/settings/channel/help.html b/doc/en/context/fr/settings/channel/help.html
deleted file mode 100644
index ed8e7f374..000000000
--- a/doc/en/context/fr/settings/channel/help.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Informations de base</dt>
- <dd>
- Une fois que vous avez enregistré un <i>compte</i> dans la matrice, vous avez également créé un <i>profil</i> et un <i>canal</i>.
- </dd>
- <dt>Compte</dt>
- <dd>
- Vous avez <i>un compte</i>. Il contient votre compte de courriel et votre mot de passe. Avec votre compte, vous accédez à votre profil et votre canal. Pensez à votre compte comme la façon dont vous vous authentifiez sur un site Hubzilla. Il vous permet de faire des choses, comme créer des profils et des canaux avec lesquels vous pouvez vous connecter à d'autres personnes.
- </dd>
- <dt>Profile</dt>
- <dd>
- Vous vous êtes sûrement inscrit à d'autres services Internet, comme les forums ou les communautés en ligne. Pour chacun d'entre eux, vous avez fourni des informations sur vous-même, telles que la date de naissance, le pays, l'âge et autres. Contrairement à d'autres services, Hubzilla vous offre l'avantage de créer <i>plus de profils</i>. Vous pouvez ainsi faire la distinction entre les profils qui s'adressent à tous (votre profil public) et vos collègues de travail, votre famille et votre partenaire. Pensez à votre profil comme étant l'information de base sur vous-même que vous donnez aux autres.
- </dd>
- <dt>Channel</dt>
- <dd>
- Lors de l'enregistrement, vous avez créé votre premier <i>canal</i>. Oui, en plus de plusieurs profils, vous pouvez avoir plusieurs canaux. Cela pourrait être un peu confus au début, mais mettons les choses au clair. Vous avez déjà créé un canal. Vous pouvez utiliser celui-ci pour le public, pour communiquer avec les gens sur la vie de tous les jours. Mais vous êtes peut-être un lecteur de livres passionné et beaucoup de gens s'ennuient. Ainsi, vous ouvrez un <i>second canal </i> juste pour les amateurs de livres, où vous pouvez tous parler de livres autant que vous le souhaitez. Il s'agit évidemment d'un nouveau flux de messages, avec un nouveau profil (ou nouveaux profils) et des contacts complètement différents. Certaines connexions peuvent exister dans les deux canaux, mais il y en aura qui sont exclusives à l'un d'entre eux. Vous-même vous changez entre les deux comme vous le feriez dans la vie réelle lorsque vous parlez à des gens que vous rencontrez dans la rue ou à des gens que vous rencontrez pour parler de livres spécifiquement. Vous pouvez même vous connecter à vous-même, ou mieux : à votre autre canal <i class="fa fa-smile-o" aria-hidden="true"></i> <i>.<br/>Penser un canal comme différents espaces dédiés à différents sujets où vous rencontrez différentes personnes.</i>.
- </dd>
-</dl>
diff --git a/doc/en/context/fr/settings/features/help.html b/doc/en/context/fr/settings/features/help.html
deleted file mode 100644
index 2811c932e..000000000
--- a/doc/en/context/fr/settings/features/help.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Informations de base</dt>
- <dd>Cette page vous permet de configurer les paramètres pour les nombreuses fonctionnalités supplémentaires de Hubzilla.</dd>
- <dt><a href='#' onclick='$("#general-settings-title h3 a").click(); setTimeout((function() {contextualHelpFocus("#general-settings-title", 0)}), 1000); return false;' title="Click to highlight element...">Paramètres généraux</a></dt>
- <dd>Les paramètres généraux des fonctionnalités incluent des options pertinentes pour votre canal, telles que la page web et l'hébergement wiki.</dd>
- <dt><a href='#' onclick='$("#composition-settings-title h3 a").click(); setTimeout((function() {contextualHelpFocus("#composition-settings-title", 0)}), 1000); return false;' title="Click to highlight element...">Fonctions de composition de messages</a></dt>
- <dd>Les fonctions de composition de messages offrent des options et des capacités supplémentaires lors de la composition de nouveaux messages.</dd>
- <dt><a href='#' onclick='$("#net_module-settings-title h3 a").click(); setTimeout((function() {contextualHelpFocus("#net_module-settings-title", 0)}), 1000); return false;' title="Click to highlight element...">Filtrage des réseaux et des flux</a></dt>
- <dd>Ces paramètres modifient les fonctions associées au filtrage et au contrôle de l'affichage des messages entrants.</dd>
- <dt><a href='#' onclick='$("#tools-settings-title h3 a").click(); setTimeout((function() {contextualHelpFocus("#tools-settings-title", 0)}), 1000); return false;' title="Click to highlight element...">Outils d'affichage et de commentaires</a></dt>
- <dd>Ceux-ci fournissent des outils supplémentaires pour catégoriser les messages et permettre des méthodes de commentaires supplémentaires telles que l'emoji ou le balisage de la communauté.</dd>
-</dl>
diff --git a/doc/en/context/fr/settings/tokens b/doc/en/context/fr/settings/tokens
deleted file mode 100644
index 6be17a615..000000000
--- a/doc/en/context/fr/settings/tokens
+++ /dev/null
@@ -1,20 +0,0 @@
-<dl class="dl-horizontal">
- <dt><a href="/help/member/member_guide#Guest_Access_Tokens">Jetons d'accès invités</a></dt>
- <dd>
- Afin de faciliter le partage de ressources privées avec des non-membres ou des membres de nœuds de fédération à découverte d'identification limitée, Hubzilla devrait fournir aux membres un mécanisme pour créer et gérer des logins temporaires (&laquo;jetables&raquo;), alias &laquo;Zot Access Tokens&raquo;. Ces jetons/accréditations peuvent être utilisés pour s'authentifier sur un site hubzilla dans le seul but d'accéder à des ressources privilégiées ou contrôlées (fichiers, photos, posts, pages web, salons de discussion, etc.
- </dd>
- <dt>Créer un jeton</dt>
- <dd>
- Le formulaire à créer/éditer accepte trois paramètres, un nom lisible par l'homme, un mot de passe ou un jeton d'accès, et une expiration facultative. Une fois expiré, le jeton d'accès n'est plus valide, ne peut plus être utilisé et sera automatiquement supprimé de la liste des comptes temporaires. Le champ mot de passe dans les formulaires de création/édition affiche le texte du jeton d'accès et non un mot de passe masqué.
- </dd>
- <dt>Partager un jeton</dt>
- <dd>
- Nous ne spécifions pas de mécanismes pour partager ces jetons avec d'autres. Toute méthode de communication peut être utilisée. Tous les jetons que vous avez créés sont ajoutés au sélecteur de liste de contrôle d'accès et peuvent être utilisés partout où des listes de contrôle d'accès sont fournies.
-
- <b>Exemple</b> : Un visiteur arrive sur votre site. Elle a un jeton d'accès que vous lui avez fourni et tente de visiter l'un de vos albums photos (qui ne peut être visionné que par vous-même et une identité temporaire). La permission est refusée.
-
- Le visiteur sélectionne maintenant &laquo;Login&raquo; dans la barre de navigation du menu. Ceci présente une page de connexion. Elle entre le nom et le mot de passe que vous lui avez fournis, et elle peut maintenant voir l'album photo restreint.
-
- Alternativement, vous pouvez partager un lien vers un fichier protégé en ajoutant un paramètre &laquo;&zat=abc123&raquo; à l'URL, où la chaîne &laquo;abc123&raquo; est la clé d'accès ou le mot de passe pour la connexion temporaire. Aucune autre négociation n'est nécessaire et le dossier est présenté.
- </dd>
-</dl>
diff --git a/doc/en/context/fr/webpages/help.html b/doc/en/context/fr/webpages/help.html
deleted file mode 100644
index c9d34c08f..000000000
--- a/doc/en/context/fr/webpages/help.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Informations de base</dt>
- <dd>Vous pouvez créer des sites Web modulaires, conscients de l'identité, composés d'éléments partageables.</dd>
- <dt>Pages</dt>
- <dd>Cette page énumère vos "pages", qui sont assignées des URLs où les gens peuvent visiter votre site. La structure des pages est généralement décrite par une <b>mise en page </b> associée , et leur contenu est construit à partir d'une collection de <b>blocs</b>.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#website-portation-tools", 1); return false;' title="Cliquez pour mettre en évidence l'élément...">Outils de portage de site Web</a></dt>
- <dd>Les outils de portation de site Web vous permettent d'importer/exporter plusieurs éléments de pages Web (pages, mises en page, mises en page, blocs). Vous pouvez <b>importer </b> soit à partir d'un fichier zip téléchargé, soit à partir d'un dossier de fichiers cloud existant. Vous pouvez <b>exporter </b> vers un fichier zip contenant un groupe choisi d'éléments de page Web sous une forme compatible avec l'outil d'importation, ou vous pouvez exporter directement vers un dossier de fichiers dans le cloud. <a target="_blank" href="help/webpages">en savoir plus...</a></dd>
-</dl>
diff --git a/doc/en/context/fr/wiki/help.html b/doc/en/context/fr/wiki/help.html
deleted file mode 100644
index af46899e4..000000000
--- a/doc/en/context/fr/wiki/help.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Informations de base</dt>
- <dd>Chaque wiki est une collection de pages, composée sous forme de fichiers texte au format Markdown.</dd>
- <dt>Liste des wikis</dt>
- <dd>Les wikis appartenant au canal <i>que vous avez la permission de voir </i> sont listés dans le panneau latéral.</dd>
- <dt>Historique de la page</dt>
- <dd>Chaque révision d'une page est sauvegardée pour permettre un retour rapide. Cliquez sur l'onglet <b>History</b> pour afficher l'historique des révisions de page, y compris la date et l'auteur de chacune d'entre elles. Le bouton Revenir chargera la révision sélectionnée mais n'enregistrera pas automatiquement la page.</dd>
- <dt>Pages</dt>
- <dd>La liste des pages du wiki est listée dans le panneau <b>Pages Wiki</b>. Avant d'enregistrer les modifications de page à l'aide du menu déroulant <b>Page</b>, vous pouvez <a href='#' onclick='contextualHelpFocus("#id_commitMsg", 0); return false;' title="Cliquez pour mettre en évidence l'élément...">entrer un message personnalisé</a> à afficher dans le <a href='#' onclick='contextualHelpFocus("#wiki-get-history", 0); return false;' title="Cliquez pour mettre en évidence l'élément...">visualiseur d'historique de page</a> avec la révision.</dd>
-</dl>
-
-
-
diff --git a/doc/en/context/it/wiki/help.html b/doc/en/context/it/wiki/help.html
deleted file mode 100644
index e93e18eb4..000000000
--- a/doc/en/context/it/wiki/help.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<dl class="dl-horizontal">
- <dt>General</dt>
- <dd>Ogni wiki è una raccolta di pagine, composta di file di testo formattati con Markdown.</dd>
- <dt><a href="#" onclick='contextualHelpFocus("#wiki_list", 1); return false;' title="Fai click per evidenziare l'elemento...">Elenco dei Wiki</a></dt>
- <dd>I Wiki del canale <i>che sei abilitato a vedere</i> sono elencati nel pannello laterale.</dd>
- <dt><a href="#" onclick='contextualHelpFocus("#wiki-get-history", 0); return false;' title="Fai click per evidenziare l'elemento...">Cronologia della pagina</a></dt>
- <dd>Le revisioni di ogni pagina vengono salvate per rendere possibile un ripristino veloce. Fai click sulla tab <b>Cronologia</b> per vedere l'elenco delle revisioni, con autore e data delle stesse. Il bottone di ripristino riporterà alla revisione selezionata, senza salvare automaticamente la pagina.</dd>
- <dt><a href="#" onclick='contextualHelpFocus("#wiki_page_list", 1); return false;' title="Fai click per evidenziare l'elemento...">Pagine</a></dt>
- <dd>L'elenco delle pagine del wiki si trova nel pannello <b>Pagine del wiki</b>. Prima di salvare le modifiche con il menu a discela <b>Pagina</b>, puoi <a href="#" onclick='contextualHelpFocus("#id_commitMsg", 0); return false;' title="Fai click per evidenziare l'elemento...">inserire un messaggio</a> che verrà visualizzato nella visualizzazione della <a href="#" onclick='contextualHelpFocus("#wiki-get-history", 0); return false;' title="Fai click per evidenziare l'elemento..."><b>storia della pagina</b></a> insieme alla revisione.</dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/pl/admin/addons/assets/addon_repo_gui_1.png b/doc/en/context/pl/admin/addons/assets/addon_repo_gui_1.png
deleted file mode 100644
index 37139b345..000000000
--- a/doc/en/context/pl/admin/addons/assets/addon_repo_gui_1.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/context/pl/admin/addons/help.html b/doc/en/context/pl/admin/addons/help.html
deleted file mode 100644
index fe63718df..000000000
--- a/doc/en/context/pl/admin/addons/help.html
+++ /dev/null
@@ -1,14 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Ogólne</dt>
- <dd>Ta strona zarządza instalacją dodatków (zwanych też <i>wtyczkami</i>).</dd>
- <dt>Zarządzanie repozytorium</dt>
- <dd>Jeśli serwer internetowy ma niezbędne uprawnienia do zapisu, zobaczysz przycisk <b>Zarządzaj repozytoriami</b>,
- co otwiera panel sterowania do zarządzania zainstalowanymi <i>repozytoriami</i> wtyczek. Te repozytoria są
- przechowywane w katalogu <span style = "font-family: monospace;">extension/addon/[nazwa repozytorium]/</span>.
- Oficjalne repozytorium dodatków Hubzilla można dodać, wprowadzając adres URL repozytorium
- <span style = "font-family: monospace;"> https://framagit.org/hubzilla/addons.git </span>
- i wybierając nazwę repozytorium, na przykład <b>oficjalne</b>. Powinieneś zobaczyć to repozytorium na liście
- podobnej do tej:
- <br>
- <img class="img-responsive" src="doc/context/en/admin/addons/assets/addon_repo_gui_1.png"></dd>
-</dl>
diff --git a/doc/en/context/pl/admin/logs/help.html b/doc/en/context/pl/admin/logs/help.html
deleted file mode 100644
index 1d79d6a8c..000000000
--- a/doc/en/context/pl/admin/logs/help.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Ogólne</dt>
- <dd>
- Ta strona umożliwia dostosowanie ustawień dziennika oraz przeglądanie
- istniejącego dziennika.
- </dd>
- <dt>Ustawienia dziennika</dt>
- <dd>
- Po włączeniu opcji debugowania informacje o zdarzeniach systemowych zaczną
- być dołączane do pliku określonego w polu "Plik dziennika" (ścieżka jest
- względna w stosunku do katalogu głównego huba, na przykład /var/www).
- Zauważ, że ten plik musi być możliwy do zapisywania przez serwer WWW.</dd>
- <dt>Pozion rejestracji</dt>
- <dd>
- Opcja poziomu dziennika umożliwia ustawienie ilości informacji dołączanych
- do pliku dziennika. Ostrzeżenie: zwiększenie tego poziomu może szybko
- zwiększyć rozmiar pliku dziennika do ponad 100 MB, szczególnie w hubach z
- więcej niż kilkoma członkami.
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/pl/admin/queue/help.html b/doc/en/context/pl/admin/queue/help.html
deleted file mode 100644
index af5c06787..000000000
--- a/doc/en/context/pl/admin/queue/help.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Ogólne</dt>
- <dd>
- Statystyki kolejki pokazują, ile wpisów znajduje się w kolejce w celu dostarczenia
- ich do innych portali. Priorytet jest związany z liczbą nieudanych prób dostawy.
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/pl/admin/security/help.html b/doc/en/context/pl/admin/security/help.html
deleted file mode 100644
index 0e582e6b4..000000000
--- a/doc/en/context/pl/admin/security/help.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Ogólne</dt>
- <dd>
- Ta strona zawiera różne ustawienia administratora związane z bezpieczeństwem.
- Aby zapisać zmiany wprowadzone w tych ustawieniach, musisz nacisnąć przycisk
- "Prześlij".
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/pl/appman/help.html b/doc/en/context/pl/appman/help.html
deleted file mode 100644
index 867b5b482..000000000
--- a/doc/en/context/pl/appman/help.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Ogólne</dt>
- <dd>
- Edytowanie poszczególnych właściwości wybranej aplikacji. Kategorie umożliwiają
- sortowanie aplikacji, aby ułatwić znajdowanie ich na liście. Wsparcie dla
- niestandardowych aplikacji, które Ty lub Twój administrator możecie wybrać,
- obejmuje pola, takie jak ""Cena aplikacji"" i "Lokalizacja zakupu"", które nie
- są zastosowane w podstawowych aplikacjach Hubzilla.
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/pl/cards/help.html b/doc/en/context/pl/cards/help.html
deleted file mode 100644
index 2e9886283..000000000
--- a/doc/en/context/pl/cards/help.html
+++ /dev/null
@@ -1,31 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Ogólne</dt>
- <dd>
- Karty reprezentują trwały obszar współpracy, który jest niezależny od
- strumienia społecznościowego. Są nieco lżejsze niż strony internetowe i wiki,
- dzięki czemu zapewniają szybką organizację informacji i mają tę zaletę, że
- umożliwiają współpracę i komentowanie. Są dobrze przystosowane do pomocy w
- organizowaniu złożonych zadań, w przypadku których często pojawiają się
- aktualizacje i informacje zwrotne.
- </dd>
- <dt>Dodanie karty</dt>
- <dd>
- Tworzenie nowej karty jest bardzo podobne do tworzenia nowego postaa.<br><br>
- <ul>
- <li>
- <b>Nazwa linku do strony</b>: nazwa linku do strony jest nazwą
- karty dla statycznego adresu URL
- </li>
- <li>
- <b>Tytuł</b>: Tytuł jest wyświetlany u góry karty
- </li>
- <li>
- <b>Kategorie</b>: Jeśli na swoim kanale masz włączoną
- <a href="/settings/features"> funkcjonalność kategorii postów </a>,
- możesz dodawać kategorie do karty. Kategorie te zawarte są na liście
- <b>Kategorie</b>, na lewym panelu i umożliwiają filtrowanie
- kolekcji kart.
- </li>
- </ul>
- </dd>
-</dl>
diff --git a/doc/en/context/pl/channel/help.html b/doc/en/context/pl/channel/help.html
deleted file mode 100644
index bd454c361..000000000
--- a/doc/en/context/pl/channel/help.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Ogólne</dt>
- <dd>
- To jest strona główna kanału. Jest ona podobna do "ściany" profilu w kontekście
- sieci społecznościowej. Posty utworzone w kanale są wyświetlane zgodnie z
- uprawnieniami obserwatora do oglądania treści.
- </dd>
- <dt>Tworzenie posta</dt>
- <dd>
- Jeśli masz uprawnienia do tworzenia postów na stronie kanału, u góry zobaczysz
- edytor postów.
- </dd>
-</dl>
diff --git a/doc/en/context/pl/chat/help.html b/doc/en/context/pl/chat/help.html
deleted file mode 100644
index 0cf063aa8..000000000
--- a/doc/en/context/pl/chat/help.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Ogólne</dt>
- <dd>
- Twórz pokoje rozmów i wykorzystuj je do komunikacji w czasie rzeczywistym,
- używając standardowego systemu uprawnień Hubzilla do kontroli dostępu do
- pokojów rozmów.
- </dd>
- <dt>Tworzenie nowego pokoju rozmów</dt>
- <dd>
- Użyj przycisku "Utwórz nowy", aby utworzyć nowy pokój rozmów. Wpisz nazwę i
- jak długo wiadomości mają być przechowywane.
- </dd>
- <dt>Czatowanie</dt>
- <dd>
- Wpisz wiadomość w polu wiadomości i naciśnij "Prześlij". Możesz ustawić status,
- wybierając przycisk menu pokoju rozmów sieciowych obok przycisku "Wyślij".
- Inne osoby "w pokoju" są widoczne w panelu bocznym w panelu "Członkowie czatu".
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/pl/cloud/help.html b/doc/en/context/pl/cloud/help.html
deleted file mode 100644
index d629e6d41..000000000
--- a/doc/en/context/pl/cloud/help.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Ogólne</dt>
- <dd>
- Ta strona wyświetla pliki "w chmurze" kanału. To co widzi przeglądajacy zależy
- od jego indywidualnych uprawnień do plików, które ustawia właściciel kanału.
- Jeśli masz uprawnienia do tworzenia i przesyłania plików, zobaczysz przyciski
- kontrolne nad listą plików.
- </dd>
- <dt><a href='#' onclick='contextualHelpFocus("#tabs-collapse-1", 0); return false;'
- title="Kliknij, aby podświetlić element...">Karty zawartości kanału</a></dt>
- <dd>
- Karty zawarości kanału to linki do innych treści publikowanych przez kanał.
- Karta <b>Informacje</b> prowadzi do profilu kanału. Karta <b>Zdjęcia</b>
- prowadzi do galerii zdjęć kanału. Zakładka <b>Pliki</b> zawiera linki do
- ogólnych plików udostępnionych do publikacji w kanale.
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/pl/connections/help.html b/doc/en/context/pl/connections/help.html
deleted file mode 100644
index 837184334..000000000
--- a/doc/en/context/pl/connections/help.html
+++ /dev/null
@@ -1,24 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Ogólne</dt>
- <dd>
- Ta strona wyświetla listę wszystkich połączeń tego kanału. Lista można
- <a href = '#' onclick = 'contextualHelpFocus (".section-title-wrapper", 0); return false; '
- title = "Kliknij, aby podświetlić element ..."> posortować i przefiltrować
- za pomocą przycisku menu obok przycisku wyszukiwania </a>.
- </dd>
- <dt>Szczegóły połączenia</dt>
- <dd>
- Każdy wpis na liście przedstawia szczegóły określonego połączenia.
- Przezroczysty obraz awatara wskazuje na zarchiwizowane połączenie.
- </dd>
- <dt>Stan połączenie</dt>
- <dd>
- Połączenie może mieć różne stany:
- <ul>
- <li> Zarchiwizowane</li>
- <li> Zignorowane</li>
- <li> Zablokowane</li>
- <li> Ukryte</li>
- </ul>
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/pl/connections/ifpending/help.html b/doc/en/context/pl/connections/ifpending/help.html
deleted file mode 100644
index b2d05947a..000000000
--- a/doc/en/context/pl/connections/ifpending/help.html
+++ /dev/null
@@ -1,24 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Ogólne</dt>
- <dd>
- Ta strona wyświetla listę wszystkich połączeń tego kanału. Lista można
- <a href = '#' onclick = 'contextualHelpFocus (".section-title-wrapper", 0); return false; '
- title = "Kliknij, aby podświetlić element ..."> posortować i przefiltrować
- za pomocą przycisku menu obok przycisku wyszukiwania</a>.
- </dd>
- <dt>Szczegóły połączenia</dt>
- <dd>
- Każdy wpis na liście przedstawia szczegóły określonego połączenia.
- Przezroczysty obraz awatara wskazuje na zarchiwizowane połączenie.
- </dd>
- <dt>Stan połączenia</dt>
- <dd>
- Połączenie może mieć różne stany:
- <ul>
- <li> Zarchiwizowane</li>
- <li> Zignorowane</li>
- <li> Zablokowane</li>
- <li> Ukryte</li>
- </ul>
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/pl/connedit/help.html b/doc/en/context/pl/connedit/help.html
deleted file mode 100644
index 746908cf2..000000000
--- a/doc/en/context/pl/connedit/help.html
+++ /dev/null
@@ -1,44 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Ogólne</dt>
- <dd>
- Ta strona umożliwia zmianę lub edycję dowolnych indywidualnych ustawień dla
- określonego połączenia lub całkowite usunięcie połączenia. Być może dotarłeś
- do tej strony po utworzeniu lub zatwierdzeniu nowego połączenia. Jeśli tak,
- nie musisz nic robić. Twoje połączenie zostało już nawiązane.
- <strong>Możesz</strong> chcieć dodać je do grupy lub dostosować specjalne
- uprawnienia, a ta strona jest prezentowana, abyś mógł to zrobić przy okazji
- ustanowienie nowego połączenia.
- </dd>
- <dt><a href='#' onclick='contextualHelpFocus(".section-title-wrapper", 0); return false;'
- title="Kliknij, aby podświetlić element...">Narzędzia do połączeń</a></dt>
- <dd>
- <A href = '#' onclick = 'contextualHelpFocus (".section-title-wrapper", 0); return false; '
- title = "Kliknij, aby podświetlić element ..."> Menu naarzędzi połączeń</a>
- umożliwia dostęp do kilku ustawień. Wyświetl profil, wyświetl ostatnią aktywność,
- odśwież uprawnienia, ustaw lub zresetuj flagi (blokuj, ignoruj, archiwizuj, ukryj)
- i usuń połączenie.
- </dd>
- <dt><a href='#' onclick='contextualHelpFocus("#group-sidebar", 0); return false;'
- title="Kliknij, aby podświetlić element...">Grupy prywatności</a></dt>
- <dd>
- Każde połączenie może być przypisane do jednej lub więcej grup prywatności
- w celu grupowania kolekcji znajomych z dostępem do określonych wpisów,
- multimediów i innych treści. Możesz dodać je tutaj do istniejącej grupy
- prywatności lub utworzyć nową grupę prywatności. Po dodaniu ich do istniejącej
- grupy akcja jest natychmiastowa i nie musisz przesyłać formularza.
- </dd>
- <dt><a href='#' onclick='contextualHelpFocus("#perms-tool", 0); return false;'
- title="Kliknij, aby podświetlić element...">Indywidualne uprawniena</a></dt>
- <dd>
- Udzielanie uprawnień jest zwykle automatyczne i nie wymaga żadnych działań
- z Twojej strony. Możesz jednak chcieć dostosować określone uprawnienia dla
- tego połączenia, które są inne niż dla innych.
- </dd>
- <dt>Ustawienia specyficznych funkcji</dt>
- <dd>
- Szereg indywidualnych ustawień jest kontrolowanych za pomocą dodatkowych
- funkcji, które mogą, ale nie muszą być aktywowane na Twoim węźle lub na Twoim
- kanale. Kilka opcjonalnych funkcji ma ustawienia dla każdego połączenia,
- które można ustawić na tej stronie za pomocą dodatkowych zakładek formularza.
- </dd>
-</dl>
diff --git a/doc/en/context/pl/events/help.html b/doc/en/context/pl/events/help.html
deleted file mode 100644
index 542661fa5..000000000
--- a/doc/en/context/pl/events/help.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Ogólne</dt>
- <dd>
- Na tej stronie wyświetlany jest kalendarz wydarzeń, który należy do Ciebie
- oraz kalendarze, które zostały Ci udostępnione z innych kanałów.
- /dd>
- <dt><a href='#' onclick='contextualHelpFocus("#title", 0); return false;'
- title="Kliknij, aby podświetlić element...">Widok kalendarza</a></dt>
- <dd>
- Kalendarz może być wyświetlany w trybie miesiąca, tygodnia lub dnia, przy użyciu
- opcji widocznych na panelu bocznym.
- </dd>
- <dt>Eksport/Import</dt>
- <dd>
- Eksportuj lub importuj wydarzenia kalendarza przy użyciu plików w standardowym
- formacie iCalendar (.ics).
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/pl/mail/help.html b/doc/en/context/pl/mail/help.html
deleted file mode 100644
index b077aa2c4..000000000
--- a/doc/en/context/pl/mail/help.html
+++ /dev/null
@@ -1,25 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Ogólne</dt>
- <dd>
- Wiadomości wyświetlane w poczcie prywatnej są widoczne tylko dla Ciebie i
- pojedynczego odbiorcy.
- </dd>
- <dt>Widok łączony</dt>
- <dd>
- Wybór opcji <b>Widok łączony</b> spowoduje wyświetlanie wszystkich rozmów w ciągłym
- wątku. Dostępne rozmowy są wyświetlane poniżej menu w panelu bocznym.
- </dd>
- <dt>Skrzynka odbiorcza/nadawcza</dt>
- <dd>
- Poszczególne wysłane wiadomości można wyświetlić, wybierając opcję
- <b>Skrzynka nadawcza</b>, a wiadomości przychodzące - za pomocą filtru
- <b>Skrzynka odbiorcza</b>.
- </dd>
- <dt>Nowa wiadomość</dt>
- <dd>
- Poszczególne wiadomości mają raporty doręczenia, które można wyświetlić za
- pomocą rozwijanego menu. Wysłane wiadomości można również odwołać, posługując
- się tym samym menu, co może uniemożliwić odbiorcy przegląd takich wiadomości,
- <i>jeśli jeszcze ich nie przeczytał</i>.
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/pl/network/help.html b/doc/en/context/pl/network/help.html
deleted file mode 100644
index f6ba75241..000000000
--- a/doc/en/context/pl/network/help.html
+++ /dev/null
@@ -1,36 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Ogólne</dt>
- <dd>
- Strona strumienia sieciowego wyświetla strumień wpisów i rozmów, zwykle
- uporządkowanych według ostatnio zaktualizowanych. Jest to strona wysoce konfigurowalna.
- </dd>
- <dt><a href='#' onclick='contextualHelpFocus("#profile-jot-wrapper", 0);
- return false;' title="Kliknij, aby podświetlić element...">Tworzenie wpisu</a></dt>
- <dd>
- U góry strony znajduje się pole tekstowe z napisem "Udostępnij". Kliknięcie
- tego pola otwiera nowy edytor wpisów. Edytor wpisów można dostosowywać, ale
- podstawowy edytor udostępnia pola dla treści wpisu i opcjonalnego <b>tytułu</b>.
- Przyciski poniżej obszaru tekstowego po lewej stronie zapewniają skróty do
- formatowania tekstu i wstawiania linków, obrazów i innych danych do wpisu.
- Przyciski po prawej stronie zapewniają podgląd wpisu, ustawienia uprawnień do
- publikowania oraz przycisk <b>Prześlij</b> do wysłania wpisu.
- </dd>
- <dt><a href='#' onclick='contextualHelpFocus("#group-sidebar", 1);
- return false;' title="Kliknij, aby podświetlić element...">Grupy prywatności</a></dt>
- <dd>
- Utworzone grupy prywatności są wyświetlane w panelu bocznym. Wybranie ich
- powoduje filtrowanie wpisów do tych utworzonych przez kanały w wybranej grupie.
- </dd>
- <dt><a href='#' onclick='$("#dbtn-acl").click(); return false;'
- title="Kliknij, aby podświetlić element...">Uprawnienia do wpisu</a></dt>
- <dd>
- Lista kontroli dostępu (ACL) służy do określania, kto może zobaczyć Twój nowy
- wpis. Naciśnięcie przycisku ACL obok przycisku Prześlij spowoduje wyświetlenie
- okna dialogowego, w którym możesz wybrać kanały albo grupy prywatności, które
- będą widzieć wpis. Możesz także wybrać, komu wyraźnie odmówiono dostęp.
- Załóżmy na przykład, że planujesz przyjęcie niespodziankę dla znajomego.
- Możesz wysłać zaproszenie do wszystkich w swojej grupie <b>Znajomi</b>
- <i>oprócz</i> znajomego, którego zaskakujesz. W tym przypadku "pokazujesz"
- go grupie <b>Znajomi</b>, ale "nie pokazujesz" tej jednej osobie.
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/pl/photos/help.html b/doc/en/context/pl/photos/help.html
deleted file mode 100644
index e9ff7ee9d..000000000
--- a/doc/en/context/pl/photos/help.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Ogólne</dt>
- <dd>
- Na tej stronie wyświetlane są albumy ze zdjęciami opublikowane w kanale.
- Widoczność obrazów zależy od indywidualnych uprawnień odwiedzającego do tych zdjęć.
- </dd>
- <dt><a href='#' onclick='contextualHelpFocus("#tabs-collapse-1", 0); return false;'
- title="Kliknij, aby podświetlić element...">Zakładki treści kanału</a></dt>
- <dd>
- Karty treści kanału to odnośniki do innych treści publikowanych przez kanał.
- Karta <b>Informacje</b> prowadzi do profilu kanału. Karta <b>Zdjęcia</b>
- prowadzi do galerii zdjęć kanału. Zakładka <b>Pliki</b> zawiera linki do
- ogólnych plików udostępnionych w kanale.
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/pl/profile/help.html b/doc/en/context/pl/profile/help.html
deleted file mode 100644
index 9ec5b0e73..000000000
--- a/doc/en/context/pl/profile/help.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Ogólne</dt>
- <dd>
- To jest strona profilu kanału. Zwykle wyświetla informacje opisujące kanał.
- Jeśli na przykład kanał reprezentuje osobę w sieci społecznościowej, profil
- może zawierać informacje kontaktowe i inne dane osobowe tej osoby. Kanały mogą
- mieć wiele profili, przy czym wyświetlany profil zależy od obserwatora.
- </dd>
- <dt><a href='#' onclick='contextualHelpFocus("#tabs-collapse-1", 0); return false;'
- title="Klknij, aby podświetlić element...">Zakładki treści kanału</a></dt>
- <dd>
- Karty treści kanału to odnośniki do innych treści publikowanych przez kanał.
- Karta <b>Informacje</b> prowadzi do profilu kanału. Karta <b>Zdjęcia</b>
- prowadzi do galerii zdjęć kanału. Zakładka <b>Pliki</b> zawiera linki do ogólnych
- plików opublikowanych i udostępnionych w kanale.
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/pl/profiles/help.html b/doc/en/context/pl/profiles/help.html
deleted file mode 100644
index 874303908..000000000
--- a/doc/en/context/pl/profiles/help.html
+++ /dev/null
@@ -1,49 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Ogólne</dt>
- <dd>
- Po zarejestrowaniu <i>konta</i> w matrycy, utworzony został również
- <i>profil</i> i <i>kanał</i>.
- </dd>
- <dt>Konto</dt>
- <dd>
- Masz teraz <i>jedno</i> konto. Składa się z Twojego adresu e-mail i
- hasła. Za pomocą swojego konta uzyskujesz dostęp do profili i kanałów.
- <i>Pomyśl o swoim koncie jak o sposobie uwierzytelniania w jednym
- serwisie Hubzilla. To pozwala wykonywać różne czynności, takie jak
- tworzenie profili i kanałów, za pomocą których można łączyć się z
- innymi osobami.</i>
- </dd>
- <dt>Profil</dt>
- <dd>
- Z pewnością już zarejestrowałeś/zarejestrowałaś się w innych usługach
- internetowych, takich jak fora lub społeczności internetowe. W nich
- wszystkich trzeba było podać pewne informacje o sobie, takie jak data
- urodzenia, kraj, wiek i upodobania. W przeciwieństwie do nich
- usługi Hubzilla daje Ci przewagę tworzenia <i>wielu profili </i>.
- W ten sposób możesz rozróżniać profil przeznaczone specjalnie dla
- wszystkich (Twój profil publiczny), od profili przeznaczonych dla
- współpracowników, rodziny czy partnera. <i> Potraktuj swój profil
- jak pojemnik zawierający podstawowe informacje o Tobie, jakie
- przekazujesz innym osobom. </i>
- </dd>
- <dt>Kanał</dt>
- <dd>
- Podczas rejestracji utworzyłeś/utworzyłaś swój pierwszy <i>kanał</i>.
- Tak, poza kilkoma profilami, również możesz mieć kilka kanałów. Na
- początku może to być nieco zagmatwane, ale wyjaśnijmy to. Już masz
- utworzony jeden kanał. Możesz używać go do publicznego komunikowania
- się z osobami w życiu codziennym. Lecz być może jesteś zapalonym
- czytelnikiem książek a wielu ludzi się tym nudzi. Otwierasz więc
- <i>drugi kanał</i> dla miłośników książek, gdzie wszyscy mogą rozmawiać
- o książkach tyle, ile zechcą. Oczywiście jest to nowy strumień wpisów,
- z nowym profilem (... lub nowymi profilami) i zupełnie z innymi
- kontaktami. Niektóre połączenia mogą istnieć w obu kanałach, ale będą
- takie, które będą występować wyłącznie w jednym z nich. Ty po prostu
- przełączaj się między nimi tak, jak w prawdziwym życiu, gdy rozmawiasz
- z ludźmi, których spotykasz na ulicy lub z osobami, które spotykasz
- specjalnie, aby porozmawiać o książkach. Możesz nawet połączyć się
- ze sobą lub lepiej: ze swoim innym kanał. :) <i> Pomyśl o kanałach
- jak o różnych przestrzeniach poświęconych różnym tematom, w których
- spotykasz się z różnymi osobami.</i>
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/pl/register/help.html b/doc/en/context/pl/register/help.html
deleted file mode 100644
index c5c879e36..000000000
--- a/doc/en/context/pl/register/help.html
+++ /dev/null
@@ -1,54 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Ogólne</dt>
- <dd>
- Na tej stronie odwiedzający mogą zarejestrować się, aby uzyskać dostęp do
- portalu za pomocą identyfikatora logowania. Zalogowani użytkownicy nie tylko
- widzą treści publiczne, ale mogą je sami publikować, komunikować się w sieciach
- społecznościowych i nie tylko.
- </dd>
- <dt>Tryby rejestracji</dt>
- <dd>
- Rejestracja jest możliwa na adres e-mail lub anonimowo, jeśli regulamin portalu
- to dopuszcza (wówczas nie wypełniaj pola e-mail). Możliwe, że masz również
- kod zaproszenia, który trzeba wprowadzić wraz z adresem e-mail. Link nad polem
- e-mail umożliwia wprowadzenie kodu zaproszenia. Ustaw też swoje hasło logowania.
- Musisz podać go dwukrotnie. Nie jest ono jawnie wyświetlane w formularzu, więc
- uważaj przy jego wprowadzaniu.
- </dd>
- <dt>Procedura rejestracji</dt>
- <dd>
- Aby zalogować się po udanej rejestracji, należy ustawić osobne hasło.
- Ze względów bezpieczeństwa wprowadź je dwukrotnie z identycznymi wartościami.
- Zarejestrowani anonimowo użytkownicy otrzymują automatycznie przypisywany identyfikator
- i nie powinni zapomnieć własnego hasła, ponieważ w przeciwieństwie do rejestracji
- adresem e-mail początkowo nie ma funkcji resetowania. W zależności od konfiguracji
- portalu może być wymagana funkcja potwierdzenia. Użytkownicy rejestracji
- przy użyciu adresu e-mail otrzymają odpowiednią wiadomość. W przypadku rejestracji
- anonimowych zostanie wyświetlone inne okno dialogowe zawierające identyfikator
- dostępu i kod PIN. Dane z tej strony powinny być przechowywana bezpiecznie i przez
- długi czas (np. przez wydruk, zrzut ekranu, zdjęcie), ponieważ dane muszą zostać
- ponownie potwierdzone w późniejszym czasie.
- </dd>
- <dt>Tożsamość cyfrowa</dt>
- <dd>
- W zależności od portalu, wyświetlaną nazwę (imię i nazwisko) oraz pseudonim można
- wprowadzić już podczas rejestracji (alternatywnie również podczas pierwszego logowania).
- Pseudonim ma bardzo ważne znaczenie i nie można go później zmienić. Jest to unikalna
- tożsamość cyfrowa (Digital Identity, DID), która jest, będzie i pozostanie powiązana
- ze wszystkimi naszymi działaniami. Ten DID nadaje się nie tylko do logowania na
- tym portalu, ale także do wszystkich połączonych portali sieci federacyjnej.
- W języku sieci federacyjnych ta tożsamość cyfrowa jest "kanałem".
- Jest to porównywalne z numerem telefonu w sieci telefonicznej. DID ma format
- kanał@portal.tld = pseudonim@portal.tld i jak wspomniano, nie można go później
- zmienić. Chociaż format wygląda jak adres e-mail, tak nie jest.
- </dd>
- <dt>Nim rozpoczniesz rejestrację ...</dt>
- <dd>
- ... należy wybrać preferowany język (na przykład polski, angielski, hiszpański,
- niemiecki) (w prawym górnym rogu menu hamburgera <span class = "fa fa-fw fa-bars"> </span>).
- Aktualny język jest używany w kolejnych krokach, a także podczas logowania i po
- zalogowaniu. Można to jednak zmienić w dowolnym momencie i w razie potrzeby.
- Należy również zauważyć, że ten portal nie jest jedyny. Przegląd innych portali
- można znaleźć <a href="./pubsites"> tutaj </a>.
- </dd>
-</dl>
diff --git a/doc/en/context/pl/settings/account/help.html b/doc/en/context/pl/settings/account/help.html
deleted file mode 100644
index dfe82f025..000000000
--- a/doc/en/context/pl/settings/account/help.html
+++ /dev/null
@@ -1,49 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Ogólne</dt>
- <dd>
- Po zarejestrowaniu <i>konta</i> w matrycy, utworzony został również
- <i>profil</i> i <i>kanał</i>.
- </dd>
- <dt>Konto</dt>
- <dd>
- Masz teraz <i>jedno</i> konto. Składa się z Twojego adresu e-mail i
- hasła. Za pomocą swojego konta uzyskujesz dostęp do profili i kanałów.
- <i>Pomyśl o swoim koncie jak o sposobie uwierzytelniania w jednym
- serwisie Hubzilla. To pozwala wykonywać różne czynności, takie jak
- tworzenie profili i kanałów, za pomocą których można łączyć się z
- innymi osobami.</i>
- </dd>
- <dt>Profil</dt>
- <dd>
- Z pewnością już zarejestrowałeś/zarejestrowałaś się w innych usługach
- internetowych, takich jak fora lub społeczności internetowe. W nich
- wszystkich trzeba było podać pewne informacje o sobie, takie jak data
- urodzenia, kraj, wiek i upodobania. W przeciwieństwie do nich
- usługi Hubzilla daje Ci przewagę tworzenia <i>wielu profili </i>.
- W ten sposób możesz rozróżniać profil przeznaczone specjalnie dla
- wszystkich (Twój profil publiczny), od profili przeznaczonych dla
- współpracowników, rodziny czy partnera. <i> Potraktuj swój profil
- jak pojemnik zawierający podstawowe informacje o Tobie, jakie
- przekazujesz innym osobom. </i>
- </dd>
- <dt>Kanał</dt>
- <dd>
- Podczas rejestracji utworzyłeś/utworzyłaś swój pierwszy <i>kanał</i>.
- Tak, poza kilkoma profilami, również możesz mieć kilka kanałów. Na
- początku może to być nieco zagmatwane, ale wyjaśnijmy to. Już masz
- utworzony jeden kanał. Możesz używać go do publicznego komunikowania
- się z osobami w życiu codziennym. Lecz być może jesteś zapalonym
- czytelnikiem książek a wielu ludzi się tym nudzi. Otwierasz więc
- <i>drugi kanał</i> dla miłośników książek, gdzie wszyscy mogą rozmawiać
- o książkach tyle, ile zechcą. Oczywiście jest to nowy strumień wpisów,
- z nowym profilem (... lub nowymi profilami) i zupełnie z innymi
- kontaktami. Niektóre połączenia mogą istnieć w obu kanałach, ale będą
- takie, które będą występować wyłącznie w jednym z nich. Ty po prostu
- przełączaj się między nimi tak, jak w prawdziwym życiu, gdy rozmawiasz
- z ludźmi, których spotykasz na ulicy lub z osobami, które spotykasz
- specjalnie, aby porozmawiać o książkach. Możesz nawet połączyć się
- ze sobą lub lepiej: ze swoim innym kanał. :) <i> Pomyśl o kanałach
- jak o różnych przestrzeniach poświęconych różnym tematom, w których
- spotykasz się z różnymi osobami.</i>
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/pl/settings/channel/help.html b/doc/en/context/pl/settings/channel/help.html
deleted file mode 100644
index dfe82f025..000000000
--- a/doc/en/context/pl/settings/channel/help.html
+++ /dev/null
@@ -1,49 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Ogólne</dt>
- <dd>
- Po zarejestrowaniu <i>konta</i> w matrycy, utworzony został również
- <i>profil</i> i <i>kanał</i>.
- </dd>
- <dt>Konto</dt>
- <dd>
- Masz teraz <i>jedno</i> konto. Składa się z Twojego adresu e-mail i
- hasła. Za pomocą swojego konta uzyskujesz dostęp do profili i kanałów.
- <i>Pomyśl o swoim koncie jak o sposobie uwierzytelniania w jednym
- serwisie Hubzilla. To pozwala wykonywać różne czynności, takie jak
- tworzenie profili i kanałów, za pomocą których można łączyć się z
- innymi osobami.</i>
- </dd>
- <dt>Profil</dt>
- <dd>
- Z pewnością już zarejestrowałeś/zarejestrowałaś się w innych usługach
- internetowych, takich jak fora lub społeczności internetowe. W nich
- wszystkich trzeba było podać pewne informacje o sobie, takie jak data
- urodzenia, kraj, wiek i upodobania. W przeciwieństwie do nich
- usługi Hubzilla daje Ci przewagę tworzenia <i>wielu profili </i>.
- W ten sposób możesz rozróżniać profil przeznaczone specjalnie dla
- wszystkich (Twój profil publiczny), od profili przeznaczonych dla
- współpracowników, rodziny czy partnera. <i> Potraktuj swój profil
- jak pojemnik zawierający podstawowe informacje o Tobie, jakie
- przekazujesz innym osobom. </i>
- </dd>
- <dt>Kanał</dt>
- <dd>
- Podczas rejestracji utworzyłeś/utworzyłaś swój pierwszy <i>kanał</i>.
- Tak, poza kilkoma profilami, również możesz mieć kilka kanałów. Na
- początku może to być nieco zagmatwane, ale wyjaśnijmy to. Już masz
- utworzony jeden kanał. Możesz używać go do publicznego komunikowania
- się z osobami w życiu codziennym. Lecz być może jesteś zapalonym
- czytelnikiem książek a wielu ludzi się tym nudzi. Otwierasz więc
- <i>drugi kanał</i> dla miłośników książek, gdzie wszyscy mogą rozmawiać
- o książkach tyle, ile zechcą. Oczywiście jest to nowy strumień wpisów,
- z nowym profilem (... lub nowymi profilami) i zupełnie z innymi
- kontaktami. Niektóre połączenia mogą istnieć w obu kanałach, ale będą
- takie, które będą występować wyłącznie w jednym z nich. Ty po prostu
- przełączaj się między nimi tak, jak w prawdziwym życiu, gdy rozmawiasz
- z ludźmi, których spotykasz na ulicy lub z osobami, które spotykasz
- specjalnie, aby porozmawiać o książkach. Możesz nawet połączyć się
- ze sobą lub lepiej: ze swoim innym kanał. :) <i> Pomyśl o kanałach
- jak o różnych przestrzeniach poświęconych różnym tematom, w których
- spotykasz się z różnymi osobami.</i>
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/pl/settings/features/help.html b/doc/en/context/pl/settings/features/help.html
deleted file mode 100644
index fc5a62363..000000000
--- a/doc/en/context/pl/settings/features/help.html
+++ /dev/null
@@ -1,50 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Informacje ogólne</dt>
- <dd>
- Ta strona umożliwia skonfigurowanie ustawień dla wielu dodatkowych funkcji Hubzilli, które możesz włączyć na swoim koncie.
- </dd>
- <dt><a href="/help/pl/feature/additional/overview#calendar_settings">Kalendarz</a></dt>
- <dd>
- Są to dodatkowe opcje, które możesz włączyć dla wszystkich swoich kalendarzy.
- Można to zmienić indywidualnie w każdym kalendarzu.<br />
- </dd>
- <dt><a href="/help/pl/feature/additional/overview#channel_main_page_settings">Strona główna kanału</a></dt>
- <dd>
- Kilka dodatkowych możliwości związanych ze stroną główną kanału.
- </dd>
- <dt><a href="/help/pl/feature/additional/overview#connections_settings">Połączenia</a></dt>
- <dd>
- Obecnie jest tu tylko ustawienie opcji umożliwiającej filtrowanie strumienia wg
- słów kluczowych lub treści (fraz).
- </dd>
- <dt><a href="/help/pl/feature/additional/overview#conversation-settings">Rozmowa</a></dt>
- <dd>
- Kilka dodatkowych opcji rozszerzających obsługę rozmów i dyskusji.
- </dd>
- <dt><a href="/help/pl/feature/additional/overview#directoty_settings">Katalog</a></dt>
- <dd>
- Dostępna tu opcja zaawansowanego przeszukiawania katalogu może być bardzo użyteczna
- dla osób chcących dotrzeć do konkretnych informacji publikowanych w sieci Hubzilla.
- </dd>
- <dt><a href="/help/pl/feature/additional/overview#manage_settings">Zarządzanie</a></dt>
- <dd>
- Dostępna tu opcja włącza funkcję zmiany kanału bezpośrednio z rozwijanego menu nawigacji.
- </dd>
- <dt><a href="/help/pl/feature/additional/overview#network-settings">Sieć</a></dt>
- <dd>
- Znajduje się tu szereg opcji włączających funkcje związane z siecią i strumieniem
- sieciowym. Przede wszystkim dostępnych jest tu kilka dodatkowych filtrów i inne
- użyteczne funkcje. Szczegóły można znaleźć dokumentacji.
- </dd>
- <dt><a href="/help/pl/feature/additional/overview#photos_settings">Zdjęcia</a></dt>
- <dd>
- Tutaj można włączyć funkcję wyświetlania na mapie lokalizacji zdjęcia, jeśli
- polik zdjęcia zawiera potrzebne metadane.
- </dd>
- <dt><a href="/help/pl/feature/additional/overview#profiles_settings">Profile</a></dt>
- <dd>
- W tej sekcji zawarte są opcje włączające dodatkowe funkcje dotyczące profilu.
- Jeśli chcesz i możesz zakładać na swoim koncie wiele profili (i tożsamości),
- włącz tu opcję "wiele profili".
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/pl/settings/tokens/help.html b/doc/en/context/pl/settings/tokens/help.html
deleted file mode 100644
index 4c79841d6..000000000
--- a/doc/en/context/pl/settings/tokens/help.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<dl class="dl-horizontal">
- <dt><a href="/help/member/member_guide#Tokeny_dost_pu_go_cia">Tokeny dostępu gościa</a></dt>
- <dd>
- Aby ułatwić udostępnianie prywatnych zasobów osobom niebędących członkami
- tego portalu lub sfederyzowanych portali (hubów) i zapewnić zabezpieczone
- wykrywaniem danych identyfikacyjnych, Hubzilla posiada mechanizm tworzenia
- i zarządzania tymczasowymi ("jednorazowymi") loginami, zwanymi "tokenami
- dostępu Zot”. Tokeny te, będące swojego rodzaju danymi uwierzytelniającymi,
- mogą być używane do uwierzytelniania w serwisie Hubzilla wyłącznie w celu
- uzyskania dostępu do uprzywilejowanych lub kontrolowanych zasobów (pliki,
- zdjęcia, wpisy, strony internetowe, pokoje rozmów itp.).
- </dd>
- <dt>Utworzenie tokenu</dt>
- <dd>
- Formularz do tworzenia i edycji akceptuje trzy parametry: czytelną dla
- człowieka nazwę, hasło lub token dostępu oraz opconalną okres ważności.
- Po wygaśnięciu token dostępu nie jest już ważny, nie może być już używany
- i będzie automatycznie usunięte z listy kont tymczasowych. Pole hasła
- w formularzach tworzenia i edycji wyświetla tekst tokenu dostępu, a nie
- zasłonięte hasło.
- </dd>
- <dt>Udostępnienie tokenu</dt>
- <dd>
- Nie jest narzucony sposób udostępniania tych tokenów innym osobom. Można
- użyć dowolnej metody komunikacji. Wszystkie utworzone tokeny są dodawane
- do selektora listy kontroli dostępu i mogą być używane wszędzie tam, gdzie
- są dostępne listy kontroli dostępu.
-
- <b>Przykład</b>: Odwiedzający nawiguje w przeglądarce do Twojąej witryny.
- Ma podany przez Ciebie token dostępu i próbuje odwiedzić jeden z Twoich
- albumów ze zdjęciami (który jest ograniczony do przeglądania tylko przez
- Ciebie i jedną tymczasową tożsamość). Odmowa dostępu.
-
- Odwiedzający wybiera teraz opcję „Zaloguj się” z paska nawigacji menu.
- To wyświetli stronę logowania. Wpisuje tam nazwę i hasło, które podałeś
- i może teraz przeglądać zabezpieczony album ze zdjęciami.
-
- Alternatywnie możesz udostępnić łącze do chronionego pliku, dodając parametr
- "&zat=abc123" do adresu URL, gdzie ciąg "abc123" to token dostępu lub
- hasło do tymczasowego logowania. Nie są wymagane dalsze negocjacje
- a żądany zasób zostanie wyświetlony.
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/pl/webpages/help.html b/doc/en/context/pl/webpages/help.html
deleted file mode 100644
index 64858fcf6..000000000
--- a/doc/en/context/pl/webpages/help.html
+++ /dev/null
@@ -1,24 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Ogólne</dt>
- <dd>
- Możesz tworzyć modułowe, rozpoznające tożsamość witryny internetowe, składające
- się z elementów, które można udostępniać.
- </dd>
- <dt>Strony</dt>
- <dd>
- Tutaj znajduje się lista Twoich "stron" z przypisanymi adresami URL,
- pod którymi osoby mogą je odwiedzać. Struktura stron jest zwykle opisywana
- przez powiązany <b>układ</b>, a ich zawartość jest tworzona ze zbioru <b>bloków</b>.
- </dd>
- <dt><a href='#' onclick='contextualHelpFocus("#website-portation-tools", 1);
- return false;' title="Click to highlight element...">Narzędzia do przenoszenia stron internetowych</a></dt>
- <dd>
- Narzędzia do przenoszenia witryn umożliwiają importowanie i eksportowanie wielu
- elementów strony internetowej (stron, układów, bloków). Możesz <b>zaimportować</b>
- te alementy albo z przesłanego pliku ZIP, albo z istniejącego folderu plików
- w chmurze. Możesz je <b>wyeksportować</b> do pliku zip zawierającego wybraną
- grupę elementów strony internetowej w formie zgodnej z narzędziem do importowania
- lub możesz wyeksportować bezpośrednio do folderu plików w chmurze.
- <a target="_blank" href="help/webpages"> Czytaj więcej ... </a>
- </dd>
-</dl> \ No newline at end of file
diff --git a/doc/en/context/pl/wiki/help.html b/doc/en/context/pl/wiki/help.html
deleted file mode 100644
index 6aa6a7192..000000000
--- a/doc/en/context/pl/wiki/help.html
+++ /dev/null
@@ -1,29 +0,0 @@
-<dl class="dl-horizontal">
- <dt>Ogólne</dt>
- <dd>
- Każda wiki to zbiór stron utworzonych jako pliki tekstowe w formacie Markdown.
- </dd>
- <dt>Wykaz Wiki</dt>
- <dd>
- Strony wiki należące do kanału, <i>do przeglądania którego masz uprawnienia</i>,
- są wymienione w panelu bocznym.
- </dd>
- <dt>Historia strony</dt>
- <dd>
- Zapisywana jest każda wersja strony, aby umożliwić szybkie przywrócenie.
- Kliknij kartę <b>Historia</b>, aby wyświetlić historię zmian strony, w tym
- datę i autora każdej z nich. Przycisk przywróć załaduje wybraną wersję, ale
- nie zapisze automatycznie strony.
- </dd>
- <dt>Strony</dt>
- <dd>
- Lista stron wiki znajduje się w panelu <b>Strony Wiki</b>. Przed zapisaniem
- zmian na stronie za pomocą rozwijanego menu <b>Strona</b> można
- <a href = '#' onclick = 'contextualHelpFocus ("# id_commitMsg", 0);
- return false; ' title = "Kliknij, aby podświetlić element ...">wprowadzić
- własny komunikat</a>, który będzie wyświetlany w
- <a href = '#' onclick = 'contextualHelpFocus ("# wiki-get-history", 0);
- return false; ' title = "Kliknij, aby podświetlić element ..."><b> Historii
- strony</b> przeglądarki </a> wraz z wersją.
- </dd>
-</dl>
diff --git a/doc/en/context/ru/cards/help.html b/doc/en/context/ru/cards/help.html
deleted file mode 100644
index ccdf9d6d9..000000000
--- a/doc/en/context/ru/cards/help.html
+++ /dev/null
@@ -1,16 +0,0 @@
-<dl class="dl-horizontal"><dt>Резюме</dt>
- <dd>Карточки используются для командной работы в виртуальном пространстве дополнительно к ленте сообщений. Карточки являются болле компактными по сравнению с веб-страницами и вики и предназначены для быстрой организации работы. Преимущество карточек в возможности совместной работы и комментирования. Карточки могут помочь организовать и упростить работу, связанную с частыми изменениями и с обсуждением в проекте.
- </dd>
- <dt>Добавить карточку</dt>
- <dd>
- Создание новой карточки подобно написанию нового поста.<br /><br /><ul><li>
- <b>Имя ссылки для веб-страницы</b>: Имя ссылки для веб-страницы - это имя карточки для статической URL
- </li>
- <li>
- <b>Заголовок</b>: Текст заголовка карточки
- </li>
- <li>
- <b>Категории</b>: Если <a href="/settings/features">Инструмент "Категории сообщения"</a> в вашем канале разрешён, то вы можете присвоить карточке определённую категорию. Эта категория появится в списке <b>Категорий</b> в левой колонке и разрешает быстрый выбор записей определённой категории из картотеки одним щёлчком мышки.
- </li>
- </ul></dd>
-</dl>
diff --git a/doc/en/context/ru/connections/help.html b/doc/en/context/ru/connections/help.html
deleted file mode 100644
index b5996e0d0..000000000
--- a/doc/en/context/ru/connections/help.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<dl class="dl-horizontal">
- <dd>На этой странице отображается список всех подключений этого канала. Список можно отсортировать и отфильтровать с помощью кнопки <a href='#' onclick='contextualHelpFocus(".section-title-wrapper", 0); return false;' title="Нажмите чтобы выделить элемент..."><button class='btn btn-outline-secondary btn-sm'><i class="bi bi-funnel-fill"></i></button></a> рядом с кнопкой поиска.</dd>
- <dt>Сведения о контакте</dt>
- <dd>Каждая запись в списке показывает информацию о конкретном контакте. Полупрозрачное изображение профиля указывает на заархивированное соединение.</dd>
- <dt>Статус контакта</dt>
- <dd>Контакт может находиться в разных состояниях: <ul><li>Заархивирован</li><li>Игнорируется</li><li>Заблокирован</li><li>Скрыт</li></ul></dd>
-</dl>
diff --git a/doc/en/context/ru/network/help.html b/doc/en/context/ru/network/help.html
deleted file mode 100644
index 19b5452e2..000000000
--- a/doc/en/context/ru/network/help.html
+++ /dev/null
@@ -1,9 +0,0 @@
-<dl class="dl-horizontal">
- <dd>Здесь отображается поток сообщений и бесед, обычно упорядоченных по последнему обновлению. Данная страница имеет гибкие настройки.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#profile-jot-wrapper", 0); return false;' title="Нажмите чтобы выделить элемент...">Создать заметку</a></dt>
- <dd>В верхней части страницы есть текстовое поле с надписью "Поделиться". Нажатие на это поле открывает редактор сообщений. Внешний ид редактора сообщений настраивается, однако по умолчанию редактор имеет поля для текста заметки и необязательного поля "Заголовок". Кнопки под полем для ввода текста слева служат для форматирования текста, вставки ссылок, изображений и других данных. Кнопки справа обеспечивают предварительный просмотр сообщения, настройку разрешений для публикации и кнопку <button class='btn btn-outline-secondary btn-sm'>Отправить</button> для её отправки.</dd>
- <dt><a href='#' onclick='contextualHelpFocus("#group-sidebar", 1); return false;' title="Нажмите чтобы выделить элемент...">Группы конфиденциальности</a></dt>
- <dd>Созданные вами группы конфиденциальности отображаются на боковой панели. Их выбор фильтрует сообщения по публикациям, которые созданы каналами в выбранной группе.</dd>
- <dt><a href='#' onclick='$("#dbtn-acl").click(); return false;' title="Нажмите чтобы выделить элемент...">Разрешения публикации</a></dt>
- <dd>Список контроля доступа (ACL) используется для того, чтобы указать, кто будет видеть вашу новую публикацию. При нажатии кнопки ACL рядом с кнопкой <button class='btn btn-outline-secondary btn-sm'>Поделиться</button> появится диалоговое окно, в котором вы можете выбрать, какие каналы и / или группы конфиденциальности могут видеть сообщение. Вы также можете выбрать, кому явно отказано в доступе. Например, вы планируете сюрприз для друга. Вы можете отправить сообщение с приглашением всем в вашей группе "Друзья" <i>кроме</i> друга, которого вы хотите поздравить. В этом случае публикацию увидят все члены группы "Друзья", кроме этого человека.</dd>
-</dl>
diff --git a/doc/en/credits.md b/doc/en/credits.md
new file mode 100644
index 000000000..ee63c04a3
--- /dev/null
+++ b/doc/en/credits.md
@@ -0,0 +1,86 @@
+## Credits
+
+Many thanks to everyone who has contributed to this project and its predecessors over the years. It is possible that we have not mentioned your name, but this is not intentional. We also thank the community and its members who have provided valuable input and without whom all this work would be pointless.
+It is also worth acknowledging the contributions and problem solving that resulted from discussions between members and developers of other, somewhat related and competing projects; even if we had occasional disagreements.
+
+- Mike Macgirvin
+- Mario Vavti
+- Scott M. Stolz
+- Chris Burger
+- Emanuel Han
+- Fabio Comuni
+- Simon L'nu
+- marijus
+- Tobias Diekershoff
+- fabrixxm
+- tommy tomson
+- Simon
+- zottel
+- Christian Vogeley
+- jeroenpraat
+- Michael Vogel
+- erik
+- Zach Prezkuta
+- Paolo T
+- Michael Meer
+- Michael
+- Abinoam P. Marques Jr
+- Tobias Hößl
+- Alexander Kampmann
+- Olaf Conradi
+- Paolo Tacconi
+- tobiasd
+- Devlon Duthie
+- Zvi ben Yaakov (a.k.a rdc)
+- Alexandre Hannud Abdo
+- Olivier Migeot
+- Chris Case
+- Klaus Weidenbach
+- Michael Johnston
+- olivierm
+- Vasudev Kamath
+- pixelroot
+- Max Weller
+- duthied
+- Martin Schmitt
+- Sebastian Egbers
+- Erkan Yilmaz
+- sasiflo
+- Stefan Parviainen
+- Haakon Meland Eriksen
+- Oliver Hartmann (23n)
+- Erik Lundin
+- habeascodice
+- sirius
+- Charles
+- Tony Baldwin
+- Hauke Zuehl
+- Keith Fernie
+- Anne Walk
+- toclimb
+- Daniel Frank
+- Matthew Exon
+- Michal Supler
+- Tobias Luther
+- U-SOUND\mike
+- mrjive
+- nostupidzone
+- tonnerkiller
+- Antoine G
+- Christian Drechsler
+- Ludovic Grossard
+- RedmatrixCanada
+- Stanislav Lechev [0xAF]
+- aweiher
+- bufalo1973
+- dsp1986
+- felixgilles
+- ike
+- maase2
+- mycocham
+- ndurchx
+- pafcu
+- Simó Albert i Beltran
+- Manuel Reva
+- Manuel Jiménez Friaza
+- Gustav Wall aka "neue medienordnung plus" \ No newline at end of file
diff --git a/doc/en/database.bb b/doc/en/database.bb
deleted file mode 100644
index a0c1e8841..000000000
--- a/doc/en/database.bb
+++ /dev/null
@@ -1,71 +0,0 @@
-[h2]Database updates[/h2]
-
-In the [observer.baseurl]/admin/dbsync page the administrator can check if any update was not successful and, if so, retry it.
-
-If an update has failed but doesn't register as failed for some reason, the administrator can attempt to re-execute the update. For example for DB update #1999, by visiting the webpage:
-
-https://hubzilla.com.bradmin/dbsync/1999
-
-
-[h2]Database Tables[/h2][table border=1][tr][th]Table[/th][th]Description[/th][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_abconfig]abconfig[/zrl][/td][td]arbitrary storage for connections of local channels[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_abook]abook[/zrl][/td][td]connections of local channels[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_account]account[/zrl][/td][td]service provider account[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_addon]addon[/zrl][/td][td]registered plugins[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_app]app[/zrl][/td][td]personal app data[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_attach]attach[/zrl][/td][td]file attachments[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_auth_codes]auth_codes[/zrl][/td][td]OAuth usage[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_cache]cache[/zrl][/td][td]OEmbed cache[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_cal]cal[/zrl][/td][td]CalDAV containers for events[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_channel]channel[/zrl][/td][td]local channels[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_chat]chat[/zrl][/td][td]chat room content[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_chatpresence]chatpresence[/zrl][/td][td]channel presence information for chat[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_chatroom]chatroom[/zrl][/td][td]data for the actual chat room[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_clients]clients[/zrl][/td][td]OAuth usage[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_config]config[/zrl][/td][td]main configuration storage[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_conv]conv[/zrl][/td][td]Diaspora private messages meta conversation structure[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_event]event[/zrl][/td][td]Events[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_pgrp_member]pgrp_member[/zrl][/td][td]privacy groups (collections), group info[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_pgrp]pgrp[/zrl][/td][td]privacy groups (collections), member info[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_hook]hook[/zrl][/td][td]plugin hook registry[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_hubloc]hubloc[/zrl][/td][td]xchan location storage, ties a hub location to an xchan[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_iconfig]iconfig[/zrl][/td][td]extensible arbitrary storage for items[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_issue]issue[/zrl][/td][td]future bug/issue database[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_item]item[/zrl][/td][td]all posts and webpages[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_item_id]item_id[/zrl][/td][td](deprecated by iconfig) other identifiers on other services for posts[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_likes]likes[/zrl][/td][td]likes of 'things'[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_mail]mail[/zrl][/td][td]private messages[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_menu]menu[/zrl][/td][td]webpage menu data[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_menu_item]menu_item[/zrl][/td][td]entries for webpage menus[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_notify]notify[/zrl][/td][td]notifications[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_obj]obj[/zrl][/td][td]object data for things (x has y)[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_outq]outq[/zrl][/td][td]output queue[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_pconfig]pconfig[/zrl][/td][td]personal (per channel) configuration storage[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_photo]photo[/zrl][/td][td]photo storage[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_poll]poll[/zrl][/td][td]data for polls[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_poll_elm]poll_elm[/zrl][/td][td]data for poll elements[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_profdef]profdef[/zrl][/td][td]custom profile field definitions[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_profext]profext[/zrl][/td][td]custom profile field data[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_profile]profile[/zrl][/td][td]channel profiles[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_profile_check]profile_check[/zrl][/td][td]DFRN remote auth use, may be obsolete[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_register]register[/zrl][/td][td]registrations requiring admin approval[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_session]session[/zrl][/td][td]web session storage[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_shares]shares[/zrl][/td][td]shared item information[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_sign]sign[/zrl][/td][td]Diaspora signatures. To be phased out.[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_site]site[/zrl][/td][td]site table to find directory peers[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_source]source[/zrl][/td][td]channel sources data[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_sys_perms]sys_perms[/zrl][/td][td]extensible permissions for OAuth[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_term]term[/zrl][/td][td]item taxonomy (categories, tags, etc.) table[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_tokens]tokens[/zrl][/td][td]OAuth usage[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_updates]updates[/zrl][/td][td]directory sync updates[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_verify]verify[/zrl][/td][td]general purpose verification structure[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_vote]vote[/zrl][/td][td]vote data for polls[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_xchan]xchan[/zrl][/td][td]list of known channels in the universe[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_xchat]xchat[/zrl][/td][td]bookmarked chat rooms[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_xconfig]xconfig[/zrl][/td][td]as pconfig but for channels with no local account[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_xign]xign[/zrl][/td][td]channels ignored by friend suggestions[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_xlink]xlink[/zrl][/td][td]"friends of friends" linkages derived from poco, also ratings storage[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_xperm]xperm[/zrl][/td][td]OAuth/OpenID-Connect extensible permissions permissions storage[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_xprof]xprof[/zrl][/td][td]if this hub is a directory server, contains basic public profile info of everybody in the network[/td][/tr]
-[tr][td][zrl=[baseurl]/help/database/db_xtag]xtag[/zrl][/td][td]if this hub is a directory server, contains tags or interests of everybody in the network[/td][/tr]
-[/table]
diff --git a/doc/en/dev-function-overview.md b/doc/en/dev-function-overview.md
deleted file mode 100644
index 4dccc8cef..000000000
--- a/doc/en/dev-function-overview.md
+++ /dev/null
@@ -1,51 +0,0 @@
-$Projectname development - some useful basic functions
-======================================================
-
-
-
-* get_account_id()
-
-Returns numeric account_id if authenticated or 0. It is possible to be authenticated and not connected to a channel.
-
-* local_channel()
-
-Returns authenticated numeric channel_id if authenticated and connected to a channel or 0. Sometimes referred to as $uid in the code.
-
-* remote_channel()
-
-Returns authenticated string hash of Red global identifier, if authenticated via remote auth, or an empty string.
-
-* App::get_observer()
-
-returns an xchan structure representing the current viewer if authenticated (locally or remotely).
-
-* get_config($family,$key), get_pconfig($uid,$family,$key)
-
-Returns the config setting for $family and $key or false if unset.
-
-Deprecated: Use Zotlabs\Lib\Config::Get instead.
-
-* set_config($family,$key,$value), set_pconfig($uid,$family,$key,$value)
-
-Sets the value of config setting for $family and $key to $value. Returns $value. The config versions operate on system-wide settings. The pconfig versions get/set the values for a specific integer uid (channel_id).
-
-Deprecated: Use Zotlabs\Lib\Config::Set instead.
-
-* dbesc()
-
-Always escape strings being used in DB queries. This function returns the escaped string. Integer DB parameters should all be proven integers by wrapping with intval()
-
-* q($sql,$var1...)
-
-Perform a DB query with the SQL statement $sql. printf style arguments %s and %d are replaced with variable arguments, which should each be appropriately dbesc() or intval(). SELECT queries return an array of results or false if SQL or DB error. Other queries return true if the command was successful or false if it wasn't.
-
-* t($string)
-
-Returns the translated variant of $string for the current language or $string (default 'en' language) if the language is unrecognised or a translated version of the string does not exist.
-
-* x($var), $x($array,$key)
-
-Shorthand test to see if variable $var is set and is not empty. Tests vary by type. Returns false if $var or $key is not set.
-If variable is set, returns 1 if has 'non-zero' value, otherwise returns 0. -- e.g. x('') or x(0) returns 0;
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/dev_beginner.bb b/doc/en/dev_beginner.bb
deleted file mode 100644
index 4ac69c301..000000000
--- a/doc/en/dev_beginner.bb
+++ /dev/null
@@ -1,419 +0,0 @@
-[h2]You want to contribute code?[/h2]
-[b]...and don't know how to start to...[/b]
-[list]
-[*] debug the red#matrix (php on the webserver),
-[*] contribute code to the project,
-[*] optionally - do it all from inside a virtual machine
-[/list]
-This manual was tested for Debian (Wheezy) as virtual machine on Lubuntu (Ubuntu 14.0) as host.
-
-Content
-
-[toc]
-
-[h2]Install a Virtual Machine (KVM)[/h2]
-
-[url=https://wiki.debian.org/KVM]Here[/url] the installation guide for Linux Debian.
-The summary:
-[list=1]
-[*] install KVM
-[code]# apt-get install qemu-kvm libvirt-bin[/code]
-[*] add yourself to the group libvirt [code]# adduser <youruser> libvirt[/code]
-[*] install gui to manage virtual machines [code]# apt-get install virt-manager[/code]
-[*] download an operating system to run inside the vm ([url=http://ftp.nl.debian.org/debian/dists/wheezy/main/installer-amd64/current/images/netboot/mini.iso]mini.iso[/url])
-[*] start the virt manager
-- create new virtual machine (click on icon)
-- choose your iso image (just downloaded) as installation source
-- optional: configure the new vm: ram, cpu's,...
-- start virtual machine > result: linux debian starts in a new window.
-[*] (optional) avoid network errors after restart of host os
-[code]# virsh net-start default
-# virsh net-autostart default[/code]
-[/list]
-
-
-[h2]Install Apache Webserver[/h2]
-
-Open a terminal and make yourself root
-[code]su -l[/code]
-
-Create the standard group for the Apache webserver
-[code]groupadd www-data[/code]
-might exist already
-
-[code]usermod -a -G www-data www-data[/code]
-
-Check if the system is really up to date
-[code]apt-get update
-apt-get upgrade[/code]
-
-Optional restart services after installation
-[code]reboot[/code]
-
-If you restarted, make yourself root
-[code]su -l[/code]
-
-Install Apache: [code]
-apt-get install apache2 apache2-doc apache2-utils[/code]
-
-Open webbrowser on PC and check [url=localhost]localhost[/url]
-Should show you a page like "It works"
-
-(Source [url=http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#]http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#[/url])
-
-
-[h2]Install PHP, MySQL, phpMyAdmin[/h2]
-
-[h3]PHP, MySQL[/h3]
-
-[code]su -l
-apt-get install libapache2-mod-php5 php5 php-pear php5-xcache php5-curl php5-mcrypt php5-xdebug
-apt-get install php5-mysql
-apt-get install mysql-server mysql-client[/code]
-enter and note the mysql passwort
-
-Optional since its already enabled during phpmyadmin setup
-[code]
-php5enmod mcrypt
-[/code]
-
-[h3]phpMyAdmin[/h3]
-
-Install php myadmin
-[code]apt-get install phpmyadmin[/code]
-
-Configuring phpmyadmin
-- Select apache2 (hint: use the tab key to select)
-- Configure database for phpmyadmin with dbconfig-common?: Choose Yes
-
-(Source #^[url=http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#]http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#[/url])
-
-[h3]Enable rewrite[/h3]
-
-The default installation of Apache2 comes with mod_rewrite installed. To check whether this is the case, verify the existence of /etc/apache2/mods-available/rewrite.load
-
-[code]
-root@debian /var/www $ nano /etc/apache2/mods-available/rewrite.load
-[/code]
-
- (You should find the content: LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so)
-To enable and load mod_rewrite, do the rest of steps.
-Create a symbolic link in /etc/apache2/mods-enabled
-
-[code]
-cd /var/www
-root@debian /var/www $ a2enmod rewrite
-[/code]
-
-Then open up the following file, and replace every occurrence of "AllowOverride None" with "AllowOverride all".
-
-[code]
-root@debian /var/www $nano /etc/apache2/apache2.conf
-[/code]
-or
-[code]
-root@debian:/var# gedit /etc/apache2/sites-enabled/000-default
-[/code]
-
-Finally, restart Apache2.
-
-[code]
-root@debian /var/www $service apache2 restart
-[/code]
-
-[h3]Test installation[/h3]
-
-[code]cd /var/www[/code]
-
-create a php file to test the php installation[code]nano phpinfo.php[/code]
-
-Insert into the file:
-[code]
-<?php
- phpinfo();
-?>
-[/code]
-(save CTRL+0, ENTER, CTRL+X)
-
-open webbrowser on PC and try #^[url=http://localhost/phpinfo.php]http://localhost/phpinfo.php[/url] (page shows infos on php)
-
-connect phpMyAdmin with MySQL database [code]nano /etc/apache2/apache2.conf
-[/code]
-- CTRL+V... to the end of the file
-- Insert at the end of the file: (save CTRL+0, ENTER, CTRL+X)[code]Include /etc/phpmyadmin/apache.conf[/code]
-
-restart apache
-[code]/etc/init.d/apache2 restart
-apt-get update
-apt-get upgrade
-reboot[/code]
-
-[b]phpMyAdmin[/b]
-
-open webbrowser on PC and try #^[url=http://localhost/phpmyadmin]http://localhost/phpmyadmin[/url]
-
-(Source #^[url=http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#]http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#[/url])
-
-[h3]Create an empty database... that is later used by the red#matrix[/h3]
-
-
-open webbrowser on PC and try #^[url=http://localhost/phpmyadmin]http://localhost/phpmyadmin[/url]
-
-Create an empty database, for example named "red".
-Create a database user, for example "red".
-Grant all rights for the user "red" to the database "red".
-
-Note the access details (hostname, username, password, database name).
-
-
-[h2]Fork the project on github[/h2]
-
-Please follow the instruction in the offiical [url=http://git-scm.com/book/en/v2/GitHub-Contributing-to-a-Project] documentation[/url] of git.
-It is a good idea to read the whole manual! Git is different to other version control systems in many ways.
-
-Now you should
-[list]
-[*] create an account at github.com
-[*] fork https://framagit.org/hubzilla/core
-[*] fork https://framagit.org/hubzilla/addons
-[/list]
-
-If you not want to use GIT from the command line - there is a usefull Eclipse plugin named ""Eclipse Mylyn to GitHub connector".
-
-
-[h2]Install RED and its Addons[/h2]
-
-[h3]Git at your computer / vm[/h3]
-
-You should have created an account on github and forked the projects befor you procceed.
-
-Delete the directory www
-[code]root@debian:/var# rm -R www/
-[/code]
-
-Install git (and optionally git-gui a client gui)
-[code]apt-get install git git-gui[/code]
-
-[h3]Download red#matri and addons[/h3]
-
-Download the main project red and red-addons
-[code]
-root@debian:/var# git clone https://github.com/yourname/red www
-root@debian:/var# cd www/
-root@debian:/var/www# git clone https://github.com/yourname/red-addons addon
-[/code]
-
-Make this extra folder
-[code]
-root@debian:/var/www# mkdir -p "store/[data]/smarty3"
-[/code]
-
-Create .htconfig.php and make it writable by the webserver
-[code]
-root@debian:/var/www# touch .htconfig.php
-root@debian:/var/www# chmod ou+w .htconfig.php
-[/code]
-
-Make user www-data (webserver) is the owner all the project files
-[code]
-root@debian:/var/www# cd ..
-root@debian:/var# chown -R www-data:www-data www/
-[/code]
-
-Add yourself ("surfer" in this example) to the group www-data. Why? Later you want to modify files in eclipse or in another editor.
-Then make all files writable by the group www-date you are now a member of.
-[code]
-root@debian:/var# cd www/
-root@debian:/var/www# usermod -G www-data surfer
-root@debian:/var# chmod -R g+w www/
-[/code]
-
-Restart the computer (or vm)
-If you are still not able to modify the project files you can check the members of the group www-data with
-[code]
-cat /etc/group
-[/code]
-
-[h3]Register yourself as admin[/h3]
-
-Open http://localhost and init the matrix
-
-Befor you register a first user switch off the registration mails.
-Open /var/www/.htconfig.php
-and make sure "0" is set in this line
-[code]
-App::$config['system']['verify_email'] = 0;
-[/code]
-You should be able to change the file as "yourself" (instead of using root or www-data).
-
-[h3]Cron and the poller[/h3]
-
-Important!
-Run the poller to pick up the recent "public" postings of your friends
-Set up a cron job or scheduled task to run the poller once every 5-10
-minutes to pick up the recent "public" postings of your friends
-
-[code]
-crontab -e
-[/code]
-
-Add
-[code]
-*/10 * * * * cd /var/www/; /usr/bin/php include/poller.php
-[/code]
-
-If you don't know the path to PHP type
-[code]
-whereis php
-[/code]
-
-
-[h2]Debug the server via eclipse[/h2]
-
-[h3]Check the configuration of xdebug[/h3]
-
-You shoud already have installed xdebug in the steps befor
-[code]
-apt-get install php5-xdebug
-[/code]
-
-Configuring Xdebug
-
-Open your terminal and type as root (su -l)
-[code]
-gedit /etc/php5/mods-available/xdebug.ini
-[/code]
-
-if the file is empty try this location
-[code]
-gedit /etc/php5/conf.d/xdebug.ini
-[/code]
-
-That command should open the text editor gedit with the Xdebug configuration file
-At the end of the file content append the following text
-
-xdebug.remote_enable=on
-xdebug.remote_handler=dbgp
-xdebug.remote_host=localhost
-xdebug.remote_port=9000
-
-Save changes and close the editor.
-In you terminal type to restart the web server.
-[code]
-service apache2 restart
-[/code]
-
-
-[h3]Install Eclipse and start debugging[/h3]
-
-Install eclipse.
-Start eclipse with default worspace (or as you like)
-
-Install the PHP plugin
-Menu > Help > Install new software...
-Install "PHP Developnent Tools ..."
-
-Optionally - Install the GitHub connector plugin
-Menu > Help > Install new software...
-Install "Eclipse Mylyn to GitHub connector"
-
-Configure the PHP plugin
-Menu > Window > Preferences...
-> General > Webbrowser > Change to "Use external web browser"
-> PHP > Debug > Debug Settings > PHP Debugger > Change to "XDebug"
-
-Create a new PHP project
-Menu > File > New Project > Choose PHP > "PHP Project"
-> Choose Create project at existing location" and "/var/www"
-
-Start debugging
-Open index.php and "Debug as..."
-Choose as Launch URL: "http://localhost/"
-
-Expected:
-[list]
-[*] The web browser starts
-[*] The debugger will stop at the first php line
-[/list]
-
-
-[h2]Contribute your changes via github[/h2]
-
-[h3]Preparations[/h3]
-
-There is a related page in this docs: [zrl=[baseurl]/help/git_for_non_developers]Git for Non-Developers[/zrl].
-As stated befor it is recommended to read the official documentation [url=http://git-scm.com/book/en/v2/GitHub-Contributing-to-a-Project]GitHub-Contributing-to-a-Project[/url] of git.
-
-Eclipse has a usefull plugin for GIT: "Eclipse Mylyn to GitHub connector".
-
-Make sure you have set your data
-[code]
-surfer@debian:/var/www$ git config --global user.name "Your Name"
-surfer@debian:/var/www$ git config --global user.email "your@mail.com"
-[/code]
-
-[h3]Your first contribution[/h3]
-
-Create a descriptive topic branch
-[code]
-surfer@debian:/var/www$ git checkout -b dev_beginning
-[/code]
-
-Make sure your local repository is up-to-date with the main project.
-Add the original repository as a remote named “upstream” if not done yet
-[code]
-surfer@debian:/var/www$ git remote add upstream https://framagit.org/hubzilla/core/
-[/code]
-
-Fetch the newest work from that remote
-[code]
-surfer@debian:/var/www$ git fetch upstream
-surfer@debian:/var/www$ git merge upstream/master
-[/code]
-
-Hint: You can list the branches
-[code]
-surfer@debian:/var/www$ git branch -v
-[/code]
-
-Make your changes. In this example it is a new doc file.
-
-Check your modifications
-[code]
-surfer@debian:/var/www$ git status
-[/code]
-
-Add (stage) the new file
-[code]
-surfer@debian:/var/www$ git add doc/dev_beginner.bb
-[/code]
-
-Commit the changes to your local branch. This will open an editor to provide a message.
-[code]
-surfer@debian:/var/www$ git commit -a
-[/code]
-
-Push back up to the same topic branch online
-[code]
-surfer@debian:/var/www$ git push
-[/code]
-
-Now you can go to your (online) account at github and create the pull request.
-
-[h3]Following contributions[/h3]
-
-In case the main devolpers want you to change something.
-Fetch the newest work from the remote upstream/master to be sure you have the latest changes.
-[code]
-surfer@debian:/var/www$ git fetch upstream
-surfer@debian:/var/www$ git merge upstream/master
-[/code]
-Make your changes, test them, commit (to local repository), push (to online repository)
-[code]
-surfer@debian:/var/www$ git status
-surfer@debian:/var/www$ git commit -a -m "added modification of branch"
-surfer@debian:/var/www$ git push
-[/code]
-
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/develop.bb b/doc/en/develop.bb
deleted file mode 100644
index 20e987a5a..000000000
--- a/doc/en/develop.bb
+++ /dev/null
@@ -1,31 +0,0 @@
-[h2]Documentation for Developers[/h2]
-
-[h3]Technical Documentation[/h3]
-[zrl=[baseurl]/help/Zot---A-High-Level-Overview]A high level overview of Zot[/zrl]
-[zrl=[baseurl]/help/zot]An introduction to Zot[/zrl]
-[zrl=[baseurl]/help/zot_structures]Zot Stuctures[/zrl]
-[zrl=[baseurl]/help/comanche]Comanche Page Descriptions[/zrl]
-[zrl=[baseurl]/help/Creating-Templates]Creating Comanche Templates[/zrl]
-[zrl=[baseurl]/help/Widgets]Widgets[/zrl]
-[zrl=[baseurl]/help/plugins]Plugins[/zrl]
-[zrl=[baseurl]/help/hooklist]Hooks (detailed - under construction)[/zrl]
-[zrl=[baseurl]/help/doco]Contributing Documentation[/zrl]
-[zrl=[baseurl]/help/DerivedTheme1]Creating Derivative Themes[/zrl]
-[zrl=[baseurl]/help/schema_development]Schemas[/zrl]
-[zrl=[baseurl]/help/Translations]Translations[/zrl]
-[zrl=[baseurl]/help/developers]Developers[/zrl]
-[zrl=[baseurl]/help/intro_for_developers]Intro for Developers[/zrl]
-[zrl=[baseurl]/help/database]Database schema documentation[/zrl]
-[zrl=[baseurl]/help/api_functions]API functions[/zrl]
-[zrl=[baseurl]/help/api_posting]Posting to $Projectname using the API[/zrl]
-[zrl=[baseurl]/help/developer_function_primer]Red Functions 101[/zrl]
-[zrl=[baseurl]/doc/html/]Code Reference (Doxygen generated - sets cookies)[/zrl]
-[zrl=[baseurl]/help/to_do_doco]To-Do list for $Projectname Documentation Project[/zrl]
-[zrl=[baseurl]/help/to_do_code]To-Do list for Developers[/zrl]
-[zrl=[baseurl]/help/roadmap]Roadmap[/zrl]
-[zrl=[baseurl]/help/git_for_non_developers]Git for Non-Developers[/zrl]
-[zrl=[baseurl]/help/dev_beginner]Step-for-step manual for beginning developers[/zrl]
-
-[h3]External Resources[/h3]
-[url=https://grid.reticu.li/channel/hubzilla]Development Channel[/url]
-[url=https://federated.social/channel/postgres]Postgres-specific $Projectname Admin Support Channel[/url]
diff --git a/doc/en/developer/API.md b/doc/en/developer/API.md
new file mode 100644
index 000000000..abb25afb8
--- /dev/null
+++ b/doc/en/developer/API.md
@@ -0,0 +1,787 @@
+### Zot API
+
+Many existing social applications and tools can interface directly using the Twitter/StatusNet API, which is available using the 'twitter_api' addon.
+
+This document describes the native API; which allows direct programmatic access to several internal data structures and libraries extending beyond the basic social interface.
+
+The API endpoints detailed below are relative to `api/z/1.0`, meaning that if an API is listed as `channel/stream` the full API URL is `https://hub.hubzilla.hu/api/z/1.0/channel/stream`.
+
+
+
+### channel/export/basic
+
+Export basic channel data
+
+Options:
+ \- sections
+ comma-separated list of data types to export
+
+ \- posts
+ if true, return default sections plus 3 months of posts
+
+ If no sections are requested, the following sections are returned:
+ channel, connections, config, apps, chatrooms, events, webpages, mail, wikis
+
+ Files and large collections of posts may run into memory limits; these must generally be
+ requested separately.
+
+
+
+### channel/stream
+
+Fetch channel conversation items
+
+
+
+### network/stream
+
+Fetch network conversation items
+
+
+
+
+
+### files
+
+List file storage (attach DB)
+
+GET /api/z/1.0/files
+
+
+Options:
+
+ \- filehash
+ return only entries matching hash (exactly)
+
+ \- filename
+ return only entries matching filename (substring)
+
+ \- filetype
+ return only entries matching filetype/mimetype (substring)
+
+ \- start
+ start at record (default 0)
+
+ \- records
+ number of records to return or 0 for unlimited
+
+
+
+Example:
+
+```
+curl -u mychannel:mypassword https://xyz.macgirvin.com/api/z/1.0/files -d filetype=multipart/mixed
+```
+
+
+Returns:
+
+```
+ {
+
+ "success": true,
+ "results": [
+ {
+ "id": "1",
+ "aid": "1",
+ "uid": "2",
+ "hash": "44ee8b2a1a7f36dea07b93b7747a2383a1bc0fdd08339e8928bfcbe45f65d939",
+ "filename": "Profile Photos",
+ "filetype": "multipart/mixed",
+ "filesize": "0",
+ "revision": "0",
+ "folder": "",
+ "os_storage": "1",
+ "is_dir": "1",
+ "is_photo": "0",
+ "flags": "0",
+ "created": "2016-01-02 21:51:17",
+ "edited": "2016-01-02 21:51:17",
+ "allow_cid": "",
+ "allow_gid": "",
+ "deny_cid": "",
+ "deny_gid": ""
+ },
+ {
+ "id": "12",
+ "aid": "1",
+ "uid": "2",
+ "hash": "71883f1fc64af33889229cbc79c5a056deeec5fc277d765f182f19073e1b2998",
+ "filename": "Cover Photos",
+ "filetype": "multipart/mixed",
+ "filesize": "0",
+ "revision": "0",
+ "folder": "",
+ "os_storage": "1",
+ "is_dir": "1",
+ "is_photo": "0",
+ "flags": "0",
+ "created": "2016-01-15 00:24:33",
+ "edited": "2016-01-15 00:24:33",
+ "allow_cid": "",
+ "allow_gid": "",
+ "deny_cid": "",
+ "deny_gid": ""
+ },
+ {
+ "id": "16",
+ "aid": "1",
+ "uid": "2",
+ "hash": "f48f7ec3278499d1dd86b72c3207beaaf4717b07df5cc9b373f14d7aad2e1bcd",
+ "filename": "2016-01",
+ "filetype": "multipart/mixed",
+ "filesize": "0",
+ "revision": "0",
+ "folder": "",
+ "os_storage": "1",
+ "is_dir": "1",
+ "is_photo": "0",
+ "flags": "0",
+ "created": "2016-01-22 03:24:55",
+ "edited": "2016-01-22 03:26:57",
+ "allow_cid": "",
+ "allow_gid": "",
+ "deny_cid": "",
+ "deny_gid": ""
+ }
+ ]
+ }
+```
+
+### filemeta
+
+Export file metadata for any uploaded file
+
+
+
+### filedata
+
+Provides the ability to download a file from cloud storage in chunks
+
+GET /api/z/1.0/filedata
+
+
+Required:
+
+ \- file_id
+ attach.hash of desired file ('begins with' match)
+
+
+Optional:
+
+ \- start
+ starting byte of returned data in file (counting from 0)
+
+ \- length
+ length (prior to base64 encoding) of chunk to download
+
+
+Returns:
+
+ attach (DB) structure with base64 encoded 'content' comprised of the desired chunk
+
+
+
+Example:
+
+`https://xyz.macgirvin.com/api/z/1.0/filedata?f=&file_id=9f5217770fd&start=0&length=48`
+
+Returns:
+
+```
+ {
+
+ "attach": {
+ "id": "107",
+ "aid": "1",
+ "uid": "2",
+ "hash": "9f5217770fd55d563bd77f84d534d8e119a187514bbd391714626cd9c0e60207",
+ "creator": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
+ "filename": "pcxtopbm.c",
+ "filetype": "application/octet-stream",
+ "filesize": "3934",
+ "revision": "0",
+ "folder": "",
+ "flags": "0",
+ "is_dir": "0",
+ "is_photo": "0",
+ "os_storage": "1",
+ "os_path": "",
+ "display_path": "",
+ "content": "LyogcGN4dG9wYm0uYyAtIGNvbnZlcnQgUEMgcGFpbnRicnVzaCAoLnBjeCkgZmls",
+ "created": "2016-07-24 23:13:01",
+ "edited": "2016-07-24 23:13:01",
+ "allow_cid": "",
+ "allow_gid": "",
+ "deny_cid": "",
+ "deny_gid": "",
+ "start": 0,
+ "length": 48
+ }
+
+ }
+```
+
+### file/export
+
+### file
+
+### albums
+
+Description: list photo albums
+
+GET /api/z/1.0/albums
+
+
+Output:
+
+ text - textual name
+
+ total - number of photos in this album
+
+ url - web URL
+
+ urlencode - textual name, urlencoded
+
+ bin2hex - textual name using bin2hex (which is used in the web URL link)
+
+
+Example:
+
+
+
+```
+ {
+
+ "success": true,
+ "albums": [
+ {
+ "text": "/",
+ "total": "2",
+ "url": "https://xyz.macgirvin.com/photos/hubzilla/album/",
+ "urlencode": "",
+ "bin2hex": ""
+ },
+ {
+ "text": "2016-01",
+ "total": "6",
+ "url": "https://xyz.macgirvin.com/photos/hubzilla/album/323031362d3031",
+ "urlencode": "2016-01",
+ "bin2hex": "323031362d3031"
+ },
+ {
+ "text": "2016-02",
+ "total": "7",
+ "url": "https://xyz.macgirvin.com/photos/hubzilla/album/323031362d3032",
+ "urlencode": "2016-02",
+ "bin2hex": "323031362d3032"
+ },
+ {
+ "text": "Cover Photos",
+ "total": "5",
+ "url": "https://xyz.macgirvin.com/photos/hubzilla/album/436f7665722050686f746f73",
+ "urlencode": "Cover+Photos",
+ "bin2hex": "436f7665722050686f746f73"
+ },
+ {
+ "text": "Profile Photos",
+ "total": "26",
+ "url": "https://xyz.macgirvin.com/photos/hubzilla/album/50726f66696c652050686f746f73",
+ "urlencode": "Profile+Photos",
+ "bin2hex": "50726f66696c652050686f746f73"
+ }
+ ]
+
+ }
+```
+
+### photos
+
+list photo metadata
+
+
+
+### photo
+
+### group
+
+```
+GET /api/z/1.0/group
+```
+
+Description: list privacy groups
+
+Returns: DB tables of all privacy groups.
+
+To use with API group_members, provide either 'group_id' from the id element returned in this call, or 'group_name' from the gname returned in this call.
+
+
+
+```
+ [
+
+ {
+ "id": "1",
+ "hash": "966c946394f3e2627bbb8a55026b5725e582407098415c02f85232de3f3fde76Friends",
+ "uid": "2",
+ "visible": "0",
+ "deleted": "0",
+ "gname": "Friends"
+ },
+ {
+ "id": "2",
+ "hash": "852ebc17f8c3ed4866f2162e384ded0f9b9d1048f93822c0c84196745f6eec66Family",
+ "uid": "2",
+ "visible": "1",
+ "deleted": "0",
+ "gname": "Family"
+ },
+ {
+ "id": "3",
+ "hash": "cc3cb5a7f9818effd7c7c80a58b09a189b62efa698a74319117babe33ee30ab9Co-workers",
+ "uid": "2",
+ "visible": "0",
+ "deleted": "0",
+ "gname": "Co-workers"
+ }
+ ]
+```
+
+### group_members
+
+```
+GET /api/z/1.0/group_members
+```
+
+Required:
+
+group_id or group_name
+
+
+Returns:
+
+group_member+abook+xchan (DB join) for each member of the privacy group
+
+
+
+```
+ [
+
+ {
+ "id": "1",
+ "uid": "2",
+ "gid": "1",
+ "xchan": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
+ "abook_id": "2",
+ "abook_account": "1",
+ "abook_channel": "2",
+ "abook_xchan": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
+ "abook_my_perms": "218555",
+ "abook_their_perms": "0",
+ "abook_closeness": "0",
+ "abook_created": "2016-01-02 21:16:26",
+ "abook_updated": "2016-01-02 21:16:26",
+ "abook_connected": "0000-00-00 00:00:00",
+ "abook_dob": "0000-00-00 00:00:00",
+ "abook_flags": "0",
+ "abook_blocked": "0",
+ "abook_ignored": "0",
+ "abook_hidden": "0",
+ "abook_archived": "0",
+ "abook_pending": "0",
+ "abook_unconnected": "0",
+ "abook_self": "1",
+ "abook_feed": "0",
+ "abook_profile": "",
+ "abook_incl": "",
+ "abook_excl": "",
+ "abook_instance": "",
+ "xchan_hash": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
+ "xchan_guid": "lql-1VnxtiO4-WF0h72wLX1Fu8szzHDOXgQaTbELwXW77k8AKFfh-hYr70vqMrc3SSvWN-Flrc5HFhRTWB7ICw",
+ "xchan_guid_sig": "PafvEL0VpKfxATxlCqDjfOeSIMdmpr3iU7X-Sysa1h5LzDpjSXsjO37tYZL-accb1M5itLlfnW5epkTa5I4flsW21zSY1A2jCuBQUTLLGV7rNyyBy7lgqJUFvAMRx0TfXzP9lcaPqlM9T1tA6jfWOsOmkdzwofGeXBnsjGfjsO2xdGYe6vwjOU0DSavukvzDMnOayB9DekpvDnaNBTxeGLM45Skzr7ZEMcNF7TeXMbnvpfLaALYEKeQs9bGH-UgAG8fBWgzVAzeBfx_XSR1rdixjyiZGP0kq0h35SlmMPcEjliodOBFwMXqpXFB7Ibp4F6o6te2p2ErViJccQVG8VNKB6SbKNXY6bhP5zVcVsJ-vR-p4xXoYJJvzTN7yTDsGAXHOLF4ZrXbo5yi5gFAlIrTLAF2EdWQwxSGyLRWKxG8PrDkzEzX6cJJ0VRcLh5z6OI5QqQNdeghPZbshMFMJSc_ApCPi9_hI4ZfctCIOi3T6bdgTNKryLm5fhy_eqjwLAZTGP-aUBgLZpb1mf2UojBn6Ey9cCyq-0T2RWyk-FcIcbV4qJ-p_8oODqw13Qs5FYkjLr1bGBq82SuolkYrXEwQClxnrfKa4KYc2_eHAXPL01iS9zVnI1ySOCNJshB97Odpooc4wk7Nb2Fo-Q6THU9zuu0uK_-JbK7IIl6go2qA",
+ "xchan_pubkey": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA18JB76lyP4zzL/y7BCej\neJnfZIWZNtM3MZvI1zEVMWmmwOS+u/yH8oPwyaDk4Y/tnj8GzMPj1lCGVRcd8EJa\nNrCMd50HODA5EsJtxpsOzRcILYjOcTtIAG1K4LtKqELi9ICAaFp0fNfa+Jf0eCek\nvPusx2/ORhy+o23hFoSMhL86o2gmaiRnmnA3Vz4ZMG92ieJEDMXt9IA1EkIqS4y5\nBPZfVPLD1pv8iivj+dtN1XjwplgjUbtxmU0/Ej808nHppscRIqx/XJ0XZU90oNGw\n/wYoK2EzJlPbRsAkwNqoFrAYlr5HPpn4BJ2ebFYQgWBUraD7HwS5atsQEaxGfO21\nlUP0+lDg9t3CXvudDj0UG1jiEKbVIGA+4aG0GN2DSC5AyRq/GRxqyay5W2vQbAZH\nyvxPGrZFO24I65g3pjhpjEsLqZ4ilTLQoLMs0drCIcRm5RxMUo4s/LMg16lT4cEk\n1qRtk2X0Sb1AMQQ2uRXiVtWz77QHMONEYkf6OW4SHbwcv5umvlv69NYEGfCcbgq0\nAV7U4/BWztUz/SWj4r194CG43I9I8dmaEx9CFA/XMePIAXQUuABfe1QMOR6IxLpq\nTHG1peZgHQKeGz4aSGrhQkZNNoOVNaZoIfcvopxcHDTZLigseEIaPPha4WFYoKPi\nUPbZ5o8gTLc750uzrnb2jwcCAwEAAQ==\n-----END PUBLIC KEY-----\n",
+ "xchan_photo_mimetype": "image/png",
+ "xchan_photo_l": "https://xyz.macgirvin.com/photo/profile/l/2",
+ "xchan_photo_m": "https://xyz.macgirvin.com/photo/profile/m/2",
+ "xchan_photo_s": "https://xyz.macgirvin.com/photo/profile/s/2",
+ "xchan_addr": "teller@xyz.macgirvin.com",
+ "xchan_url": "https://xyz.macgirvin.com/channel/teller",
+ "xchan_connurl": "https://xyz.macgirvin.com/poco/teller",
+ "xchan_follow": "https://xyz.macgirvin.com/follow?f=&url=%s",
+ "xchan_connpage": "",
+ "xchan_name": "Teller",
+ "xchan_network": "zot",
+ "xchan_instance_url": "",
+ "xchan_flags": "0",
+ "xchan_photo_date": "2016-10-19 01:26:50",
+ "xchan_name_date": "2016-01-02 21:16:26",
+ "xchan_hidden": "0",
+ "xchan_orphan": "0",
+ "xchan_censored": "0",
+ "xchan_selfcensored": "0",
+ "xchan_system": "0",
+ "xchan_pubforum": "0",
+ "xchan_deleted": "0"
+ },
+ {
+ "id": "12",
+ "uid": "2",
+ "gid": "1",
+ "xchan": "xuSMUYxw1djBB97qXsbrBN1nzJH_gFwQL6pS4zIy8fuusOfBxNlMiVb4h_q5tOEvpE7tYf1EsryjNciMuPIj5w",
+ "abook_id": "24",
+ "abook_account": "1",
+ "abook_channel": "2",
+ "abook_xchan": "xuSMUYxw1djBB97qXsbrBN1nzJH_gFwQL6pS4zIy8fuusOfBxNlMiVb4h_q5tOEvpE7tYf1EsryjNciMuPIj5w",
+ "abook_my_perms": "218555",
+ "abook_their_perms": "218555",
+ "abook_closeness": "80",
+ "abook_created": "2016-01-27 00:48:43",
+ "abook_updated": "2016-12-04 17:16:58",
+ "abook_connected": "2016-12-04 17:16:58",
+ "abook_dob": "0001-01-01 00:00:00",
+ "abook_flags": "0",
+ "abook_blocked": "0",
+ "abook_ignored": "0",
+ "abook_hidden": "0",
+ "abook_archived": "0",
+ "abook_pending": "0",
+ "abook_unconnected": "0",
+ "abook_self": "0",
+ "abook_feed": "0",
+ "abook_profile": "debb5236efb1626cfbad33ccb49892801e5f844aa04bf81f580cfa7d13204819",
+ "abook_incl": "",
+ "abook_excl": "",
+ "abook_instance": "",
+ "xchan_hash": "xuSMUYxw1djBB97qXsbrBN1nzJH_gFwQL6pS4zIy8fuusOfBxNlMiVb4h_q5tOEvpE7tYf1EsryjNciMuPIj5w",
+ "xchan_guid": "d5EMLlt1tHHZ0dANoA7B5Wq9UgXoWcFS9-gXOkL_AAejcPApoQRyxfHTuu8DoTbUaO-bYmX5HPuWuK9PHyqNmA",
+ "xchan_guid_sig": "CVWEMRPtzI1YcHfnnWHTuv3H964OAmSElgUfxMoX6RdQdxNpqb_POirpVuyP8s3W17mVCfO5V9IAjkg5iKcqCk6YcvOD_egmMy-AnM9TC1kKndQHw55CunD82Q8K_xBNSXkSROizcNkKh9DVLjJPFjW1AqtI4njkZ3EMgrWqnbFRM1qPToUoCY9zM3tEMHoAD9YX1zP90wl40LzfN-dtcNWpSBbiz9owou62uzLbN7mrCwKOMlXLjwwGswRnxIsEnb3O-FXOs8hs0mArKe9snq1-BKeD16LyzxgwlpVLElzIJZGEZGtMdIJgeRzKuBvPjsOIpQ1yAkuOpFJ3nGCM-IPOIIjAmyVl5zD3xPVcxxpZlJRn5fG1Y-gnqTgsrEQCA7M6XPWQdrdHU4akZfyUyFJDhv3uM-jon9VzrYTBw68R0WA-1Z8WafEHA4qh5OWAj85lUarwhr7iTiEckH51ypPCPs6VbT6Pw7yMaxfjFOcipashQagx0tfOlDhE5dQANOXKASFtH1J9-CZY2MQdLPQ6u54d5whuHKMGaJ0V68pnmZ2rOn7g344Ah2WCJrm17jj60QsRMorqRFj7GMdPIA1XB8Wrk88MuYOe3Dhyuu6ZWKI7YTWJS690ZVkKUqAiNHqj0W86DtaiPUc_mmGR0fHl4Gksnko3WmCFv9q2X2E",
+ "xchan_pubkey": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoj2xCJktBA8Ww7Hp+ZNL\nrNuQpo8UB/bfvRkIy+yua3xpF1TuXcnAH61kyRz8vXgOu/l2CyxQbIoaGslCV5Sy\n8JKeNXe+IilUdSSEjMIwCPfSPsYnMHsSnHWmPmclvJwEtQUKOZmW5mMuVBvXy7D2\njomFwc69AYphdyys6eQ7Dcn6+FRBiQbyMprZ5lxyVW+O4DuXVNa3ej2ebx0gCJZ4\ntTIlBoKwEey91dY+FyKVFjdwfNczpmL7LgmZXqcVx+MG3mYgibwdVMiXVj5X06cs\nV9hJ5Xi+Aklsv/UWJtjw9FVt7y9TLptnhh4Ra6T/MDmnBBIAkOR7P/X8cRv078MT\nl0IMsP0RJcDEtTLtwHFVtDs6p52KDFqclKWbqmxmxqV3OTPVYtArRGIzgnJi/5ur\nHRr5G6Cif7QY3UowsIOf78Qvy28LwSbdymgBAWwPPKIviXWxGO+9kMWdmPSUQrWy\nK0+7YA9P9fBUFfn9Hc+p8SJQmQ6OAqLwrDGiPSOlGaNrbEqwqLGgIpXwK+lEFcFJ\n3SPOjJRWdR2whlMxvpwX+39+H7dWN3vSa3Al4/Sq7qW8yW2rYwf+eGyp4Z0lRR+8\nJxFMCwZkSw5g14YdlikAPojv5V1c6KuA5ieg8G1hwyONV7A4JHPyEdPt0W0TZi6C\nCOVkPaC3xGrguETZpJfVpwUCAwEAAQ==\n-----END PUBLIC KEY-----\n",
+ "xchan_photo_mimetype": "image/png",
+ "xchan_photo_l": "https://xyz.macgirvin.com/photo/9da63aa910ea14e1501ee1a749d181a6-4",
+ "xchan_photo_m": "https://xyz.macgirvin.com/photo/9da63aa910ea14e1501ee1a749d181a6-5",
+ "xchan_photo_s": "https://xyz.macgirvin.com/photo/9da63aa910ea14e1501ee1a749d181a6-6",
+ "xchan_addr": "cloner@xyz.macgirvin.com",
+ "xchan_url": "http://abc.macgirvin.com/channel/cloner",
+ "xchan_connurl": "http://abc.macgirvin.com/poco/cloner",
+ "xchan_follow": "https://xyz.macgirvin.com/follow?f=&url=%s",
+ "xchan_connpage": "",
+ "xchan_name": "Karen",
+ "xchan_network": "zot",
+ "xchan_instance_url": "",
+ "xchan_flags": "0",
+ "xchan_photo_date": "2016-03-31 19:59:20",
+ "xchan_name_date": "2016-01-26 23:23:42",
+ "xchan_hidden": "0",
+ "xchan_orphan": "0",
+ "xchan_censored": "0",
+ "xchan_selfcensored": "0",
+ "xchan_system": "0",
+ "xchan_pubforum": "0",
+ "xchan_deleted": "0"
+ }
+
+ ]
+```
+
+### xchan
+
+An xchan is a global location independent channel and is the primary record for a network
+identity. It may refer to channels on other websites, networks, or services.
+
+```
+GET /api/z/1.0/xchan
+```
+
+Required: one of [ address, hash, guid ] as GET parameters
+
+Returns a portable xchan structure
+
+Example: `https://xyz.macgirvin.com/api/z/1.0/xchan?f=&address=mike@macgirvin.com`
+
+Returns:
+
+```
+ {
+ "hash": "jr54M_y2l5NgHX5wBvP0KqWcAHuW23p1ld-6Vn63_pGTZklrI36LF8vUHMSKJMD8xzzkz7s2xxCx4-BOLNPaVA",
+ "guid": "sebQ-IC4rmFn9d9iu17m4BXO-kHuNutWo2ySjeV2SIW1LzksUkss12xVo3m3fykYxN5HMcc7gUZVYv26asx-Pg",
+ "guid_sig": "Llenlbl4zHo6-g4sa63MlQmTP5dRCrsPmXHHFmoCHG63BLq5CUZJRLS1vRrrr_MNxr7zob_Ykt_m5xPKe5H0_i4pDj-UdP8dPZqH2fqhhx00kuYL4YUMJ8gRr5eO17vsZQ3XxTcyKewtgeW0j7ytwMp6-hFVUx_Cq08MrXas429ZrjzaEwgTfxGnbgeQYQ0R5EXpHpEmoERnZx77VaEahftmdjAUx9R4YKAp13pGYadJOX5xnLfqofHQD8DyRHWeMJ4G1OfWPSOlXfRayrV_jhnFlZjMU7vOdQwHoCMoR5TFsRsHuzd-qepbvo3pzvQZRWnTNu6oPucgbf94p13QbalYRpBXKOxdTXJrGdESNhGvhtaZnpT9c1QVqC46jdfP0LOX2xrVdbvvG2JMWFv7XJUVjLSk_yjzY6or2VD4V6ztYcjpCi9d_WoNHruoxro_br1YO3KatySxJs-LQ7SOkQI60FpysfbphNyvYMkotwUFI59G08IGKTMu3-GPnV1wp7NOQD1yzJbGGEGSEEysmEP0SO9vnN45kp3MiqbffBGc1r4_YM4e7DPmqOGM94qksOcLOJk1HNESw2dQYWxWQTBXPfOJT6jW9_crGLMEOsZ3Jcss0XS9KzBUA2p_9osvvhUKuKXbNztqH0oZIWlg37FEVsDs_hUwUJpv2Ar09k4",
+ "pubkey": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7QCwvuEIwCHjhjbpz3Oc\ntyei/Pz9nDksNbsc44Cm8jxYGMXsTPFXDZYCcCB5rcAhPPdZSlzaPkv4vPVcMIrw\n5cdX0tvbwa3rNTng6uFE7qkt15D3YCTkwF0Y9FVZiZ2Ko+G23QeBt9wqb9dlDN1d\nuPmu9BLYXIT/JXoBwf0vjIPFM9WBi5W/EHGaiuqw7lt0qI7zDGw77yO5yehKE4cu\n7dt3SakrXphL70LGiZh2XGoLg9Gmpz98t+gvPAUEotAJxIUqnoiTA8jlxoiQjeRK\nHlJkwMOGmRNPS33awPos0kcSxAywuBbh2X3aSqUMjcbE4cGJ++/13zoa6RUZRObC\nZnaLYJxqYBh13/N8SfH7d005hecDxWnoYXeYuuMeT3a2hV0J84ztkJX5OoxIwk7S\nWmvBq4+m66usn6LNL+p5IAcs93KbvOxxrjtQrzohBXc6+elfLVSQ1Rr9g5xbgpub\npSc+hvzbB6p0tleDRzwAy9X16NI4DYiTj4nkmVjigNo9v2VPnAle5zSam86eiYLO\nt2u9YRqysMLPKevNdj3CIvst+BaGGQONlQalRdIcq8Lin+BhuX+1TBgqyav4XD9K\nd+JHMb1aBk/rFLI9/f2S3BJ1XqpbjXz7AbYlaCwKiJ836+HS8PmLKxwVOnpLMbfH\nPYM8k83Lip4bEKIyAuf02qkCAwEAAQ==\n-----END PUBLIC KEY-----\n",
+ "photo_mimetype": "image/jpeg",
+ "photo_l": "https://xyz.macgirvin.com/photo/350b74555c04429148f2e12775f6c403-4",
+ "photo_m": "https://xyz.macgirvin.com/photo/350b74555c04429148f2e12775f6c403-5",
+ "photo_s": "https://xyz.macgirvin.com/photo/350b74555c04429148f2e12775f6c403-6",
+ "address": "mike@macgirvin.com",
+ "url": "https://macgirvin.com/channel/mike",
+ "connurl": "https://macgirvin.com/poco/mike",
+ "follow": "https://macgirvin.com/follow?f=&url=%s",
+ "connpage": "https://macgirvin.com/connect/mike",
+ "name": "Mike Macgirvin",
+ "network": "zot",
+ "instance_url": "",
+ "flags": "0",
+ "photo_date": "2012-12-06 05:06:11",
+ "name_date": "2012-12-06 04:59:13",
+ "hidden": "1",
+ "orphan": "0",
+ "censored": "0",
+ "selfcensored": "0",
+ "system": "0",
+ "pubforum": "0",
+ "deleted": "0"
+ }
+```
+
+### item/update
+
+Create or update an item (post, activity, webpage, etc.)
+
+Usage: `POST /api/z/1.0/item/update`
+
+Description: item/update posts an item (typically a conversation item or post, but can be any item) using form input.
+
+
+Required:
+
+\- body
+
+ text/bbcode contents by default.
+
+
+Optional:
+
+\- $_FILES['media']
+
+ uploaded media file to include with post
+
+\- title
+
+ title of post/item
+
+\- contact_allow
+
+ array of xchan.xchan_hash allowed to view this item
+
+\- group_allow
+
+ array of group.hash allowed to view this item
+
+\- contact_deny
+
+ array of xchan.xchan_hash not allowed to view this item
+
+\- group_deny
+
+ array of group.hash not allowed to view this item
+
+\- coord
+
+ geographic coordinates
+
+\- location
+
+ freefrom location
+
+\- expire
+
+ datetime this post will expire or be removed
+
+\- mimetype
+
+ mimetype if not text/bbcode
+
+\- parent
+
+ item.id of parent to this post (makes it a comment)
+
+\- parent_mid
+
+ alternate form of parent using message_id
+
+\- remote_xchan
+
+ xchan.xchan_hash of this message author if not the channel owner
+
+\- consensus
+
+ boolean set to true if this is a consensus or voting item (default false)
+
+\- nocomment
+
+ boolean set to true if comments are to be disabled (default false)
+
+\- origin
+
+ do not use this without reading the code
+
+\- namespace
+
+ persistent identity for a remote network or service
+
+\- remote_id
+
+ message_id of this resource on a remote network or service
+
+\- message_id
+
+ message_id of this item (leave unset to generate one)
+
+\- created
+
+ datetime of message creation
+
+\- post_id
+
+ existing item.id if this is an edit operation
+
+\- app
+
+ application or network name to display with item
+
+\- category
+
+ comma separated categories for this item
+
+\- webpage
+
+ item.page_type if not 0
+
+\- pagetitle
+
+ for webpage and design elements, the 'page name'
+
+\- layout_mid
+
+ item.mid of layout for this design element
+
+\- plink
+
+ permalink for this item if different than the default
+
+\- verb
+
+ activitystream verb for this item/activity
+
+\- obj_type
+
+ activitystream object type for this item/activity
+
+
+
+Example:
+
+
+
+```
+curl -u mychannel:mypassword https://xyz.macgirvin.com/api/z/1.0/item/update -d body="hello world"
+```
+
+
+Returns:
+
+
+
+```
+ {
+
+ "success": true,
+ "item_id": "2245",
+ "item": {
+ "id": "2245",
+ "mid": "14135cdecf6b8e3891224e4391748722114da6668eebbcb56fe4667b60b88249@xyz.macgirvin.com",
+ "aid": "1",
+ "uid": "2",
+ "parent": "2245",
+ "parent_mid": "14135cdecf6b8e3891224e4391748722114da6668eebbcb56fe4667b60b88249@xyz.macgirvin.com",
+ "thr_parent": "14135cdecf6b8e3891224e4391748722114da6668eebbcb56fe4667b60b88249@xyz.macgirvin.com",
+ "created": "2016-12-03 20:00:12",
+ "edited": "2016-12-03 20:00:12",
+ "expires": "0001-01-01 00:00:00",
+ "commented": "2016-12-03 20:00:12",
+ "received": "2016-12-03 20:00:12",
+ "changed": "2016-12-03 20:00:12",
+ "comments_closed": "0001-01-01 00:00:00",
+ "owner_xchan": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
+ "author_xchan": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
+ "source_xchan": "",
+ "mimetype": "text/bbcode",
+ "title": "",
+ "body": "hello world",
+ "html": "",
+ "app": "",
+ "lang": "",
+ "revision": "0",
+ "verb": "http://activitystrea.ms/schema/1.0/post",
+ "obj_type": "http://activitystrea.ms/schema/1.0/note",
+ "obj": "",
+ "tgt_type": "",
+ "target": "",
+ "layout_mid": "",
+ "postopts": "",
+ "route": "",
+ "llink": "https://xyz.macgirvin.com/display/14135cdecf6b8e3891224e4391748722114da6668eebbcb56fe4667b60b88249@xyz.macgirvin.com",
+ "plink": "https://xyz.macgirvin.com/channel/mychannel/?f=&mid=14135cdecf6b8e3891224e4391748722114da6668eebbcb56fe4667b60b88249@xyz.macgirvin.com",
+ "resource_id": "",
+ "resource_type": "",
+ "attach": "",
+ "sig": "sa4TOQNfHtV13HDZ1tuQGWNBpZp-nWhT2GMrZEmelXxa_IvEepD2SEsCTWOBqM8OKPJLfNy8_i-ORXjrOIIgAa_aT8cw5vka7Q0C8L9eEb_LegwQ_BtH0CXO5uT30e_8uowkwzh6kmlVg1ntD8QqrGgD5jTET_fMQOIw4gQUBh40GDG9RB4QnPp_MKsgemGrADnRk2vHO7-bR32yQ0JI-8G-eyeqGaaJmIwkHoi0vXsfjZtU7ijSLuKEBWboNjKEDU89-vQ1c5Kh1r0pmjiDk-a5JzZTYShpuhVA-vQgEcADA7wkf4lJZCYNwu3FRwHTvhSMdF0nmyv3aPFglQDky38-SAXZyQSvd7qlABHGCVVDmYrYaiq7Dh4rRENbAUf-UJFHPCVB7NRg34R8HIqmOKq1Su99bIWaoI2zuAQEVma9wLqMoFsluFhxX58KeVtlCZlro7tZ6z619-dthS_fwt0cL_2dZ3QwjG1P36Q4Y4KrCTpntn9ot5osh-HjVQ01h1I9yNCj6XPgYJ8Im3KT_G4hmMDFM7H9RUrYLl2o9XYyiS2nRrf4aJHa0UweBlAY4zcQG34bw2AMGCY53mwsSArf4Hs3rKu5GrGphuwYX0lHa7XEKMglwBWPWHI49q7-oNWr7aWwn1FnfaMfl4cQppCMtKESMNRKm_nb9Dsh5e0",
+ "diaspora_meta": "",
+ "location": "",
+ "coord": "",
+ "public_policy": "",
+ "comment_policy": "contacts",
+ "allow_cid": "",
+ "allow_gid": "",
+ "deny_cid": "",
+ "deny_gid": "",
+ "item_restrict": "0",
+ "item_flags": "0",
+ "item_private": "0",
+ "item_origin": "1",
+ "item_unseen": "0",
+ "item_starred": "0",
+ "item_uplink": "0",
+ "item_consensus": "0",
+ "item_wall": "1",
+ "item_thread_top": "1",
+ "item_notshown": "0",
+ "item_nsfw": "0",
+ "item_relay": "0",
+ "item_mentionsme": "0",
+ "item_nocomment": "0",
+ "item_obscured": "0",
+ "item_verified": "1",
+ "item_retained": "0",
+ "item_rss": "0",
+ "item_deleted": "0",
+ "item_type": "0",
+ "item_hidden": "0",
+ "item_unpublished": "0",
+ "item_delayed": "0",
+ "item_pending_remove": "0",
+ "item_blocked": "0"
+ }
+
+ }
+```
+
+### item/full
+
+Get all data associated with an item
+
+
+
+### abook
+
+Connections
+
+
+
+### abconfig
+
+Connection metadata (such as permissions)
+
+
+
+### perm_allowed
+
+Check a permission for a given xchan \ No newline at end of file
diff --git a/doc/en/developer/Plugins.md b/doc/en/developer/Plugins.md
new file mode 100644
index 000000000..78b2dab68
--- /dev/null
+++ b/doc/en/developer/Plugins.md
@@ -0,0 +1,259 @@
+
+### Creating Plugins/Addons for Hubzilla
+
+
+So you want to make hubzilla do something it doesn't already do. There are lots of ways. But let's learn how to write a plugin or addon.
+
+
+In your hubzilla folder/directory, you will probably see a sub-directory called 'addon'. If you don't have one already, go ahead and create it.
+
+
+ mkdir addon
+
+Then figure out a name for your addon. You probably have at least a vague idea of what you want it to do. For our example I'm going to create a plugin called 'randplace' that provides a somewhat random location for each of your posts. The name of your plugin is used to find the functions we need to access and is part of the function names, so to be safe, use only simple text characters.
+
+Once you've chosen a name, create a directory beneath 'addon' to hold your working file or files.
+
+ mkdir addon/randplace
+
+Now create your plugin file. It needs to have the same name, and it's a PHP script, so using your favourite editor, create the file
+
+ addon/randplace/randplace.php
+
+The very first line of this file needs to be
+
+ <?php
+
+Then we're going to create a comment block to describe the plugin. There's a special format for this. We use /* ... */ comment-style and some tagged lines consisting of
+
+ /**
+ *
+ * Name: Random Place (here you can use better descriptions than you could in the filename)
+ * Description: Sample hubzilla plugin, Sets a random place when posting.
+ * Version: 1.0
+ * Author: Mike Macgirvin <mike@zothub.com>
+ *
+ */
+
+These tags will be seen by the site administrator when he/she installs or manages plugins from the admin panel. There can be more than one author. Just add another line starting with 'Author:'.
+
+The typical plugin will have at least the following functions:
+
+* pluginname_load()
+* pluginname_unload()
+
+In our case, we'll call them randplace_load() and randplace_unload(), as that is the name of our plugin. These functions are called whenever we wish to either initialise the plugin or remove it from the current webpage. Also if your plugin requires things like altering the database schema before it can run for the very first time, you would likely place these instructions in the functions named
+
+* pluginname_install()
+* pluginname_uninstall()
+
+
+Next we'll talk about **hooks**. Hooks are places in hubzilla code where we allow plugins to do stuff. There are a [lot of these](help/Hooks), and they each have a name. What we normally do is use the pluginname_load() function to register a "handler function" for any hooks you are interested in. Then when any of these hooks are triggered, your code will be called.
+
+We register hook handlers with the 'register_hook()' function. It takes 3 arguments. The first is the hook we wish to catch, the second is the filename of the file to find our handler function (relative to the base of your hubzilla installation), and the third is the function name of your handler function. So let's create our randplace_load() function right now.
+
+
+ function randplace_load() {
+ register_hook('post_local', 'addon/randplace/randplace.php', 'randplace_post_hook');
+
+ register_hook('feature_settings', 'addon/randplace/randplace.php', 'randplace_settings');
+ register_hook('feature_settings_post', 'addon/randplace/randplace.php', 'randplace_settings_post');
+
+ }
+
+
+So we're going to catch three events, 'post_local' which is triggered when a post is made on the local system, 'feature_settings' to set some preferences for our plugin, and 'feature_settings_post' to store those settings.
+
+Next we'll create an unload function. This is easy, as it just unregisters our hooks. It takes exactly the same arguments.
+
+ function randplace_unload() {
+ unregister_hook('post_local', 'addon/randplace/randplace.php', 'randplace_post_hook');
+
+ unregister_hook('feature_settings', 'addon/randplace/randplace.php', 'randplace_settings');
+ unregister_hook('feature_settings_post', 'addon/randplace/randplace.php', 'randplace_settings_post');
+
+ }
+
+
+Hooks are called with two arguments. The first is always $a, which is our global App structure and contains a huge amount of information about the state of the web request we are processing; as well as who the viewer is, and what our login state is, and the current contents of the web page we're probably constructing.
+
+The second argument is specific to the hook you're calling. It contains information relevant to that particular place in the program, and often allows you to look at, and even change it. In order to change it, you need to add '&' to the variable name so it is passed to your function by reference. Otherwise it will create a copy and any changes you make will be lost when the hook process returns. Usually (but not always) the second argument is a named array of data structures. Please see the "hook reference" (not yet written as of this date) for details on any specific hook. Occasionally you may need to view the program source to see precisely how a given hook is called and how the results are processed.
+
+Let's go ahead and add some code to implement our post_local hook handler.
+
+ function randplace_post_hook($a, &$item) {
+
+ /**
+ *
+ * An item was posted on the local system.
+ * We are going to look for specific items:
+ * - A status post by a profile owner
+ * - The profile owner must have allowed our plugin
+ *
+ */
+
+ logger('randplace invoked');
+
+ if(! local_channel()) /* non-zero if this is a logged in user of this system */
+ return;
+
+ if(local_channel() != $item['uid']) /* Does this person own the post? */
+ return;
+
+ if(($item['parent']) || (! is_item_normal($item))) {
+ /* If the item has a parent, or isn't "normal", this is a comment or something else, not a status post. */
+ return;
+ }
+
+ /* Retrieve our personal config setting */
+
+ $active = get_pconfig(local_channel(), 'randplace', 'enable');
+
+ if(! $active)
+ return;
+ /**
+ *
+ * OK, we're allowed to do our stuff.
+ * Here's what we are going to do:
+ * load the list of timezone names, and use that to generate a list of world cities.
+ * Then we'll pick one of those at random and put it in the "location" field for the post.
+ *
+ */
+
+ $cities = array();
+ $zones = timezone_identifiers_list();
+ foreach($zones as $zone) {
+ if((strpos($zone,'/')) && (! stristr($zone,'US/')) && (! stristr($zone,'Etc/')))
+ $cities[] = str_replace('_', ' ',substr($zone,strpos($zone,'/') + 1));
+ }
+
+ if(! count($cities))
+ return;
+ $city = array_rand($cities,1);
+ $item['location'] = $cities[$city];
+
+ return;
+ }
+
+
+Now let's add our functions to create and store preference settings.
+
+ /**
+ *
+ * Callback from the settings post function.
+ * $post contains the global $_POST array.
+ * We will make sure we've got a valid user account
+ * and that only our own submit button was clicked
+ * and if so set our configuration setting for this person.
+ *
+ */
+
+ function randplace_settings_post($a,$post) {
+ if(! local_channel())
+ return;
+ if($_POST['randplace-submit'])
+ set_pconfig(local_channel(),'randplace','enable',intval($_POST['randplace']));
+ }
+
+
+
+ /**
+ *
+ * Called from the Feature Setting form.
+ * The second argument is a string in this case, the HTML content region of the page.
+ * Add our own settings info to the string.
+ *
+ * For uniformity of settings pages, we use the following convention
+ * <div class="settings-block">
+ * <h3>title</h3>
+ * .... settings html - many elements will be floated...
+ * <div class="clear"></div> <!-- generic class which clears all floats -->
+ * <input type="submit" name="pluginnname-submit" class="settings-submit" ..... />
+ * </div>
+ */
+
+
+
+ function randplace_settings(&$a,&$s) {
+
+ if(! local_channel())
+ return;
+
+ /* Add our stylesheet to the page so we can make our settings look nice */
+
+ head_add_css('/addon/randplace/randplace.css');
+
+ /* Get the current state of our config variable */
+
+ $enabled = get_pconfig(local_channel(),'randplace','enable');
+
+ $checked = (($enabled) ? ' checked="checked" ' : '');
+
+ /* Add some HTML to the existing form */
+
+ $s .= '<div class="settings-block">';
+ $s .= '<h3>' . t('Randplace Settings') . '</h3>';
+ $s .= '<div id="randplace-enable-wrapper">';
+ $s .= '<label id="randplace-enable-label" for="randplace-checkbox">' . t('Enable Randplace Plugin') . '</label>';
+ $s .= '<input id="randplace-checkbox" type="checkbox" name="randplace" value="1" ' . $checked . '/>';
+ $s .= '</div><div class="clear"></div>';
+
+ /* provide a submit button */
+
+ $s .= '<div class="settings-submit-wrapper" ><input type="submit" name="randplace-submit" class="settings-submit" value="' . t('Submit') . '" /></div></div>';
+
+ }
+
+
+
+
+
+#### *Advanced Plugins*
+
+Sometimes your plugins want to provide a range of new functionality which isn't provided at all or is clumsy to provide using hooks. In this case your plugin can also act as a 'module'. A module in our case refers to a structured webpage handler which responds to a given URL. Then anything which accesses that URL will be handled completely by your plugin.
+
+The key to this is to create a simple function named pluginname_module() which does nothing.
+
+ function randplace_module() { return; }
+
+Once this function exists, the URL `https://yoursite/randplace` will access your plugin as a module. Then you can define functions which are called at various points to build a webpage just like the modules in the mod/ directory. The typical functions and the order which they are called is
+
+ modulename_init($a) // (e.g. randplace_init($a);) called first - if you wish to emit json or xml,
+ // you should do it here, followed by killme() which will avoid the default action of building a webpage
+ modulename_aside($a) // Often used to create sidebar content
+ modulename_post($a) // Called whenever the page is accessed via the "post" method
+ modulename_content($a) // called to generate the central page content. This function should return a string
+ // consisting of the central page content.
+
+Your module functions have access to the URL path as if they were standalone programs in the Unix operating system. For instance if you visit the page
+
+ https://yoursite/randplace/something/somewhere/whatever
+
+we will create an argc/argv list for use by your module functions
+
+ $x = argc(); $x will be 4, the number of path arguments after the sitename
+
+ for($x = 0; $x < argc(); $x ++)
+ echo $x . ' ' . argv($x);
+
+
+ 0 randplace
+ 1 something
+ 2 somewhere
+ 3 whatever
+
+#### *Porting Friendica Plugins*
+
+Hubzilla uses a similar plugin architecture to the Friendica project. The authentication, identity, and permissions systems are completely different. Many Friendica plugins can be ported reasonably easily by renaming a few functions - and then ensuring that the permissions model is adhered to. The functions which need to be renamed are:
+
+* Friendica's pluginname_install() is pluginname_load()
+
+* Friendica's pluginname_uninstall() is pluginname_unload()
+
+Hubzilla has _install and _uninstall functions but these are used differently.
+
+* Friendica's "plugin_settings" hook is called "feature_settings"
+
+* Friendica's "plugin_settings_post" hook is called "feature_settings_post"
+
+Changing these will often allow your plugin to function, but please double check all your permission and identity code because the concepts behind it are completely different in hubzilla. Many structured data names (especially DB schema columns) are also quite different.
diff --git a/doc/en/developer/api_zot.bb b/doc/en/developer/api_zot.bb
deleted file mode 100644
index ff937bfa5..000000000
--- a/doc/en/developer/api_zot.bb
+++ /dev/null
@@ -1,766 +0,0 @@
-[h3]Zot API[/h3]
-
-Many existing social applications and tools can interface directly using the Twitter/StatusNet API, which is available using the 'twitter_api' addon.
-
-This document describes the native API; which allows direct programmatic access to several internal data structures and libraries extending beyond the basic social interface.
-
-The API endpoints detailed below are relative to [code]api/z/1.0[/code], meaning that if an API is listed as [code]channel/stream[/code] the full API URL is [code][baseurl]/api/z/1.0/channel/stream[/code].
-
-[h3]channel/export/basic[/h3]
-
-Export basic channel data
-
-Options:
- - sections
- comma-separated list of data types to export
-
- - posts
- if true, return default sections plus 3 months of posts
-
- If no sections are requested, the following sections are returned:
- channel, connections, config, apps, chatrooms, events, webpages, mail, wikis
-
- Files and large collections of posts may run into memory limits; these must generally be
- requested separately.
-
-
-[h3]channel/stream[/h3]
-
-Fetch channel conversation items
-
-[h3]network/stream[/h3]
-
-
-Fetch network conversation items
-
-
-
-[h3]files[/h3]
-
-
-List file storage (attach DB)
-
-GET /api/z/1.0/files
-
-
-Options:
-
- - filehash
- return only entries matching hash (exactly)
-
- - filename
- return only entries matching filename (substring)
-
- - filetype
- return only entries matching filetype/mimetype (substring)
-
- - start
- start at record (default 0)
-
- - records
- number of records to return or 0 for unlimited
-
-
-
-Example:
-
-curl -u mychannel:mypassword https://xyz.macgirvin.com/api/z/1.0/files -d filetype=multipart/mixed
-
-
-Returns:
-[code nowrap]
- {
-
- "success": true,
- "results": [
- {
- "id": "1",
- "aid": "1",
- "uid": "2",
- "hash": "44ee8b2a1a7f36dea07b93b7747a2383a1bc0fdd08339e8928bfcbe45f65d939",
- "filename": "Profile Photos",
- "filetype": "multipart/mixed",
- "filesize": "0",
- "revision": "0",
- "folder": "",
- "os_storage": "1",
- "is_dir": "1",
- "is_photo": "0",
- "flags": "0",
- "created": "2016-01-02 21:51:17",
- "edited": "2016-01-02 21:51:17",
- "allow_cid": "",
- "allow_gid": "",
- "deny_cid": "",
- "deny_gid": ""
- },
- {
- "id": "12",
- "aid": "1",
- "uid": "2",
- "hash": "71883f1fc64af33889229cbc79c5a056deeec5fc277d765f182f19073e1b2998",
- "filename": "Cover Photos",
- "filetype": "multipart/mixed",
- "filesize": "0",
- "revision": "0",
- "folder": "",
- "os_storage": "1",
- "is_dir": "1",
- "is_photo": "0",
- "flags": "0",
- "created": "2016-01-15 00:24:33",
- "edited": "2016-01-15 00:24:33",
- "allow_cid": "",
- "allow_gid": "",
- "deny_cid": "",
- "deny_gid": ""
- },
- {
- "id": "16",
- "aid": "1",
- "uid": "2",
- "hash": "f48f7ec3278499d1dd86b72c3207beaaf4717b07df5cc9b373f14d7aad2e1bcd",
- "filename": "2016-01",
- "filetype": "multipart/mixed",
- "filesize": "0",
- "revision": "0",
- "folder": "",
- "os_storage": "1",
- "is_dir": "1",
- "is_photo": "0",
- "flags": "0",
- "created": "2016-01-22 03:24:55",
- "edited": "2016-01-22 03:26:57",
- "allow_cid": "",
- "allow_gid": "",
- "deny_cid": "",
- "deny_gid": ""
- }
- ]
- }
-[/code]
-
-
-
-[h3]filemeta[/h3]
-
-Export file metadata for any uploaded file
-
-
-[h3]filedata[/h3]
-
-
-Provides the ability to download a file from cloud storage in chunks
-
-GET /api/z/1.0/filedata
-
-
-Required:
-
- - file_id
- attach.hash of desired file ('begins with' match)
-
-
-Optional:
-
- - start
- starting byte of returned data in file (counting from 0)
-
- - length
- length (prior to base64 encoding) of chunk to download
-
-
-Returns:
-
- attach (DB) structure with base64 encoded 'content' comprised of the desired chunk
-
-
-
-Example:
-
- https://xyz.macgirvin.com/api/z/1.0/filedata?f=&file_id=9f5217770fd&start=0&length=48
-
-Returns:
-[code nowrap]
- {
-
- "attach": {
- "id": "107",
- "aid": "1",
- "uid": "2",
- "hash": "9f5217770fd55d563bd77f84d534d8e119a187514bbd391714626cd9c0e60207",
- "creator": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
- "filename": "pcxtopbm.c",
- "filetype": "application/octet-stream",
- "filesize": "3934",
- "revision": "0",
- "folder": "",
- "flags": "0",
- "is_dir": "0",
- "is_photo": "0",
- "os_storage": "1",
- "os_path": "",
- "display_path": "",
- "content": "LyogcGN4dG9wYm0uYyAtIGNvbnZlcnQgUEMgcGFpbnRicnVzaCAoLnBjeCkgZmls",
- "created": "2016-07-24 23:13:01",
- "edited": "2016-07-24 23:13:01",
- "allow_cid": "",
- "allow_gid": "",
- "deny_cid": "",
- "deny_gid": "",
- "start": 0,
- "length": 48
- }
-
- }
-[/code]
-
-[h3]file/export[/h3]
-
-
-[h3]file[/h3]
-
-
-[h3]albums[/h3]
-
-
-Description: list photo albums
-
-GET /api/z/1.0/albums
-
-
-Output:
-
- text - textual name
-
- total - number of photos in this album
-
- url - web URL
-
- urlencode - textual name, urlencoded
-
- bin2hex - textual name using bin2hex (which is used in the web URL link)
-
-
-Example:
-
-[code nowrap]
- {
-
- "success": true,
- "albums": [
- {
- "text": "/",
- "total": "2",
- "url": "https://xyz.macgirvin.com/photos/hubzilla/album/",
- "urlencode": "",
- "bin2hex": ""
- },
- {
- "text": "2016-01",
- "total": "6",
- "url": "https://xyz.macgirvin.com/photos/hubzilla/album/323031362d3031",
- "urlencode": "2016-01",
- "bin2hex": "323031362d3031"
- },
- {
- "text": "2016-02",
- "total": "7",
- "url": "https://xyz.macgirvin.com/photos/hubzilla/album/323031362d3032",
- "urlencode": "2016-02",
- "bin2hex": "323031362d3032"
- },
- {
- "text": "Cover Photos",
- "total": "5",
- "url": "https://xyz.macgirvin.com/photos/hubzilla/album/436f7665722050686f746f73",
- "urlencode": "Cover+Photos",
- "bin2hex": "436f7665722050686f746f73"
- },
- {
- "text": "Profile Photos",
- "total": "26",
- "url": "https://xyz.macgirvin.com/photos/hubzilla/album/50726f66696c652050686f746f73",
- "urlencode": "Profile+Photos",
- "bin2hex": "50726f66696c652050686f746f73"
- }
- ]
-
- }
-[/code]
-
-
-[h3]photos[/h3]
-
-
-list photo metadata
-
-
-[h3]photo[/h3]
-
-
-
-[h3]group[/h3]
-
-
-[code]GET /api/z/1.0/group[/code]
-
-Description: list privacy groups
-
-Returns: DB tables of all privacy groups.
-
-To use with API group_members, provide either 'group_id' from the id element returned in this call, or 'group_name' from the gname returned in this call.
-
-[code nowrap]
- [
-
- {
- "id": "1",
- "hash": "966c946394f3e2627bbb8a55026b5725e582407098415c02f85232de3f3fde76Friends",
- "uid": "2",
- "visible": "0",
- "deleted": "0",
- "gname": "Friends"
- },
- {
- "id": "2",
- "hash": "852ebc17f8c3ed4866f2162e384ded0f9b9d1048f93822c0c84196745f6eec66Family",
- "uid": "2",
- "visible": "1",
- "deleted": "0",
- "gname": "Family"
- },
- {
- "id": "3",
- "hash": "cc3cb5a7f9818effd7c7c80a58b09a189b62efa698a74319117babe33ee30ab9Co-workers",
- "uid": "2",
- "visible": "0",
- "deleted": "0",
- "gname": "Co-workers"
- }
- ]
-[/code]
-[h3]group_members[/h3]
-
-
-[code]GET /api/z/1.0/group_members[/code]
-
-Required:
-
-group_id or group_name
-
-
-Returns:
-
-group_member+abook+xchan (DB join) for each member of the privacy group
-
-[code nowrap]
- [
-
- {
- "id": "1",
- "uid": "2",
- "gid": "1",
- "xchan": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
- "abook_id": "2",
- "abook_account": "1",
- "abook_channel": "2",
- "abook_xchan": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
- "abook_my_perms": "218555",
- "abook_their_perms": "0",
- "abook_closeness": "0",
- "abook_created": "2016-01-02 21:16:26",
- "abook_updated": "2016-01-02 21:16:26",
- "abook_connected": "0000-00-00 00:00:00",
- "abook_dob": "0000-00-00 00:00:00",
- "abook_flags": "0",
- "abook_blocked": "0",
- "abook_ignored": "0",
- "abook_hidden": "0",
- "abook_archived": "0",
- "abook_pending": "0",
- "abook_unconnected": "0",
- "abook_self": "1",
- "abook_feed": "0",
- "abook_profile": "",
- "abook_incl": "",
- "abook_excl": "",
- "abook_instance": "",
- "xchan_hash": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
- "xchan_guid": "lql-1VnxtiO4-WF0h72wLX1Fu8szzHDOXgQaTbELwXW77k8AKFfh-hYr70vqMrc3SSvWN-Flrc5HFhRTWB7ICw",
- "xchan_guid_sig": "PafvEL0VpKfxATxlCqDjfOeSIMdmpr3iU7X-Sysa1h5LzDpjSXsjO37tYZL-accb1M5itLlfnW5epkTa5I4flsW21zSY1A2jCuBQUTLLGV7rNyyBy7lgqJUFvAMRx0TfXzP9lcaPqlM9T1tA6jfWOsOmkdzwofGeXBnsjGfjsO2xdGYe6vwjOU0DSavukvzDMnOayB9DekpvDnaNBTxeGLM45Skzr7ZEMcNF7TeXMbnvpfLaALYEKeQs9bGH-UgAG8fBWgzVAzeBfx_XSR1rdixjyiZGP0kq0h35SlmMPcEjliodOBFwMXqpXFB7Ibp4F6o6te2p2ErViJccQVG8VNKB6SbKNXY6bhP5zVcVsJ-vR-p4xXoYJJvzTN7yTDsGAXHOLF4ZrXbo5yi5gFAlIrTLAF2EdWQwxSGyLRWKxG8PrDkzEzX6cJJ0VRcLh5z6OI5QqQNdeghPZbshMFMJSc_ApCPi9_hI4ZfctCIOi3T6bdgTNKryLm5fhy_eqjwLAZTGP-aUBgLZpb1mf2UojBn6Ey9cCyq-0T2RWyk-FcIcbV4qJ-p_8oODqw13Qs5FYkjLr1bGBq82SuolkYrXEwQClxnrfKa4KYc2_eHAXPL01iS9zVnI1ySOCNJshB97Odpooc4wk7Nb2Fo-Q6THU9zuu0uK_-JbK7IIl6go2qA",
- "xchan_pubkey": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA18JB76lyP4zzL/y7BCej\neJnfZIWZNtM3MZvI1zEVMWmmwOS+u/yH8oPwyaDk4Y/tnj8GzMPj1lCGVRcd8EJa\nNrCMd50HODA5EsJtxpsOzRcILYjOcTtIAG1K4LtKqELi9ICAaFp0fNfa+Jf0eCek\nvPusx2/ORhy+o23hFoSMhL86o2gmaiRnmnA3Vz4ZMG92ieJEDMXt9IA1EkIqS4y5\nBPZfVPLD1pv8iivj+dtN1XjwplgjUbtxmU0/Ej808nHppscRIqx/XJ0XZU90oNGw\n/wYoK2EzJlPbRsAkwNqoFrAYlr5HPpn4BJ2ebFYQgWBUraD7HwS5atsQEaxGfO21\nlUP0+lDg9t3CXvudDj0UG1jiEKbVIGA+4aG0GN2DSC5AyRq/GRxqyay5W2vQbAZH\nyvxPGrZFO24I65g3pjhpjEsLqZ4ilTLQoLMs0drCIcRm5RxMUo4s/LMg16lT4cEk\n1qRtk2X0Sb1AMQQ2uRXiVtWz77QHMONEYkf6OW4SHbwcv5umvlv69NYEGfCcbgq0\nAV7U4/BWztUz/SWj4r194CG43I9I8dmaEx9CFA/XMePIAXQUuABfe1QMOR6IxLpq\nTHG1peZgHQKeGz4aSGrhQkZNNoOVNaZoIfcvopxcHDTZLigseEIaPPha4WFYoKPi\nUPbZ5o8gTLc750uzrnb2jwcCAwEAAQ==\n-----END PUBLIC KEY-----\n",
- "xchan_photo_mimetype": "image/png",
- "xchan_photo_l": "https://xyz.macgirvin.com/photo/profile/l/2",
- "xchan_photo_m": "https://xyz.macgirvin.com/photo/profile/m/2",
- "xchan_photo_s": "https://xyz.macgirvin.com/photo/profile/s/2",
- "xchan_addr": "teller@xyz.macgirvin.com",
- "xchan_url": "https://xyz.macgirvin.com/channel/teller",
- "xchan_connurl": "https://xyz.macgirvin.com/poco/teller",
- "xchan_follow": "https://xyz.macgirvin.com/follow?f=&url=%s",
- "xchan_connpage": "",
- "xchan_name": "Teller",
- "xchan_network": "zot",
- "xchan_instance_url": "",
- "xchan_flags": "0",
- "xchan_photo_date": "2016-10-19 01:26:50",
- "xchan_name_date": "2016-01-02 21:16:26",
- "xchan_hidden": "0",
- "xchan_orphan": "0",
- "xchan_censored": "0",
- "xchan_selfcensored": "0",
- "xchan_system": "0",
- "xchan_pubforum": "0",
- "xchan_deleted": "0"
- },
- {
- "id": "12",
- "uid": "2",
- "gid": "1",
- "xchan": "xuSMUYxw1djBB97qXsbrBN1nzJH_gFwQL6pS4zIy8fuusOfBxNlMiVb4h_q5tOEvpE7tYf1EsryjNciMuPIj5w",
- "abook_id": "24",
- "abook_account": "1",
- "abook_channel": "2",
- "abook_xchan": "xuSMUYxw1djBB97qXsbrBN1nzJH_gFwQL6pS4zIy8fuusOfBxNlMiVb4h_q5tOEvpE7tYf1EsryjNciMuPIj5w",
- "abook_my_perms": "218555",
- "abook_their_perms": "218555",
- "abook_closeness": "80",
- "abook_created": "2016-01-27 00:48:43",
- "abook_updated": "2016-12-04 17:16:58",
- "abook_connected": "2016-12-04 17:16:58",
- "abook_dob": "0001-01-01 00:00:00",
- "abook_flags": "0",
- "abook_blocked": "0",
- "abook_ignored": "0",
- "abook_hidden": "0",
- "abook_archived": "0",
- "abook_pending": "0",
- "abook_unconnected": "0",
- "abook_self": "0",
- "abook_feed": "0",
- "abook_profile": "debb5236efb1626cfbad33ccb49892801e5f844aa04bf81f580cfa7d13204819",
- "abook_incl": "",
- "abook_excl": "",
- "abook_instance": "",
- "xchan_hash": "xuSMUYxw1djBB97qXsbrBN1nzJH_gFwQL6pS4zIy8fuusOfBxNlMiVb4h_q5tOEvpE7tYf1EsryjNciMuPIj5w",
- "xchan_guid": "d5EMLlt1tHHZ0dANoA7B5Wq9UgXoWcFS9-gXOkL_AAejcPApoQRyxfHTuu8DoTbUaO-bYmX5HPuWuK9PHyqNmA",
- "xchan_guid_sig": "CVWEMRPtzI1YcHfnnWHTuv3H964OAmSElgUfxMoX6RdQdxNpqb_POirpVuyP8s3W17mVCfO5V9IAjkg5iKcqCk6YcvOD_egmMy-AnM9TC1kKndQHw55CunD82Q8K_xBNSXkSROizcNkKh9DVLjJPFjW1AqtI4njkZ3EMgrWqnbFRM1qPToUoCY9zM3tEMHoAD9YX1zP90wl40LzfN-dtcNWpSBbiz9owou62uzLbN7mrCwKOMlXLjwwGswRnxIsEnb3O-FXOs8hs0mArKe9snq1-BKeD16LyzxgwlpVLElzIJZGEZGtMdIJgeRzKuBvPjsOIpQ1yAkuOpFJ3nGCM-IPOIIjAmyVl5zD3xPVcxxpZlJRn5fG1Y-gnqTgsrEQCA7M6XPWQdrdHU4akZfyUyFJDhv3uM-jon9VzrYTBw68R0WA-1Z8WafEHA4qh5OWAj85lUarwhr7iTiEckH51ypPCPs6VbT6Pw7yMaxfjFOcipashQagx0tfOlDhE5dQANOXKASFtH1J9-CZY2MQdLPQ6u54d5whuHKMGaJ0V68pnmZ2rOn7g344Ah2WCJrm17jj60QsRMorqRFj7GMdPIA1XB8Wrk88MuYOe3Dhyuu6ZWKI7YTWJS690ZVkKUqAiNHqj0W86DtaiPUc_mmGR0fHl4Gksnko3WmCFv9q2X2E",
- "xchan_pubkey": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAoj2xCJktBA8Ww7Hp+ZNL\nrNuQpo8UB/bfvRkIy+yua3xpF1TuXcnAH61kyRz8vXgOu/l2CyxQbIoaGslCV5Sy\n8JKeNXe+IilUdSSEjMIwCPfSPsYnMHsSnHWmPmclvJwEtQUKOZmW5mMuVBvXy7D2\njomFwc69AYphdyys6eQ7Dcn6+FRBiQbyMprZ5lxyVW+O4DuXVNa3ej2ebx0gCJZ4\ntTIlBoKwEey91dY+FyKVFjdwfNczpmL7LgmZXqcVx+MG3mYgibwdVMiXVj5X06cs\nV9hJ5Xi+Aklsv/UWJtjw9FVt7y9TLptnhh4Ra6T/MDmnBBIAkOR7P/X8cRv078MT\nl0IMsP0RJcDEtTLtwHFVtDs6p52KDFqclKWbqmxmxqV3OTPVYtArRGIzgnJi/5ur\nHRr5G6Cif7QY3UowsIOf78Qvy28LwSbdymgBAWwPPKIviXWxGO+9kMWdmPSUQrWy\nK0+7YA9P9fBUFfn9Hc+p8SJQmQ6OAqLwrDGiPSOlGaNrbEqwqLGgIpXwK+lEFcFJ\n3SPOjJRWdR2whlMxvpwX+39+H7dWN3vSa3Al4/Sq7qW8yW2rYwf+eGyp4Z0lRR+8\nJxFMCwZkSw5g14YdlikAPojv5V1c6KuA5ieg8G1hwyONV7A4JHPyEdPt0W0TZi6C\nCOVkPaC3xGrguETZpJfVpwUCAwEAAQ==\n-----END PUBLIC KEY-----\n",
- "xchan_photo_mimetype": "image/png",
- "xchan_photo_l": "https://xyz.macgirvin.com/photo/9da63aa910ea14e1501ee1a749d181a6-4",
- "xchan_photo_m": "https://xyz.macgirvin.com/photo/9da63aa910ea14e1501ee1a749d181a6-5",
- "xchan_photo_s": "https://xyz.macgirvin.com/photo/9da63aa910ea14e1501ee1a749d181a6-6",
- "xchan_addr": "cloner@xyz.macgirvin.com",
- "xchan_url": "http://abc.macgirvin.com/channel/cloner",
- "xchan_connurl": "http://abc.macgirvin.com/poco/cloner",
- "xchan_follow": "https://xyz.macgirvin.com/follow?f=&url=%s",
- "xchan_connpage": "",
- "xchan_name": "Karen",
- "xchan_network": "zot",
- "xchan_instance_url": "",
- "xchan_flags": "0",
- "xchan_photo_date": "2016-03-31 19:59:20",
- "xchan_name_date": "2016-01-26 23:23:42",
- "xchan_hidden": "0",
- "xchan_orphan": "0",
- "xchan_censored": "0",
- "xchan_selfcensored": "0",
- "xchan_system": "0",
- "xchan_pubforum": "0",
- "xchan_deleted": "0"
- }
-
- ]
-[/code]
-
-[h3]xchan[/h3]
-
-
-An xchan is a global location independent channel and is the primary record for a network
-identity. It may refer to channels on other websites, networks, or services.
-
-[code]GET /api/z/1.0/xchan[/code]
-
-Required: one of [ address, hash, guid ] as GET parameters
-
-Returns a portable xchan structure
-
-Example: https://xyz.macgirvin.com/api/z/1.0/xchan?f=&address=mike@macgirvin.com
-
-Returns:
-[code nowrap]
- {
- "hash": "jr54M_y2l5NgHX5wBvP0KqWcAHuW23p1ld-6Vn63_pGTZklrI36LF8vUHMSKJMD8xzzkz7s2xxCx4-BOLNPaVA",
- "guid": "sebQ-IC4rmFn9d9iu17m4BXO-kHuNutWo2ySjeV2SIW1LzksUkss12xVo3m3fykYxN5HMcc7gUZVYv26asx-Pg",
- "guid_sig": "Llenlbl4zHo6-g4sa63MlQmTP5dRCrsPmXHHFmoCHG63BLq5CUZJRLS1vRrrr_MNxr7zob_Ykt_m5xPKe5H0_i4pDj-UdP8dPZqH2fqhhx00kuYL4YUMJ8gRr5eO17vsZQ3XxTcyKewtgeW0j7ytwMp6-hFVUx_Cq08MrXas429ZrjzaEwgTfxGnbgeQYQ0R5EXpHpEmoERnZx77VaEahftmdjAUx9R4YKAp13pGYadJOX5xnLfqofHQD8DyRHWeMJ4G1OfWPSOlXfRayrV_jhnFlZjMU7vOdQwHoCMoR5TFsRsHuzd-qepbvo3pzvQZRWnTNu6oPucgbf94p13QbalYRpBXKOxdTXJrGdESNhGvhtaZnpT9c1QVqC46jdfP0LOX2xrVdbvvG2JMWFv7XJUVjLSk_yjzY6or2VD4V6ztYcjpCi9d_WoNHruoxro_br1YO3KatySxJs-LQ7SOkQI60FpysfbphNyvYMkotwUFI59G08IGKTMu3-GPnV1wp7NOQD1yzJbGGEGSEEysmEP0SO9vnN45kp3MiqbffBGc1r4_YM4e7DPmqOGM94qksOcLOJk1HNESw2dQYWxWQTBXPfOJT6jW9_crGLMEOsZ3Jcss0XS9KzBUA2p_9osvvhUKuKXbNztqH0oZIWlg37FEVsDs_hUwUJpv2Ar09k4",
- "pubkey": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7QCwvuEIwCHjhjbpz3Oc\ntyei/Pz9nDksNbsc44Cm8jxYGMXsTPFXDZYCcCB5rcAhPPdZSlzaPkv4vPVcMIrw\n5cdX0tvbwa3rNTng6uFE7qkt15D3YCTkwF0Y9FVZiZ2Ko+G23QeBt9wqb9dlDN1d\nuPmu9BLYXIT/JXoBwf0vjIPFM9WBi5W/EHGaiuqw7lt0qI7zDGw77yO5yehKE4cu\n7dt3SakrXphL70LGiZh2XGoLg9Gmpz98t+gvPAUEotAJxIUqnoiTA8jlxoiQjeRK\nHlJkwMOGmRNPS33awPos0kcSxAywuBbh2X3aSqUMjcbE4cGJ++/13zoa6RUZRObC\nZnaLYJxqYBh13/N8SfH7d005hecDxWnoYXeYuuMeT3a2hV0J84ztkJX5OoxIwk7S\nWmvBq4+m66usn6LNL+p5IAcs93KbvOxxrjtQrzohBXc6+elfLVSQ1Rr9g5xbgpub\npSc+hvzbB6p0tleDRzwAy9X16NI4DYiTj4nkmVjigNo9v2VPnAle5zSam86eiYLO\nt2u9YRqysMLPKevNdj3CIvst+BaGGQONlQalRdIcq8Lin+BhuX+1TBgqyav4XD9K\nd+JHMb1aBk/rFLI9/f2S3BJ1XqpbjXz7AbYlaCwKiJ836+HS8PmLKxwVOnpLMbfH\nPYM8k83Lip4bEKIyAuf02qkCAwEAAQ==\n-----END PUBLIC KEY-----\n",
- "photo_mimetype": "image/jpeg",
- "photo_l": "https://xyz.macgirvin.com/photo/350b74555c04429148f2e12775f6c403-4",
- "photo_m": "https://xyz.macgirvin.com/photo/350b74555c04429148f2e12775f6c403-5",
- "photo_s": "https://xyz.macgirvin.com/photo/350b74555c04429148f2e12775f6c403-6",
- "address": "mike@macgirvin.com",
- "url": "https://macgirvin.com/channel/mike",
- "connurl": "https://macgirvin.com/poco/mike",
- "follow": "https://macgirvin.com/follow?f=&url=%s",
- "connpage": "https://macgirvin.com/connect/mike",
- "name": "Mike Macgirvin",
- "network": "zot",
- "instance_url": "",
- "flags": "0",
- "photo_date": "2012-12-06 05:06:11",
- "name_date": "2012-12-06 04:59:13",
- "hidden": "1",
- "orphan": "0",
- "censored": "0",
- "selfcensored": "0",
- "system": "0",
- "pubforum": "0",
- "deleted": "0"
- }
-[/code]
-[h3]item/update[/h3]
-
-
-Create or update an item (post, activity, webpage, etc.)
-
-Usage: [code]POST /api/z/1.0/item/update[/code]
-
-Description: item/update posts an item (typically a conversation item or post, but can be any item) using form input.
-
-
-Required:
-
-- body
-
- text/bbcode contents by default.
-
-
-Optional:
-
-- $_FILES['media']
-
- uploaded media file to include with post
-
-- title
-
- title of post/item
-
-- contact_allow
-
- array of xchan.xchan_hash allowed to view this item
-
-- group_allow
-
- array of group.hash allowed to view this item
-
-- contact_deny
-
- array of xchan.xchan_hash not allowed to view this item
-
-- group_deny
-
- array of group.hash not allowed to view this item
-
-- coord
-
- geographic coordinates
-
-- location
-
- freefrom location
-
-- expire
-
- datetime this post will expire or be removed
-
-- mimetype
-
- mimetype if not text/bbcode
-
-- parent
-
- item.id of parent to this post (makes it a comment)
-
-- parent_mid
-
- alternate form of parent using message_id
-
-- remote_xchan
-
- xchan.xchan_hash of this message author if not the channel owner
-
-- consensus
-
- boolean set to true if this is a consensus or voting item (default false)
-
-- nocomment
-
- boolean set to true if comments are to be disabled (default false)
-
-- origin
-
- do not use this without reading the code
-
-- namespace
-
- persistent identity for a remote network or service
-
-- remote_id
-
- message_id of this resource on a remote network or service
-
-- message_id
-
- message_id of this item (leave unset to generate one)
-
-- created
-
- datetime of message creation
-
-- post_id
-
- existing item.id if this is an edit operation
-
-- app
-
- application or network name to display with item
-
-- category
-
- comma separated categories for this item
-
-- webpage
-
- item.page_type if not 0
-
-- pagetitle
-
- for webpage and design elements, the 'page name'
-
-- layout_mid
-
- item.mid of layout for this design element
-
-- plink
-
- permalink for this item if different than the default
-
-- verb
-
- activitystream verb for this item/activity
-
-- obj_type
-
- activitystream object type for this item/activity
-
-
-
-Example:
-
- curl -u mychannel:mypassword https://xyz.macgirvin.com/api/z/1.0/item/update -d body="hello world"
-
-
-Returns:
-
-[code nowrap]
- {
-
- "success": true,
- "item_id": "2245",
- "item": {
- "id": "2245",
- "mid": "14135cdecf6b8e3891224e4391748722114da6668eebbcb56fe4667b60b88249@xyz.macgirvin.com",
- "aid": "1",
- "uid": "2",
- "parent": "2245",
- "parent_mid": "14135cdecf6b8e3891224e4391748722114da6668eebbcb56fe4667b60b88249@xyz.macgirvin.com",
- "thr_parent": "14135cdecf6b8e3891224e4391748722114da6668eebbcb56fe4667b60b88249@xyz.macgirvin.com",
- "created": "2016-12-03 20:00:12",
- "edited": "2016-12-03 20:00:12",
- "expires": "0001-01-01 00:00:00",
- "commented": "2016-12-03 20:00:12",
- "received": "2016-12-03 20:00:12",
- "changed": "2016-12-03 20:00:12",
- "comments_closed": "0001-01-01 00:00:00",
- "owner_xchan": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
- "author_xchan": "pgcJx1IQjuPkx8aI9qheJlBMZzJz-oTPjHy3h5pWlOVOriBO_cSiUhhqwhuZ74TYJ8_ECO3pPiRMWC0q8YPCQg",
- "source_xchan": "",
- "mimetype": "text/bbcode",
- "title": "",
- "body": "hello world",
- "html": "",
- "app": "",
- "lang": "",
- "revision": "0",
- "verb": "http://activitystrea.ms/schema/1.0/post",
- "obj_type": "http://activitystrea.ms/schema/1.0/note",
- "obj": "",
- "tgt_type": "",
- "target": "",
- "layout_mid": "",
- "postopts": "",
- "route": "",
- "llink": "https://xyz.macgirvin.com/display/14135cdecf6b8e3891224e4391748722114da6668eebbcb56fe4667b60b88249@xyz.macgirvin.com",
- "plink": "https://xyz.macgirvin.com/channel/mychannel/?f=&mid=14135cdecf6b8e3891224e4391748722114da6668eebbcb56fe4667b60b88249@xyz.macgirvin.com",
- "resource_id": "",
- "resource_type": "",
- "attach": "",
- "sig": "sa4TOQNfHtV13HDZ1tuQGWNBpZp-nWhT2GMrZEmelXxa_IvEepD2SEsCTWOBqM8OKPJLfNy8_i-ORXjrOIIgAa_aT8cw5vka7Q0C8L9eEb_LegwQ_BtH0CXO5uT30e_8uowkwzh6kmlVg1ntD8QqrGgD5jTET_fMQOIw4gQUBh40GDG9RB4QnPp_MKsgemGrADnRk2vHO7-bR32yQ0JI-8G-eyeqGaaJmIwkHoi0vXsfjZtU7ijSLuKEBWboNjKEDU89-vQ1c5Kh1r0pmjiDk-a5JzZTYShpuhVA-vQgEcADA7wkf4lJZCYNwu3FRwHTvhSMdF0nmyv3aPFglQDky38-SAXZyQSvd7qlABHGCVVDmYrYaiq7Dh4rRENbAUf-UJFHPCVB7NRg34R8HIqmOKq1Su99bIWaoI2zuAQEVma9wLqMoFsluFhxX58KeVtlCZlro7tZ6z619-dthS_fwt0cL_2dZ3QwjG1P36Q4Y4KrCTpntn9ot5osh-HjVQ01h1I9yNCj6XPgYJ8Im3KT_G4hmMDFM7H9RUrYLl2o9XYyiS2nRrf4aJHa0UweBlAY4zcQG34bw2AMGCY53mwsSArf4Hs3rKu5GrGphuwYX0lHa7XEKMglwBWPWHI49q7-oNWr7aWwn1FnfaMfl4cQppCMtKESMNRKm_nb9Dsh5e0",
- "diaspora_meta": "",
- "location": "",
- "coord": "",
- "public_policy": "",
- "comment_policy": "contacts",
- "allow_cid": "",
- "allow_gid": "",
- "deny_cid": "",
- "deny_gid": "",
- "item_restrict": "0",
- "item_flags": "0",
- "item_private": "0",
- "item_origin": "1",
- "item_unseen": "0",
- "item_starred": "0",
- "item_uplink": "0",
- "item_consensus": "0",
- "item_wall": "1",
- "item_thread_top": "1",
- "item_notshown": "0",
- "item_nsfw": "0",
- "item_relay": "0",
- "item_mentionsme": "0",
- "item_nocomment": "0",
- "item_obscured": "0",
- "item_verified": "1",
- "item_retained": "0",
- "item_rss": "0",
- "item_deleted": "0",
- "item_type": "0",
- "item_hidden": "0",
- "item_unpublished": "0",
- "item_delayed": "0",
- "item_pending_remove": "0",
- "item_blocked": "0"
- }
-
- }
-[/code]
-[h3]item/full[/h3]
-
-
-Get all data associated with an item
-
-[h3]abook[/h3]
-
-
-Connections
-
-[h3]abconfig[/h3]
-
-
-Connection metadata (such as permissions)
-
-[h3]perm_allowed[/h3]
-
-
-Check a permission for a given xchan
diff --git a/doc/en/developer/attribution.md b/doc/en/developer/attribution.md
new file mode 100644
index 000000000..1c06e0b0d
--- /dev/null
+++ b/doc/en/developer/attribution.md
@@ -0,0 +1,3 @@
+#### Attribution
+
+This Code of Conduct is an adaptation of the [Contributor Covenant](http://contributor-covenant.org/?f=&zid=pepecyb@hub.hubzilla.hu), version 1.4, available at [http://contributor-covenant.org/version/1/4.](http://contributor-covenant.org/version/1/4/?f=&zid=pepecyb@hub.hubzilla.hu) \ No newline at end of file
diff --git a/doc/en/developer/code_of_conduct.md b/doc/en/developer/code_of_conduct.md
new file mode 100644
index 000000000..ecdb9194b
--- /dev/null
+++ b/doc/en/developer/code_of_conduct.md
@@ -0,0 +1,8 @@
+### Code of conduct for contributors
+
+#include doc/en/developer/our_pledge.md;
+#include doc/en/developer/our_standards.md;
+#include doc/en/developer/our_responsibilities.md;
+#include doc/en/developer/scope.md;
+#include doc/en/developer/enforcement.md;
+#include doc/en/developer/attribution.md;
diff --git a/doc/en/developer/coding_style.md b/doc/en/developer/coding_style.md
new file mode 100644
index 000000000..23af95274
--- /dev/null
+++ b/doc/en/developer/coding_style.md
@@ -0,0 +1,12 @@
+### Coding style
+
+In the interest of consistency, we use the following code style. We also accept patches that use other styles, but please try to use a consistent coding style if possible. We will not argue or discuss the merits of this style, and it is irrelevant what project ‘xyz’ uses. This is not project ‘xyz’. This is a baseline to try to keep the code readable now and in the future.
+
+- All comments should be in English.
+- We use Doxygen to create documentation. This has not been used consistently, but it is highly recommended to learn and use it.
+- Indentation is mainly done by tabs with a tab width of 4.
+- String concatenations and operators should be separated by spaces. e.g. `$foo = $bar . ‘abc’;` instead of `$foo=$bar.”abc’;`
+- In general, we use single quotes for string variables and double quotes for SQL statements. ‘Here documents’ should be avoided. Sometimes using strings in double quotes with variable substitution is the most efficient way to create the string. In most cases, you should use single quotes.
+- Use spaces liberally to improve readability. When creating arrays with many elements, a key/value pair is often set per line, which is indented accordingly from the parent line. Lining up the assignment operators requires a little more work, but also increases readability.
+- In general, opening curly brackets are placed on the same line as what the bracket opens. They are the last character in the line. Closing brackets are placed on a separate line.
+- Some functions take arguments in argc/argv style, such as main() in C or function args in Bash or Perl. Urls are split within a module. e.g. for `http://example.com/module/arg1/arg2` $this->argc is 3 (integer) and $this->argv contains: [0] => ‘module’, [1] => ‘arg1’, [2] => ‘arg2’. Only one argument is ever specified. If a naked domain URL is specified, $this->argv[0] is set to ‘home’. \ No newline at end of file
diff --git a/doc/en/developer/covenant.bb b/doc/en/developer/covenant.bb
deleted file mode 100644
index 431cc74e9..000000000
--- a/doc/en/developer/covenant.bb
+++ /dev/null
@@ -1,47 +0,0 @@
-[size=large]Contributor Covenant Code of Conduct[/size]
-
-[h3]Our Pledge[/h3]
-
-In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
-
-[h3]Our Standards[/h3]
-
-Examples of behavior that contributes to creating a positive environment
-include:
-
-[list]
- [*]Using welcoming and inclusive language
- [*]Being respectful of differing viewpoints and experiences
- [*]Gracefully accepting constructive criticism
- [*]Focusing on what is best for the community
- [*]Showing empathy towards other community members
-[/list]
-
-Examples of unacceptable behavior by participants include:
-
-[list]
- [*]The use of sexualized language or imagery and unwelcome sexual attention or advances
- [*]Trolling, insulting/derogatory comments, and personal or political attacks
- [*]Public or private harassment
- [*]Publishing others' private information, such as a physical or electronic address, without explicit permission
- [*]Other conduct which could reasonably be considered inappropriate in a professional setting
-[/list]
-
-[h3]Our Responsibilities[/h3]
-
-Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
-
-[h3]Scope[/h3]
-
-This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
-
-[h3]Enforcement[/h3]
-
-Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at project&#x40;hubzilla.org. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
-
-Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
-
-[h3]Attribution[/h3]
-
-This Code of Conduct is adapted from the [url=http://contributor-covenant.org]Contributor Covenant[/url], version 1.4, available at [url=http://contributor-covenant.org/version/1/4/]http://contributor-covenant.org/version/1/4[/url].
-
diff --git a/doc/en/developer/dev-function-overview.md b/doc/en/developer/dev-function-overview.md
new file mode 100644
index 000000000..1db5ee6c6
--- /dev/null
+++ b/doc/en/developer/dev-function-overview.md
@@ -0,0 +1,49 @@
+### Hubzilla development - some useful basic functions
+
+
+
+* `get_account_id()`
+
+Returns numeric account_id if authenticated or 0. It is possible to be authenticated and not connected to a channel.
+
+* `local_channel()`
+
+Returns authenticated numeric channel_id if authenticated and connected to a channel or 0. Sometimes referred to as $uid in the code.
+
+* `remote_channel()`
+
+Returns authenticated string hash of Red global identifier, if authenticated via remote auth, or an empty string.
+
+* `App::get_observer()`
+
+returns an xchan structure representing the current viewer if authenticated (locally or remotely).
+
+* `get_config($family,$key), get_pconfig($uid,$family,$key)`
+
+Returns the config setting for $family and $key or false if unset.
+
+Deprecated: Use `Zotlabs\Lib\Config::Get` instead.
+
+* `set_config($family,$key,$value), set_pconfig($uid,$family,$key,$value)`
+
+Sets the value of config setting for $family and $key to $value. Returns $value. The config versions operate on system-wide settings. The pconfig versions get/set the values for a specific integer uid (channel_id).
+
+Deprecated: Use `Zotlabs\Lib\Config::Set` instead.
+
+* `dbesc()`
+
+Always escape strings being used in DB queries. This function returns the escaped string. Integer DB parameters should all be proven integers by wrapping with intval()
+
+* `q($sql,$var1...)`
+
+Perform a DB query with the SQL statement $sql. printf style arguments %s and %d are replaced with variable arguments, which should each be appropriately dbesc() or intval(). SELECT queries return an array of results or false if SQL or DB error. Other queries return true if the command was successful or false if it wasn't.
+
+* `t($string)`
+
+Returns the translated variant of $string for the current language or $string (default 'en' language) if the language is unrecognised or a translated version of the string does not exist.
+
+* `x($var), $x($array,$key)`
+
+Shorthand test to see if variable $var is set and is not empty. Tests vary by type. Returns false if $var or $key is not set.
+If variable is set, returns 1 if has 'non-zero' value, otherwise returns 0. -- e.g. x('') or x(0) returns 0;
+
diff --git a/doc/en/developer/dev_beginner.md b/doc/en/developer/dev_beginner.md
new file mode 100644
index 000000000..63b12d97c
--- /dev/null
+++ b/doc/en/developer/dev_beginner.md
@@ -0,0 +1,548 @@
+### You want to contribute code?
+
+**...and don't know how to start to...**
+
+- debug hubzilla (php on the webserver),
+- contribute code to the project,
+- optionally - do it all from inside a virtual machine
+
+This manual was tested for Debian (Wheezy) as virtual machine on Lubuntu (Ubuntu 14.0) as host.
+
+
+
+#### Install a Virtual Machine (KVM)
+
+[Here](https://wiki.debian.org/KVM) the installation guide for Linux Debian.
+The summary:
+
+1. install KVM
+
+```
+# apt-get install qemu-kvm libvirt-bin
+```
+
+2. add yourself to the group libvirt
+```
+# adduser <youruser> libvirt
+```
+
+3. install gui to manage virtual machines
+```
+# apt-get install virt-manager
+```
+
+4. download an operating system to run inside the vm ([mini.iso](http://ftp.nl.debian.org/debian/dists/wheezy/main/installer-amd64/current/images/netboot/mini.iso))
+5. start the virt manager
+- create new virtual machine (click on icon)
+- choose your iso image (just downloaded) as installation source
+- optional: configure the new vm: ram, cpu's,...
+- start virtual machine > result: linux debian starts in a new window.
+6. (optional) avoid network errors after restart of host os
+
+```
+# virsh net-start default
+# virsh net-autostart default
+```
+
+
+
+#### Install Apache Webserver
+
+Open a terminal and make yourself root
+
+```
+su -l
+```
+
+
+Create the standard group for the Apache webserver
+
+```
+groupadd www-data
+```
+
+might exist already
+
+
+```
+usermod -a -G www-data www-data
+```
+
+
+Check if the system is really up to date
+
+```
+apt-get update
+apt-get upgrade
+```
+
+
+Optional restart services after installation
+
+```
+reboot
+```
+
+
+If you restarted, make yourself root
+
+```
+su -l
+```
+
+
+Install Apache:
+```
+apt-get install apache2 apache2-doc apache2-utils
+```
+
+
+Open webbrowser on PC and check [localhost](localhost)
+Should show you a page like "It works"
+
+(Source [http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#](http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#))
+
+#### Install PHP, MySQL, phpMyAdmin
+
+##### PHP, MySQL
+
+
+```
+su -l
+apt-get install libapache2-mod-php8.2 php8.2 php-pear php8.2-xcache php8.2-curl php8.2-mcrypt php8.2-xdebug
+apt-get install php8.2-mysql
+apt-get install mysql-server mysql-client
+```
+
+enter and note the mysql passwort
+
+Optional since its already enabled during phpmyadmin setup
+
+```
+phpenmod mcrypt
+```
+
+##### phpMyAdmin
+
+Install php myadmin
+
+```
+apt-get install phpmyadmin
+```
+
+
+Configuring phpmyadmin
+- Select apache2 (hint: use the tab key to select)
+- Configure database for phpmyadmin with dbconfig-common?: Choose Yes
+
+(Source [http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#](http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#))
+
+##### Enable rewrite
+
+The default installation of Apache2 comes with mod_rewrite installed. To check whether this is the case, verify the existence of `/etc/apache2/mods-available/rewrite.load`
+
+
+```
+nano /etc/apache2/mods-available/rewrite.load
+```
+
+ (You should find the content: `LoadModule rewrite_module` `/usr/lib/apache2/modules/mod_rewrite.so`)
+To enable and load mod_rewrite, do the rest of steps.
+Create a symbolic link in `/etc/apache2/mods-enabled`
+
+
+```
+cd /var/www
+root@debian /var/www $ a2enmod rewrite
+```
+
+
+Then open up the following file, and replace every occurrence of "`AllowOverride None`" with "`AllowOverride all`".
+
+
+```
+nano /etc/apache2/apache2.conf
+```
+
+or
+
+```
+gedit /etc/apache2/sites-enabled/000-default
+```
+
+
+Finally, restart Apache2.
+
+
+```
+service apache2 restart
+```
+
+##### Test installation
+
+
+```
+cd /var/www
+```
+
+
+create a php file to test the php installation
+```
+nano phpinfo.php
+```
+
+
+Insert into the file:
+
+```
+<?php
+ phpinfo();
+?>
+```
+
+(save CTRL+0, ENTER, CTRL+X)
+
+open webbrowser on PC and try `http://localhost/phpinfo.php` (page shows infos on php)
+
+connect phpMyAdmin with MySQL database
+```
+nano /etc/apache2/apache2.conf
+```
+
+- CTRL+V... to the end of the file
+- Insert at the end of the file: (save CTRL+0, ENTER, CTRL+X)
+```
+Include /etc/phpmyadmin/apache.conf
+```
+
+
+restart apache
+
+```
+/etc/init.d/apache2 restart
+apt-get update
+apt-get upgrade
+reboot
+```
+
+**phpMyAdmin**
+
+open webbrowser on PC and try `http://localhost/phpmyadmin`
+
+(Source [http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#](http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#))
+
+##### Create an empty database... that is later used by hubzilla
+
+
+open webbrowser on PC and try `http://localhost/phpmyadmin`
+
+Create an empty database, for example named "red".
+Create a database user, for example "red".
+Grant all rights for the user "red" to the database "red".
+
+Note the access details (hostname, username, password, database name).
+
+##### Fork the project on github
+
+Please follow the instruction in the offiical [ documentation](http://git-scm.com/book/en/v2/GitHub-Contributing-to-a-Project) of git.
+It is a good idea to read the whole manual! Git is different to other version control systems in many ways.
+
+Now you should
+
+- create an account at github.com
+- fork `https://framagit.org/hubzilla/core`
+- fork `https://framagit.org/hubzilla/addons`
+
+
+
+If you not want to use GIT from the command line - there is a usefull Eclipse plugin named ""Eclipse Mylyn to GitHub connector".
+
+#### Install Hubzilla and its Addons
+
+##### Git at your computer / vm
+
+You should have created an account on github and forked the projects befor you procceed.
+
+Delete the directory www
+
+```
+rm -R www/
+```
+
+
+Install git (and optionally git-gui a client gui)
+
+```
+apt-get install git git-gui
+```
+
+##### Download hubzilla and addons
+
+Download the main project hubzilla and hubzilla-addons
+
+```
+git clone https://github.com/yourname/hubzilla www
+cd www/
+git clone https://github.com/yourname/hubzilla-addons addon
+```
+
+
+Make this extra folder
+
+```
+mkdir -p "store/[data]/smarty3"
+```
+
+
+Create .htconfig.php and make it writable by the webserver
+
+```
+touch .htconfig.php
+chmod ou+w .htconfig.php
+```
+
+
+Make user www-data (webserver) is the owner all the project files
+
+```
+cd ..
+chown -R www-data:www-data www/
+```
+
+Add yourself ("surfer" in this example) to the group www-data. Why? Later you want to modify files in eclipse or in another editor.
+
+Then make all files writable by the group www-date you are now a member of.
+
+```
+cd www/
+usermod -G www-data surfer
+chmod -R g+w www/
+```
+
+Restart the computer (or vm)
+
+If you are still not able to modify the project files you can check the members of the group www-data with
+
+```
+cat /etc/group
+```
+
+##### Register yourself as admin
+
+Open `http://localhost` and init the matrix
+
+Befor you register a first user switch off the registration mails.
+Open `/var/www/.htconfig.php`
+and make sure "0" is set in this line
+
+```
+App::$config['system']['verify_email'] = 0;
+```
+
+You should be able to change the file as "yourself" (instead of using root or www-data).
+
+##### Cron and the poller
+
+Important!
+Run the poller to pick up the recent "public" postings of your friends
+Set up a cron job or scheduled task to run the poller once every 5-10
+minutes to pick up the recent "public" postings of your friends
+
+
+```
+crontab -e
+```
+
+
+Add
+
+```
+*/10 * * * * cd /var/www/; /usr/bin/php include/poller.php
+```
+
+
+If you don't know the path to PHP type
+
+```
+which php
+```
+
+
+
+#### Debug the server via eclipse
+
+##### Check the configuration of xdebug
+
+You shoud already have installed xdebug in the steps befor
+
+```
+apt-get install php5-xdebug
+```
+
+
+Configuring Xdebug
+
+Open your terminal and type as root (su -l)
+
+```
+gedit /etc/php5/mods-available/xdebug.ini
+```
+
+
+if the file is empty try this location
+
+```
+gedit /etc/php5/conf.d/xdebug.ini
+```
+
+
+That command should open the text editor gedit with the Xdebug configuration file
+At the end of the file content append the following text
+
+```
+xdebug.remote_enable=on
+xdebug.remote_handler=dbgp
+xdebug.remote_host=localhost
+xdebug.remote_port=9000
+```
+
+Save changes and close the editor.
+In you terminal type to restart the web server.
+
+```
+service apache2 restart
+```
+
+
+
+##### Install Eclipse and start debugging
+
+Install eclipse.
+Start eclipse with default worspace (or as you like)
+
+Install the PHP plugin
+Menu > Help > Install new software...
+Install "PHP Developnent Tools ..."
+
+Optionally - Install the GitHub connector plugin
+Menu > Help > Install new software...
+Install "Eclipse Mylyn to GitHub connector"
+
+Configure the PHP plugin
+Menu > Window > Preferences...
+> General > Webbrowser > Change to "Use external web browser"
+> PHP > Debug > Debug Settings > PHP Debugger > Change to "XDebug"
+
+Create a new PHP project
+Menu > File > New Project > Choose PHP > "PHP Project"
+> Choose Create project at existing location" and "/var/www"
+
+Start debugging
+Open index.php and "Debug as..."
+Choose as Launch URL: "`http://localhost/`"
+
+Expected:
+
+- The web browser starts
+- The debugger will stop at the first php line
+
+
+
+#### Contribute your changes via github
+
+##### Preparations
+
+There is a related page in this docs: [zrl=[baseurl]/help/git_for_non_developers]Git for Non-Developers[/zrl].
+As stated befor it is recommended to read the official documentation [GitHub-Contributing-to-a-Project](http://git-scm.com/book/en/v2/GitHub-Contributing-to-a-Project) of git.
+
+Eclipse has a usefull plugin for GIT: "Eclipse Mylyn to GitHub connector".
+
+Make sure you have set your data
+
+```
+git config --global user.name "Your Name"
+git config --global user.email "your@mail.com"
+```
+
+##### Your first contribution
+
+Create a descriptive topic branch
+
+```
+git checkout -b dev_beginning
+```
+
+
+Make sure your local repository is up-to-date with the main project.
+Add the original repository as a remote named “upstream” if not done yet
+
+```
+git remote add upstream https://framagit.org/hubzilla/core/
+```
+
+
+Fetch the newest work from that remote
+
+```
+git fetch upstream
+git merge upstream/master
+```
+
+
+Hint: You can list the branches
+
+```
+git branch -v
+```
+
+
+Make your changes. In this example it is a new doc file.
+
+Check your modifications
+
+```
+git status
+```
+
+
+Add (stage) the new file
+
+```
+git add doc/dev_beginner.bb
+```
+
+
+Commit the changes to your local branch. This will open an editor to provide a message.
+
+```
+git commit -a
+```
+
+
+Push back up to the same topic branch online
+
+```
+git push
+```
+
+
+Now you can go to your (online) account at github and create the pull request.
+
+##### Following contributions
+
+In case the main devolpers want you to change something.
+Fetch the newest work from the remote upstream/master to be sure you have the latest changes.
+
+```
+git fetch upstream
+git merge upstream/master
+```
+
+Make your changes, test them, commit (to local repository), push (to online repository)
+
+```
+git status
+git commit -a -m "added modification of branch"
+git push
+```
+
diff --git a/doc/en/developer/developer_guide.bb b/doc/en/developer/developer_guide.bb
deleted file mode 100644
index d04cec121..000000000
--- a/doc/en/developer/developer_guide.bb
+++ /dev/null
@@ -1,176 +0,0 @@
-[h3]Who is a $Projectname developer? Should I read this?[/h3]
-
-Anyone who contributes to making $Projectname better is a developer. There are many different and important ways you can contribute to this amazing technology, [i]even if you do not know how to write code[/i]. The software itself is only a part of the $Projectname project. You can contribute by
-[list]
-[*] translating text to your language so that people around the world have the opportunity to use $Projectname
-[*] promoting $Projectname and spreading awareness of the platform through blog posts, articles, and word-of-mouth
-[*] creating artwork and graphics for project assets such as icons and marketing material
-[*] supporting project infrastructure like the project website and demo servers
-[/list]
-[i]Software[/i] developers are of course welcomed; there are so many great ideas to implement and not enough people to make them all a reality! The $Projectname code base is an advanced and mature system, but the platform is still very flexible and responsive to new ideas.
-
-We're pretty relaxed when it comes to developers. We don't have a lot of rules. Some of us are over-worked and if you want to help we're happy to let you help. That said, attention to a few guidelines will make the process smoother and make it easier to work together. All developers are expected to abide by our [zrl=[baseurl]/help/developer/covenant]code of conduct[/zrl]. We have developers from across the globe with different abilities and different cultural backgrounds and different levels of patience. Our primary rule is to respect others. Sometimes this is hard and sometimes we have very different opinions of how things should work, but if everybody makes an effort, we'll get along just fine.
-
-This document will help you get started learning and contributing to $Projectname.
-
-[h3]Versions and Releases[/h3]
-
-$Projectname currently uses a standard version numbering sequence of $x.$y(.$z), for instance '1.12' or '1.12.1'. The first digit is the major version number. Major versions are released "roughly" once per year; often in December.
-
-The second digit is the minor release number. If this number is odd, it is a development version. If the number is even, it is a released version. Minor versions are released (moved from dev to master) typically once per month when development is 'stable', but this is likely to increase. Going forward minor releases will be made somewhere between one and three months; corresponding to a stable code point and when there is general community consensus that the current code base is stable enough to consider a release.
-
-The final digit is an interface or patch designator.
-
-The release process involves changing the version number (by definition the minor version number will be odd, and the minor number will be incremented). Once a year for a major release the major version will be incremented, and the minor number reset to 0.
-
-The release candidate is moved to a new branch; and testing will commence/continue for a period of 1-2 weeks afterward or until any significant issues have been resolved. This branch is usually labelled with RC (release candidate); for instance 1.8RC represents the pending release of version 1.8. At this time, the minor version number on the dev branch is incremented to the next odd number. (For instance 1.9). New development can then take place in the dev branch.
-
-Bug fixes should always be applied to 'dev' and from there merged forward (typically with git cherry-pick) to the RC branch and if necessary applied to the master or official release branch.
-
-At the time a release candidate is produced, the language strings file is frozen until a release is made. Translation work may continue, but all translations should be submitted to 'dev' and merged forward to RC.
-
-Once RC testing is completed, RC is merged to 'master' and the RC version designator removed; resulting in one final checkin to change the version number. The CHANGELOG file should also be updated at or just prior to this time. If there are merge conflicts during this final merge, the merge will be abandoned; and 'git merge -s ours' applied. This results in a replacement of master with the contents of the RC branch. Conflicts often arise with string updates which were made to master after the last release and cannot easily be resolved without hand editing. Since this is a release of tested code, hand editing is discouraged, and the replacement merge strategy should be used instead. It is assumed that RC now contains the most recent well-tested code.
-
-Once the release is live and merged to master, the RC branch may be removed.
-
-Fixes may be made to master after release. Where possible these should be made to dev and 'git cherry-pick' used to merge forward; which preserves the commit info and prevents merge conflicts in the next cycle. Only rarely does a patch only apply to the master branch. If necessary this can be made. If the change is severe, the interface version number should be incremented. This is at the discretion of the community. In any event, a 'git pull' of the master branch should always result in the latest release with any post-release patches applied.
-
-The interface number (the $z in $x.$y.$z) should be incremented in dev whenever a change is made which changes the interfaces or API in incompatible ways so that any external packages (especially addons and API clients) relying on a the current behaviour can discover and change their own interfaces accordingly at the point that it changed.
-
-[h3]Git repository branches[/h3]
-
-There are two official branches of the $Projectname git repo.
-[list]
-[*] The stable version is maintained on the [b]master[/b] branch. The latest commit in this branch is considered to be suitable for production hubs.
-[*] Experimental development occurs on the [b]dev[/b] branch, which is merged into [b]master[/b] when it is deemed tested and stable enough.
-[/list]
-
-[h3]Developer tools and workflows[/h3]
-
-[h4]Hub Snapshots[/h4]
-
-The [url=[baseurl]/help/admin/hub_snapshots]hub snapshots[/url] page provides instructions and scripts for taking complete snapshots of a hub to support switching between consistent and completely known states. This is useful to prevent situations where the content or database schema might be incompatible with the code.
-
-[h3]Translations[/h3]
-
-Our translations are managed through Transifex. If you wish to help out translating $Projectname to another language, sign up on transifex.com, visit [url=https://www.transifex.com/Friendica/hubzilla/]Transifex[/url] and request to join one of the existing language teams or create a new one. Notify one of the core developers when you have a translation update which requires merging, or ask about merging it yourself if you're comfortable with git and PHP. We have a string file called 'messages.po' which is gettext compliant and a handful of email templates, and from there we automatically generate the application's language files.
-
-[h4]Translation Process[/h4]
-
-The strings used in the UI of $Projectname is translated at [url=https://www.transifex.com/Friendica/hubzilla/]Transifex[/url] and then
-included in the git repository at github. If you want to help with translation
-for any language, be it correcting terms or translating $Projectname to a
-currently not supported language, please register an account at transifex.com
-and contact the translation team there.
-
-Translating $Projectname is simple. Just use the online tool at transifex. If you
-don't want to deal with git & co. that is fine, we check the status of the translations
-regularly and import them into the source tree at github so that others can use them.
-
-We do not include every translation from transifex in the source tree to avoid
-a scattered and disturbed overall experience. As an uneducated guess we have a
-lower limit of 50% translated strings before we include the language. This
-limit is judging only by the amount of translated strings under the assumption
-that the most prominent strings for the UI will be translated first by a translation
-team. If you feel your translation useable before this limit,
-please contact us and we will probably include your teams work in the source tree.
-
-If you want to get your work into the source tree yourself, feel free to do so
-and contact us with and question that arises. The process is simple and
-$Projectname ships with all the tools necessary.
-
-The location of the translated files in the source tree is
- /view/LNG-CODE/
-where LNG-CODE is the language code used, e.g. de for German or fr for French.
-For the email templates (the *.tpl files) just place them into the directory
-and you are done. The translated strings come as a "hmessages.po" file from
-Transifex which needs to be translated into the PHP file $Projectname uses. To do
-so, place the file in the directory mentioned above and use the "po2php"
-utility from the util directory of your $Projectname installation.
-
-Assuming you want to convert the German localization which is placed in
-view/de/hmessages.po you would do the following.
-
-1. Navigate at the command prompt to the base directory of your
- $Projectname installation
-
-2. Execute the po2php script, which will place the translation
- in the hstrings.php file that is used by $Projectname.
-
- $> php util/po2php.php view/de/hmessages.po
-
- The output of the script will be placed at view/de/hstrings.php where
- froemdoca os expecting it, so you can test your translation mmediately.
-
-3. Visit your $Projectname page to check if it still works in the language you
- just translated. If not try to find the error, most likely PHP will give
- you a hint in the log/warnings.about the error.
-
- For debugging you can also try to "run" the file with PHP. This should
- not give any output if the file is ok but might give a hint for
- searching the bug in the file.
-
- $> php view/de/hstrings.php
-
-4. commit the two files with a meaningful commit message to your git
- repository, push it to your fork of the $Projectname repository at github and
- issue a pull request for that commit.
-
-[h4]Utilities[/h4]
-
-Additional to the po2php script there are some more utilities for translation
-in the "util" directory of the $Projectname source tree. If you only want to
-translate $Projectname into another language you wont need any of these tools most
-likely but it gives you an idea how the translation process of $Projectname
-works.
-
-For further information see the utils/README file.
-
-[h4]Known Problems[/h4]
-
-* $Projectname uses the language setting of the visitors browser to determain the
- language for the UI. Most of the time this works, but there are some known
- quirks.
-* the early translations are based on the friendica translations, if you
- some rough translations please let us know or fix them at Transifex.
-
-[h3]Licensing[/h3]
-
-All code contributed to the project falls under the MIT license, unless otherwise specified. We will accept third-party code which falls under MIT, BSD and LGPL, but copyleft licensing (GPL, and AGPL) is only permitted in addons. It must be possible to completely remove the GPL (copyleft) code from the main project without breaking anything.
-
-[h3]Coding Style[/h3]
-
-In the interests of consistency we adopt the following code styling. We may accept patches using other styles, but where possible please try to provide a consistent code style. We aren't going to argue or debate the merits of this style, and it is irrelevant what project 'xyz' uses. This is not project 'xyz'. This is a baseline to try and keep the code readable now and in the future.
-[list]
-[*]All comments should be in English.
-[*]We use doxygen to generate documentation. This hasn't been consistently applied, but learning it and using it are highly encouraged.
-[*]Indentation is accomplished primarily with tabs using a tab-width of 4.
-[*]String concatenation and operators should be separated by whitespace. e.g. "$foo = $bar . 'abc';" instead of "$foo=$bar.'abc';"
-[*]Generally speaking, we use single quotes for string variables and double quotes for SQL statements. "Here documents" should be avoided. Sometimes using double quoted strings with variable replacement is the most efficient means of creating the string. In most cases, you should be using single quotes.
-[*]Use whitespace liberally to enhance readability. When creating arrays with many elements, we will often set one key/value pair per line, indented from the parent line appropriately. Lining up the assignment operators takes a bit more work, but also increases readability.
-[*]Generally speaking, opening braces go on the same line as the thing which opens the brace. They are the last character on the line. Closing braces are on a line by themselves.
-[*]Some functions take arguments in argc/argv style like main() in C or function args in bash or Perl. Urls are broken up within a module. e.g, given "http://example.com/module/arg1/arg2", then $this->argc will be 3 (integer) and $this->argv will contain: [0] => 'module', [1] => 'arg1', [2] => 'arg2'. There will always be one argument. If provided a naked domain URL, $this->argv[0] is set to "home".
-[/list]
-
-[h3]File system layout[/h3]
-[table border=0]
-[th]Directory[/th][th]Description[/th][/tr]
-[tr][td]addon[/td][td]optional addons/plugins[/td][/tr]
-[tr][td]boot.php[/td][td]Every process uses this to bootstrap the application structure[/td][/tr]
-[tr][td]doc[/td][td]Help Files[/td][/tr]
-[tr][td]images[/td][td]core required images[/td][/tr]
-[tr][td]include[/td][td]The "model" in MVC - (back-end functions), also contains PHP "executables" for background processing[/td][/tr]
-[tr][td]index.php[/td][td]The front-end controller for web access[/td][/tr]
-[tr][td]install[/td][td]Installation and upgrade files and DB schema[/td][/tr]
-[tr][td]library[/td][td]Third party modules (must be license compatible)[/td][/tr]
-[tr][td]mod[/td][td]Controller modules based on URL pathname (e.g. [url=http://sitename/foo]http://sitename/foo[/url] loads mod/foo.php)[/td][/tr]
-[tr][td]mod/site/[/td][td]site-specific mod overrides, excluded from git[/td][/tr]
-[tr][td]util[/td][td]translation tools, main English string database and other miscellaneous utilities[/td][/tr]
-[tr][td]version.inc[/td][td]contains current version (auto-updated via cron for the master repository and distributed via git)[/td][/tr]
-[tr][td]view[/td][td]theming and language files[/td][/tr]
-[tr][td]view/(css,js,img,php,tpl)[/td][td]default theme files[/td][/tr]
-[tr][td]view/(en,it,es ...)[/td][td]language strings and resources[/td][/tr]
-[tr][td]view/theme/[/td][td]individual named themes containing (css,js,img,php,tpl) over-rides[/td][/tr]
-[/table]
-
-[b][url=[baseurl]/help/developer/unorganized]More information needing re-organization and updating...[/url][/b]
diff --git a/doc/en/developer/developers_guide.md b/doc/en/developer/developers_guide.md
new file mode 100644
index 000000000..0f6ee6e7c
--- /dev/null
+++ b/doc/en/developer/developers_guide.md
@@ -0,0 +1,27 @@
+## Developers Guide
+
+Information for Hubzilla developers
+
+#include doc/en/developer/who_is_a_hubzilla_developer.md;
+#include doc/en/developer/dev_beginner.md;
+#include doc/en/developer/versions.md;
+#include doc/en/developer/git_repository.md;
+#include doc/en/developer/git_for_non_developers.md;
+#include doc/en/developer/tools_workflows.md;
+#include doc/en/developer/doco.md;
+#include doc/en/developer/translations.md;
+#include doc/en/developer/licensing.md;
+#include doc/en/developer/coding_style.md;
+#include doc/en/developer/file_system_layour.md;
+#include doc/en/developer/dev-functions-overview.md;
+#include doc/en/developer/Plugins.md;
+#include doc/en/developer/testing.md;
+#include doc/en/developer/federate.md;
+#include doc/en/developer/code_of_conduct.md;
+#include doc/en/developer/nomad_protocol.md;
+#include doc/en/developer/technical_introductions.md;
+#include doc/en/developer/magic_auth.md;
+#include doc/en/developer/nomad_structures.md;
+#include doc/en/developer/API.md;
+#include doc/en/developer/hooks.md;
+#include doc/en/developer/unorganized.md;
diff --git a/doc/en/developer/doco.md b/doc/en/developer/doco.md
new file mode 100644
index 000000000..f6bab3911
--- /dev/null
+++ b/doc/en/developer/doco.md
@@ -0,0 +1,34 @@
+### Creating Documentation
+
+To contribute documentation, simply put some words in a cunning order, and make their existence known to a developer. You can do this literally anywhere as long as a developer can see it. Once made aware, somebody will check it in for you. You should try to avoid proprietary formats, or locations that require authentication with methods other than Nomad in order to make it easy for a developer to access, but even this is not a strict requirement.
+
+If you wish to contribute directly, that's fine too. To contribute directly, documentation should be in one of the following formats:
+
+- Markdown
+- BBCode
+- HTML
+- Plain Text
+- Other formats are also allowed, but support for the format must be added to mod/help.php first.
+
+
+If editing a plain text file, please keep column width to 80. This is because plain text is used in instances where we may not have a working installation - the installation documentation, for example - and it should be easy to read these from a CLI text editor.
+
+The advantage of Markdown is that it is human readable.
+
+The advantage of BBCode is that it is identity aware.
+
+Therefore, if using BBCode, try to make the most of it:
+
+- Use ZRL links where appropriate to ensure a link to another site retains authentication and keeps identity based documentation working
+- Use baseurl or observer.baseurl tags where appropriate instead of example.com for authenticated viewers.
+- Support non-authenticated users with observer=0 tags. We presently do not do this due to historical oversights. This needs adding everywhere
+
+**Translations**
+
+To translate documentation, or provided documentation in languages other than English:
+
+- Create a directory in doc/ with your two letter country code if it doesn't already exist (eg, doc/de/ for German or doc/fr/ for French)
+- Create a document with the same filename as the English version, but with content in your own language. This allows us to fallback to the English if the translation for a particular page is not provided
+
+To create documentation that has no equivalent file in English, you can create a new file with a name of your choosing - but you'll also need to provide a localised version of the index page (main.bb in English) to make it accessible from the menu.
+
diff --git a/doc/en/developer/enforcement.md b/doc/en/developer/enforcement.md
new file mode 100644
index 000000000..753b786f2
--- /dev/null
+++ b/doc/en/developer/enforcement.md
@@ -0,0 +1,4 @@
+#### Enforcement
+
+Instances of offensive, harassing or otherwise unacceptable behaviour can be reported to the project team at [project@hubzilla.org](mailto:project@hubzilla.org). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is committed to confidentiality towards the person reporting an incident. Further details of specific enforcement policies may be published separately.
+Project supervisors who fail to follow or enforce the Code of Conduct in good faith may face temporary or permanent consequences as determined by other members of the project management team. \ No newline at end of file
diff --git a/doc/en/developer/federate.md b/doc/en/developer/federate.md
new file mode 100644
index 000000000..0bb370c8a
--- /dev/null
+++ b/doc/en/developer/federate.md
@@ -0,0 +1,69 @@
+### Creating protocol federation services
+
+There are three main components to writing federation plugins. These are:
+
+1. Channel discovery and making connections
+2. Sending posts/messages
+3. Receiving posts/messages
+
+In addition, federation drivers must handle
+
+4. differences in privacy policies (and content formats)
+
+#### Making connections
+
+The core application provides channel discovery in the following sequence:
+
+1. Nomad channel discovery
+2. Webfinger (channel@hub) lookup
+ 2.1 RFC7033 webfinger
+ 2.2 XRD based webfinger (old style)
+3. URL discovery (currently only used to discover RSS feeds)
+4. If all of these fail, the network is "unknown" and we are unable to communicate with or connect with the channel. An 'xchan' record *may* still be created **if** there is enough information known to identify a unique channel.
+
+Any of the lookup services may be bound to a plugin and extended. When a channel is discovered, we create an 'xchan' record which is a platform neutral representation of the channel identity. If we need to send information to the channel, a 'hubloc' (hub location) record is also generally required. A 'follow' plugin hook is provided to bypass webfinger and this discovery sequence completely.
+
+The final step in gluing this together is to create an 'abook' record, which attaches an xchan in a relationship to a local channel with a given set of permissions.
+
+For networks which do not support nomadic identity, your plugin must also set "abook_instance" which is a comma-separated list of local URLs that the remote channel is connected with. For instance if your member was connected to my channel clone at https://example.com, the abook_instance for that connection would read 'https://example.com'. If you also connected to my clone at https://abc.example.com, the string would be changed to 'https://example.com,https://abc.example.com'. This allows local channels to differentiate which instance a given remote channel is connected with and avoid delivery failures to those channels from other clone instances.
+
+A federation plugin for a webfinger based system needs only to examine the webfinger or XRD results and identify a protocol stack which can be used to connect or communicate. Then create an xchan record with the given 'xchan_network' type and a hubloc record with the same 'hubloc_network' with the information given. Currently the plugin will need to create the entire record, but in the future this will be extended so that the plugin only need identify a network name and the record will be populated with all other known values.
+
+An xchan record is always required in order to connect. To connect, create an abook record with the desired permissions.
+
+Additional information that your plugin requires for communication can be stored in the xconfig table and/or abconfig table if there is no convenient or appropriate table column in the xchan or hubloc tables.
+
+When a connection is made, we generally call the notifier (include/notifier.php) to send a message to the remote channel. This is bound to the hook 'permissions_create'. Your plugin will need to handle this in order to send a "follow" or "make friends" message to the other network.
+
+Notes: The first stage Nomad lookup will be replaced with a webfinger lookup. This work is in progress. A separate lookup was required initially as webfinger does not allow non-SSL connections. We will provide non-SSL Nomad lookups (usually test and development sites) via the "old" XRD based webfinger to avoid this limitation.
+
+The core application will attempt to create xchan records for projects identified as members of the "open web"; currently Hubzilla, Friendica, Diaspora, GNU-Social and Pump.io. This is so that comments can be passed amongst project sites and the network correctly identified. A federation plugin is required to fully federate with other networks, but comments may be passed to sites without such a plugin installed so that there are no unexplained holes in conversations.
+
+The core application must also provide signing ability for Diaspora comments since they require a special signing format and must be signed by the comment author regardless of whether that channel federates with Diaspora. The owner of the conversation may federate with Diaspora so the comments must be signed. This is unfortunate but necessary.
+
+#### Sending Messages
+
+Whenever any message is sent (with the sole exception of directory communications), we invoke the notifier (include/notifier.php), and pass it the type of message and generally an identifier to lookup the information being sent from the database (items or conversational things, private mail, permissions updates, etc.).
+
+The notifier has several hooks which may be used by plugins in different ways, depending on how their delivery loop works. For different message types and complex delivery situations you may need to tie into multiple hooks. The 'permissions_create' hook was mentioned in the first section. There is also a 'permissions_update' message if permissions have changed and the other end of the link needs to be advised. Few services will provide or handle this (as their permissions are static), but it is also used for instance to send profile and profile photo update messages and you may wish to handle this.
+
+The next plugin hook is 'notifier_process'. It is passed an array providing the complete state of the notifier and is called once per notifier invocation. It contains the complete list of recipients (with xchan_network set for each).
+
+There is also 'notifier_hub' which like 'notifier_process' is passed the complete state of the notifier, but the difference is that it is called for each individual hub or distinct URL delivery and may be matched on the hubloc_network type. Hub delivery is much more efficient than recipient delivery but may not be suitable for all protocol stacks.
+
+
+Your plugin will be required to understand the message state and recipients and translate the sent item to the desired format. You will also be required to check privacy and block communication to anybody but the intended recipients - *if* it is a private communication. The plugin will often at this point stick the message into the queue and return the queue id to the notifier.
+
+
+Queue handlers exist already for simple posted data. If you create a queue entry with 'post' type we'll open an HTTP POST request and post the data provided and acknowledge success or failure. You can create other forms of communication by providing a different outq_driver type and handling the processing of queue requests yourself. Delivery reporting is available if you wish to acknowledge different error conditions, or anything beyond success/failure. Advanced delivery reporting will also require a custom queue type. The basic 'post' type only deals with success (communication successful with the remote site) and failure.
+
+​
+
+#### Receiving Messages
+
+
+Receiving messages from the remote network will probably require a 'receive' endpoint or module dedicated to your network communication protocol. This is a URL route that your plugin may need to register with the'module_loaded' hook. You module will then take responsibility for importing any data which arrives at that endpoint and translating it to the format required for this project and storing the resulting data. The basic structure we use is an extensible activitystream item but with slightly different field names and several optional fields. It can be easily mapped to an activitystream. Additional data can be stored in the "iconfig" table. item_store() and item_store_update() are generally used to store the data and send appropriate notifications. Similiar facilities are available for private mail and profile information.
+
+
+
+
diff --git a/doc/en/developer/file_system_layout.md b/doc/en/developer/file_system_layout.md
new file mode 100644
index 000000000..63f42f0fc
--- /dev/null
+++ b/doc/en/developer/file_system_layout.md
@@ -0,0 +1,20 @@
+### File system layout
+
+| Directory | Description |
+| ------------------------- | ------------------------------------------------------------ |
+| addon | Optional addons/plugins |
+| boot.php | Each process uses this to boot the application structure |
+| doc | Help files |
+| images | required images |
+| include | The ‘model’ in MVC - (back-end functions), also contains PHP ‘executables’ for background processing |
+| index.php | The front-end controller for web access |
+| install | Installation and upgrade files and DB schema |
+| library | Third-party modules (must be licence-compatible) |
+| mod | Control modules based on URL path names (e.g. http://sitename/foo loads mod/foo.php) |
+| mod/site/ | Site-specific mod overrides that are excluded from Git |
+| util | Translation tools, main database for English strings and other various utilities |
+| version.inc | contains the current version (which is automatically updated via cron for the main repository and distributed via git) |
+| view | Theme and language files |
+| view/(css,js,img,php,tpl) | Standard theme files |
+| view/(en,it,es ...) | Language strings and resources |
+| view/theme/ | Single named themes that contain (css,js,img,php,tpl) overrides | \ No newline at end of file
diff --git a/doc/en/developer/git_for_non_developers.md b/doc/en/developer/git_for_non_developers.md
new file mode 100644
index 000000000..c944ec383
--- /dev/null
+++ b/doc/en/developer/git_for_non_developers.md
@@ -0,0 +1,100 @@
+### Git For Non-Developers
+
+So you're handling a translation, or you're contributing to a theme, and every time you make a pull request you have to talk to one of the developers before your changes can be merged in?
+
+Chances are, you just haven't found a quick how-to explaining how to keep things in sync on your end. It's really very easy.
+
+After you've created a fork of the repo (just click "fork" at github), you need to clone your own copy.
+
+For the sake of examples, we'll assume you're working on a theme called redexample (which does not exist).
+
+
+```
+git clone https://github.com/username/red.git
+```
+
+
+Once you've done that, cd into the directory, and add an upstream.
+
+
+```
+cd red
+git remote add upstream https://framagit.org/hubzilla/core/
+```
+
+
+From now on, you can pull upstream changes with the command
+
+```
+git fetch upstream
+```
+
+
+Before your changes can be merged automatically, you will often need to merge upstream changes.
+
+
+```
+git merge upstream/master
+```
+
+
+You should always merge upstream before pushing any changes, and *must* merge upstream with any pull requests to make them automatically mergeable.
+
+99% of the time, this will all go well. The only time it won't is if somebody else has been editing the same files as you - and often, only if they have been editing the same lines of the same files. If that happens, that would be a good time to request help until you get the hang of handling your own merge conflicts.
+
+Then you just need to add your changes
+```
+git add view/theme/redexample/
+```
+
+
+This will add all the files in view/theme/redexample and any subdirectories. If your particular files are mixed throughout the code, you should add one at a time. Try not to do git add -a, as this will add everything, including temporary files (we mostly, but not always catch those with a .gitignore) and any local changes you have, but did not intend to commit.
+
+Once you have added all the files you have changed, you need to commit them.
+```
+git commit
+```
+
+
+This will open up an editor where you can describe the changes you have made. Save this file, and exit the editor.
+
+Finally, push the changes to your own git
+
+```
+git push
+```
+
+
+And that's it, your repo is up to date!
+
+All you need to do now is actually create the pull request. There are two ways to do this.
+
+The easy way, if you're using Github is to simply click the green button at the top of your own copy of the repository, enter a description of the changes, and click 'create pull request'. The
+main repository, themes, and addons all have their main branch at Github, so this method can be used most of the time.
+
+Most people can stop here.
+
+Some projects in the extended RedMatrix ecosphere have no Github presence, to pull request these is a bit different - you'll have to create your pull request manually. Fortunately, this isn't
+much harder.
+
+
+```
+git request-pull -p <start> <url>
+```
+
+
+Start is the name of a commit to start at. This must exist upstream. Normally, you just want master.
+
+URL is the URL of *your* repo.
+
+One can also specify `<end>`. This defaults to HEAD.
+
+Example:
+
+```
+git request-pull master https://example.com/project
+```
+
+
+And simply send the output to the project maintainer.
+
diff --git a/doc/en/developer/git_repository.md b/doc/en/developer/git_repository.md
new file mode 100644
index 000000000..b2650b5b2
--- /dev/null
+++ b/doc/en/developer/git_repository.md
@@ -0,0 +1,6 @@
+### Git repository branches
+
+There are two official branches of the Hubzilla Git repository.
+
+- The stable version is maintained in the **master branch**. The latest commit in this branch is considered suitable for production hubs.
+- Experimental development takes place in the **dev branch**, which is transferred to the **master branch** as soon as it is deemed tested and stable enough. \ No newline at end of file
diff --git a/doc/en/developer/hooks.md b/doc/en/developer/hooks.md
new file mode 100644
index 000000000..9a1c0605b
--- /dev/null
+++ b/doc/en/developer/hooks.md
@@ -0,0 +1,458 @@
+### Hooks
+
+Hooks allow plugins/addons to ‘hook’ into the code in many places and change the behaviour or otherwise perform independent actions when an activity takes place or certain data structures are accessed. There are many hooks that allow you to hook into the software at almost any point and do something other than what is intended by default. Two variables are passed to these hooks. The first is the app structure, which contains details about the overall state of the page request as we build the resulting page. The second is unique to the specific hook being called and provides specific details about what is happening in the software at the time the hook is called.
+
+[Created index of all hooks and the files they call](https://hub.hubzilla.hu/help/hooks)
+
+[module_mod_aftercontent](https://hub.hubzilla.hu/help/hook/module_mod_aftercontent) General hook for each module, executed after mod_content(). Replace ‘module’ with the name of the module, e.g. ‘photos_mod_aftercontent’.
+
+[module_mod_content](https://hub.hubzilla.hu/help/hook/module_mod_content) General hook for any module, executed before mod_content(). Replace ‘module’ with the module name, e.g. ‘photos_mod_content’.
+
+[module_mod_init](https://hub.hubzilla.hu/help/hook/module_mod_init) General hook for any module, executed before mod_init(). Replace ‘module’ with the module name, e.g. ‘photos_mod_init’.
+
+[module_mod_post](https://hub.hubzilla.hu/help/hook/module_mod_post) General hook for any module, executed before mod_post(). Replace ‘module’ with the name of the module, e.g. ‘photos_mod_post’.
+
+[about_hook](https://hub.hubzilla.hu/help/hook/about_hook) Called from the siteinfo page
+
+[accept_follow](https://hub.hubzilla.hu/help/hook/accept_follow) Called when a connection is accepted (friend request)
+
+[account_downgrade](https://hub.hubzilla.hu/help/hook/account_downgrade) Called when an account has expired, indicating a possible downgrade to the ‘basic’ class of service
+
+[account_settings](https://hub.hubzilla.hu/help/hook/account_settings) Called when the account settings form is created
+
+[account_settings_post](https://hub.hubzilla.hu/help/hook/account_settings_post) Called when posting from the account settings form
+
+[activity_filter](https://hub.hubzilla.hu/help/hook/activity_filter) Called when creating the list of filters for the network page
+
+[activity_mapper](https://hub.hubzilla.hu/help/hook/activity_filter) Called when determining the activity type for the transfer.
+
+[activity_decode_mapper](https://hub.hubzilla.hu/help/hook/activity_filter) Called when the activity type for the transfer is determined.
+
+[activity_obj_mapper](https://hub.hubzilla.hu/help/hook/activity_filter) Called when the object type for the transfer is determined.
+
+[activity_obj_decode_mapper](https://hub.hubzilla.hu/help/hook/activity_filter) Is called when the object type for the transfer is determined.
+
+[activity_order](https://hub.hubzilla.hu/help/hook/activity_order) Called when generating the list of order options for the network page
+
+[addon_app_installed_filter](https://hub.hubzilla.hu/help/hook/addon_app_installed_filter) Called when determining whether an addon_app is[installed](https://hub.hubzilla.hu/help/hook/addon_app_installed_filter)
+
+[activity_received](https://hub.hubzilla.hu/help/hook/activity_received) Called when an activity (post, comment, like, etc.) has been received from a Nomad source
+
+[admin_aside](https://hub.hubzilla.hu/help/hook/admin_aside) Is called when the sidebar widget of the administration page is created
+
+[affinity_labels](https://hub.hubzilla.hu/help/hook/affinity_labels) Is used to generate alternative labels for the affinity slider.
+
+[api_perm_is_allowed](https://hub.hubzilla.hu/help/hook/api_perm_is_allowed) Called when perm_is_allowed() is executed by an API call.
+
+[app_destroy](https://hub.hubzilla.hu/help/hook/app_destroy) Called when an app is deleted.
+
+[app_installed_filter](https://hub.hubzilla.hu/help/hook/app_installed_filter) Called when it is determined whether an app is[installed](https://hub.hubzilla.hu/help/hook/app_installed_filter)
+
+[app_menu](https://hub.hubzilla.hu/help/hook/app_menu) Called when the app_menu dropdown is created (may be deprecated)
+
+[attach_delete](https://hub.hubzilla.hu/help/hook/attach_delete) Called when attachments are deleted from the attach table
+
+[atom_author](https://hub.hubzilla.hu/help/hook/atom_author) Called when an author or owner element is created for an Atom ActivityStream feed
+
+[atom_entry](https://hub.hubzilla.hu/help/hook/atom_entry) Called when generating each entry of an Atom ActivityStream feed
+
+[atom_feed](https://hub.hubzilla.hu/help/hook/atom_feed) Called when an Atom ActivityStreams feed is generated
+
+[atom_feed_end](https://hub.hubzilla.hu/help/hook/atom_feed_end) Called when the generation of an Atom ActivityStreams feed is complete
+
+[attach_upload_file](https://hub.hubzilla.hu/help/hook/attach_upload_file) Called when a file is uploaded
+
+[authenticate](https://hub.hubzilla.hu/help/hook/authenticate) Can provide alternative authentication mechanisms
+
+[author_is_pmable](https://hub.hubzilla.hu/help/hook/author_is_pmable) Called from the thread's action menu to determine if we can send a private email to the author of the post
+
+[bb2diaspora](https://hub.hubzilla.hu/help/hook/bb2diaspora) Called when converting bbcode to Markdown
+
+[bbcode](https://hub.hubzilla.hu/help/hook/bbcode) Called at the end of the conversion from bbcode to HTML
+
+[bbcode_filter](https://hub.hubzilla.hu/help/hook/bbcode_filter) Called at the beginning of the conversion from bbcode to HTML
+
+[bb_translate_video](https://hub.hubzilla.hu/help/hook/bb_translate_video) Called when extracting embedded services from bbcode video elements (rarely used)
+
+[build_pagehead](https://hub.hubzilla.hu/help/hook/build_pagehead) Called when the HTML page header is created
+
+[can_comment_on_post](https://hub.hubzilla.hu/help/hook/can_comment_on_post) Called when deciding whether or not to display a comment field for a post
+
+[change_channel](https://hub.hubzilla.hu/help/hook/change_channel) Called when you log in to a channel (either during login or afterwards via the channel manager)
+
+[channel_remove](https://hub.hubzilla.hu/help/hook/channel_remove) Called when a channel is removed
+
+[channel_links](https://hub.hubzilla.hu/help/hook/channel_links) Is called when the link is generated: HTTP header for a channel
+
+[channel_settings](https://hub.hubzilla.hu/help/hook/channel_settings) Called when the channel settings page is displayed
+
+[chat_message](https://hub.hubzilla.hu/help/hook/chat_message) Called to create a chat message.
+
+[chat_post](https://hub.hubzilla.hu/help/hook/chat_post) Called when a chat message has been posted.
+
+[check_account_email](https://hub.hubzilla.hu/help/hook/check_account_email) Checks the email specified during account registration
+
+[check_account_invite](https://hub.hubzilla.hu/help/hook/check_account_invite) Validation of an invitation code when using website invitations
+[check_account_password](https://hub.hubzilla.hu/help/hook/check_account_password) Used to check account passwords (minimum length, inclusion of character sets, etc.)
+[check_channelallowed](https://hub.hubzilla.hu/help/hook/check_channelallowed) Used to override or bypass black and white channel block lists.
+
+[check_siteallowed](https://hub.hubzilla.hu/help/hook/check_siteallowed) Is used to override or bypass the black/white block lists for websites.
+
+[collect_public_recipients](https://hub.hubzilla.hu/help/hook/collect_public_recipients) Used to create a list of recipients to send a public message to.
+
+[comment_buttons](https://hub.hubzilla.hu/help/hook/comment_buttons) Called when the comment edit buttons are displayed.
+
+[comments_are_now_closed](https://hub.hubzilla.hu/help/hook/comments_are_now_closed) Called when deciding whether or not to display a comment box for a post
+
+[connect_premium](https://hub.hubzilla.hu/help/hook/connect_premium) Called when a connection to a premium channel is established
+
+[connection_remove](https://hub.hubzilla.hu/help/hook/connection_remove) Called when a connection is deleted/removed
+
+[connector_settings](https://hub.hubzilla.hu/help/hook/connector_settings) Called when the page with the features/addon settings is called up
+
+[construct_page](https://hub.hubzilla.hu/help/hook/construct_page) General hook for providing content for specific page areas. Is called when the Comanche page is created.
+
+[contact_block_end](https://hub.hubzilla.hu/help/hook/contact_block_end) Called when the ‘Connections’ widget is created in the sidebar
+
+[contact_edit](https://hub.hubzilla.hu/help/hook/contact_edit) Called when editing a connection via connedit
+
+[contact_edit_post](https://hub.hubzilla.hu/help/hook/contact_edit_post) Is called when a post is sent to connedit
+[contact_selection_options](https://hub.hubzilla.hu/help/hook/contact_select_options) Deprecated/unused
+
+[content_security_policy](https://hub.hubzilla.hu/help/hook/content_security_policy) Called before the Content-Security-Policy header is output
+
+[conversation_start](https://hub.hubzilla.hu/help/hook/conversation_start) Called at the beginning of the rendering of a conversation (message or message collection or stream)
+
+[cover_photo_content_end](https://hub.hubzilla.hu/help/hook/cover_photo_content_end) Called after a cover photo has been uploaded
+
+[create_identity](https://hub.hubzilla.hu/help/hook/create_identity) Called when a channel is created
+
+[cron](https://hub.hubzilla.hu/help/hook/cron) Called when a scheduled task (poller) is executed
+
+[cron_daily](https://hub.hubzilla.hu/help/hook/cron_daily) Called when daily scheduled tasks are executed
+
+[cron_weekly](https://hub.hubzilla.hu/help/hook/cron_weekly) Called when weekly scheduled tasks are executed
+
+[crypto_methods](https://hub.hubzilla.hu/help/hook/crypto_methods) Called when a list of crypto algorithms is created in the locally preferred order
+
+[daemon_addon](https://hub.hubzilla.hu/help/hook/daemon_addon) Called when the extensible background daemon is called
+
+[daemon_master_release](https://hub.hubzilla.hu/help/hook/daemon_master_release) Called at the beginning of the processing of \Zotlabs\Daemon\Master::Release()
+
+[directory_item](https://hub.hubzilla.hu/help/hook/directory_item) Called when creating a directory listing for display
+
+[discover_channel_webfinger](https://hub.hubzilla.hu/help/hook/discover_channel_webfinger) Called when a webfinger lookup is performed
+
+[display_item](https://hub.hubzilla.hu/help/hook/display_item) Called for each element that is displayed in a conversation thread
+
+[display_settings](https://hub.hubzilla.hu/help/hook/display_settings) Called by the settings module when the ‘display settings’ section is displayed
+
+[display_settings_post](https://hub.hubzilla.hu/help/hook/display_settings_post) Called when a post from the ‘display settings’ form of the settings module is displayed
+
+[donate_contributors](https://hub.hubzilla.hu/help/hook/donate_contributors) Called by the ‘donate’ addon when a list of donation recipients is created
+
+[donate_plugin](https://hub.hubzilla.hu/help/hook/donate_plugin) is called by the ‘donate’ addon
+
+[donate_sponsors](https://hub.hubzilla.hu/help/hook/donate_sponsors) Called by the ‘donate’ addon
+
+[dreport_is_storable](https://hub.hubzilla.hu/help/hook/dreport_is_storable) is called before saving a Dreport record to determine if it should be saved
+
+[dreport_process](https://hub.hubzilla.hu/help/hook/dreport_process) is called for each valid delivery report
+
+[dropdown_extras](https://hub.hubzilla.hu/help/hook/dropdown_extras) Add additional items to the dropdown menu when item/threads are displayed.
+
+[drop_item](https://hub.hubzilla.hu/help/hook/drop_item) is called when an ‘item’ is removed
+
+[encode_object](https://hub.hubzilla.hu/help/hook/encode_object) is called when an object is encoded for transmission.
+
+[enotify](https://hub.hubzilla.hu/help/hook/enotify) is called before each notification
+
+[enotify_mail](https://hub.hubzilla.hu/help/hook/enotify_mail) is called when a notification email is sent
+
+[enotify_store](https://hub.hubzilla.hu/help/hook/enotify_store) is called when a notification data record is saved
+
+[enotify_store_end](https://hub.hubzilla.hu/help/hook/enotify_store_end) is called after a notification record has been saved
+
+[event_created](https://hub.hubzilla.hu/help/hook/event_created) is called when an event record is created
+
+[event_store_event](https://hub.hubzilla.hu/help/hook/event_store_event) is called when an event record is created or updated
+
+[event_updated](https://hub.hubzilla.hu/help/hook/event_updated) is called when an event record is changed
+
+[externals_url_select](https://hub.hubzilla.hu/help/hook/externals_url_select) is called when a list of random websites from which to retrieve public posts is created
+
+[feature_enabled](https://hub.hubzilla.hu/help/hook/feature_enabled) is called when ‘feature_enabled()’ is used
+
+[feature_settings](https://hub.hubzilla.hu/help/hook/feature_settings) is called from the settings page when visiting ‘addon/feature settings’
+
+[feature_settings_post](https://hub.hubzilla.hu/help/hook/feature_settings_post) is called from the settings page when posting from ‘addon/feature settings’
+
+[fetch_and_store](https://hub.hubzilla.hu/help/hook/fetch_and_store) is called to enable filtering of ‘decrypted’ elements before saving.
+
+[file_thumbnail](https://hub.hubzilla.hu/help/hook/file_thumbnail) is called when creating thumbnails for the cloud page in ‘show tiles’ mode
+
+[follow](https://hub.hubzilla.hu/help/hook/follow) is called when a follow operation takes place
+
+[follow_from_feed](https://hub.hubzilla.hu/help/hook/follow_from_feed) is called when a follow operation takes place in an RSS feed
+
+[follow_allow](https://hub.hubzilla.hu/help/hook/follow_allow) is called before the results of a follow operation are saved
+
+[gender_selector](https://hub.hubzilla.hu/help/hook/gender_selector) is called when the ‘Gender’ drop-down list is created (extended profile)
+
+[gender_selector_min](https://hub.hubzilla.hu/help/hook/gender_selector_min) is called when the ‘Gender’ drop-down list is created (normal profile)
+
+[generate_map](https://hub.hubzilla.hu/help/hook/generate_map) is called to generate the HTML code for displaying a location on the map by coordinates
+
+[generate_named_map](https://hub.hubzilla.hu/help/hook/generate_named_map) is called to generate the HTML file for displaying a map location by text
+
+[get_all_api_perms](https://hub.hubzilla.hu/help/hook/get_all_api_perms) Called when the permissions for API uses are retrieved
+
+[get_all_perms](https://hub.hubzilla.hu/help/hook/get_all_perms) is called when get_all_perms() is used
+[get_best_language](https://hub.hubzilla.hu/help/hook/get_best_language) is called when the preferred language for the page is selected
+[get_default_export_sections](https://hub.hubzilla.hu/help/hook/get_default_export_sections) Called to get the default list of function data groups to be exported in identity_basic_export()
+
+[get_features](https://hub.hubzilla.hu/help/hook/get_features) Called when get_features() is called
+
+[get_photo](https://hub.hubzilla.hu/help/hook/get_photo) Called when photo content (except profile photos) is retrieved in mod_photo
+
+[get_profile_photo](https://hub.hubzilla.hu/help/hook/get_profile_photo) Called when the content of the local profile photo is retrieved in mod_photo
+
+[get_role_perms](https://hub.hubzilla.hu/help/hook/get_role_perms) Called when get_role_perms() is called to get permissions for named permission roles
+
+[global_permissions](https://hub.hubzilla.hu/help/hook/global_permissions) Called when the global permissions list is created
+
+[home_content](https://hub.hubzilla.hu/help/hook/home_content) Called by mod_home to replace the content of the home page
+
+[home_init](https://hub.hubzilla.hu/help/hook/home_init) Called by the home_init() function of the home page
+
+[hostxrd](https://hub.hubzilla.hu/help/hook/hostxrd) Called when generating .well-known/hosts-meta for ‘old webfinger’ (used by the Diaspora protocol)
+
+[html2bb_video](https://hub.hubzilla.hu/help/hook/html2bb_video) Called when html2bbcode translation is used to handle embedded media
+
+[html2bbcode](https://hub.hubzilla.hu/help/hook/html2bbcode) Called when using the html2bbcode translation
+
+[identity_basic_export](https://hub.hubzilla.hu/help/hook/identity_basic_export) Called when the basic information of a channel is exported for backup or transfer.
+
+[import_author_xchan](https://hub.hubzilla.hu/help/hook/import_author_xchan) Called when searching for an author of a post with xchan_hash to make sure they have an xchan entry on our website
+
+[import_channel](https://hub.hubzilla.hu/help/hook/import_channel) Called when a channel is imported from a file or API source
+
+[import_directory_profile](https://hub.hubzilla.hu/help/hook/import_directory_profile) Called when processing the delivery of a profile structure from an external source (usually for storage in directories)
+
+[import_xchan](https://hub.hubzilla.hu/help/hook/import_xchan) Called when processing the result of zot_finger() to save the result
+
+[item_photo_menu](https://hub.hubzilla.hu/help/hook/item_photo_menu) Called when the list of actions associated with a displayed conversation item is generated
+
+[item_store](https://hub.hubzilla.hu/help/hook/item_store) Called when item_store() stores a record of type item
+
+[item_stored](https://hub.hubzilla.hu/help/hook/item_stored) Called after item_store() has stored a record of type item in the database.
+
+[item_custom](https://hub.hubzilla.hu/help/hook/item_custom) Is called before item_store() saves a data record of the type item (so that addons can process ITEM_TYPE_CUSTOM elements).
+
+[item_store_update](https://hub.hubzilla.hu/help/hook/item_store_update) Called when item_store_update() is called to update a stored item.
+
+[item_stored_update](https://hub.hubzilla.hu/help/hook/item_stored_update) Called after item_store_update() has updated a[stored](https://hub.hubzilla.hu/help/hook/item_stored_update) item.
+
+[item_translate](https://hub.hubzilla.hu/help/hook/item_translate) Called by item_store and item_store_update after the language of the item has been automatically recognised.
+
+[jot_networks](https://hub.hubzilla.hu/help/hook/jot_networks) Called to generate the list of additional post plugins to be activated from the ACL form
+
+[jot_tool](https://hub.hubzilla.hu/help/hook/jot_tool) Obsolete and possibly superfluous. Enables action buttons to be added to the post editor.
+
+[jot_tpl_filter](https://hub.hubzilla.hu/help/hook/jot_tpl_filter) Called to filter template variables before replacing them in jot.tpl.
+
+[jot_header_tpl_filter](https://hub.hubzilla.hu/help/hook/jot_header_tpl_filter) Called to filter template variables before replacing them in jot_header.tpl.
+
+[legal_webbie](https://hub.hubzilla.hu/help/hook/legal_webbie) Called to validate a channel address
+
+[legal_webbie_text](https://hub.hubzilla.hu/help/hook/legal_webbie_text) Provides an explanation of the text/character restrictions for legal_webbie()
+
+[load_pdl](https://hub.hubzilla.hu/help/hook/load_pdl) Called when we load a PDL file or description
+
+[local_dir_update](https://hub.hubzilla.hu/help/hook/local_dir_update) Called when a directory update is processed by a channel on the directory server
+
+[location_move](https://hub.hubzilla.hu/help/hook/location_move) Called when a UNO channel has been notified of a new location (indicating a move and not a clone)
+
+[logged](https://hub.hubzilla.hu/help/hook/logged_in) Called when authentication was successful in any way
+
+[Logger](https://hub.hubzilla.hu/help/hook/logger) Called when an entry is made in the application's log file
+
+[logging_out](https://hub.hubzilla.hu/help/hook/logging_out) Called when logging[out](https://hub.hubzilla.hu/help/hook/logging_out)
+
+[login_hook](https://hub.hubzilla.hu/help/hook/login_hook) Called when the login form is generated
+
+[magic_auth](https://hub.hubzilla.hu/help/hook/magic_auth) Called when processing a magic-auth sequence
+
+[markdown_to_bb](https://hub.hubzilla.hu/help/hook/markdown_to_bb) Called when processing the Markdown conversion
+
+[match_webfinger_location](https://hub.hubzilla.hu/help/hook/match_webfinger_location) Called when processing webfinger requests
+
+[magic_auth_openid_success](https://hub.hubzilla.hu/help/hook/magic_auth_openid_success) Called when a magic-auth was successful due to openid credentials
+
+[magic_auth_success](https://hub.hubzilla.hu/help/hook/magic_auth_success) Called when a magic-auth was successful
+
+[main_slider](https://hub.hubzilla.hu/help/hook/main_slider) Called when the affinity tool is generated
+
+[marital_selector](https://hub.hubzilla.hu/help/hook/marital_selector) Called when the selection list for the drop-down menu of the ‘Marital status’ profile is created (extended profile)
+
+[marital_selector_min](https://hub.hubzilla.hu/help/hook/marital_selector_min) Called when the selection list for the ‘Marital status’ drop-down profile is created (normal profile)
+
+[module_loaded](https://hub.hubzilla.hu/help/hook/module_loaded) Is called when a module has been successfully localised for a URL request on the server.
+
+[mood_verbs](https://hub.hubzilla.hu/help/hook/mood_verbs) Called when the list of moods is created
+[nav](https://hub.hubzilla.hu/help/hook/nav) Called when the navigation bar is created
+
+[network_content_init](https://hub.hubzilla.hu/help/hook/network_content_init) Called when loading the content for the network page
+
+[network_ping](https://hub.hubzilla.hu/help/hook/network_ping) Called when a ping request is made
+[network_to_name](https://hub.hubzilla.hu/help/hook/network_to_name) Deprecated
+
+[notifier_end](https://hub.hubzilla.hu/help/hook/notifier_end) Called when a delivery loop is completed
+
+[notifier_hub](https://hub.hubzilla.hu/help/hook/notifier_hub) Called when a hub has been delivered
+
+[notifier_normal](https://hub.hubzilla.hu/help/hook/notifier_normal) Called when the notifier is called for a ‘normal’ delivery
+
+[notifier_process](https://hub.hubzilla.hu/help/hook/notifier_process) Called when the notifier processes a message/event
+
+[obj_verbs](https://hub.hubzilla.hu/help/hook/obj_verbs) Called when the list of verbs available for the ‘things’ profile is created.
+
+[oembed_action](https://hub.hubzilla.hu/help/hook/oembed_action) Called when deciding whether to filter, block or approve an oembed url
+
+[oembed_probe](https://hub.hubzilla.hu/help/hook/oembed_probe) Called when a search for Oembed content is performed.
+
+[other_encapsulate](https://hub.hubzilla.hu/help/hook/other_encapsulate) Called when encrypting content for which the algorithm is unknown (see also crypto_methods)
+
+[other_unencapsulate](https://hub.hubzilla.hu/help/hook/other_unencapsulate) Called when decrypting content for which the algorithm is unknown (see also crypto_methods)
+
+[page_content_top](https://hub.hubzilla.hu/help/hook/page_content_top) Called when we generate a web page (before calling the module content function)
+
+[page_end](https://hub.hubzilla.hu/help/hook/page_end) Called after we have generated the page content
+
+[page_header](https://hub.hubzilla.hu/help/hook/page_header) Called when the navigation bar is generated
+
+[page_meta](https://hub.hubzilla.hu/help/hook/page_header) Called when generating the metadata in the page header.
+
+[parse_atom](https://hub.hubzilla.hu/help/hook/parse_atom) Called when an Atom/RSS feed element is parsed.
+
+[parse_link](https://hub.hubzilla.hu/help/hook/parse_link) Is called when a URL is queried to generate a post from it
+
+[pdl_selector](https://hub.hubzilla.hu/help/hook/pdl_selector) Called when creating a layout selection in a form
+
+[perm_is_allowed](https://hub.hubzilla.hu/help/hook/perm_is_allowed) Called during perm_is_allowed() to determine whether authorisation is permitted for this channel and observer
+
+[permissions_create](https://hub.hubzilla.hu/help/hook/permissions_create) Called when a book entry (connection) is created
+
+[permissions_update](https://hub.hubzilla.hu/help/hook/permissions_update) Is called when an authorisation update is transferred
+
+[permit_hook](https://hub.hubzilla.hu/help/hook/permit_hook) Called before a registered hook is actually executed to determine whether it should be allowed or blocked
+
+[personal_xrd](https://hub.hubzilla.hu/help/hook/personal_xrd) Called when generating the personal XRD for ‘old webfinger’ (Diaspora)
+
+[photo_post_end](https://hub.hubzilla.hu/help/hook/photo_post_end) Called after a photo has been uploaded
+
+[photo_upload_begin](https://hub.hubzilla.hu/help/hook/photo_upload_begin) Called when an attempt is made to upload a photo
+
+[photo_upload_end](https://hub.hubzilla.hu/help/hook/photo_upload_end) Called when a photo upload has been processed
+
+[photo_upload_file](https://hub.hubzilla.hu/help/hook/photo_upload_file) Called to generate alternative file names for an upload
+
+[photo_upload_form](https://hub.hubzilla.hu/help/hook/photo_upload_form) Called when a photo upload form is generated
+
+[photo_view_filter](https://hub.hubzilla.hu/help/hook/photo_view_filter) Called before the data is passed to the photo_view template
+
+[poke_verbs](https://hub.hubzilla.hu/help/hook/poke_verbs) Called when creating the list of actions for the ‘poke’ module
+
+[post_local](https://hub.hubzilla.hu/help/hook/post_local) Called when an article has been set on this computer via mod/item.php (also via API)
+
+[post_local_end](https://hub.hubzilla.hu/help/hook/post_local_end) Is called when a local post process has been completed
+
+[post_local_start](https://hub.hubzilla.hu/help/hook/post_local_start) Is called when a local post process begins
+[post_mail](https://hub.hubzilla.hu/help/hook/post_mail) Called when a mail message has been created
+
+[post_mail_end](https://hub.hubzilla.hu/help/hook/post_mail_end) Called when a mail message has been delivered
+
+[post_remote](https://hub.hubzilla.hu/help/hook/post_remote) Called when an activity arrives from another location
+
+[post_remote_end](https://hub.hubzilla.hu/help/hook/post_remote_end) Called after a remote post has been processed
+
+[post_remote_update](https://hub.hubzilla.hu/help/hook/post_remote_update) Called when processing a remote post that includes an edit or update
+
+[post_remote_update_end](https://hub.hubzilla.hu/help/hook/post_remote_update_end) Called after processing a remote post that included an edit or update
+
+[prepare_body](https://hub.hubzilla.hu/help/hook/prepare_body) Is called when the HTML code for a displayed conversation object is generated.
+
+[prepare_body_final](https://hub.hubzilla.hu/help/hook/prepare_body_final) Called after the HTML for a displayed conversation item has been generated
+
+[prepare_body_init](https://hub.hubzilla.hu/help/hook/prepare_body_init) Called before the HTML for a displayed conversation element is generated
+
+[privacygroup_extras](https://hub.hubzilla.hu/help/hook/privacygroup_extras) Called before generating the HTML for the privacy group editing options
+
+[privacygroup_extras_delete](https://hub.hubzilla.hu/help/hook/privacygroup_extras_delete) Called after the privacy group has been deleted.
+
+[privacygroup_extras_post](https://hub.hubzilla.hu/help/hook/privacygroup_extras_post) Is called when the form for editing the privacy group is sent.
+
+[proc_run](https://hub.hubzilla.hu/help/hook/proc_run) Called when PHP sub-processes are called
+[process_channel_sync_delivery](https://hub.hubzilla.hu/help/hook/process_channel_sync_delivery) Called when a ‘sync package’ with structure and table updates is received from a channel clone.
+
+[profile_advanced](https://hub.hubzilla.hu/help/hook/profile_advanced) Called when an advanced profile page is generated
+
+[profile_edit](https://hub.hubzilla.hu/help/hook/profile_edit) Called when editing a profile
+
+[profile_photo_content_end](https://hub.hubzilla.hu/help/hook/profile_photo_content_end) Called when a profile photo is changed
+
+[profile_post](https://hub.hubzilla.hu/help/hook/profile_post) Called when an edited profile is posted
+
+[profile_sidebar](https://hub.hubzilla.hu/help/hook/profile_sidebar) Is called up when the ‘channel sidebar’ or the mini profile is created
+
+[profile_sidebar_enter](https://hub.hubzilla.hu/help/hook/profile_sidebar_enter) Called before the ‘channel sidebar’ or mini-profile is created
+
+[queue_deliver](https://hub.hubzilla.hu/help/hook/queue_deliver) Called when a message is delivered in the queue
+
+[register_account](https://hub.hubzilla.hu/help/hook/register_account) Called when an account has been created
+
+[render_location](https://hub.hubzilla.hu/help/hook/render_location) Called to create an inactive inline map
+
+[replace_macros](https://hub.hubzilla.hu/help/hook/replace_macros) Called before the template processor is called
+
+[reverse_magic_auth](https://hub.hubzilla.hu/help/hook/reverse_magic_auth) Called before calling reverse magic auth to send you to your own website so you can authenticate on that website
+
+[settings_account](https://hub.hubzilla.hu/help/hook/settings_account) Called when the account settings form is created
+
+[settings_form](https://hub.hubzilla.hu/help/hook/settings_form) Called when creating the channel settings form
+
+[settings_post](https://hub.hubzilla.hu/help/hook/settings_post) Called when posting from the channel settings form
+
+[sexpref_selector](https://hub.hubzilla.hu/help/hook/sexpref_selector) Called when creating a drop-down menu for sexual preferences (advanced profile)
+
+[sexpref_selector_min](https://hub.hubzilla.hu/help/hook/sexpref_selector_min) Called when a drop-down list of sexual preferences is created (normal profile)
+
+[smilie](https://hub.hubzilla.hu/help/hook/smilie) Called when translating emoticons
+
+[status_editor](https://hub.hubzilla.hu/help/hook/status_editor) Called when the status_editor is created.
+
+[stream_item](https://hub.hubzilla.hu/help/hook/stream_item) Called for each item that is rendered for display via conversation()
+
+[system_app_installed_filter](https://hub.hubzilla.hu/help/hook/system_app_installed_filter) Called when it is determined whether a system app is[installed](https://hub.hubzilla.hu/help/hook/system_app_installed_filter).
+
+[tagged](https://hub.hubzilla.hu/help/hook/tagged) Called when a delivery is processed that results in you being tagged
+
+[thumbnail](https://hub.hubzilla.hu/help/hook/thumbnail) Called when creating thumbnails for the cloud storage tile view
+
+[update_unseen](https://hub.hubzilla.hu/help/hook/update_unseen) Called before automatically tagging programmes loaded in the browser that have been viewed
+
+[validate_channelname](https://hub.hubzilla.hu/help/hook/validate_channelname) Used to validate the names used by a channel
+
+[webfinger](https://hub.hubzilla.hu/help/hook/webfinger) Called when visiting the webfinger service (RFC7033)
+
+[well_known](https://hub.hubzilla.hu/help/hook/well_known) Called when accessing the special ‘.well-known’ site addresses
+
+[wiki_preprocess](https://hub.hubzilla.hu/help/hook/wiki_preprocess) Called before markdown/bcode processors are executed for wiki pages
+
+[zot_best_algorithm](https://hub.hubzilla.hu/help/hook/zot_best_algorithm) Called when negotiating encryption algorithms with remote sites
+
+[zid](https://hub.hubzilla.hu/help/hook/zid) Called when the observer's zid is added to a URL
+
+[zid_init](https://hub.hubzilla.hu/help/hook/zid_init) Called when authenticating a visitor who has used zid
+
+[zot_finger](https://hub.hubzilla.hu/help/hook/zot_finger) Called when a Nomad info packet has been requested (this is our web finger detection mechanism) \ No newline at end of file
diff --git a/doc/en/developer/licensing.md b/doc/en/developer/licensing.md
new file mode 100644
index 000000000..96bf15e85
--- /dev/null
+++ b/doc/en/developer/licensing.md
@@ -0,0 +1,3 @@
+### Licensing
+
+All code contributed to the project is subject to the MIT licence unless otherwise stated. We accept third party code that falls under MIT, BSD and LGPL, but copyleft licences (GPL and AGPL) are only allowed in addons. It must be possible to completely remove the GPL (copyleft) code from the main project without destroying anything. \ No newline at end of file
diff --git a/doc/en/developer/magic_auth.md b/doc/en/developer/magic_auth.md
new file mode 100644
index 000000000..fc9650823
--- /dev/null
+++ b/doc/en/developer/magic_auth.md
@@ -0,0 +1,49 @@
+### Magic Auth
+
+The so-called ‘magic auth’ takes place via a special exchange. On the remote computer, a redirection is made to the Nomad endpoint with special GET parameters.
+
+Endpoint: [https://example.com/post/name](https://example.com/post/name?f=&zid=pepecyb@hub.hubzilla.hu)
+
+where ‘name’ is the left-hand side of the channel webbie, for example ‘mike’ if the webbie is [‘mike@zothub.com’](mailto:mike@zothub.com).
+
+In addition, four parameters are passed:
+
+- auth => the webbie of the person requesting access
+- dest => the desired destination URL (urlencoded)
+- sec => a random string, which is also stored locally for use during the verification phase.
+- version => the Zot revision
+
+When this packet is received, a Nomad message is sent to the auth identity:
+
+```
+ {
+ ‘type’: ‘auth_check’,
+ ‘sender’:{
+ ‘guid’: ‘kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA’,
+ ‘guid_sig’: "PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK- R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81- Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91- ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD- yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q’,
+ ‘url’: ‘http:\/\/podunk.edu’,
+ ‘url_sig’: "T8Bp7j5DHHhQDCFcAHXfuhUfGk2P3inPbImwaXXF1xJd3TGgluoXyyKDx6WDm07x0hqbupoAoZB1qBP3_WfvWiJVAK4N1FD77EOYttUEHZ7L43xy5PCpojJQmkppGbPJc2jnTIc_F1vvGvw5fv8gBWZvPqTdb6LWF6FLrzwesZpi7j2rsioZ3wyUkqb5TDZaNNeWQrIEYXrEnWkRI_qTSOzx0dRTsGO6SpU1fPWuOOYMZG8Nh18nay0kLpxReuHCiCdxjXRVvk5k9rkcMbDBJcBovhiSioPKv_yJxcZVBATw3z3TTE95kGi4wxCEenxwhSpvouwa5b0hT7NS4Ay70QaxoKiLb3ZjhZaUUn4igCyZM0h6fllR5I6J_sAQxiMYD0v5ouIlb0u8YVMni93j3zlqMWdDUZ4WgTI7NNbo8ug9NQDHd92TPmSE1TytPTgya3tsFMzwyq0LZ0b- g- zSXWIES__jKQ7vAtIs9EwlPxqJXEDDniZ2AJ6biXRYgE2Kd6W_nmI7w31igwQTms3ecXe5ENI3ckEPUAq__llNnND7mxp5ZrdXzd5HHU9slXwDShYcW3yDeQLEwAVomTGSFpBrCX8W77n9hF3JClkWaeS4QcZ3xUtsSS81yLrp__ifFfQqx9_Be89WVyIOoF4oydr08EkZ8zwlAsbZLG7eLXY"
+ },
+ ‘recipients’:{
+ {
+ ‘guid’: ‘ZHSqb3yGar3TYV_o9S-JkD-6o_V4DhUcxtv0VeyX8Kj_ENHPI_M3SyAUucU835-mIayGMmTpqJz3ujPkcavVhA’,
+ ‘guid_sig’: "JsAAXigNghTkkbq8beGMJjj9LBKZn28hZ-pHSsoQuwYWvBJ2lSnfc4r9l--WgO6sucH-SR6RiBo78eWn1cZrh_cRMu3x3LLx4y-tjixg- oOOgtZakkBg4vmOhkKPkci0mFtzvUrpY4YHySqsWTuPwRx_vOlIYIGEY5bRXpnkNCoC8J4EJnRucDrgSARJvA8QQeQQL0H4mWEdGL7wlsZp_2VTC6nEMQ48Piu6Czu5ThvLggGPDbr7PEMUD2cZ0jE4SeaC040REYASq8IdXIEDMm6btSlGPuskNh3cD0AGzH2dMciFtWSjmMVuxBU59U1I6gHwcxYEV6BubWt_jQSfmA3EBiPhKLyu02cBMMiOvYIdJ3xmpGoMY1Cn__vhHnx_vEofFOIErb6nRzbD- pY49C28AOdBA5ffzLW3ss99d0A-6GxZmjsyYhgJu4tFUAa7JUl84tMbq28Tib0HW6qYo6QWw8K1HffxcTpHtwSL5Ifx0PAoGMJsGDZDD1y_r9a4vH5pjqmGrjL3RXJJUy- m4eLV5r7xMWXsxjqu3D8r04_dcw4hwwexpMT1Nwf8CTB0TKb8ElgeOpDFjYVgrqMYWP0XdhatcFtAJI7gsS-JtzsIwON9Kij66-VAkqy_z1IXI0ziyqV1yapSVYoUV1vMScRZ_nMqwiB5rEDx-XLfzko"
+ }
+ }
+ ‘callback’:‘\/post’,
+ ‘version’:1,
+ ‘secret’:‘1eaa6613699be6ebb2adcefa5379c61a3678aa0df89025470fac871431b70467’,
+ ‘secret_sig’: "eKV968b1sDkOVdSMi0tLRtOhQw4otA8yFKaVg6cA4I46_zlAQGbFptS-ODiZlSAqR7RiiZQv4E2uXCKar52dHo0vvNKbpOp_ezWYcwKRu1shvAlYyytsflH5acnDWL-FKOOgz5zqLLZ6cKXFMoR1VJGG_Od- DKjSwajyV9uVzTry58Hz_w0W2pjxwQ-Xv11rab5R2O4kKSW77YzPG2R5E6Q7HN38FrLtyWD_ai3K9wlsFOpwdYC064dk66X7BZtcIbKtM6zKwMywcfJzvS5_0U5yc5GGbIY_lY6SViSfx9shOKyxkEKHfS29Ynk9ATYYGnwO-jnlMqkJC7t149H- sI9hYWMkLuCzaeLP56k2B2B2TmtnYvE_vHNQjzVhTwuHCIRVr-p6nplQn_P3SkOpYqPi3k_tnnOLa2d3Wtga8ClEY90oLWFJC3j2UkBf_VEOBNcg-t5XO3T-j9O4Sbk96k1Qoalc-QlznbGx4bOVsGkRBBMiH4YUqiiWB_OkFHtdqv7dqGeC- u-B4u9IxzYst46vvmyA3O-Q4APSZ1RY8ITUH0jLTbh6EAV7Oki8pIbOg0t56p-8RlanOZqmFvR-grVSc7Ak1ZcD8NACmvidUpa1B7WEvRcOeffx9lype0bt5XenDnMyx6szevwxZIiM8qGM2lsSk4fu8HI9cW0mLywzZT0"
+ }
+```
+
+auth_check messages MUST be encrypted. This message is sent to the originating site, which checks whether the ‘secret’ matches the ‘sec’ it originally transmitted. It also checks secret_sig, which is signed with the private key of the destination channel and encoded with base64url. If everything is OK, a json packet is returned:
+
+```
+ {
+ ‘success’:1,
+ ‘confirm’: "q0Ysovd1uQRsur2xG9Tg6bC23ynzw0191SkVd7CJcYoaePy6e_v0vnmPg2xBUtIaHpx_aSuhgAkd3aVjPeaVBmts6aakT6a_yAEy7l2rBydntu2tvrHhoVqRNOmw0Q1tI6hwobk1BgK9Pm0lwOeAo8Q98BqIJxf47yO9pATa0wktOg6a7LMogC2zkkhwOV5oEqjJfeHeo27TiHr1e2WaphfCusjmk27V_FAYTzw05HvW4SPCx55EeeTJYIwDfQwjLfP4aKV- I8HQCINt-2yxJvzH7Izy9AW- 7rYU0Il_gW5hrhIS5MTM12GBXLVs2Ij1CCLXIs4cO0x6e8KEIKwIjf7iAu60JPmnb_fx4QgBlF2HLw9vXMwZokor8yktESoGl1nvf5VV5GHWSIKAur3KPS2Tb0ekNh-tIk9u-xob4d9eIf6tge_d3aq1LcAtrDBDLk8AD0bho5zrVuTmZ9k- lBVPr_DRHSV_dlpu088j3ThaBsuV1olHK3vLFRhYCDIO0CqqK5IuhqtRNnRaqhlNN6fQUHpXk2SwHiJ2W36RCYMTnno6ezFk_tN-RA2ly- FomNZoC5FPA9gFwoJR7ZmVFDmUeK3bW-zYTA5vu15lpBPnt7Up_5rZKkr0WQVbhWJmylqOuwuNWbn3SrMQ8rYFZ23Tv300cOfKVgRBaePWQb4"
+ }
+```
+
+‘confirm’ in this case is the base64url-encoded RSA signature of the concatenation of “secret” with the base64url-encoded whirlpool hash of the source guid and guid_sig; signed with the private key of the source channel. This prevents a manin-the-middle from inserting a fraudulent success packet. Upon receipt and successful verification of this packet, the destination page is redirected to the original destination URL and displays a successful remote login. \ No newline at end of file
diff --git a/doc/en/developer/nomad_protocol.md b/doc/en/developer/nomad_protocol.md
new file mode 100644
index 000000000..c734ab9eb
--- /dev/null
+++ b/doc/en/developer/nomad_protocol.md
@@ -0,0 +1,44 @@
+### The Nomad protocol
+
+#### What is Nomad?
+
+Nomad is the revolutionary protocol that powers Hubzilla, enabling **communication**, **identity management** and **access control** across a fully **decentralised** network of independent websites, often referred to as ‘the grid’. The resulting platform is a robust system that supports privacy and security while enabling the kind of rich web services typically found only in centralised, proprietary solutions.
+
+Consider this typical scenario:
+
+Jaquelina wants to share photos from her blog at **jaquelina.org** with Roberto, but with no one else. Roberto maintains his own family hub at **roberto.net** on a completely independent server. Nomad allows Jaquelina to publish her photos with an *access control list (ACL)* that only includes Roberto. This means that while Roberto can see the photos when he visits her blog, his brother Marco cannot, nor can any other family member who has an account on **roberto.net**.
+
+The twist in this scenario is the fact that Roberto never logged in to Jaquelina's website. Instead, he only had to log in once with his password on his *own* **roberto.net** website. When Roberto visits **jaquelina.org**, he is seamlessly authenticated by their hub by querying his server in the background.
+
+It's not uncommon for servers to have technical issues or become inaccessible for various reasons. Nomad provides robustness to Roberto's online activities by allowing him to have *clones* of his online identity or *channel* on multiple independent hubs. Imagine that Roberto's server goes down for some reason and he can no longer log in there. He simply logs into one of his clones on **gadfly.com**, a website run by his friend Peter. Once he has authenticated himself at **gadfly.com**, Roberto can view Jaquelina's blog as before, without Jaquelina having to grant additional access!
+
+#### Communication
+
+Communication and social networks are an essential part of the grid. Any channel (and any service provided by that channel) can take full advantage of feature-rich social communication on a global scale. These communications can be public or private - and private communications include not only fully encrypted transport, but also encrypted storage to protect against accidental snooping and disclosure by rogue system administrators and ISPs.
+
+Nomad supports a wide range of background services in the grid, from friend suggestions to directory services. New content and data updates are passed in the background between hubs across the grid according to the access control lists and permissions set by the *sender and* receiver channels. Data is also synchronised between any number of channel clones so that hub members can access data and continue to collaborate seamlessly even if their primary hub is unavailable or offline.
+
+#### Identity
+
+Nomad's identity layer is unique. It provides an **invisible single sign-on** for all locations in the grid.
+It also provides a **nomadic identity** so that your communication with friends, family or other people you communicate with is not affected by the loss of your primary communication hub - either temporarily or permanently.
+
+The important parts of your identity and relationships can be backed up on a USB stick or your laptop and appear on any node on the network at any time - with all your friends and preferences.
+Crucially, these nomadic instances are kept synchronised so that any instance can take over if another is compromised or corrupted. This not only protects you from major system failures, but also from temporary website overload and government tampering or censorship.
+
+We believe that Hubzilla's nomadic identity, single sign-on and decentralisation bring a high level of **resilience** and **consistency** to internet communications, which is much needed in the face of global trends towards corporate centralisation and mass and indiscriminate government surveillance and censorship.
+When browsing the web, viewing channels and their unique content, you are seamlessly authenticated, even across completely different server hubs. No need to enter passwords. You don't have to type anything. You are simply greeted with your name on every new page you visit.
+
+How does this work with Nomad? We call it **‘magic-auth’** because Hubzilla hides the details of the complexity of single sign-on logins and nomadic identities from web browsing. This is one of Hubzilla's design goals: to increase privacy and freedom on the Internet while reducing the complexity and tedium of having to enter new passwords and login names every time you visit the Internet. You only log in once to your home hub (or a nomadic backup hub of your choice). This allows you to access all authenticated services offered anywhere on the web - such as shopping, blogs, forums and access to private information. Your password is not stored on a thousand different websites, but on servers that you control or trust.
+
+They cannot be silenced. They cannot be removed from the network unless you choose to leave it yourself.
+
+#### Access control
+
+Nomad's identity layer allows you to assign fine-grained permissions to any content you want to publish - and these permissions extend across the entire grid. It's like having a huge website made up of an army of small individual websites - where each channel in the grid can fully control its privacy and sharing preferences for all the web resources it creates.
+
+Currently, Hubzilla supports access control for many types of data, including discussion posts and comments, photo albums, events, cloud files, web pages, wikis and more. Each item and how and with whom it is shared is completely under your control.
+
+This type of control is trivial with large enterprise providers as they own the user database. In the Grid, you don't need a huge user database on your computer - because the Grid **is** your user database. It has essentially infinite capacity (limited by the total number of hubs online on the Internet) and is distributed across hundreds, possibly even millions of computers.
+
+Access can be granted or denied to any resource, channel or group of channels - anywhere on the grid. Others can access your content if you allow them to, and they don't even need to have an account in your hub. \ No newline at end of file
diff --git a/doc/en/developer/nomad_structures.md b/doc/en/developer/nomad_structures.md
new file mode 100644
index 000000000..b96279a97
--- /dev/null
+++ b/doc/en/developer/nomad_structures.md
@@ -0,0 +1,110 @@
+### Nomad structures
+
+#### Nomad signatures
+
+All signed data in Nomad is generated by an RSA signature operation with the initiator's private key. The binary result is then encoded for transport with base64url.
+
+#### Nomad encryption
+
+Encryption is currently performed using AES256CTR. Other algorithms MAY be supported. A 32-octet key and a 16-octet initialisation vector are generated at random. The desired data is then encoded with these generated strings and the result is base64url encoded. An array is then created:
+
+- data
+ The base64url-encoded encrypted data
+- alg
+ The selected algorithm, in this case the character string ‘aes256ctr’.
+- key
+ The randomly generated key, RSA-encrypted with the recipient's public key, and the base64url-encoded result
+- iv
+ The randomly generated initialisation vector, RSA-encrypted with the recipient's public key, and the base64url-encoded result
+
+#### Basic Nomad packet
+
+Used to initiate a dialogue with another Nomad site. This packet MAY be encrypted. The presence of an array element ‘iv’ indicates that encryption has been performed. When sending an ‘auth_check’ packet, this packet MUST be encrypted, using the target site's public key (the site key, as opposed to a sender key).
+
+```
+ {
+ ‘type’: ‘notify’,
+ ‘sender’:{
+ ‘guid’: ‘kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA’,
+ ‘guid_sig’: "PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK- R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81- Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91- ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD- yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q’,
+ ‘url’: ‘http:\/\/podunk.edu’,
+ ‘url_sig’: "T8Bp7j5DHHhQDCFcAHXfuhUfGk2P3inPbImwaXXF1xJd3TGgluoXyyKDx6WDm07x0hqbupoAoZB1qBP3_WfvWiJVAK4N1FD77EOYttUEHZ7L43xy5PCpojJQmkppGbPJc2jnTIc_F1vvGvw5fv8gBWZvPqTdb6LWF6FLrzwesZpi7j2rsioZ3wyUkqb5TDZaNNeWQrIEYXrEnWkRI_qTSOzx0dRTsGO6SpU1fPWuOOYMZG8Nh18nay0kLpxReuHCiCdxjXRVvk5k9rkcMbDBJcBovhiSioPKv_yJxcZVBATw3z3TTE95kGi4wxCEenxwhSpvouwa5b0hT7NS4Ay70QaxoKiLb3ZjhZaUUn4igCyZM0h6fllR5I6J_sAQxiMYD0v5ouIlb0u8YVMni93j3zlqMWdDUZ4WgTI7NNbo8ug9NQDHd92TPmSE1TytPTgya3tsFMzwyq0LZ0b- g- zSXWIES__jKQ7vAtIs9EwlPxqJXEDDniZ2AJ6biXRYgE2Kd6W_nmI7w31igwQTms3ecXe5ENI3ckEPUAq__llNnND7mxp5ZrdXzd5HHU9slXwDShYcW3yDeQLEwAVomTGSFpBrCX8W77n9hF3JClkWaeS4QcZ3xUtsSS81yLrp__ifFfQqx9_Be89WVyIOoF4oydr08EkZ8zwlAsbZLG7eLXY"
+ ‘sitekey’:"-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxTeIwXZWrw/S+Ju6gewh
+LgkKnNNe2uCUqCqMZoYgJar3T5sHCDhvXc4dDCbDkxVIaA/+V1mURtBV60a3IGjn
+OAO0W0XGGLe2ED7G5o9U8T9mVGq8Mauv0v1oQ5wIR1gEAhBavkQ2OUGuF/YKn2nj
+HlKsv9HzUAHpcDMUe3Uklc2RhQbMcnJxEgkyjCkDyrTtCZzISkTAocHvpCG1KSog
+njUZdiz9UWxvM4rCFkCJvQU4RwRZJb7GA9ul+9JrF7NvUQTx8csRP2weBk1E9yyj
+wbe187E0eVj9RXX2Mx3mYhgrTdodxLOVMSXZLg1/SMpeFFl7QBhuM0SiOPg8a7Et
+e2iNA/RD4WiUFqCDfafasRa1TOozOm7LA+08lkAh5PeQPJsJbrX0wVVft++Y+5/z
+BvcUOP73vcbz7j5hJ7HLsbQtye/UUCfODBFybuDqRM84Aet8rjZohX7vukXdMD4I
+2HuB7pjR4uIfyYr0J63ANkvbsn8LR+RnirmHrK5H/OgxxjXcfYbGEQgFxvxhF6lA
+FpMu6Do4dx3CIb6pRmZ8bjSImXexJ0BSo9n3gtrz0XYLecsYFlQ9+QQjm83qxyLb
+M23in0xqMVsyQvzjNkpImrO/QdbEFRIIMee83IHq+adbyjQR49Z2hNEIZhkLPc3U
+2cJJ2HkzkOoF2K37qwIzk68CAwEAAQ==
+-----END PUBLIC KEY-----
+"
+ },
+ ‘recipients’:{
+ {
+ ‘guid’: ‘lql-1VnxtiO4-WF0h72wLX1Fu8szzHDOXgQaTbELwXW77k8AKFfh-hYr70vqMrc3SSvWN-Flrc5HFhRTWB7ICw’,
+ ‘guid_sig’: "PafvEL0VpKfxATxlCqDjfOeSIMdmpr3iU7X-Sysa1h5LzDpjSXsjO37tYZL- accb1M5itLlfnW5epkTa5I4flsW21zSY1A2jCuBQUTLLGV7rNyyBy7lgqJUFvAMRx0TfXzP9lcaPqlM9T1tA6jfWOsOmkdzwofGeXBnsjGfjsO2xdGYe6vwjOU0DSavukvzDMnOayB9DekpvDnaNBTxeGLM45Skzr7ZEMcNF7TeXMbnvpfLaALYEKeQs9bGH- UgAG8fBWgzVAzeBfx_XSR1rdixjyiZGP0kq0h35SlmMPcEjliodOBFwMXqpXFB7Ibp4F6o6te2p2ErViJccQVG8VNKB6SbKNXY6bhP5zVcVsJ- vR-p4xXoYJJvzTN7yTDsGAXHOLF4ZrXbo5yi5gFAlIrTLAF2EdWQwxSGyLRWKxG8PrDkzEzX6cJJ0VRcLh5z6OI5QqQNdeghPZbshMFMJSc_ApCPi9_hI4ZfctCIOi3T6bdgTNKryLm5fhy_eqjwLAZTGP- aUBgLZpb1mf2UojBn6Ey9cCyq-0T2RWyk-FcIcbV4qJ-p_8oODqw13Qs5FYkjLr1bGBq82SuolkYrXEwQClxnrfKa4KYc2_eHAXPL01iS9zVnI1ySOCNJshB97Odpooc4wk7Nb2Fo-Q6THU9zuu0uK_-JbK7IIl6go2qA"
+ },
+ },
+ ‘callback’:‘\/post’,
+ ‘version’: ‘1.2’,
+ ‘encryption’:{
+ ‘aes256ctr’
+ },
+ ‘secret’:‘1eaa6613699be6ebb2adcefa5379c61a3678aa0df89025470fac871431b70467’,
+ ‘secret_sig’: "0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm- FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs- pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD- yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q"
+ }
+```
+
+type
+
+The message type: **notify, purge, refresh, force_refresh, auth_check, ping** or **pickup**. The content of the packets varies depending on the message type. The **notify packet** is described here.
+
+callback
+
+A character string that is appended to the URL and identifies the Nomad communication endpoint on this system. This is usually the character string ‘/post’.
+
+version
+
+The identifier of the Nomad protocol so that future protocol revisions can co-exist.
+
+encryption
+
+Array of supported encryption algorithms, ordered by decreasing preference. If no compatible encryption methods are specified, applications MUST use ‘aes256cbc’.
+
+secret
+
+A 64-character string randomly generated by the sending side.
+
+secret_sig
+
+The RSA signature of the secret, signed with the sender's private key.
+
+sender
+
+An array of four components that provide a portable identity. We can contact the given URL and download a Nomad info packet to obtain the sender's public key and use it to verify the sender's guid and the signatures of the sending URL.
+
+- guid
+ Usually a 64 character base64url encoded string. It is generated when an identity is created and an attempt is made to make it unique, but this is not required.
+- guid_sig
+ The RSA signature of the guid, signed with the sender's private key and base64url-encoded.
+- url
+ The base URL of the location from which this post originated.
+- url_sig
+ The RSA signature of the url, signed with the sender's private key and base64url encoded.
+- sitekey
+ The public key of the website specified in the url
+
+recipients
+
+Only used for private messages. An array of envelope recipients. Each recipient is represented by an array of guid and guid_sig. If recipients are specified, the entire packet is also encapsulated with a negotiated cryptographic algorithm or ‘aes256cbc’ if none could be negotiated.
+
+- guid
+ The guid of a private recipient.
+- guid_sig
+ The RSA signature of the guid, signed with the recipient's private key and base64url-encoded \ No newline at end of file
diff --git a/doc/en/developer/our_pledge.md b/doc/en/developer/our_pledge.md
new file mode 100644
index 000000000..7f8f9da0b
--- /dev/null
+++ b/doc/en/developer/our_pledge.md
@@ -0,0 +1,3 @@
+#### Our pledge
+
+In the interest of fostering an open and welcoming environment, we as contributors and carers pledge to make participation in our project and community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, experience level, nationality, personal appearance, ethnicity, religion or sexual identity and orientation. \ No newline at end of file
diff --git a/doc/en/developer/our_responsibilities.md b/doc/en/developer/our_responsibilities.md
new file mode 100644
index 000000000..d083a570c
--- /dev/null
+++ b/doc/en/developer/our_responsibilities.md
@@ -0,0 +1,3 @@
+#### Our responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable behaviour and are expected to take appropriate and fair corrective action when unacceptable behaviour is identified. Project maintainers have the right and responsibility to remove, edit or reject comments, commits, code, wiki edits, issues and other contributions that do not comply with this Code of Conduct, or to temporarily or permanently ban contributions that they deem inappropriate, threatening, offensive or harmful. \ No newline at end of file
diff --git a/doc/en/developer/our_standards.md b/doc/en/developer/our_standards.md
new file mode 100644
index 000000000..583614347
--- /dev/null
+++ b/doc/en/developer/our_standards.md
@@ -0,0 +1,17 @@
+#### Our standards
+
+Examples of behaviours that contribute to creating a positive environment include
+
+- Using welcoming and inclusive language
+- Respecting different points of view and experiences
+- Accepting constructive criticism gracefully
+- Focusing on what is best for the community
+- Showing empathy towards other members of the community
+
+Examples of unacceptable behaviour from participants include
+
+- The use of sexualised language or images and unwanted sexual attention or advances
+- Trolling, offensive/degrading comments and personal or political attacks
+- Public or private harassment
+- Publishing the private information of others, such as a physical or electronic address, without express permission
+- Other behaviour that could be considered inappropriate in a professional environment \ No newline at end of file
diff --git a/doc/en/developer/scope.md b/doc/en/developer/scope.md
new file mode 100644
index 000000000..84c617cbb
--- /dev/null
+++ b/doc/en/developer/scope.md
@@ -0,0 +1,3 @@
+#### Scope
+
+This Code of Conduct applies both within the project areas and in the public sphere when a person is representing the project or its community. Examples of representing a project or community include using an official project email address, posting via an official social media account or acting as an appointed representative at an online or offline event. The representation of a project can be further defined and specified by the project supervisors. \ No newline at end of file
diff --git a/doc/en/developer/technical_introduction.md b/doc/en/developer/technical_introduction.md
new file mode 100644
index 000000000..731fd5934
--- /dev/null
+++ b/doc/en/developer/technical_introduction.md
@@ -0,0 +1,292 @@
+### Technical introduction
+
+Nomad is a JSON-based web framework for implementing secure decentralised communication and services. To provide this functionality, Nomad creates a decentralised, globally unique identifier for each node in the network. This global identifier is not inextricably linked to the DNS, ensuring the required mobility. Many of the existing decentralised communication frameworks provide the communication aspect, but no remote access control and authentication. Furthermore, most of these systems are based on the ‘web finger’, which still ties identity to domain names and cannot support nomadic identity.
+The main problems that Nomad addresses are
+
+- Fully decentralised communication
+- Independence from DNS-based identity
+- node mobility
+- seamless remote authentication
+- high performance
+
+We will rely on DNS-based user@host addresses as a ‘user-friendly’ mechanism to tell others where you are, namely on a named host with a specific username, and communication with DNS entities is handled over TCP and the web.
+
+However, the underlying protocol provides an abstraction layer on top of this so that a communication node (e.g. ‘identity’) can move to a different DNS location and recover (to the best of its ability) from location changes and/or maintain pre-existing communication relationships. A side effect of this requirement is the ability to communicate from alternative/multiple DNS locations and service providers and still maintain a single online identity.
+
+We call this overlay network the ‘grid’. The servers connected to this network are called ‘hubs’ and can support any number of individual identities.
+
+An identity does not necessarily have to correspond to a person. It is merely something that requires the ability to communicate within the grid.
+
+The ability to recover is achieved by communicating with the original location when a new or replacement identity is created, or as a fallback to a stored file describing the identity and its contacts in the event that the old location no longer responds.
+
+At least in the short term, mobility of existing content is not the highest priority. This may or may not happen at a later date. The most important things we want to keep are your identity and your friends.
+Addresses that are shared by multiple people are user@host and describe the **current** local credentials for a particular identity. They are DNS-based addresses that are used as a seed to localise a particular identity within the network. Machine communication binds this address to a globally unique ID. A single globally unique ID can be attached or bound to any number of DNS locations. Once an identity is mapped or bound to a DNS location, communication consists only of knowing the globally unique address and the DNS (url) currently in use (to recall and verify/complete the current communication). These concepts will be specified in more detail in the future.
+
+For an identity to persist across locations, one must be able to provide or recover
+
+- the globally unique ID for that identity
+- the private key assigned to that identity
+- (if the original server no longer exists) an address book of contacts for that identity.
+
+This information can be exported from the original server via an API and/or downloaded to a hard drive or USB stick.
+
+We can also attempt to restore the identity with even less information, but this is vulnerable to account hijacking and requires your contacts to confirm the change.
+
+To enable high performance communication, the data transfer format for all aspects of Nomad is JSON. XML communication requires far too much overhead.
+
+Bi-directional encryption is based on 4096-bit RSA keys expressed in DER/ASN.1 format using the PKCS#8 encoding variant, with AES encryption of variable length or large elements. The exact encryption algorithms are negotiable between sites.
+
+Some aspects of the well-known ‘federation protocols’ (Webfinger, Salmon, Activitystreams, Portablecontacts, etc.) can be used in Nomad, but we are not and will not be bound by them. The Hubzilla project is trying some fairly novel developments in the field of decentralised communication, and if it should be necessary to deviate from such ‘standard protocols’, we will do so without question or hesitation.
+To create a globally unique ID, we will base it on a whirlpool hash of the origin node's identity URL and a pseudo-random number, which should give us a 256-bit ID with an extremely low collision probability (256 bits equals about 115 quattuorviginitillion or 1.16 X 10^77 unique numbers). This is represented in the communication as a base64url encoded string. However, we will not rely on probabilities, and the ID must also be associated with a public key, using public key cryptography to provide an identity guarantee that has not been copied or somehow collided in the whirlpool hash space.
+
+Using the DNS as the basis for the ID provides a globally unique seed, which would not be the case if the ID was based entirely on pseudo-random number generation.
+
+We refer to the encoded globally unique uid string as zot_uid
+
+As there may be more than one DNS location associated/connected to a particular zot_uid identity, the delivery processes should deliver to all of these locations as we do not know with certainty which hub instance can be accessed at any given time. However, we will designate one DNS location as ‘primary’ which will be the preferred location for displaying web profile information.
+
+We also need to provide the ability to change the primary location to a new location. A lookup of information about the current primary location can provide a ‘redirect pointer’ that instructs us to update our listings and move everything to the new location. There is also the possibility that the primary location no longer exists and is no longer responding. In this case, a location that is already assigned to this zot_uid can take control and declare itself as primary. In both cases, the primary designation is automatically confirmed and moved. A request can also be made from a primary location requesting the removal of another location.
+
+To assign a zot_uid to a second (or tertiary) location, a secure exchange is required to verify that the new location is in possession of the private key for that zot_uid. The security of the private key is therefore essential to prevent identity hijacking.
+
+Communication then requires
+
+- zot_uid (character string)
+- uid_sig
+- callback (current location Nomad endpoint url)
+- callback_sig
+- spec (int)
+
+is transferred with every message. The spec is a revision number of the current Nomad specification so that communication with hubs that use older and possibly incompatible protocol specifications can be maintained. Communication is verified using a stored public key that was copied to this zot_uid when the connection was first established.
+
+The revocation and replacement of the key must be carried out from the primary hub. The exact form for this is still being worked out, but will likely be a notification to all other bound hubs to ‘phone home’ (to the primary hub) and request a copy of the new key. This notification should be verified with a site or hub key, as the original identity key may have been compromised and cannot be trusted.
+To avoid confusion, there should be exactly one canonical url for each hub so that it can be indexed and uniquely referenced.
+
+To avoid ambiguity of the scheme, it is strongly recommended that all addresses should be https with a browser valid certificate and a single valid host component (either [www.domain.ext](www.domain.ext) or domain.ext, but not both) used in all communication. Multiple URLs can be specified locally, but only a single URL should be used for all Nomad communication within the grid.
+
+Test installations that are not connected to the public network can use non-SSL, but all traffic flowing over public networks should be protected from session hijacking, preferably with a ‘browser recognised’ certificate.
+
+Where possible, the Nomad recommends the use of ‘batching’ to minimise network traffic between two hubs. This means that ‘Site A’ can send multiple messages to ‘Site B’ in a single transaction and also consolidate the delivery of identical messages to multiple recipients in the same hub.
+
+The messages themselves may or may not be encrypted in transit, depending on the private nature of the messages. SSL (strongly recommended) provides unconditional encryption of the data stream, however it makes little sense to encrypt public messages that have been categorised as having unrestricted visibility. Encryption of data storage and so-called ‘end-to-end encryption’ are outside the scope of Nomad. It is assumed that node operators take appropriate security precautions to ensure the security of their data stores, and these are functions of application and site integrity as opposed to protocol integrity.
+
+#### Messaging
+
+Given the constraints listed above, a Nomad message should therefore be a json array of individual messages. These can be mixed and combined within the same transmission.
+
+Each message then requires:
+
+- Type
+- (optional) recipient list
+
+The absence of a recipient list would indicate an unencrypted public message or a site-level message. The recipient list would contain an array of zot_uid with an individual decryption key and a common iv. The decryption key is encrypted with the public key of the recipient identity. The iv is encrypted with the private key of the sender.
+
+All messages should be digitally signed by the sender.
+
+The type can be one of the following (this list can be extended):
+
+- Mail (or activity)
+- Mail
+- identity
+- authenticate
+
+Identity messages have no recipients and notify the system social graph of an identity update, which can be a new or deleted identity, a new or deleted location, or a change to the primary hub. The signature for these messages uses system keys as opposed to identity-specific keys.
+
+Posts include many different types of activity, such as top-level posts, likes/dislikes, comments, tagging activity, etc. These types are specified in the message structure.
+
+Authentication messages result in mutual authentication and browser redirection to protected resources on the remote hub, such as the ability to post wall-to-wall messages and view private photo albums and events, etc.
+
+#### Exploration
+
+A known URL is used to probe a hub for Nomad capabilities and identity queries, including discovery of public keys, profile locations, profile photos, and the primary hub location.
+
+The location for this service is /.well-known/zot-info - and must be available in the root directory of the selected domain.
+
+To perform a query, a POST request is made to the discovery location with the following parameters:
+Required:
+
+Address => an address on the target system such as ‘john’
+
+Optional:
+
+target => the observer's Nomad ‘guid’ for the evaluation of authorisations
+
+target_sig => an RSA signature (base64url-encoded) of the guid
+
+key => The public key required to verify the signature
+
+token => a character string selected by the requesting service (possibly random). If present, an entry in the discovered packet labelled ‘signed_token’ is provided, which consists of the base64url_encoded RSA signature of the concatenation of the string ‘token.’ and the provided token using the private key of the discovered channel. This can be verified with the provided ‘key’ entry and provides assurance that the server is in possession of the private key for the discovered identity. After 1 January 2017, it is **required** that a server provides a signed_token *if* a token has been specified in the request.
+
+If no target is specified, the returned authorisations are generic authorisations for unknown or unauthenticated observers
+
+Example of a detection package for ‘`mike@zothub.com`’
+
+```
+ {
+
+ ‘success’: true,
+ ‘signed_token’: "KBJrKTq1qrctNuxF3GwVh3GAGRqmgkirlXANPcJZAeWlvSt_9TMV097slR4AYnYCBEushbVqHEJ9Rb5wHTa0HzMbfRo8cRdl2yAirvvv5d98dtwHddQgX1jB0xEypXtmIYMdPGDLvhI1RNdIBhHkkrRcNreRzoy4xD- -HM6m1W0-A8PJJ9BcNxmGPcBtLzW08wzoP9trJ3M7DQ6Gkk6j7iwVsyApw1ZBaDvabGTdc_SFV-Iegtqw3rjzT_xXWsfzMlKBy-019MYn_KS- gu23YzjvGu5tS_zDfkQb8DMUlPLz5yyxM0yOMlUDtG2qQgIJAU2O0X6T5xDdJ6mtolNyhepg845PvFDEqBQGMIH1nc47CNumeudDi8IWymEALhjG_U8KAK7JVlQTJj2EKUb0au1g6fpiBFab5mmxCMtZEX3Jreyak5GOcFFz- WpxuXJD9TdSoIvaBfBFOoJnXkg2zE4RHXeQzZ2FotmrbBG5dm8B- _6byYGoHBc08ZsWze1K96JIeRnLpBaj6ifUDcVHxZMPcGHHT27dvU2PNbgLiBjlAsxhYqkhN5qOHN8XBcg2KRjcMBaI3V0YMxlzXz5MztmZq3fcB1p-ccIoIyMPMzSj3yMB7J9CEU2LYPSTHMdPkIeDE6GaCkQKviaQJQde346tK_YjA2k7_SOBmvPYE’,
+ ‘guid’: ‘sebQ-IC4rmFn9d9iu17m4BXO-kHuNutWo2ySjeV2SIW1LzksUkss12xVo3m3fykYxN5HMcc7gUZVYv26asx-Pg’,
+ ‘guid_sig’: "Llenlbl4zHo6-g4sa63MlQmTP5dRCrsPmXHHFmoCHG63BLq5CUZJRLS1vRrrrr_MNxr7zob_Ykt_m5xPKe5H0_i4pDj-UdP8dPZqH2fqhhx00kuYL4YUMJ8gRr5eO17vsZQ3XxTcyKewtgeW0j7ytwMp6- hFVUx_Cq08MrXas429ZrjzaEwgTfxGnbgeQYQ0R5EXpHpEmoERnZx77VaEahftmdjAUx9R4YKAp13pGYadJOX5xnLfqofHQD8DyRHWeMJ4G1OfWPSOlXfRayrV_jhnFlZjMU7vOdQwHoCMoR5TFsRsHuzd- qepbvo3pzvQZRWnTNu6oPucgbf94p13QbalYRpBXKOxdTXJrGdESNhGvhtaZnpT9c1QVqC46jdfP0LOX2xrVdbvvvG2JMWFv7XJUVjLSk_yjzY6or2VD4V6ztYcjpCi9d_WoNHruoxro_br1YO3KatySxJs-LQ7SOkQI60FpysfbphNyvYMkotwUFI59G08IGKTMu3- GPnV1wp7NOQD1yzJbGGEGSEEysmEP0SO9vnN45kp3MiqbffBGc1r4_YM4e7DPmqOGM94qksOcLOJk1HNESw2dQYWxWQTBXPfOJT6jW9_crGLMEOsZ3Jcss0XS9KzBUA2p_9osvvhUKuKXbNztqH0oZIWlg37FEVsDs_hUwUJpv2Ar09k4’,
+ ‘key’: "-----BEGIN PUBLIC KEY----- \nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7QCwvuEIwCHjhjbpz3Oc\ntyei/Pz9nDksNbsc44Cm8jxYGMXsTPFXDZYCcCB5rcAhPPdZSlzaPkv4vPVcMIrw\n5cdX0tvbwa3rNTng6uFE7qkt15D3YCTkwF0Y9FVZiZ2Ko+G23QeBt9wqb9dlDN1d\nuPmu9BLYXIT/JXoBwf0vjIPFM9WBi5W/EHGaiuqw7lt0qI7zDGw77yO5yehKE4cu\n7dt3SakrXphL70LGiZh2XGoLg9Gmpz98t+gvPAUEotAJxIUqnoiTA8jlxoiQjeRK\nHlJkwMOGmRNPS33awPos0kcSxAywuBbh2X3aSqUMjcbE4cGJ++/13zoa6RUZRObC\nZnaLYJxqYBh13/N8SfH7d005hecDxWnoYXeYuuMeT3a2hV0J84ztkJX5OoxIwk7S\nWmvBq4+m66usn6LNL+p5IAcs93KbvOxxrjtQrzohBXc6+elfLVSQ1Rr9g5xbgpub\npSc+hvzbB6p0tleDRzwAy9X16NI4DYiTj4nkmVjigNo9v2VPnAle5zSam86eiYLO\nt2u9YRqysMLPKevNdj3CIvst+BaGGQONlQalRdIcq8Lin+BhuX+1TBgqyav4XD9K\nd+JHMb1aBk/rFLI9/f2S3BJ1XqpbjXz7AbYlaCwKiJ836+HS8PmLKxwVOnpLMbfH\nPYM8k83Lip4bEKIyAuf02qkCAwEAAQ==\n- ----END PUBLIC KEY-----\n’,
+ ‘name’: ‘Mike Macgirvin’,
+ ‘name_updated’: ‘2012-12-06 04:59:13’,
+ ‘address’: ‘mike@zothub.com’,
+ ‘photo_mimetype’: ‘image/jpeg’,
+ ‘photo’: ‘https://zothub.com/photo/profile/l/1’,
+ ‘photo_updated’: ‘2012-12-06 05:06:11’,
+ ‘url’: ‘https://zothub.com/channel/mike’,
+ ‘connections_url’: ‘https://zothub.com/poco/mike’,
+ ‘target’: ‘’,
+ ‘target_sig’: ‘’,
+ ‘searchable’: false,
+ ‘permissions’: {
+ ‘view_stream’: true,
+ ‘view_profile’: true,
+ ‘view_photos’: true,
+ ‘view_contacts’: true,
+ ‘view_storage’: true,
+ ‘view_pages’: true,
+ ‘send_stream’: false,
+ ‘post_wall’: false,
+ ‘post_comments’: false,
+ ‘post_mail’: false,
+ ‘post_photos’: false,
+ ‘tag_deliver’: false,
+ ‘chat’: false,
+ ‘write_storage’: false,
+ ‘write_pages’: false,
+ ‘delegate’: false
+ },
+ ‘profile’: {
+ ‘description’: ‘Freedom Fighter’,
+ ‘birthday’: ‘0000-05-14’,
+ ‘next_birthday’: ‘2013-05-14 00:00:00’,
+ ‘gender’: ‘Male’,
+ ‘marital’: ‘It's complicated’,
+ ‘sexual’: ‘Females’,
+ ‘locale’: ‘’,
+ ‘region’: ‘’,
+ ‘postcode’: ‘’,
+ ‘country’: ‘Australia’
+ },
+ ‘locations’: [
+ {
+ ‘host’: ‘zothub.com’,
+ ‘address’: ‘mike@zothub.com’,
+ ‘primary’: true,
+ ‘url’: ‘https://zothub.com’,
+ ‘url_sig’: "eqkB_9Z8nduBYyyhaSQPPDN1AhSm5I4R0yfcFxPeFpuu17SYk7jKD7QzvmsyahM5Kq7vDW6VE8nx8kdFYpcNaurqw0_IKI2SWg15pGrhkZfrCnM- g6A6qbCv_gKCYqXvwpSMO8SMIO2mjQItbBrramSbWClUd2yO0ZAceq3Z_zhirCK1gNm6mGRJaDOCuuTQNb6D66TF80G8kGLklv0o8gBfxQTE12Gd0ThpUb5g6_1L3eDHcsArW_RWM2XnNPi_atGNyl9bS_eLI2TYq0fuxkEdcjjYx9Ka0- Ws-lXMGpTnynQNCaSFqy-Fe1aYF7X1JJVJIO01LX6cCs- kfSoz29ywnntj1I8ueYldLB6bUtu4t7eeo__4t2CUWd2PCZkY3PKcoOrrnm3TJP5_yVFV_VpjkcBCRj3skjoCwISPcGYrXDufJxfp6bayGKwgaCO6QoLPtqqjPGLFm- fbn8sVv3fYUDGilaR3sFNxdo9mQ3utxM291XE2Pd0jGgeUtpxZSRzBuhYeOybu9DPusID320QbgNcbEbEbEImO8DuGIxuVRartzEXQF4WSYRdraZzbOqCzmU0O55P836JAfrWjgxTQkXlYCic- DBk-iE75JeT72smCtZ4AOtoFWCjZAABCw42J7JELY9APixZXWriKtjy6JI0G9d3fs6r7SrXr1JMy0’,
+ ‘callback’: ‘https://zothub.com/post’,
+ ‘sitekey’: "-----BEGIN PUBLIC KEY----- \nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1IWXwd/BZuevq8jzNFoR\n3VkenduQH2RpR3Wy9n4+ZDpbrUKGJddUGm/zUeWEdKMVkgyllVA/xHdB7jdyKs1X\nuIet9mIdnzvhdLO/JFD5hgbNG2wpSBIUY6aSNeCFTzszqXmuSXMW5U0Ef5pCbzEA\nnhoCoGL1KAgPqyxnGKUlj7q2aDwC9IRNtAqNyFQL67oT91vOQxuMThjlDhbR/29Q\ncYR4i1RzyahgEPCnHCPkT2GbRrkAPjNZAdlnk9UesgP16o8QB3tE2j50TVrbVc/d\nYRbzC56QMPP9UgUsapNeSJBHji75Ip/E5Eg/kfJC/HEQgyCqjCGfb7XeUaeQ7lLO\nqc7CGuMP+Jqr/cE54/aSHg8boTwxkMp11Ykb+ng17fl57MHTM2RJ99qZ1KBkXezR\nuH1lyvjzeJPxEFr9rkUqc4GH74/AgfbgaFvQc8TS7ovEa5I/7Pg04m7vLSEYc6UF\nYJYxXKrzmZT2TDoKeJzaBBx5MFLhW19l68h9dQ8hJXIpTP0hJrpI+Sr6VUAEfFQC\ndIDRiFcgjz6j7T/x8anqh63/hpsyf2PMYph1+4/fxtSCWJdvf+9jCRM8F1IDfluX\n87gm+88KBNaklYpchmGIohbjivJyru41CsSLe0uinQFvA741W00w6JrcrOAX+hkS\nRQuK1dDVwGKoIY85KtTUiMcCAwEAAQ==\n- ----END PUBLIC KEY-----\n"
+ }
+ ],
+ ‘site’: {
+ ‘url’: ‘https://zothub.com’,
+ ‘directory_mode’: ‘primary’,
+ ‘directory_url’: ‘https://zothub.com/dirsearch’
+ }
+
+ }
+```
+
+Discovery returns a JSON array with the following components:
+
+‘success’ => (true or false) Operation was successful if true. Otherwise, there may be an optional ‘message’ indicating the cause of the error.
+
+‘signed_token’ => If a token parameter was specified in the request, it is prefixed with the text ‘token.’ and then RSA-signed and base64url-encoded with the private key of the channel and returned as ‘signed_token’.
+
+guid' => the guid of the address on the target system
+
+guid_sig’ => the RSA signature of the guid encoded with base64url, signed with the private key associated with this guid.
+
+‘key’ => the public key associated with this guid
+
+name' => name of the channel
+
+name_updated' => timestamp in MySQL style “2012-01-01 00:00:00”, when the name was last changed (UTC)
+
+address' => “webbie” or user@host address associated with this channel
+
+photo' => URL of a profile photo for this channel (ideally 175x175)
+
+photo_mimetype' => content type of the profile photo
+
+photo_updated' => MySQL-style timestamp of when the photo was last updated (UTC)
+
+‘url’ => location of the channel homepage
+
+‘connections_url’ => location of the URL for portable contacts (extended for Nomad) for this channel
+
+target' => if an authorisation target has been specified, it will be mirrored
+
+‘target_sig’ => if an authorisation target has been specified, the signature is mirrored
+
+‘searchable’ => (true or false) true means that this entry can be searched for in a directory
+
+##### Authorisations
+
+‘permissions’ => expandable array of permissions suitable for this target, values are true or false
+Permissions can include
+
+- view_stream
+- view_profile
+- view_photos
+- view_contacts
+- view_storage
+- view_pages
+- send_stream
+- post_wall
+- post_comments
+- post_mail
+- post_photos
+- tag_deliver
+- chat
+- write_storage
+- write_pages
+- delegate
+
+##### profile
+
+‘Profile’ => Array with important profile fields
+
+- description
+- birthday YYYY-MM-DD , all fields are optional, any field (such as year) may be zero
+- next_birthday => MySQL datetime string representing the next upcoming birthday, converted from the channel's default timezone to UTC.
+- gender (free form)
+- marital (marital status)
+- sexual (preference)
+- locale (city)
+- region (state)
+- postcode
+- country
+
+##### locations
+
+‘locations’ => Array of registered locations (DNS locations) from which this channel can be visible or can be booked
+
+Each location is an array of
+
+‘host’ => DNS host name, e.g. example.com
+
+‘address’ => the webbie or user@host identifier associated with this location
+
+‘primary’ => (true or false) whether this is the primary location for this channel where files and web pages are generally found
+
+‘url’ => url of the root directory of this DNS location, e.g. `https://example`.
+
+url_sig' => base64url encoded RSA signature of the URL, signed with the private key of the channel
+
+‘callback’ => Nomad communication endpoint on this site, normally `https://example.com/post`
+
+sitekey' => public key of this site/host
+
+##### site
+
+‘site’ => array specifying the directory role of the site responding to this request
+
+url' => url of this site e.g. `https://example`.
+
+directory_mode' => one of “primary”, “secondary”, “normal” or “standalone”
+
+directory_url' => if it is a directory server or a standalone site, the URL for the directory search module \ No newline at end of file
diff --git a/doc/en/developer/testing.md b/doc/en/developer/testing.md
index f7e8ffeba..3ca1ba0aa 100644
--- a/doc/en/developer/testing.md
+++ b/doc/en/developer/testing.md
@@ -1,12 +1,12 @@
-# Testing
+### Testing
Hubzilla uses [PHPUnit] for automated testing, often called _unit testing_ or _integration testing_.
The tests are written as PHP classes, and live under the `tests/unit` subdirectory of the main Hubzilla repository. This guide will explain how to run the tests, how the tests are structured, and how you can write your own tests.
-## Running the test suite
+#### Running the test suite
-### Installing the dependencies
+##### Installing the dependencies
To be able to run the tests you have to install the developer dependencies listen in the `composer.json` file. Make sure you have [composer installed] on your development system, and then run:
@@ -16,7 +16,7 @@ This will install phpunit and a few other dependencies. It will also update some
**Warning:** Do not commit the changes to the files in the `vendor/composer` directory to your repository!
-### Setting up the test database
+##### Setting up the test database
We have included a script (`tests/create_test_db.sh`) that will help you set up the test database. You can run it like this:
@@ -28,7 +28,7 @@ If you are running PostgreSQL instead, you create the test db like this:
The script make some assumptions about your setup, but everything is configurable using environment variables. If you need any customization, see the script source for the details.
-### Running the tests
+##### Running the tests
Once you have installed the developer dependencies and set up the test database, you can run the tests like this:
@@ -42,7 +42,7 @@ You can also run a specific test, or a specific set of tests using the `--filter
Will run any test mathcing "Autoname".
-### Generating coverage reports
+##### Generating coverage reports
To generate coverage reports you need a driver that is able to generate the coverage info that PHPUnit will collect. We recommend [Xdebug], but see the PHPUnit [page on code coverage analysis](https://docs.phpunit.de/en/9.6/code-coverage-analysis.html) for alternatives and details.
@@ -54,11 +54,11 @@ This will generate a number of HTML files in directories under the `tests/result
Open the `index.php file in your web browser to view the stats.
-### Debugging
+##### Debugging
With Xdebug installed, you can also do step debugging and a number of other things to debug and get information about the execution of the tests. See the [Xdebug] pages and your prefered editor for how to set this up.
-## Test structure and organization
+#### Test structure and organization
Tests are located in the `tests/unit` subdirectory, and subdirectories under there again, more or less reflecting the directory layout of the core code repository.
@@ -91,9 +91,9 @@ The class can also contain any number of other functions to keep things tidy and
Results and artifacts from the test runs will be left in the `tests/results/` directory. This will typically include the test log, code coverage report etc.
-### Hubzilla specific features
+#### Hubzilla specific features
-#### Test database access:
+##### Test database access:
Ideally it should be able to test each part of the code in isolation, where any dependencies should be replaced by stubs, mocks or fakes.
@@ -107,7 +107,7 @@ When the test finishes, the test database is wiped clean, so that we have the sa
All of this is handled by the `UnitTestCase` base class.
-#### Database fixtures:
+##### Database fixtures:
We need some predictable content in the database to set up things like logging, and other content that's useful in general for the tests. These are database fixtures that are loaded into the database for each test run.
@@ -115,16 +115,8 @@ The database fixtures are located in the `tests/unit/include/dba/_files` directo
While database fixtures are nice to have, we try to keep the number of them as minimal as possible. It's usually better to add any content needed for a specific test in the test itself.
-#### Fakes:
+##### Fakes:
Fakes are classes that we can pass to the code under test that will look and (to the code under test) behave the same as the original class. These are useful when we're able to pass objects to the code under test, as we can control it completely from the test code.
Fakes are located under the `tests/fakes` directory.
-
-## Writing your own tests
-
-To be done
-
-[PHPUnit]: https://phpunit.de
-[composer installed]: https://getcomposer.org/
-[Xdebug]: https://xdebug.org/ \ No newline at end of file
diff --git a/doc/en/developer/tools_workflows.md b/doc/en/developer/tools_workflows.md
new file mode 100644
index 000000000..ab55f75f8
--- /dev/null
+++ b/doc/en/developer/tools_workflows.md
@@ -0,0 +1,5 @@
+### Tools and workflows for developers
+
+#### Hub snapshots
+
+The [hub snapshots](/help/adminmanual/hub_snapshot_tools.md) page contains instructions and scripts for creating full snapshots of a hub to support switching between consistent and fully known states. This is useful to avoid situations where the content or database schema may be incompatible with the code.
diff --git a/doc/en/developer/translations.md b/doc/en/developer/translations.md
new file mode 100644
index 000000000..474fd392f
--- /dev/null
+++ b/doc/en/developer/translations.md
@@ -0,0 +1,51 @@
+### Translations
+
+Our translations are managed through Transifex. If you would like to help translate Hubzilla into another language, sign up at transifex.com, visit [Transifex](https://www.transifex.com/Friendica/hubzilla/?f=&zid=pepecyb@hub.hubzilla.hu) and apply to join one of the existing language teams or create a new one. Notify one of the lead developers if you have a translation update that needs merging, or ask if you can merge it yourself if you are familiar with Git and PHP. We have a string file called ‘messages.po’ which is gettext compatible, and a handful of email templates from which we automatically generate the application language files.
+
+#### The translation process
+
+The strings used in Hubzilla's user interface are translated at [Transifex](https://www.transifex.com/Friendica/hubzilla/?f=&zid=pepecyb@hub.hubzilla.hu) and then added to the Git repository on github. If you would like to help translate a language, be it correcting terms or translating Hubzilla into a currently unsupported language, please register an account on transifex.com and contact the translation team there.
+
+Translating Hubzilla is easy. Simply use the online tool on transifex. If you don't want to bother with Git & Co. it's no problem, we regularly check the status of the translations and import them into the source tree on github for others to use.
+
+We do not include every translation of transifex in the source tree to avoid a scattered and disrupted overall experience. As a rough estimate, we have a lower limit of 50% translated strings before we include the language. This limit is only based on the amount of translated strings, assuming that the most important strings for the user interface are translated first by a translation team. If you feel that your translation is usable before this limit, please contact us and we will likely include your team's work in the source tree.
+If you would like to add your work to the source tree yourself, please feel free to do so and contact us with any questions that arise. The process is simple and Hubzilla comes with all the necessary tools.
+
+The location of the translated files in the source tree is
+
+`/View/LNG-CODE/`
+
+where LNG-CODE is the code of the language used, e.g. de for German or fr for French. For the e-mail templates (the *.tpl files), simply place them in the directory and you're done. The translated strings come as an ‘hmessages.po’ file from Transifex, which needs to be translated into the PHP file that Hubzilla uses. To do this, place the file in the directory mentioned above and use the ‘po2php’ utility from the util directory of your Hubzilla installation.
+
+Assuming you want to convert the German localisation, which is located in view/en/hmessages.po, you would proceed as follows.
+
+1. At the command prompt, navigate to the base directory of your
+
+Hubzilla installation
+
+1. Execute the po2php script that translates the
+
+into the hstrings.php file used by Hubzilla.
+
+`$> php util/po2php.php view/en/hmessages.po`
+
+The output of the script will be placed in the view/en/hstrings.php file where froemdoca expects it, so you can test your translation immediately.
+
+1. Visit your Hubzilla page and check that it is in the language you just translated.
+
+language you have just translated. If not, try to find the error, probably PHP will give you a hint in the log/warnings.about the error.
+
+For debugging you can also try to ‘execute’ the file with PHP. This should give no output if the file is ok, but may give you a hint for the error in the file.
+
+`$> php view/en/hstrings.php`
+
+1. Commit the two files to your git repository with a meaningful commit message, push it to your fork of the Hubzilla repository on github and make a pull request for this commit.
+
+#### Utilities
+
+In addition to the po2php script, there are several other utilities for translation in the ‘util’ directory of the Hubzilla source tree. If you just want to translate Hubzilla into another language, you probably won't need any of these tools, but it will give you an idea of how Hubzilla's translation process works.
+See the utils/README file for more information.
+
+#### Known issues
+
+\* Hubzilla uses the language setting of the visitor's browser to set the language for the user interface. Most of the time this works, but there are some known quirks. * the early translations are based on the friendica translations, if you find some rough translations please let us know or correct them at Transifex. \ No newline at end of file
diff --git a/doc/en/developer/unorganized.md b/doc/en/developer/unorganized.md
index 5ba719226..e27581ce0 100644
--- a/doc/en/developer/unorganized.md
+++ b/doc/en/developer/unorganized.md
@@ -30,7 +30,7 @@ First, you'll need to create a new theme. This is in /view/theme, and I chose to
Oh, and don't forget to rename the _init function in /php/theme.php to be _init() instead of redbasic_init().
-At that point, if you need to add javascript or css files, add them to /js or /css, and then "register" them in _init() through head_add_js('file.js') and head_add_css('file.css').
+At that point, if you need to add javascript or css files, add them to /js or /css, and then "register" them in `_init()` through `head_add_js('file.js')` and `head_add_css('file.css')`.
Now you'll probably want to alter a template. These can be found in in /view/tpl OR view//tpl. All you should have to do is copy whatever you want to tweak from the first place to your theme's own tpl directory.
@@ -44,9 +44,9 @@ doing development.
Create your own github account.
-You may fork/clone the Red repository from [url=https://framagit.org/hubzilla/core.git]https://framagit.org/hubzilla/core.git[/url]
+You may fork/clone the hubzilla repository from https://framagit.org/hubzilla/core.git
-Follow the instructions provided here: [url=http://help.github.com/fork-a-repo/]http://help.github.com/fork-a-repo/[/url]
+Follow the instructions provided here: http://help.github.com/fork-a-repo/]http://help.github.com/fork-a-repo/
to create and use your own tracking fork on github
Then go to your github page and create a "Pull request" when you are ready
@@ -59,15 +59,15 @@ Please pull in any changes from the project repository and merge them with your
Also - **test your changes**. Don't assume that a simple fix won't break something else. If possible get an experienced Red developer to review the code.
-Further documentation can be found at the Github wiki pages at: [url=https://github.com/friendica/red/wiki]https://github.com/friendica/red/wiki[/url]
+Further documentation can be found at the Github wiki pages at: https://github.com/friendica/red/wiki
**Concensus Building**
Code changes which fix an obvious bug are pretty straight-forward. For instance if you click "Save" and the thing you're trying to save isn't saved, it's fairly obvious what the intended behaviour should be. Often when developing feature requests, it may affect large numbers of community members and it's possible that other members of the community won't agree with the need for the feature, or with your proposed implementation. They may not see something as a bug or a desirable feature.
-We encourage consensus building within the community when it comes to any feature which might be considered controversial or where there isn't unanimous decision that the proposed feature is the correct way to accomplish the task. The first place to pitch your ideas is to [url=https://zothub.com/channel/one]Channel One[/url]. Others may have some input or be able to point out facets of your concept which might be problematic in our environment. But also, you may encounter opposition to your plan. This doesn't mean you should stop and/or ignore the feature. Listen to the concerns of others and try and work through any implementation issues.
+We encourage consensus building within the community when it comes to any feature which might be considered controversial or where there isn't unanimous decision that the proposed feature is the correct way to accomplish the task. The first place to pitch your ideas is to [Channel One](https://zothub.com/channel/one)]. Others may have some input or be able to point out facets of your concept which might be problematic in our environment. But also, you may encounter opposition to your plan. This doesn't mean you should stop and/or ignore the feature. Listen to the concerns of others and try and work through any implementation issues.
There are places where opposition cannot be resolved. In these cases, please consider making your feature **optional** or non-default behaviour that must be specifically enabled. This technique can often be used when a feature has significant but less than unanimous support. Those who desire the feature can turn it on and those who don't want it - will leave it turned off.
If a feature uses other networks or websites and or is only seen as desirable by a small minority of the community, consider making the functionality available via an addon or plugin. Once again, those who don't desire the feature won't need to install it. Plugins are relatively easy to create and "hooks" can be easily added or modified if the current hooks do not do what is needed to allow your plugin to work.
-
+ ``
diff --git a/doc/en/developer/versions.md b/doc/en/developer/versions.md
new file mode 100644
index 000000000..00384b295
--- /dev/null
+++ b/doc/en/developer/versions.md
@@ -0,0 +1,22 @@
+### Versions and releases
+
+Hubzilla currently uses a standard version numbering sequence of $x.$y(.$z), for example ‘1.12’ or ‘1.12.1’. The first digit is the major version number. Major versions are released ‘approximately’ once a year, often in December.
+
+The second digit is the minor version number. If this number is odd, it is a development version. If the number is even, it is a released version. Minor versions are usually released once a month (moved from the development version to the major version) when development is ‘stable’, but this is likely to increase. In the future, minor releases will be released between one and three months; this corresponds to a stable code point and when there is a general consensus in the community that the current code base is stable enough to consider a release.
+
+The last digit is an interface or patch identifier.
+
+The release process involves changing the version number (by definition, the minor version number is odd, and the minor number is incremented). Once a year, the major version number of a major version is incremented and the minor version number is reset to 0.
+
+The version candidate is moved to a new branch and testing begins or continues for a period of 1-2 weeks, or until all major issues have been resolved. This branch is usually labelled RC (Release Candidate); for example, 1.8RC stands for the upcoming release of version 1.8. At this point, the version number of the development branch is increased to the next odd number. (For example 1.9). New developments can then take place in the development branch.
+
+Bug fixes should always be applied to ‘dev’ and transferred from there (usually with git cherry-pick) to the RC branch and, if necessary, to the master or official release branch.
+
+At the time a release candidate is created, the language string file is frozen until a new version is released. Translation work can continue, but all translations should be submitted to ‘dev’ and merged into RC.
+Once RC testing is complete, RC will be merged into ‘master’ and the RC version identifier removed; this will result in a final checkin to change the version number. The CHANGELOG file should also be updated at this point or shortly before. If there are conflicts during this final merge, the merge is cancelled and ‘git merge -s ours’ is applied. This results in the master branch being replaced by the content of the RC branch. Conflicts often arise due to string updates that were made to master after the last release and cannot simply be resolved without manual editing. As this is a release of tested code, manual editing is not recommended and the replacement merge strategy should be used instead. It is assumed that RC now contains the latest, well-tested code.
+
+Once the release is live and merged into the master branch, the RC branch can be removed.
+
+After the release, corrections can be made to master. If possible, these should be made in dev and ‘git cherry-pick’ should be used to merge; this preserves the commit information and avoids conflicts when merging in the next cycle. Only rarely does a patch only apply to the master branch. If necessary, this can be done. If the change is major, the version number of the interface should be increased. This is at the discretion of the community. In any case, a ‘git pull’ of the master branch should always result in the latest version, to which all patches are applied after release.
+
+The interface number (the $z in $x.$y.$z) should be incremented in dev whenever a change is made that alters the interfaces or API in an incompatible way, so that any external packages (especially addons and API clients) that rely on the current behaviour can detect their own interfaces at the point where it has changed and change them accordingly. \ No newline at end of file
diff --git a/doc/en/developer/who_is_a_hubzilla_developer.md b/doc/en/developer/who_is_a_hubzilla_developer.md
new file mode 100644
index 000000000..a6047788d
--- /dev/null
+++ b/doc/en/developer/who_is_a_hubzilla_developer.md
@@ -0,0 +1,14 @@
+### Who is a Hubzilla developer? Should I be reading this?
+
+Anyone who contributes to making Hubzilla better is a developer. There are many different and important ways you can contribute to this amazing technology, *even if you don't know how to write code*. The software itself is only one part of the Hubzilla project. You can contribute by
+
+- translating the text into your language so that people all over the world have the opportunity to use Hubzilla
+- Promote Hubzilla and spread awareness of the platform through blog posts, articles and word of mouth
+- Create artwork and graphics for project resources such as icons and marketing materials
+- Supporting the project infrastructure such as the project website and demo servers
+
+*Software developers* are of course welcome; there are so many great ideas that can be realised and not enough people to make them all happen! The Hubzilla codebase is an advanced and mature system, but the platform is still very flexible and open to new ideas.
+
+We are pretty relaxed when it comes to developers. We don't have a lot of rules. Some of us are overworked, and if you want to help, we're happy to let you help. However, if you follow a few guidelines, the process will be smoother and collaboration easier. All developers are expected to adhere to our [code of conduct](https://hub.hubzilla.hu/help/developer/covenant). We have developers from all over the world with different skills, different cultural backgrounds and different levels of patience. Our number one rule is to respect others. Sometimes this is difficult, and sometimes we have very different views on how things should work, but if everyone makes an effort, we will get along well.
+
+This document will help you get to know and help shape Hubzilla. \ No newline at end of file
diff --git a/doc/en/developer/zot_protocol.bb b/doc/en/developer/zot_protocol.bb
deleted file mode 100644
index e9355bca8..000000000
--- a/doc/en/developer/zot_protocol.bb
+++ /dev/null
@@ -1,478 +0,0 @@
-[h3] What is Zot?[/h3]
-Zot is the revolutionary protocol that powers $Projectname, providing [b]communications[/b], [b]identity management[/b], and [b]access control[/b] across a fully [b]decentralised[/b] network of independent websites, often called "the grid". The resulting platform is a robust system that supports privacy and security while enabling the kind of rich web services typically seen only in centralized, proprietary solutions.
-
-Consider this typical scenario:
-
-Jaquelina wishes to share photos with Roberto from her blog at [b]jaquelina.org[/b], but to nobody else. Roberto maintains his own family hub at [b]roberto.net[/b] on a completely independent server. Zot allows Jaquelina to publish her photos using an [i]access control list (ACL)[/i] that includes only Roberto. That means that while Roberto can see the photos when he visits her blog, his brother Marco cannot, and neither can any of his other family members who have accounts on [b]roberto.net[/b].
-
-The magic in this scenario comes from the fact that Roberto never logged in to Jaquelina's website. Instead, he had to login only once using his password on his [i]own[/i] website at [b]roberto.net[/b]. When Roberto visits [b]jaquelina.org[/b], her hub seamlessly authenticates him by remotely querying his server in the background.
-
-It is not uncommon for servers to have technical problems or become inaccessible for a variety of reasons. Zot provides robustness for Roberto's online activities by allowing him to have [i]clones[/i] of his online identity, or [i]channel[/i], on multiple independent hubs. Imagine that Roberto's server crashes for some reason and he cannot log in there. He simply logs in to one of his clones at [b]gadfly.com[/b], a site operated by his friend Peter. Once authenticated at [b]gadfly.com[/b], Roberto can view Jaquelina's blog as before, without Jaquelina having to grant any additional access!
-
-[h4] Communications[/h4]
-Communications and social networking are an integral part of the grid. Any channel (and any services provided by that channel) can make full use of feature-rich social communications on a global scale. These communications may be public or private - and private communications comprise not only fully encrypted transport, but also encrypted storage to help protect against accidental snooping and disclosure by rogue system administrators and internet service providers.
-
-Zot supports a wide array of background services in the grid, from friend suggestions to directory services. New content and data updates are propagated in the background between hubs across the grid according to access control lists and permissions specified by both sender [i]and[/i] receiver channels. Data is also synchronized between an arbitrary number of channel clones, allowing hub members to access data and continue collaborating seamlessly in the event that their primary hub is inaccessible or offline.
-
-[h4] Identity [/h4]
-Zot's identity layer is unique. It provides [b]invisible single sign-on[/b] across all sites in the grid.
-
-It also provides [b]nomadic identity[/b], so that your communications with friends, family, and or anyone else you're communicating with won't be affected by the loss of your primary communication node - either temporarily or permanently.
-
-The important bits of your identity and relationships can be backed up to a thumb drive, or your laptop, and may appear at any node in the grid at any time - with all your friends and preferences intact.
-
-Crucially, these nomadic instances are kept in sync so any instance can take over if another one is compromised or damaged. This protects you against not only major system failure, but also temporary site overloads and governmental manipulation or censorship.
-
-Nomadic identity, single sign-on, and $Projectname's decentralisation of hubs, we believe, introduce a high degree of degree of [b]resiliency[/b] and [b]persistence[/b] in internet communications, that are sorely needed amidst global trends towards corporate centralization, as well as mass and indiscriminate government surveillance and censorship.
-
-As you browse the grid, viewing channels and their unique content, you are seamlessly authenticated as you go, even across completely different server hubs. No passwords to enter. Nothing to type. You're just greeted by name on every new site you visit.
-
-How does Zot do that? We call it [b]magic-auth[/b], because $Projectname hides the details of the complexities that go into single sign-on logins, and nomadic identities, from the experience of browsing on the grid. This is one of the design goals of $Projectname: to increase privacy, and freedom on the web, while reducing the complexity and tedium brought by the need to enter new passwords and login names for every different sight that someone might visit online. You login only once on your home hub (or any nomadic backup hub you have chosen). This allows you to access any authenticated services provided anywhere in the grid - such as shopping, blogs, forums, and access to private information. Your password isn't stored on a thousand different sites; it is stored on servers that you control or that you have chosen to trust.
-
-You cannot be silenced. You cannot be removed from the grid unless you yourself choose to exit it.
-
-[h4] Access Control[/h4]
-Zot's identity layer allows you to provide fine-grained permissions to any content you wish to publish - and these permissions extend across the grid. This is like having one super huge website made up of an army of small individual websites - and where each channel in the grid can completely control their privacy and sharing preferences for any web resources they create.
-
-Currently, $Projectname supports access control for many types of data, including post/comment discussion threads, photo albums, events, cloud files, web pages, wikis, and more. Every object and how it is shared and with whom is completely under your control.
-
-This type of control is trivial on large corporate providers because they own the user database. Within the grid, there is no need for a huge user database on your machine - because the grid [b]is[/b] your user database. It has what is essentially infinite capacity (limited by the total number of hubs online across the internet), and is spread amongst hundreds, and potentially millions of computers.
-
-Access can be granted or denied for any resource, to any channel, or any group of channels; anywhere within the grid. Others can access your content if you permit them to do so, and they do not even need to have an account on your hub.
-
-[h3]Technical Introduction[/h3]
-Zot is a JSON-based web framework for implementing secure decentralised communications and services. In order to provide this functionality, Zot creates a decentralised globally unique identifier for each hub on the network. This global identifier is not linked inextricably to DNS, providing the requisite mobility. Many existing decentralised communications frameworks provide the communication aspect, but do not provide remote access control and authentication. Additionally most of these are based on 'webfinger', which still binds identity to domain names and cannot support nomadic identity.
-
-The primary issues Zot addresses are
-
-[list]
-[*] completely decentralised communications
-[*] independence from DNS-based identity
-[*] node mobility
-[*] seamless remote authentication
-[*] high performance
-[/list]
-We will rely on DNS-based user@host addresses as a "user-friendly" mechanism to let people know where you are, specifically on a named host with a given username, and communication will be carried out to DNS entities using TCP and the web.
-
-But the underlying protocol will provide an abstraction layer on top of this, so that a communications node (e.g. "identity") can move to an alternate DNS location and (to the best of our ability) gracefully recover from site re-locations and/or maintain pre-existing communication relationships. A side effect of this requirement is the ability to communicate from alternate/multiple DNS locations and service providers and yet maintain a single online identity.
-
-We will call this overlay network the "grid". Servers attached to this network are known as "hubs" and may support any number of individual identities.
-
-An identity does not necessarily correspond to a person. It is just something which requires the ability to communicate within the grid.
-
-The ability to recover will be accomplished by communication to the original location when creating a new or replacement identity, or as a fallback from a stored file describing the identity and their contacts in the case where the old location no longer responds.
-
-At least on the short term, the mobility of existing content is not a top priority. This may or may not take place at a later stage. The most important things we want to keep are your identity and your friends.
-
-Addresses which are shared amongst people are user@host, and which describe the [b]current[/b] local account credentials for a given identity. These are DNS based addresses and used as a seed to locate a given identity within the grid. The machine communications will bind this address to a globally unique ID. A single globally unique ID may be attached or bound to any number of DNS locations. Once an identity has been mapped or bound to a DNS location, communications will consist of just knowing the globally unique address, and what DNS (url) is being used currently (in order to call back and verify/complete the current communication). These concepts will be specified in better detail.
-
-In order for an identity to persist across locations, one must be able to provide or recover
-
-[list]
-[*] the globally unique ID for that identity
-[*] the private key assigned to that identity
-[*] (if the original server no longer exists) an address book of contacts for that identity.
-[/list]
-This information will be exportable from the original server via API, and/or downloadable to disk or thumb-drive.
-
-We may also attempt to recover with even less information, but doing so is prone to account hijacking and will require that your contacts confirm the change.
-
-In order to implement high performance communications, the data transfer format for all aspects of Zot is JSON. XML communications require way too much overhead.
-
-Bi-directional encryption is based on RSA 4096-bit keys expressed in DER/ASN.1 format using the PKCS#8 encoding variant, with AES encryption of variable length or large items. The precise encryption algorithms are negotiable between sites.
-
-Some aspects of well known "federation protocols" (webfinger, salmon, activitystreams, portablecontacts, etc.) may be used in zot, but we are not tied to them and will not be bound by them. $Projectname project is attempting some rather novel developments in decentralised communications and if there is any need to diverge from such "standard protocols" we will do so without question or hesitation.
-
-In order to create a globally unique ID, we will base it on a whirlpool hash of the identity URL of the origination node and a psuedo-random number, which should provide us with a 256 bit ID with an extremely low probability of collision (256 bits represents approximately 115 quattuorviginitillion or 1.16 X 10^77 unique numbers). This will be represented in communications as a base64url-encoded string. We will not depend on probabilities however and the ID must also be attached to a public key with public key cryptography used to provide an assurance of identity which has not been copied or somehow collided in whirlpool hash space.
-
-Basing this ID on DNS provides a globally unique seed, which would not be the case if it was based completely on psuedo-random number generation.
-
-We will refer to the encoded globally unique uid string as zot_uid
-
-As there may be more than one DNS location attached/bound to a given zot_uid identity, delivery processes should deliver to all of them - as we do not know for sure which hub instance may be accessed at any given time. However we will designate one DNS location as "primary" and which will be the preferred location to view web profile information.
-
-We must also provide the ability to change the primary to a new location. A look-up of information on the current primary location may provide a "forwarding pointer" which will tell us to update our records and move everything to the new location. There is also the possibility that the primary location is defunct and no longer responding. In that case, a location which has already been mapped to this zot_uid can seize control, and declare itself to be primary. In both cases the primary designation is automatically approved and moved. A request can also be made from a primary location which requests that another location be removed.
-
-In order to map a zot_uid to a second (or tertiary) location, we require a secure exchange which verifies that the new location is in possession of the private key for this zot_uid. Security of the private key is thus essential to avoid hijacking of identities.
-
-Communications will then require
-[list]
-[*] zot_uid (string)
-[*] uid_sig
-[*] callback (current location Zot endpoint url)
-[*] callback_sig
-[*] spec (int)
-[/list]
-passed with every communique. The spec is a revision number of the applicable Zot spec so that communications can be maintained with hubs using older and perhaps incompatible protocol specifications. Communications are verified using a stored public key which was copied when the connection to this zot_uid was first established.
-
-Key revocation and replacement must take place from the primary hub. The exact form for this is still being worked out, but will likely be a notification to all other bound hubs to "phone home" (to the primary hub) and request a copy of the new key. This communique should be verified using a site or hub key; as the original identity key may have been compromised and cannot be trusted.
-
-In order to eliminate confusion, there should be exactly one canonical url for any hub, so that these can be indexed and referenced without ambiguity.
-
-So as to avoid ambiguity of scheme, it is strongly encouraged that all addresses to be https with a "browser valid" cert and a single valid host component (either www.domain.ext or domain.ext, but not both), which is used in all communications. Multiple URLs may be provided locally, but only one unique URL should be used for all Zot communications within the grid.
-
-Test installation which do not connect to the public grid may use non-SSL, but all traffic flowing over public networks should be safe from session-hijacking, preferably with a "browser recognised" cert.
-
-Where possible, Zot encourages the use of "batching" to minimise network traffic between two hubs. This means that site 'A' can send multiple messages to site 'B' in a single transaction, and also consolidate deliveries of identical messages to multiple recipients on the same hub.
-
-Messages themselves may or may not be encrypted in transit, depending on the private nature of the messages. SSL (strongly encouraged) provides unconditional encryption of the data stream, however there is little point in encrypting public communications which have been designated as having unrestricted visibility. The encryption of data storage and so-called "end-to-end encryption" is outside the scope of zot. It is presumed that hub operators will take adequate safeguards to ensure the security of their data stores and these are functions of application and site integrity as opposed to protocol integrity.
-
-
-[h4]Messages[/h4]
-
-Given the constraints listed previously, a Zot communique should therefore be a json array of individual messages. These can be mixed and combined within the same transmission.
-
-Each message then requires:
-[list]
-[*] type
-[*] (optional) recipient list
-[/list]
-Lack of a recipient list would indicate an unencrypted public or site level message. The recipient list would contain an array of zot_uid with an individual decryption key, and a common iv. The decryption key is encoded with the recipient identity's public key. The iv is encrypted with the sender's private key.
-
-All messages should be digitally signed by the sender.
-
-The type can be one of (this list is extensible):
-[list]
-[*] post (or activity)
-[*] mail
-[*] identity
-[*] authenticate
-[/list]
-Identity messages have no recipients and notify the system social graph of an identity update, which could be a new or deleted identity, a new or deleted location, or a change in primary hub. The signature for these messages uses system keys as opposed to identity-specific keys.
-
-Posts include many different types of activities, such as top-level posts, likes/dislikes, comments, tagging activities, etc. These types are indicated within the message sturcture.
-
-authenticate messages result in mutual authentication and browser redirect to protected resources on the remote hub such as the ability to post wall-to-wall messages and view private photo albums and events, etc.
-
-[h4]Discovery[/h4]
-
-A well-known url is used to probe a hub for Zot capabilities and identity lookups, including the discovery of public keys, profile locations, profile photos, and primary hub location.
-
-The location for this service is /.well-known/zot-info - and must be available at the root of the chosen domain.
-
-To perform a lookup, a POST request is made to the discovery location with the following parameters:
-
-Required:
-
-address => an address on the target system such as "john"
-
-Optional
-
-target => the Zot "guid" of the observer for evaluating permissions
-
-target_sig => an RSA signature (base64url encoded) of the guid
-
-key => The public key needed to verify the signature
-
-token => a string (possibly random) chosen by the requesting service. If provided, an entry in the discovered packet will be provided called 'signed_token' which consists of the base64url_encoded RSA signature of the concatenation of the string 'token.' and the provided token using the private key of the discovered channel. This can be verified using the provided 'key' entry, and provides assurance that the server is in possession of the private key for the discovered identity. After 2017-01-01 it is [b]required[/b] that a server provide a signed_token [i]if[/i] a token was provided in the request.
-
-With no target provided, the permissions returned will be generic permissions
-for unknown or unauthenticated observers
-
-Example of discovery packet for 'mike@zothub.com'
-[code nowrap]
- {
-
- "success": true,
- "signed_token": "KBJrKTq1qrctNuxF3GwVh3GAGRqmgkirlXANPcJZAeWlvSt_9TMV097slR4AYnYCBEushbVqHEJ9Rb5wHTa0HzMbfRo8cRdl2yAirvvv5d98dtwHddQgX1jB0xEypXtmIYMdPGDLvhI1RNdIBhHkkrRcNreRzoy4xD--HM6m1W0-A8PJJJ9BcNxmGPcBtLzW08wzoP9trJ3M7DQ6Gkk6j7iwVsyApw1ZBaDvabGTdc_SFV-Iegtqw3rjzT_xXWsfzMlKBy-019MYn_KS-gu23YzjvGu5tS_zDfkQb8DMUlPLz5yyxM0yOMlUDtG2qQgIJAU2O0X6T5xDdJ6mtolNyhepg845PvFDEqBQGMIH1nc47CNumeudDi8IWymEALhjG_U8KAK7JVlQTJj2EKUb0au1g6fpiBFab5mmxCMtZEX3Jreyak5GOcFFz-WpxuXJD9TdSoIvaBfBFOoJnXkg2zE4RHXeQzZ2FotmrbBG5dm8B-_6byYGoHBc08ZsWze1K96JIeRnLpBaj6ifUDcVHxZMPcGHHT27dvU2PNbgLiBjlAsxhYqkhN5qOHN8XBcg2KRjcMBaI3V0YMxlzXz5MztmZq3fcB1p-ccIoIyMPMzSj3yMB7J9CEU2LYPSTHMdPkIeDE6GaCkQKviaQQJQde346tK_YjA2k7_SOBmvPYE",
- "guid": "sebQ-IC4rmFn9d9iu17m4BXO-kHuNutWo2ySjeV2SIW1LzksUkss12xVo3m3fykYxN5HMcc7gUZVYv26asx-Pg",
- "guid_sig": "Llenlbl4zHo6-g4sa63MlQmTP5dRCrsPmXHHFmoCHG63BLq5CUZJRLS1vRrrr_MNxr7zob_Ykt_m5xPKe5H0_i4pDj-UdP8dPZqH2fqhhx00kuYL4YUMJ8gRr5eO17vsZQ3XxTcyKewtgeW0j7ytwMp6-hFVUx_Cq08MrXas429ZrjzaEwgTfxGnbgeQYQ0R5EXpHpEmoERnZx77VaEahftmdjAUx9R4YKAp13pGYadJOX5xnLfqofHQD8DyRHWeMJ4G1OfWPSOlXfRayrV_jhnFlZjMU7vOdQwHoCMoR5TFsRsHuzd-qepbvo3pzvQZRWnTNu6oPucgbf94p13QbalYRpBXKOxdTXJrGdESNhGvhtaZnpT9c1QVqC46jdfP0LOX2xrVdbvvG2JMWFv7XJUVjLSk_yjzY6or2VD4V6ztYcjpCi9d_WoNHruoxro_br1YO3KatySxJs-LQ7SOkQI60FpysfbphNyvYMkotwUFI59G08IGKTMu3-GPnV1wp7NOQD1yzJbGGEGSEEysmEP0SO9vnN45kp3MiqbffBGc1r4_YM4e7DPmqOGM94qksOcLOJk1HNESw2dQYWxWQTBXPfOJT6jW9_crGLMEOsZ3Jcss0XS9KzBUA2p_9osvvhUKuKXbNztqH0oZIWlg37FEVsDs_hUwUJpv2Ar09k4",
- "key": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7QCwvuEIwCHjhjbpz3Oc\ntyei/Pz9nDksNbsc44Cm8jxYGMXsTPFXDZYCcCB5rcAhPPdZSlzaPkv4vPVcMIrw\n5cdX0tvbwa3rNTng6uFE7qkt15D3YCTkwF0Y9FVZiZ2Ko+G23QeBt9wqb9dlDN1d\nuPmu9BLYXIT/JXoBwf0vjIPFM9WBi5W/EHGaiuqw7lt0qI7zDGw77yO5yehKE4cu\n7dt3SakrXphL70LGiZh2XGoLg9Gmpz98t+gvPAUEotAJxIUqnoiTA8jlxoiQjeRK\nHlJkwMOGmRNPS33awPos0kcSxAywuBbh2X3aSqUMjcbE4cGJ++/13zoa6RUZRObC\nZnaLYJxqYBh13/N8SfH7d005hecDxWnoYXeYuuMeT3a2hV0J84ztkJX5OoxIwk7S\nWmvBq4+m66usn6LNL+p5IAcs93KbvOxxrjtQrzohBXc6+elfLVSQ1Rr9g5xbgpub\npSc+hvzbB6p0tleDRzwAy9X16NI4DYiTj4nkmVjigNo9v2VPnAle5zSam86eiYLO\nt2u9YRqysMLPKevNdj3CIvst+BaGGQONlQalRdIcq8Lin+BhuX+1TBgqyav4XD9K\nd+JHMb1aBk/rFLI9/f2S3BJ1XqpbjXz7AbYlaCwKiJ836+HS8PmLKxwVOnpLMbfH\nPYM8k83Lip4bEKIyAuf02qkCAwEAAQ==\n-----END PUBLIC KEY-----\n",
- "name": "Mike Macgirvin",
- "name_updated": "2012-12-06 04:59:13",
- "address": "mike@zothub.com",
- "photo_mimetype": "image/jpeg",
- "photo": "https://zothub.com/photo/profile/l/1",
- "photo_updated": "2012-12-06 05:06:11",
- "url": "https://zothub.com/channel/mike",
- "connections_url": "https://zothub.com/poco/mike",
- "target": "",
- "target_sig": "",
- "searchable": false,
- "permissions": {
- "view_stream": true,
- "view_profile": true,
- "view_photos": true,
- "view_contacts": true,
- "view_storage": true,
- "view_pages": true,
- "send_stream": false,
- "post_wall": false,
- "post_comments": false,
- "post_mail": false,
- "post_photos": false,
- "tag_deliver": false,
- "chat": false,
- "write_storage": false,
- "write_pages": false,
- "delegate": false
- },
- "profile": {
- "description": "Freedom Fighter",
- "birthday": "0000-05-14",
- "next_birthday": "2013-05-14 00:00:00",
- "gender": "Male",
- "marital": "It's complicated",
- "sexual": "Females",
- "locale": "",
- "region": "",
- "postcode": "",
- "country": "Australia"
- },
- "locations": [
- {
- "host": "zothub.com",
- "address": "mike@zothub.com",
- "primary": true,
- "url": "https://zothub.com",
- "url_sig": "eqkB_9Z8nduBYyyhaSQPPDN1AhSm5I4R0yfcFxPeFpuu17SYk7jKD7QzvmsyahM5Kq7vDW6VE8nx8kdFYpcNaurqw0_IKI2SWg15pGrhkZfrCnM-g6A6qbCv_gKCYqXvwpSMO8SMIO2mjQItbBrramSbWClUd2yO0ZAceq3Z_zhirCK1gNm6mGRJaDOCuuTQNb6D66TF80G8kGLklv0o8gBfxQTE12Gd0ThpUb5g6_1L3eDHcsArW_RWM2XnNPi_atGNyl9bS_eLI2TYq0fuxkEdcjjYx9Ka0-Ws-lXMGpTnynQNCaSFqy-Fe1aYF7X1JJVJIO01LX6cCs-kfSoz29ywnntj1I8ueYldLB6bUtu4t7eeo__4t2CUWd2PCZkY3PKcoOrrnm3TJP5_yVFV_VpjkcBCRj3skjoCwISPcGYrXDufJxfp6bayGKwgaCO6QoLPtqqjPGLFm-fbn8sVv3fYUDGilaR3sFNxdo9mQ3utxM291XE2Pd0jGgeUtpxZSRzBuhYeOybu9DPusID320QbgNcbEbEImO8DuGIxuVRartzEXQF4WSYRdraZzbOqCzmU0O55P836JAfrWjgxTQkXlYCic-DBk-iE75JeT72smCtZ4AOtoFWCjZAABCw42J7JELY9APixZXWriKtjy6JI0G9d3fs6r7SrXr1JMy0",
- "callback": "https://zothub.com/post",
- "sitekey": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1IWXwd/BZuevq8jzNFoR\n3VkenduQH2RpR3Wy9n4+ZDpbrUKGJddUGm/zUeWEdKMVkgyllVA/xHdB7jdyKs1X\nuIet9mIdnzvhdLO/JFD5hgbNG2wpSBIUY6aSNeCFTzszqXmuSXMW5U0Ef5pCbzEA\nnhoCoGL1KAgPqyxnGKUlj7q2aDwC9IRNtAqNyFQL67oT91vOQxuMThjlDhbR/29Q\ncYR4i1RzyahgEPCnHCPkT2GbRrkAPjNZAdlnk9UesgP16o8QB3tE2j50TVrbVc/d\nYRbzC56QMPP9UgUsapNeSJBHji75Ip/E5Eg/kfJC/HEQgyCqjCGfb7XeUaeQ7lLO\nqc7CGuMP+Jqr/cE54/aSHg8boTwxkMp11Ykb+ng17fl57MHTM2RJ99qZ1KBkXezR\nuH1lyvjzeJPxEFr9rkUqc4GH74/AgfbgaFvQc8TS7ovEa5I/7Pg04m7vLSEYc6UF\nYJYxXKrzmZT2TDoKeJzaBBx5MFLhW19l68h9dQ8hJXIpTP0hJrpI+Sr6VUAEfFQC\ndIDRiFcgjz6j7T/x8anqh63/hpsyf2PMYph1+4/fxtSCWJdvf+9jCRM8F1IDfluX\n87gm+88KBNaklYpchmGIohbjivJyru41CsSLe0uinQFvA741W00w6JrcrOAX+hkS\nRQuK1dDVwGKoIY85KtTUiMcCAwEAAQ==\n-----END PUBLIC KEY-----\n"
- }
- ],
- "site": {
- "url": "https://zothub.com",
- "directory_mode": "primary",
- "directory_url": "https://zothub.com/dirsearch"
- }
-
- }
-[/code]
-
-
-Discovery returns a JSON array with the following components:
-
-'success' => (true or false) Operation was successful if true. Otherwise an optional 'message' may be present indicating the source of error.
-
-'signed_token' => If a token parameter was provided in the request, it is prepended with the text 'token.' and then RSA signed with the channel private key and base64url encoded and returned as 'signed_token'.
-
-'guid' => the guid of the address on the target system
-
-'guid_sig' => the base64url encoded RSA signature of the guid, signed with the private key associated with that guid.
-
-'key' => the public key associated with that guid
-
-'name' => channel name
-
-'name_updated' => MySQL style timestamp '2012-01-01 00:00:00' when the name was last changed (UTC)
-
-'address' => "webbie" or user@host address associated with this channel
-
-'photo' => URL of a profile photo for this channel (ideally 175x175)
-
-'photo_mimetype' => content-type of the profile photo
-
-'photo_updated' => MySQL style timestamp when photo was last updated (UTC)
-
-'url' => location of channel homepage
-
-'connections_url' => location of Portable Contacts (extended for zot) URL for this channel
-
-'target' => if a permissions target was specified, it is mirrored
-
-'target_sig' => if a permissions target was specified, the signature is mirrored.
-
-'searchable' => (true or false) true indicates this entry can be searched in a directory
-
-[h5]Permissions[/h5]
-
-'permissions' => extensible array of permissions appropriate to this target, values are true or false
-
- Permissions may include:
-[list]
-[*] view_stream
-[*] view_profile
-[*] view_photos
-[*] view_contacts
-[*] view_storage
-[*] view_pages
-[*] send_stream
-[*] post_wall
-[*] post_comments
-[*] post_mail
-[*] post_photos
-[*] tag_deliver
-[*] chat
-[*] write_storage
-[*] write_pages
-[*] delegate
-[/list]
-
-
-[h5]Profile[/h5]
-
-
-'profile' => array of important profile fields
-[list]
-[*] description
-[*] birthday YYYY-MM-DD , all fields are optional, any field (such as year) may be zero
-[*] next_birthday => MySQL datetime string representing the next upcoming birthday, converted from the channel's default timezone to UTC.
-[*] gender (free form)
-[*] marital (marital status)
-[*] sexual (preference)
-[*] locale (city)
-[*] region (state)
-[*] postcode
-[*] country
-[/list]
-
-[h5]Locations[/h5]
-
-
-'locations' => array of registered locations (DNS locations) this channel may be visible or may be posting from
-
-Each location is an array of
-
-'host' => DNS hostname, e.g. example.com
-
-'address' => the webbie or user@host identifier associated with this location
-
-'primary' => (true or false) whether or not this is the primary location for this channel where files and web pages are generally found
-
-'url' => url of the root of this DNS location e.g. https://example.com
-
-'url_sig' => base64url encoded RSA signature of the URL, signed with the channel private key
-
-'callback' => Zot communications endpoint on this site, usually https://example.com/post
-
-'sitekey' => public key of this site/host
-
-
-[h5]Site[/h5]
-
-
-'site' => array providing the directory role of the site responding to this request
-
-'url' => url of this site e.g. https://example.com
-
-'directory_mode' => one of 'primary', 'secondary', 'normal', or 'standalone'
-
-'directory_url' => if this is a directory server or standalone, the URL for the directory search module
-
-
-
-[h3]Magic Auth[/h3]
-
-So-called "magic auth" takes place by a special exchange. On the remote computer, a redirection is made to the Zot endpoint with special GET parameters.
-
-Endpoint: https://example.com/post/name
-
-where 'name' is the left hand side of the channel webbie, for instance 'mike' where the webbie is 'mike@zothub.com'
-
-Additionally four parameters are supplied:
-[list]
-[*] auth => the webbie of the person requesting access
-[*] dest => the desired destination URL (urlencoded)
-[*] sec => a random string which is also stored locally for use during the verification phase.
-[*] version => the Zot revision
-[/list]
-When this packet is received, a Zot message is initiated to the auth identity:
-
-[code nowrap]
- {
- "type":"auth_check",
- "sender":{
- "guid":"kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA",
- "guid_sig":"PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK-R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q",
- "url":"http:\/\/podunk.edu",
- "url_sig":"T8Bp7j5DHHhQDCFcAHXfuhUfGk2P3inPbImwaXXF1xJd3TGgluoXyyKDx6WDm07x0hqbupoAoZB1qBP3_WfvWiJVAK4N1FD77EOYttUEHZ7L43xy5PCpojJQmkppGbPJc2jnTIc_F1vvGvw5fv8gBWZvPqTdb6LWF6FLrzwesZpi7j2rsioZ3wyUkqb5TDZaNNeWQrIEYXrEnWkRI_qTSOzx0dRTsGO6SpU1fPWuOOYMZG8Nh18nay0kLpxReuHCiCdxjXRVvk5k9rkcMbDBJcBovhiSioPKv_yJxcZVBATw3z3TTE95kGi4wxCEenxwhSpvouwa5b0hT7NS4Ay70QaxoKiLb3ZjhZaUUn4igCyZM0h6fllR5I6J_sAQxiMYD0v5ouIlb0u8YVMni93j3zlqMWdDUZ4WgTI7NNbo8ug9NQDHd92TPmSE1TytPTgya3tsFMzwyq0LZ0b-g-zSXWIES__jKQ7vAtIs9EwlPxqJXEDDniZ2AJ6biXRYgE2Kd6W_nmI7w31igwQTms3ecXe5ENI3ckEPUAq__llNnND7mxp5ZrdXzd5HHU9slXwDShYcW3yDeQLEwAVomTGSFpBrCX8W77n9hF3JClkWaeS4QcZ3xUtsSS81yLrp__ifFfQqx9_Be89WVyIOoF4oydr08EkZ8zwlAsbZLG7eLXY"
- },
- "recipients":{
- {
- "guid":"ZHSqb3yGar3TYV_o9S-JkD-6o_V4DhUcxtv0VeyX8Kj_ENHPI_M3SyAUucU835-mIayGMmTpqJz3ujPkcavVhA",
- "guid_sig":"JsAAXigNghTkkbq8beGMJjj9LBKZn28hZ-pHSsoQuwYWvBJ2lSnfc4r9l--WgO6sucH-SR6RiBo78eWn1cZrh_cRMu3x3LLx4y-tjixg-oOOgtZakkBg4vmOhkKPkci0mFtzvUrpY4YHySqsWTuPwRx_vOlIYIGEY5bRXpnkNCoC8J4EJnRucDrgSARJvA8QQeQQL0H4mWEdGL7wlsZp_2VTC6nEMQ48Piu6Czu5ThvLggGPDbr7PEMUD2cZ0jE4SeaC040REYASq8IdXIEDMm6btSlGPuskNh3cD0AGzH2dMciFtWSjmMVuxBU59U1I6gHwcxYEV6BubWt_jQSfmA3EBiPhKLyu02cBMMiOvYIdJ3xmpGoMY1Cn__vhHnx_vEofFOIErb6nRzbD-pY49C28AOdBA5ffzLW3ss99d0A-6GxZmjsyYhgJu4tFUAa7JUl84tMbq28Tib0HW6qYo6QWw8K1HffxcTpHtwSL5Ifx0PAoGMJsGDZDD1y_r9a4vH5pjqmGrjL3RXJJUy-m4eLV5r7xMWXsxjqu3D8r04_dcw4hwwexpMT1Nwf8CTB0TKb8ElgeOpDFjYVgrqMYWP0XdhatcFtAJI7gsS-JtzsIwON9Kij66-VAkqy_z1IXI0ziyqV1yapSVYoUV1vMScRZ_nMqwiB5rEDx-XLfzko"
- }
- }
- "callback":"\/post",
- "version":1,
- "secret":"1eaa6613699be6ebb2adcefa5379c61a3678aa0df89025470fac871431b70467",
- "secret_sig":"eKV968b1sDkOVdSMi0tLRtOhQw4otA8yFKaVg6cA4I46_zlAQGbFptS-ODiZlSAqR7RiiZQv4E2uXCKar52dHo0vvNKbpOp_ezWYcwKRu1shvAlYytsflH5acnDWL-FKOOgz5zqLLZ6cKXFMoR1VJGG_Od-DKjSwajyV9uVzTry58Hz_w0W2pjxwQ-Xv11rab5R2O4kKSW77YzPG2R5E6Q7HN38FrLtyWD_ai3K9wlsFOpwdYC064dk66X7BZtcIbKtM6zKwMywcfJzvS5_0U5yc5GGbIY_lY6SViSfx9shOKyxkEKHfS29Ynk9ATYGnwO-jnlMqkJC7t149H-sI9hYWMkLuCzaeLP56k2B2TmtnYvE_vHNQjzVhTwuHCIRVr-p6nplQn_P3SkOpYqPi3k_tnnOLa2d3Wtga8ClEY90oLWFJC3j2UkBf_VEOBNcg-t5XO3T-j9O4Sbk96k1Qoalc-QlznbGx4bOVsGkRBBMiH4YUqiiWB_OkFHtdqv7dqGeC-u-B4u9IxzYst46vvmyA3O-Q4APSZ1RY8ITUH0jLTbh6EAV7Oki8pIbOg0t56p-8RlanOZqmFvR-grVSc7Ak1ZcD8NACmvidUpa1B7WEvRcOeffx9lype0bt5XenDnMyx6szevwxZIiM8qGM2lsSk4fu8HI9cW0mLywzZT0"
- }
-[/code]
-
-auth_check messages MUST be encrypted. This message is sent to the origination site, which checks the 'secret' to see if it is the same as the 'sec' which it passed originally. It also checks the secret_sig which is the secret signed by the destination channel's private key and base64url encoded. If everything checks out, a json packet is returned:
-[code nowrap]
- {
- "success":1,
- "confirm":"q0Ysovd1uQRsur2xG9Tg6bC23ynzw0191SkVd7CJcYoaePy6e_v0vnmPg2xBUtIaHpx_aSuhgAkd3aVjPeaVBmts6aakT6a_yAEy7l2rBydntu2tvrHhoVqRNOmw0Q1tI6hwobk1BgK9Pm0lwOeAo8Q98BqIJxf47yO9pATa0wktOg6a7LMogC2zkkhwOV5oEqjJfeHeo27TiHr1e2WaphfCusjmk27V_FAYTzw05HvW4SPCx55EeeTJYIwDfQwjLfP4aKV-I8HQCINt-2yxJvzH7Izy9AW-7rYU0Il_gW5hrhIS5MTM12GBXLVs2Ij1CCLXIs4cO0x6e8KEIKwIjf7iAu60JPmnb_fx4QgBlF2HLw9vXMwZokor8yktESoGl1nvf5VV5GHWSIKAur3KPS2Tb0ekNh-tIk9u-xob4d9eIf6tge_d3aq1LcAtrDBDLk8AD0bho5zrVuTmZ9k-lBVPr_DRHSV_dlpu088j3ThaBsuV1olHK3vLFRhYCDIO0CqqK5IuhqtRNnRaqhlNN6fQUHpXk2SwHiJ2W36RCYMTnno6ezFk_tN-RA2ly-FomNZoC5FPA9gFwoJR7ZmVFDmUeK3bW-zYTA5vu15lpBPnt7Up_5rZKkr0WQVbhWJmylqOuwuNWbn3SrMQ8rYFZ23Tv300cOfKVgRBaePWQb4"
- }
-[/code]
-'confirm' in this case is the base64url encoded RSA signature of the concatenation of 'secret' with the base64url encoded whirlpool hash of the source guid and guid_sig; signed with the source channel private key. This prevents a man-in-the-middle from inserting a rogue success packet. Upon receipt and successful verification of this packet, the destination site will redirect to the original destination URL and indicate a successful remote login.
-
-[h3]Zot Structures[/h3]
-[h4]Zot Signatures[/h4]
-All signed data in Zot is accomplished by performing an RSA sign operation using the private key of the initiator. The binary result is then base64url encoded for transport.
-[h4]Zot Encryption[/h4]
-Encryption is currently provided by AES256CTR. Additional algorithms MAY be supported. A 32-octet key and 16-octet initialisation vector are randomly generated. The desired data is then encrypted using these generated strings and the result base64url encoded. Then we build an array:
-
-[dl terms="b"]
-[*= data]The base64url encoded encrypted data
-[*= alg]The chosen algorithm, in this case the string 'aes256ctr'.
-[*= key]The randomly generated key, RSA encrypted using the recipients public key, and the result base64url encoded
-[*= iv]The randomly generated initialization vector, RSA encrypted using the recipient's public key, and the result base64url encoded
-[/dl]
-
-[h4]Basic Zot Packet[/h4]
-Used for initiating a dialogue with another Zot site. This packet MAY be encrypted. The presence of an array element 'iv' indicates encryption has been applied. When sending an 'auth_check' packet type, this packet MUST be encrypted, using the public key of the destination site (the site key, as opposed to a sender key).
-
-[code nowrap]
- {
- "type":"notify",
- "sender":{
- "guid":"kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA",
- "guid_sig":"PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK-R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q",
- "url":"http:\/\/podunk.edu",
- "url_sig":"T8Bp7j5DHHhQDCFcAHXfuhUfGk2P3inPbImwaXXF1xJd3TGgluoXyyKDx6WDm07x0hqbupoAoZB1qBP3_WfvWiJVAK4N1FD77EOYttUEHZ7L43xy5PCpojJQmkppGbPJc2jnTIc_F1vvGvw5fv8gBWZvPqTdb6LWF6FLrzwesZpi7j2rsioZ3wyUkqb5TDZaNNeWQrIEYXrEnWkRI_qTSOzx0dRTsGO6SpU1fPWuOOYMZG8Nh18nay0kLpxReuHCiCdxjXRVvk5k9rkcMbDBJcBovhiSioPKv_yJxcZVBATw3z3TTE95kGi4wxCEenxwhSpvouwa5b0hT7NS4Ay70QaxoKiLb3ZjhZaUUn4igCyZM0h6fllR5I6J_sAQxiMYD0v5ouIlb0u8YVMni93j3zlqMWdDUZ4WgTI7NNbo8ug9NQDHd92TPmSE1TytPTgya3tsFMzwyq0LZ0b-g-zSXWIES__jKQ7vAtIs9EwlPxqJXEDDniZ2AJ6biXRYgE2Kd6W_nmI7w31igwQTms3ecXe5ENI3ckEPUAq__llNnND7mxp5ZrdXzd5HHU9slXwDShYcW3yDeQLEwAVomTGSFpBrCX8W77n9hF3JClkWaeS4QcZ3xUtsSS81yLrp__ifFfQqx9_Be89WVyIOoF4oydr08EkZ8zwlAsbZLG7eLXY"
- "sitekey":"-----BEGIN PUBLIC KEY-----
-MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxTeIwXZWrw/S+Ju6gewh
-LgkKnNNe2uCUqCqMZoYgJar3T5sHCDhvXc4dDCbDkxVIaA/+V1mURtBV60a3IGjn
-OAO0W0XGGLe2ED7G5o9U8T9mVGq8Mauv0v1oQ5wIR1gEAhBavkQ2OUGuF/YKn2nj
-HlKsv9HzUAHpcDMUe3Uklc2RhQbMcnJxEgkyjCkDyrTtCZzISkTAocHvpCG1KSog
-njUZdiz9UWxvM4rCFkCJvQU4RwRZJb7GA9ul+9JrF7NvUQTx8csRP2weBk1E9yyj
-wbe187E0eVj9RXX2Mx3mYhgrTdodxLOVMSXZLg1/SMpeFFl7QBhuM0SiOPg8a7Et
-e2iNA/RD4WiUFqCDfafasRa1TOozOm7LA+08lkAh5PeQPJsJbrX0wVVft++Y+5/z
-BvcUOP73vcbz7j5hJ7HLsbQtye/UUCfODBFybuDqRM84Aet8rjZohX7vukXdMD4I
-2HuB7pjR4uIfyYr0J63ANkvbsn8LR+RnirmHrK5H/OgxxjXcfYbGEQgFxvxhF6lA
-FpMu6Do4dx3CIb6pRmZ8bjSImXexJ0BSo9n3gtrz0XYLecsYFlQ9+QQjm83qxyLb
-M23in0xqMVsyQvzjNkpImrO/QdbEFRIIMee83IHq+adbyjQR49Z2hNEIZhkLPc3U
-2cJJ2HkzkOoF2K37qwIzk68CAwEAAQ==
------END PUBLIC KEY-----
-"
- },
- "recipients":{
- {
- "guid":"lql-1VnxtiO4-WF0h72wLX1Fu8szzHDOXgQaTbELwXW77k8AKFfh-hYr70vqMrc3SSvWN-Flrc5HFhRTWB7ICw",
- "guid_sig":"PafvEL0VpKfxATxlCqDjfOeSIMdmpr3iU7X-Sysa1h5LzDpjSXsjO37tYZL-accb1M5itLlfnW5epkTa5I4flsW21zSY1A2jCuBQUTLLGV7rNyyBy7lgqJUFvAMRx0TfXzP9lcaPqlM9T1tA6jfWOsOmkdzwofGeXBnsjGfjsO2xdGYe6vwjOU0DSavukvzDMnOayB9DekpvDnaNBTxeGLM45Skzr7ZEMcNF7TeXMbnvpfLaALYEKeQs9bGH-UgAG8fBWgzVAzeBfx_XSR1rdixjyiZGP0kq0h35SlmMPcEjliodOBFwMXqpXFB7Ibp4F6o6te2p2ErViJccQVG8VNKB6SbKNXY6bhP5zVcVsJ-vR-p4xXoYJJvzTN7yTDsGAXHOLF4ZrXbo5yi5gFAlIrTLAF2EdWQwxSGyLRWKxG8PrDkzEzX6cJJ0VRcLh5z6OI5QqQNdeghPZbshMFMJSc_ApCPi9_hI4ZfctCIOi3T6bdgTNKryLm5fhy_eqjwLAZTGP-aUBgLZpb1mf2UojBn6Ey9cCyq-0T2RWyk-FcIcbV4qJ-p_8oODqw13Qs5FYkjLr1bGBq82SuolkYrXEwQClxnrfKa4KYc2_eHAXPL01iS9zVnI1ySOCNJshB97Odpooc4wk7Nb2Fo-Q6THU9zuu0uK_-JbK7IIl6go2qA"
- },
- },
- "callback":"\/post",
- "version":"1.2",
- "encryption":{
- "aes256ctr"
- },
- "secret":"1eaa6613699be6ebb2adcefa5379c61a3678aa0df89025470fac871431b70467",
- "secret_sig":"0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD-yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q"
- }
-[/code]
-
-[dl terms="b"]
-[*= type] The message type: [b]notify, purge, refresh, force_refresh, auth_check, ping[/b] or [b]pickup[/b]. The packet contents vary by message type. Here we will describe the [b]notify[/b] packet.
-[*= callback]A string to be appended onto the url which identifies the Zot communications endpoint on this system. It is typically the string "/post".
-[*= version]The Zot protocol identifier, to allow future protocol revisions to co-exist.
-[*= encryption] array of supported encryption algorithms, order by decreasing preference. If no compatible encryption methods are provided, applications MUST use 'aes256cbc'.
-[*= secret]A 64-char string which is randomly generated by the sending site.
-[*= secret_sig]The RSA signature of the secret, signed with the sender's private key.
-[*= sender] An array of four components that provide a portable identity. We can contact the URL provided and download a Zot info packet to obtain the public key of the sender, and use that to verify the sender guid and the posting URL signatures.
- [dl terms="b"]
- [*= guid]Typically a 64 character base64url encoded string. This is generated when an identity is created and an attempt is made that it be unique; though this isn't required.
- [*= guid_sig]The RSA signature of the guid, signed by the sender's private key and base64url encoded.
- [*= url]The base url of the location this post is originating from.
- [*= url_sig]The RSA signature of url, signed by the sender's private key and base64url encoded.
- [*= sitekey]The public key of the website specified in the url
- [/dl]
-[*= recipients] Only used for private messages. An array of envelope recipients. Each recipient is represented by an array of guid and guid_sig. When recipients are specified, the entire packet is also encapsulated using a negotiated cryptographic algorithm or 'aes256cbc' if none could be negotiated.
- [dl terms="b"]
- [*= guid]The guid of a private recipient.
- [*= guid_sig]The RSA signature of the guid, signed by the recipient's private key and base64url encoded
- [/dl]
-[/dl]
diff --git a/doc/en/developer/zot_protocol.md b/doc/en/developer/zot_protocol.md
new file mode 100644
index 000000000..c734ab9eb
--- /dev/null
+++ b/doc/en/developer/zot_protocol.md
@@ -0,0 +1,44 @@
+### The Nomad protocol
+
+#### What is Nomad?
+
+Nomad is the revolutionary protocol that powers Hubzilla, enabling **communication**, **identity management** and **access control** across a fully **decentralised** network of independent websites, often referred to as ‘the grid’. The resulting platform is a robust system that supports privacy and security while enabling the kind of rich web services typically found only in centralised, proprietary solutions.
+
+Consider this typical scenario:
+
+Jaquelina wants to share photos from her blog at **jaquelina.org** with Roberto, but with no one else. Roberto maintains his own family hub at **roberto.net** on a completely independent server. Nomad allows Jaquelina to publish her photos with an *access control list (ACL)* that only includes Roberto. This means that while Roberto can see the photos when he visits her blog, his brother Marco cannot, nor can any other family member who has an account on **roberto.net**.
+
+The twist in this scenario is the fact that Roberto never logged in to Jaquelina's website. Instead, he only had to log in once with his password on his *own* **roberto.net** website. When Roberto visits **jaquelina.org**, he is seamlessly authenticated by their hub by querying his server in the background.
+
+It's not uncommon for servers to have technical issues or become inaccessible for various reasons. Nomad provides robustness to Roberto's online activities by allowing him to have *clones* of his online identity or *channel* on multiple independent hubs. Imagine that Roberto's server goes down for some reason and he can no longer log in there. He simply logs into one of his clones on **gadfly.com**, a website run by his friend Peter. Once he has authenticated himself at **gadfly.com**, Roberto can view Jaquelina's blog as before, without Jaquelina having to grant additional access!
+
+#### Communication
+
+Communication and social networks are an essential part of the grid. Any channel (and any service provided by that channel) can take full advantage of feature-rich social communication on a global scale. These communications can be public or private - and private communications include not only fully encrypted transport, but also encrypted storage to protect against accidental snooping and disclosure by rogue system administrators and ISPs.
+
+Nomad supports a wide range of background services in the grid, from friend suggestions to directory services. New content and data updates are passed in the background between hubs across the grid according to the access control lists and permissions set by the *sender and* receiver channels. Data is also synchronised between any number of channel clones so that hub members can access data and continue to collaborate seamlessly even if their primary hub is unavailable or offline.
+
+#### Identity
+
+Nomad's identity layer is unique. It provides an **invisible single sign-on** for all locations in the grid.
+It also provides a **nomadic identity** so that your communication with friends, family or other people you communicate with is not affected by the loss of your primary communication hub - either temporarily or permanently.
+
+The important parts of your identity and relationships can be backed up on a USB stick or your laptop and appear on any node on the network at any time - with all your friends and preferences.
+Crucially, these nomadic instances are kept synchronised so that any instance can take over if another is compromised or corrupted. This not only protects you from major system failures, but also from temporary website overload and government tampering or censorship.
+
+We believe that Hubzilla's nomadic identity, single sign-on and decentralisation bring a high level of **resilience** and **consistency** to internet communications, which is much needed in the face of global trends towards corporate centralisation and mass and indiscriminate government surveillance and censorship.
+When browsing the web, viewing channels and their unique content, you are seamlessly authenticated, even across completely different server hubs. No need to enter passwords. You don't have to type anything. You are simply greeted with your name on every new page you visit.
+
+How does this work with Nomad? We call it **‘magic-auth’** because Hubzilla hides the details of the complexity of single sign-on logins and nomadic identities from web browsing. This is one of Hubzilla's design goals: to increase privacy and freedom on the Internet while reducing the complexity and tedium of having to enter new passwords and login names every time you visit the Internet. You only log in once to your home hub (or a nomadic backup hub of your choice). This allows you to access all authenticated services offered anywhere on the web - such as shopping, blogs, forums and access to private information. Your password is not stored on a thousand different websites, but on servers that you control or trust.
+
+They cannot be silenced. They cannot be removed from the network unless you choose to leave it yourself.
+
+#### Access control
+
+Nomad's identity layer allows you to assign fine-grained permissions to any content you want to publish - and these permissions extend across the entire grid. It's like having a huge website made up of an army of small individual websites - where each channel in the grid can fully control its privacy and sharing preferences for all the web resources it creates.
+
+Currently, Hubzilla supports access control for many types of data, including discussion posts and comments, photo albums, events, cloud files, web pages, wikis and more. Each item and how and with whom it is shared is completely under your control.
+
+This type of control is trivial with large enterprise providers as they own the user database. In the Grid, you don't need a huge user database on your computer - because the Grid **is** your user database. It has essentially infinite capacity (limited by the total number of hubs online on the Internet) and is distributed across hundreds, possibly even millions of computers.
+
+Access can be granted or denied to any resource, channel or group of channels - anywhere on the grid. Others can access your content if you allow them to, and they don't even need to have an account in your hub. \ No newline at end of file
diff --git a/doc/en/developer/zot_structures.md b/doc/en/developer/zot_structures.md
new file mode 100644
index 000000000..b96279a97
--- /dev/null
+++ b/doc/en/developer/zot_structures.md
@@ -0,0 +1,110 @@
+### Nomad structures
+
+#### Nomad signatures
+
+All signed data in Nomad is generated by an RSA signature operation with the initiator's private key. The binary result is then encoded for transport with base64url.
+
+#### Nomad encryption
+
+Encryption is currently performed using AES256CTR. Other algorithms MAY be supported. A 32-octet key and a 16-octet initialisation vector are generated at random. The desired data is then encoded with these generated strings and the result is base64url encoded. An array is then created:
+
+- data
+ The base64url-encoded encrypted data
+- alg
+ The selected algorithm, in this case the character string ‘aes256ctr’.
+- key
+ The randomly generated key, RSA-encrypted with the recipient's public key, and the base64url-encoded result
+- iv
+ The randomly generated initialisation vector, RSA-encrypted with the recipient's public key, and the base64url-encoded result
+
+#### Basic Nomad packet
+
+Used to initiate a dialogue with another Nomad site. This packet MAY be encrypted. The presence of an array element ‘iv’ indicates that encryption has been performed. When sending an ‘auth_check’ packet, this packet MUST be encrypted, using the target site's public key (the site key, as opposed to a sender key).
+
+```
+ {
+ ‘type’: ‘notify’,
+ ‘sender’:{
+ ‘guid’: ‘kgVFf_1_SSbyqH-BNWjWuhAvJ2EhQBTUdw-Q1LwwssAntr8KTBgBSzNVzUm9_RwuDpxI6X8me_QQhZMf7RfjdA’,
+ ‘guid_sig’: "PT9-TApzpm7QtMxC63MjtdK2nUyxNI0tUoWlOYTFGke3kNdtxSzSvDV4uzq_7SSBtlrNnVMAFx2_1FDgyKawmqVtRPmT7QSXrKOL2oPzL8Hu_nnVVTs_0YOLQJJ0GYACOOK- R5874WuXLEept5-KYg0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81- Xvs8I2kj6y5vfFtm-FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91- ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs-pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD- yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q’,
+ ‘url’: ‘http:\/\/podunk.edu’,
+ ‘url_sig’: "T8Bp7j5DHHhQDCFcAHXfuhUfGk2P3inPbImwaXXF1xJd3TGgluoXyyKDx6WDm07x0hqbupoAoZB1qBP3_WfvWiJVAK4N1FD77EOYttUEHZ7L43xy5PCpojJQmkppGbPJc2jnTIc_F1vvGvw5fv8gBWZvPqTdb6LWF6FLrzwesZpi7j2rsioZ3wyUkqb5TDZaNNeWQrIEYXrEnWkRI_qTSOzx0dRTsGO6SpU1fPWuOOYMZG8Nh18nay0kLpxReuHCiCdxjXRVvk5k9rkcMbDBJcBovhiSioPKv_yJxcZVBATw3z3TTE95kGi4wxCEenxwhSpvouwa5b0hT7NS4Ay70QaxoKiLb3ZjhZaUUn4igCyZM0h6fllR5I6J_sAQxiMYD0v5ouIlb0u8YVMni93j3zlqMWdDUZ4WgTI7NNbo8ug9NQDHd92TPmSE1TytPTgya3tsFMzwyq0LZ0b- g- zSXWIES__jKQ7vAtIs9EwlPxqJXEDDniZ2AJ6biXRYgE2Kd6W_nmI7w31igwQTms3ecXe5ENI3ckEPUAq__llNnND7mxp5ZrdXzd5HHU9slXwDShYcW3yDeQLEwAVomTGSFpBrCX8W77n9hF3JClkWaeS4QcZ3xUtsSS81yLrp__ifFfQqx9_Be89WVyIOoF4oydr08EkZ8zwlAsbZLG7eLXY"
+ ‘sitekey’:"-----BEGIN PUBLIC KEY-----
+MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxTeIwXZWrw/S+Ju6gewh
+LgkKnNNe2uCUqCqMZoYgJar3T5sHCDhvXc4dDCbDkxVIaA/+V1mURtBV60a3IGjn
+OAO0W0XGGLe2ED7G5o9U8T9mVGq8Mauv0v1oQ5wIR1gEAhBavkQ2OUGuF/YKn2nj
+HlKsv9HzUAHpcDMUe3Uklc2RhQbMcnJxEgkyjCkDyrTtCZzISkTAocHvpCG1KSog
+njUZdiz9UWxvM4rCFkCJvQU4RwRZJb7GA9ul+9JrF7NvUQTx8csRP2weBk1E9yyj
+wbe187E0eVj9RXX2Mx3mYhgrTdodxLOVMSXZLg1/SMpeFFl7QBhuM0SiOPg8a7Et
+e2iNA/RD4WiUFqCDfafasRa1TOozOm7LA+08lkAh5PeQPJsJbrX0wVVft++Y+5/z
+BvcUOP73vcbz7j5hJ7HLsbQtye/UUCfODBFybuDqRM84Aet8rjZohX7vukXdMD4I
+2HuB7pjR4uIfyYr0J63ANkvbsn8LR+RnirmHrK5H/OgxxjXcfYbGEQgFxvxhF6lA
+FpMu6Do4dx3CIb6pRmZ8bjSImXexJ0BSo9n3gtrz0XYLecsYFlQ9+QQjm83qxyLb
+M23in0xqMVsyQvzjNkpImrO/QdbEFRIIMee83IHq+adbyjQR49Z2hNEIZhkLPc3U
+2cJJ2HkzkOoF2K37qwIzk68CAwEAAQ==
+-----END PUBLIC KEY-----
+"
+ },
+ ‘recipients’:{
+ {
+ ‘guid’: ‘lql-1VnxtiO4-WF0h72wLX1Fu8szzHDOXgQaTbELwXW77k8AKFfh-hYr70vqMrc3SSvWN-Flrc5HFhRTWB7ICw’,
+ ‘guid_sig’: "PafvEL0VpKfxATxlCqDjfOeSIMdmpr3iU7X-Sysa1h5LzDpjSXsjO37tYZL- accb1M5itLlfnW5epkTa5I4flsW21zSY1A2jCuBQUTLLGV7rNyyBy7lgqJUFvAMRx0TfXzP9lcaPqlM9T1tA6jfWOsOmkdzwofGeXBnsjGfjsO2xdGYe6vwjOU0DSavukvzDMnOayB9DekpvDnaNBTxeGLM45Skzr7ZEMcNF7TeXMbnvpfLaALYEKeQs9bGH- UgAG8fBWgzVAzeBfx_XSR1rdixjyiZGP0kq0h35SlmMPcEjliodOBFwMXqpXFB7Ibp4F6o6te2p2ErViJccQVG8VNKB6SbKNXY6bhP5zVcVsJ- vR-p4xXoYJJvzTN7yTDsGAXHOLF4ZrXbo5yi5gFAlIrTLAF2EdWQwxSGyLRWKxG8PrDkzEzX6cJJ0VRcLh5z6OI5QqQNdeghPZbshMFMJSc_ApCPi9_hI4ZfctCIOi3T6bdgTNKryLm5fhy_eqjwLAZTGP- aUBgLZpb1mf2UojBn6Ey9cCyq-0T2RWyk-FcIcbV4qJ-p_8oODqw13Qs5FYkjLr1bGBq82SuolkYrXEwQClxnrfKa4KYc2_eHAXPL01iS9zVnI1ySOCNJshB97Odpooc4wk7Nb2Fo-Q6THU9zuu0uK_-JbK7IIl6go2qA"
+ },
+ },
+ ‘callback’:‘\/post’,
+ ‘version’: ‘1.2’,
+ ‘encryption’:{
+ ‘aes256ctr’
+ },
+ ‘secret’:‘1eaa6613699be6ebb2adcefa5379c61a3678aa0df89025470fac871431b70467’,
+ ‘secret_sig’: "0uShifsvhHnxnPIlDM9lWuZ1hSJTrk3NN9Ds6AKpyNRqf3DUdz81-Xvs8I2kj6y5vfFtm- FPKAqu77XP05r74vGaWbqb1r8zpWC7zxXakVVOHHC4plG6rLINjQzvdSFKCQb5R_xtGsPPfvuE24bv4fvN4ZG2ILvb6X4Dly37WW_HXBqBnUs24mngoTxFaPgNmz1nDQNYQu91-ekX4-BNaovjDx4tP379qIG3-NygHTjFoOMDVUvs- pOPi1kfaoMjmYF2mdZAmVYS2nNLWxbeUymkHXF8lT_iVsJSzyaRFJS1Iqn7zbvwH1iUBjD_pB9EmtNmnUraKrCU9eHES27xTwD- yaaH_GHNc1XwXNbhWJaPFAm35U8ki1Le4WbUVRluFx0qwVqlEF3ieGO84PMidrp51FPm83B_oGt80xpvf6P8Ht5WvVpytjMU8UG7-js8hAzWQeYiK05YTXk-78xg0AO6NoNe_RSRk05zYpF6KlA2yQ_My79rZBv9GFt4kUfIxNjd9OiV1wXdidO7Iaq_Q"
+ }
+```
+
+type
+
+The message type: **notify, purge, refresh, force_refresh, auth_check, ping** or **pickup**. The content of the packets varies depending on the message type. The **notify packet** is described here.
+
+callback
+
+A character string that is appended to the URL and identifies the Nomad communication endpoint on this system. This is usually the character string ‘/post’.
+
+version
+
+The identifier of the Nomad protocol so that future protocol revisions can co-exist.
+
+encryption
+
+Array of supported encryption algorithms, ordered by decreasing preference. If no compatible encryption methods are specified, applications MUST use ‘aes256cbc’.
+
+secret
+
+A 64-character string randomly generated by the sending side.
+
+secret_sig
+
+The RSA signature of the secret, signed with the sender's private key.
+
+sender
+
+An array of four components that provide a portable identity. We can contact the given URL and download a Nomad info packet to obtain the sender's public key and use it to verify the sender's guid and the signatures of the sending URL.
+
+- guid
+ Usually a 64 character base64url encoded string. It is generated when an identity is created and an attempt is made to make it unique, but this is not required.
+- guid_sig
+ The RSA signature of the guid, signed with the sender's private key and base64url-encoded.
+- url
+ The base URL of the location from which this post originated.
+- url_sig
+ The RSA signature of the url, signed with the sender's private key and base64url encoded.
+- sitekey
+ The public key of the website specified in the url
+
+recipients
+
+Only used for private messages. An array of envelope recipients. Each recipient is represented by an array of guid and guid_sig. If recipients are specified, the entire packet is also encapsulated with a negotiated cryptographic algorithm or ‘aes256cbc’ if none could be negotiated.
+
+- guid
+ The guid of a private recipient.
+- guid_sig
+ The RSA signature of the guid, signed with the recipient's private key and base64url-encoded \ No newline at end of file
diff --git a/doc/en/developer_function_primer.bb b/doc/en/developer_function_primer.bb
deleted file mode 100644
index f39954d73..000000000
--- a/doc/en/developer_function_primer.bb
+++ /dev/null
@@ -1,47 +0,0 @@
-[b]$Projectname development - some useful basic functions[/b]
-
-[b]get_account_id()[/b]
-
-Returns numeric account_id if authenticated or 0. It is possible to be authenticated and not connected to a channel.
-
-[b]local_channel()[/b]
-
-Returns authenticated numeric channel_id if authenticated and connected to a channel or 0. Sometimes referred to as $uid in the code.
-
-[b]remote_channel()[/b]
-
-Returns authenticated string hash of Red global identifier, if authenticated via remote auth, or an empty string.
-
-[b]App::get_observer()[/b]
-
-returns an xchan structure representing the current viewer if authenticated (locally or remotely).
-
-[b]get_config($family,$key), get_pconfig($uid,$family,$key), get_xconfig($xchan_hash,$family,$key)[/b]
-
-Returns the config setting for $family and $key or false if unset.
-
-Deprecated: Use Zotlabs\Lib\Config::Get instead.
-
-[b] set_config($family,$key,$value), set_pconfig($uid,$family,$key,$value)[/b]
-
-Sets the value of config setting for $family and $key to $value. Returns $value. The config versions operate on system-wide settings. The pconfig versions get/set the values for a specific integer uid (channel_id). The xconfig version get/sets the value for a specific xchan hash - generally used for remote users.
-
-Deprecated: Use Zotlabs\Lib\Config::Set instead.
-
-[b]dbesc()[/b]
-
-Always escape strings being used in DB queries. This function returns the escaped string. Integer DB parameters should all be proven integers by wrapping with intval()
-
-[b]q($sql,$var1...)[/b]
-
-Perform a DB query with the SQL statement $sql. printf style arguments %s and %d are replaced with variable arguments, which should each be appropriately dbesc() or intval(). SELECT queries return an array of results or false if SQL or DB error. Other queries return true if the command was successful or false if it wasn't.
-
-[b]t($string)[/b]
-
-Returns the translated variant of $string for the current language or $string (default 'en' language) if the language is unrecognised or a translated version of the string does not exist.
-
-[b]x($var), $x($array,$key)[/b]
-
-Shorthand test to see if variable $var is set and is not empty. Tests vary by type. Returns false if $var or $key is not set. If variable is set, returns 1 if has 'non-zero' value, otherwise returns 0. -- e.g. x('') or x(0) returns 0;
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/developers.bb b/doc/en/developers.bb
deleted file mode 100644
index 22dc82d7e..000000000
--- a/doc/en/developers.bb
+++ /dev/null
@@ -1,71 +0,0 @@
-[b]$Projectname Developer Guide[/b]
-
-We're pretty relaxed when it comes to developers. We don't have a lot of rules. Some of us are over-worked and if you want to help we're happy to let you help. That said, attention to a few guidelines will make the process smoother and make it easier to work together. All developers are expected to abide by our [zrl=[baseurl]/help/contributor/covenant]code of conduct[/zrl]. We have developers from across the globe with different abilities and different cultural backgrounds and different levels of patience. Our primary rule is to respect others. Sometimes this is hard and sometimes we have very different opinions of how things should work, but if everybody makes an effort, we'll get along just fine.
-
-[b]Here is how you can join us.[/b]
-
-First, get yourself a working git package on the system where you will be
-doing development.
-
-Create your own github account.
-
-You may fork/clone the project repository from [url=https://framagit.org/hubzilla/core.git]https://framagit.org/hubzilla/core.git[/url]
-
-Follow the instructions provided here: [url=http://help.github.com/fork-a-repo/]http://help.github.com/fork-a-repo/[/url]
-to create and use your own tracking fork on framagit
-
-Then go to your framagit page and create a &quot;Pull request&quot; when you are ready
-to notify us to merge your work.
-
-[b]Translations[/b]
-
-Our translations are managed through Transifex. If you wish to help out translating $Projectname to another language, sign up on transifex.com, visit [url=https://www.transifex.com/projects/p/hubzilla/]https://www.transifex.com/projects/p/hubzilla/[/url] and request to join one of the existing language teams or create a new one. Notify one of the core developers when you have a translation update which requires merging, or ask about merging it yourself if you're comfortable with git and PHP. We have a string file called 'hmessages.po' which is gettext compliant and a handful of email templates, and from there we automatically generate the application's language files.
-
-[zrl=[baseurl]/help/Translations]Translations - More Info[/zrl]
-
-[b]Important[/b]
-
-Please pull in any changes from the project repository and merge them with your work **before** issuing a pull request. We reserve the right to reject any patch which results in a large number of merge conflicts. This is especially true in the case of language translations - where we may not be able to understand the subtle differences between conflicting versions.
-
-Also - **test your changes**. Don't assume that a simple fix won't break something else. If possible get an experienced Red developer to review the code.
-
-
-[b]Licensing[/b]
-
-All code contributed to the project falls under the MIT license, unless otherwise specified. We will accept third-party code which falls under MIT, BSD and LGPL, but copyleft licensing (GPL, and AGPL) is only permitted in addons. It must be possible to completely remove the GPL (copyleft) code from the main project without breaking anything.
-
-[b]Concensus Building[/b]
-
-Code changes which fix an obvious bug are pretty straight-forward. For instance if you click "Save" and the thing you're trying to save isn't saved, it's fairly obvious what the intended behaviour should be. Often when developing feature requests, it may affect large numbers of community members and it's possible that other members of the community won't agree with the need for the feature, or with your proposed implementation. They may not see something as a bug or a desirable feature.
-
-We encourage consensus building within the community when it comes to any feature which might be considered controversial or where there isn't unanimous decision that the proposed feature is the correct way to accomplish the task. The first place to pitch your ideas is to the [url=https://project.hubzilla.org/channel/hubzilla]Hubzilla Development[/url] forum. Others may have some input or be able to point out facets of your concept which might be problematic in our environment. But also, you may encounter opposition to your plan. This doesn't mean you should stop and/or ignore the feature. Listen to the concerns of others and try and work through any implementation issues.
-
-There are places where opposition cannot be resolved. In these cases, please consider making your feature [b]optional[/b] or non-default behaviour that must be specifically enabled. This technique can often be used when a feature has significant but less than unanimous support. Those who desire the feature can turn it on and those who don't want it - will leave it turned off.
-
-If a feature uses other networks or websites and or is only seen as desirable by a small minority of the community, consider making the functionality available via an addon or plugin. Once again, those who don't desire the feature won't need to install it. Plugins are relatively easy to create and "hooks" can be easily added or modified if the current hooks do not do what is needed to allow your plugin to work.
-
-
-[b]Coding Style[/b]
-
-In the interests of consistency we adopt the following code styling. We may accept patches using other styles, but where possible please try to provide a consistent code style. We aren't going to argue or debate the merits of this style, and it is irrelevant what project 'xyz' uses. This is not project 'xyz'. This is a baseline to try and keep the code readable now and in the future.
-
-[li] All comments should be in English.[/li]
-
-[li] We use doxygen to generate documentation. This hasn't been consistently applied, but learning it and using it are highly encouraged.[/li]
-
-[li] Indentation is accomplished primarily with tabs using a tab-width of 4.[/li]
-
-[li] String concatenation and operators should be separated by whitespace. e.g. &quot;$foo = $bar . 'abc';&quot; instead of &quot;$foo=$bar.'abc';&quot;[/li]
-
-[li] Generally speaking, we use single quotes for string variables and double quotes for SQL statements. &quot;Here documents&quot; should be avoided. Sometimes using double quoted strings with variable replacement is the most efficient means of creating the string. In most cases, you should be using single quotes.[/li]
-
-[li] Use whitespace liberally to enhance readability. When creating arrays with many elements, we will often set one key/value pair per line, indented from the parent line appropriately. Lining up the assignment operators takes a bit more work, but also increases readability.[/li]
-
-[li] Generally speaking, opening braces go on the same line as the thing which opens the brace. They are the last character on the line. Closing braces are on a line by themselves. [/li]
-
-[li] Some functions take arguments in argc/argv style like main() in C or function args in bash or Perl. Urls are broken up within a module. e.g, given "http://example.com/module/arg1/arg2", then $this->argc will be 3 (integer) and $this->argv will contain: [0] => 'module', [1] => 'arg1', [2] => 'arg2'. There will always be one argument. If provided a naked domain URL, $this->argv[0] is set to "home".[/li]
-
-[b]See Also[/b]
-[zrl=[baseurl]/help/sql_conventions]SQL Conventions[/zrl]
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/diaspora_compat.bb b/doc/en/diaspora_compat.bb
deleted file mode 100644
index f27a63b9d..000000000
--- a/doc/en/diaspora_compat.bb
+++ /dev/null
@@ -1,68 +0,0 @@
-[h3]Diaspora Compatibility[/h3]
-
-The Diaspora Protocol addon allows a site to communicate using the Diaspora protocol, which allows communications and connections to be made with Diaspora members (and also Friendica members, since that network also provides the Diaspora Protocol).
-
-This addon is available in the 'basic' and 'standard' server configurations. It is not available with and the plugin is disabled completely when you are using the 'pro' server configuration. The reason for this is that the Diaspora protocol is not very sophisticated and many $projectname features do not work well with it.
-
-Members will have to be aware of limitations of the protocol or limit their own activities to those which are compatible with Diaspora. The 'pro' server configuration is free from these limitations and you may use all of the project features and abilities without regard for how they translate to other networks. Many features are unique to $Projectname and are supported by the &quot;Zot&quot; protocol, which is our native communications language between servers/hubs.
-
-If you are using a configuration which allows direct Diaspora communications you should be aware of the limitations presented here.
-
-[ul]
-[*]Private mail retraction (unsend) is not possible for Diaspora connections.
-
-[*]Private posts and their associated comments are sent in plaintext email notifications in Diaspora and Friendica. This is a major privacy issue and affects any private communications you have where *any* member of the conversation is on another network. Be aware of it.
-
-[*]Access control only works on posts and comments. Diaspora members will get permission denied trying to access any other access controlled hubzilla objects such as files, photos, webpages, chatrooms, etc. In the case of private photos that are linked to posts, they will see a "prohibited sign" instead of the photo. Diaspora has no concept of private media and provides an illusion of photo privacy by using obscured URLs rather than protecting the photo from snooping by unauthorised viewers.
-
-There is no workaround except to make your media resources public (to everybody on the internet).
-
-
-[*]Edited posts will not be delivered. Diaspora members will see the original post/comment without edits. There is no mechanism in the protocol to update an existing post. We cannot delete it and submit another invisibly because the message-id will change and we need to keep the same message-id on our own network. The only workaround is to delete the post/comment and do it over. (If this is a post, this will delete any existing likes/comments). We may eventually provide a way to delete the out of date copy only from Diaspora and keep it intact on networks that can handle edits.
-
-[*]Nomadic identity ($projectname 'standard' only) will not work with Diaspora. We may eventually provide an **option** which will allow you to "start sharing" from all of your clones when you make the first connection. The Diaspora person does not have to accept this, but it will allow your communications to continue if they accept this connection. Without this option, if you go to another server from where you made the connection originally or you make the connection before creating the clone, you will need to connect with them again from the new location.
-
-[*]Post expiration is not supported on Diaspora. We may provide you an option to not send expiring posts to that network. In the future this may be provided with a remote delete request.
-
-[*]End-to-end encryption is not supported. We will translate these posts into a lock icon, which can never be unlocked from the Diaspora side.
-
-[*]Message verification will eventually be supported.
-
-[*]Multiple profiles are not supported. Diaspora members can only see your default profile.
-
-[*]Birthday events will not appear in Diaspora. Other events will be translated and sent as a post, but all times will either be in the origination channel's timezone or in GMT. We do not know the recipient's timezone because Diaspora doesn't have this concept.
-
-[*]We currently allow tags to be hijacked by default. An option is provided to allow you to prevent the other end of the network from hijacking your tags and point them at its own resources.
-
-[*]Community tags will not work. We will send a tagging activity as a comment. It won't do anything.
-
-[*]Privacy tags (@!somebody) will not be available to Diaspora members. These tags may have to be stripped or obscured to prevent them from being hijacked - which could result in privacy issues.
-
-[*]Plus-tagged hubzilla forums should work from Diaspora.
-
-
-[*]You cannot use Diaspora channels as channel sources.
-
-
-[*]Dislikes of posts will be converted to comments and you will have the option to send these as comments or not send them to Diaspora (which does not provide dislike). Currently they are not sent.
-
-[*]We will do the same for both likes and dislikes of [b][i]comments[/i][/b]. They can either be sent as comments or you will have the ability to prevent them from being transmitted to Diaspora. Currently they are not sent.
-
-[*]Emojis are currently untranslated.
-
-[*]"observer tags" will be converted to empty text.
-
-
-[*]Embedded apps will be translated into links.
-
-
-[*]Embedded page design elements (work in progress) will be either stripped or converted to an error message.
-
-[*]Diaspora members will not appear in the directory.
-
-
-[*]There are differences in oembed compatibility between the networks. Some embedded resources will turn into a link on one side or the other.
-
-[/ul]
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/directories.bb b/doc/en/directories.bb
deleted file mode 100644
index 60a0b624d..000000000
--- a/doc/en/directories.bb
+++ /dev/null
@@ -1,95 +0,0 @@
-[h3]Directory Configuration[/h3]
-
-Directories in $Projectname serve the purpose of searching and locating members anywhere in the network. They are also used to store and query "ratings" of members and sites. The directory services are distributed and mirrored so that a failure of one will not take down or disrupt the entire network.
-
-[b]Standard Configuration[/b]
-
-New sites operating as directory clients will automatically select from a hard-coded list of directory servers during their first directory access. You may examine or over-ride this decision using
-
-[code]
-util/config system directory_server
-[/code]
-
-To set a different server,
-
-[code]
-util/config system directory_server https://newdirectory.something
-[/code]
-
-
-[b]Standalone configuration[/b]
-
-Some sites may wish to operate in 'standalone' mode and not connect to any external directory services. This is useful for isolated sites ("off the gird") and test sites, but can also be useful for small organisations who do not wish to connect with other sites in the network.
-
-To configure this, please look in your .htconfig.php file for the following text and set the configuration accordingly.
-
-[code]
-// Configure how we communicate with directory servers.
-// DIRECTORY_MODE_NORMAL = directory client, we will find a directory
-// DIRECTORY_MODE_SECONDARY = caching directory or mirror
-// DIRECTORY_MODE_PRIMARY = main directory server
-// DIRECTORY_MODE_STANDALONE = "off the grid" or private directory services
-
-App::$config['system']['directory_mode'] = DIRECTORY_MODE_STANDALONE;
-[/code]
-
-
-[b]Secondary server configuration[/b]
-
-You may also configure your site as a secondary server. This operates as a mirror of the primary directory and allows disitribution of the load amongst available servers. There is very little functional difference between a primary and secondary sever, however there may only be *one* primary directory server per realm (realms are discussed later in this document).
-
-Before choosing to be a directory server, please be advised that you should be an active member of the network and have the resources and time available to manage these services. They don't typically require management, but the requirement is more for stability as losing a directory server can cause issues to directory clients which are reliant on it.
-
-
-[b]Changing the directory server[/b]
-
-If a directory server indicates that it is no longer a directory server, this should be detected by the software and the configuration for that server will be removed (blanked). If it goes offline permanently without warning, you will only know if site members report that directory services are unavailable. Currently this can only be repaired manually by the site administrator by selecting a new directory and performing:
-
-[code]
-util/config system directory_server https://newdirectory.something
-[/code]
-
-Eventually we hope to make this a selectable box from the site admin panel.
-
-
-[h2]Directory realms[/h2]
-
-Large organisations may wish to use directory 'realms' rather than a single standalone directory. The standard and default realm is known as RED_GLOBAL. By creating a new realm, your organisation has the ability to create its own hierarchy of primary and secondary servers and clients.
-
-[code]
-util/config system directory_realm MY_REALM
-[/code]
-
-Your realm *must* have a primary directory. Create this first. Then set the realm the same on all sites within your directory realm (servers and clients).
-
-You may also provide a "sub-realm" that operates indepently from the RED_GLOBAL realm (or any other realm) but allows cross membership and some ability to lookup members of the entire directory space. This has only undergone light testing so be prepared to help out and fix any issues that may arise. A sub-realm contains its parent realm within the realm name.
-
-
-[code]
-util/config system directory_realm RED_GLOBAL:MY_REALM
-[/code]
-
-
-[b]Realm access[/b]
-
-You may wish that your directory servers and services are only used by members of your realm. To do this a token or password must be supplied to access the realm directory services. This token is not encrypted during transit, but is sufficient to prevent casual access to your directory servers. The following must be configured for all sites (clients and directory servers) within the realm:
-
-[code]
-util/config system realm_token my-secret-realm-password
-[/code]
-
-
-
-[h2]Directory mirrors[/h2]
-
-Mirroring occurs with a daily transaction log of activities which are shared between directory servers. In the case of directory and profile updates, the channel address performing the update is transmitted, and the other directory servers probe that channel at its source for changes. We do not and should not trust any information given us by other directory servers. We always check the information at the source.
-
-Ratings are handled slightly differently - an encrypted packet (signed by the channel that created the rating) is passed between the servers. This signature needs to be verified before the rating is accepted. Ratings are always published to the primary directory server and propagated to all other directory servers from there. For this reason there can only be one primary server in a realm. If a misconfigured site claims to be a primary directory, it is ignored in the RED_GLOBAL realm. For other realms there is currently no such protection. Be aware of this when working with alternate realms.
-
-Newly created directory servers are not provided a "full dump", but for performance reasons and minimal disruption to the other servers in the network, they are brought online slowly. It may take up to a month for a new secondary directory server to provide a full view of the network. Please do not add any secondary servers to the hard-coded list of fallback directory servers until it has been operating as a directory for at least a month.
-
-All channels are configured to "ping" their directory server once a month, at somewhat random times during the month. This gives the ability for the directory to discover dead channels and sites (they stop pinging). Subsequently they are marked dead or unreachable and over time will be removed from the directory results.
-
-Channels may be configured to be "hidden" from the directory. These channels may still exist in the directory but will be un-searchable and some "sensitive" personal information will not be stored at all.
-
- \ No newline at end of file
diff --git a/doc/en/dnt-policy.txt b/doc/en/dnt-policy.txt
deleted file mode 100644
index ad946d1f8..000000000
--- a/doc/en/dnt-policy.txt
+++ /dev/null
@@ -1,218 +0,0 @@
-Do Not Track Compliance Policy
-
-Version 1.0
-
-This domain complies with user opt-outs from tracking via the "Do Not Track"
-or "DNT" header [http://www.w3.org/TR/tracking-dnt/]. This file will always
-be posted via HTTPS at https://example-domain.com/.well-known/dnt-policy.txt
-to indicate this fact.
-
-SCOPE
-
-This policy document allows an operator of a Fully Qualified Domain Name
-("domain") to declare that it respects Do Not Track as a meaningful privacy
-opt-out of tracking, so that privacy-protecting software can better determine
-whether to block or anonymize communications with this domain. This policy is
-intended first and foremost to be posted on domains that publish ads, widgets,
-images, scripts and other third-party embedded hypertext (for instance on
-widgets.example.com), but it can be posted on any domain, including those users
-visit directly (such as www.example.com). The policy may be applied to some
-domains used by a company, site, or service, and not to others. Do Not Track
-may be sent by any client that uses the HTTP protocol, including websites,
-mobile apps, and smart devices like TVs. Do Not Track also works with all
-protocols able to read HTTP headers, including SPDY.
-
-NOTE: This policy contains both Requirements and Exceptions. Where possible
-terms are defined in the text, but a few additional definitions are included
-at the end.
-
-REQUIREMENTS
-
-When this domain receives Web requests from a user who enables DNT by actively
-choosing an opt-out setting in their browser or by installing software that is
-primarily designed to protect privacy ("DNT User"), we will take the following
-measures with respect to those users' data, subject to the Exceptions, also
-listed below:
-
-1. END USER IDENTIFIERS:
-
- a. If a DNT User has logged in to our service, all user identifiers, such as
- unique or nearly unique cookies, "supercookies" and fingerprints are
- discarded as soon as the HTTP(S) response is issued.
-
- Data structures which associate user identifiers with accounts may be
- employed to recognize logged in users per Exception 4 below, but may not
- be associated with records of the user's activities unless otherwise
- excepted.
-
- b. If a DNT User is not logged in to our service, we will take steps to ensure
- that no user identifiers are transmitted to us at all.
-
-2. LOG RETENTION:
-
- a. Logs with DNT Users' identifiers removed (but including IP addresses and
- User Agent strings) may be retained for a period of 10 days or less,
- unless an Exception (below) applies. This period of time balances privacy
- concerns with the need to ensure that log processing systems have time to
- operate; that operations engineers have time to monitor and fix technical
- and performance problems; and that security and data aggregation systems
- have time to operate.
-
- b. These logs will not be used for any other purposes.
-
-3. OTHER DOMAINS:
-
- a. If this domain transfers identifiable user data about DNT Users to
- contractors, affiliates or other parties, or embeds from or posts data to
- other domains, we will either:
-
- b. ensure that the operators of those domains abide by this policy overall
- by posting it at /.well-known/dnt-policy.txt via HTTPS on the domains in
- question,
-
- OR
-
- ensure that the recipient's policies and practices require the recipient
- to respect the policy for our DNT Users' data.
-
- OR
-
- obtain a contractual commitment from the recipient to respect this policy
- for our DNT Users' data.
-
- NOTE: if an “Other Domain” does not receive identifiable user information
- from the domain because such information has been removed, because the
- Other Domain does not log that information, or for some other reason, these
- requirements do not apply.
-
- c. "Identifiable" means any records which are not Anonymized or otherwise
- covered by the Exceptions below.
-
-4. PERIODIC REASSERTION OF COMPLIANCE:
-
- At least once every 12 months, we will take reasonable steps commensurate
- with the size of our organization and the nature of our service to confirm
- our ongoing compliance with this document, and we will publicly reassert our
- compliance.
-
-5. USER NOTIFICATION:
-
- a. If we are required by law to retain or disclose user identifiers, we will
- attempt to provide the users with notice (unless we are prohibited or it
- would be futile) that a request for their information has been made in
- order to give the users an opportunity to object to the retention or
- disclosure.
-
- b. We will attempt to provide this notice by email, if the users have given
- us an email address, and by postal mail if the users have provided a
- postal address.
-
- c. If the users do not challenge the disclosure request, we may be legally
- required to turn over their information.
-
- d. We may delay notice if we, in good faith, believe that an emergency
- involving danger of death or serious physical injury to any person
- requires disclosure without delay of information relating to the
- emergency.
-
-EXCEPTIONS
-
-Data from DNT Users collected by this domain may be logged or retained only in
-the following specific situations:
-
-1. CONSENT / "OPT BACK IN"
-
- a. DNT Users are opting out from tracking across the Web. It is possible
- that for some feature or functionality, we will need to ask a DNT User to
- "opt back in" to be tracked by us across the entire Web.
-
- b. If we do that, we will take reasonable steps to verify that the users who
- select this option have genuinely intended to opt back in to tracking.
- One way to do this is by performing scientifically reasonable user
- studies with a representative sample of our users, but smaller
- organizations can satisfy this requirement by other means.
-
- c. Where we believe that we have opt back in consent, our server will
- send a tracking value status header "Tk: C" as described in section 6.2
- of the W3C Tracking Preference Expression draft:
-
- http://www.w3.org/TR/tracking-dnt/#tracking-status-value
-
-2. TRANSACTIONS
-
- If a DNT User actively and knowingly enters a transaction with our
- services (for instance, clicking on a clearly-labeled advertisement,
- posting content to a widget, or purchasing an item), we will retain
- necessary data for as long as required to perform the transaction. This
- may for example include keeping auditing information for clicks on
- advertising links; keeping a copy of posted content and the name of the
- posting user; keeping server-side session IDs to recognize logged in
- users; or keeping a copy of the physical address to which a purchased
- item will be shipped. By their nature, some transactions will require data
- to be retained indefinitely.
-
-3. TECHNICAL AND SECURITY LOGGING:
-
- a. If, during the processing of the initial request (for unique identifiers)
- or during the subsequent 10 days (for IP addresses and User Agent strings),
- we obtain specific information that causes our employees or systems to
- believe that a request is, or is likely to be, part of a security attack,
- spam submission, or fraudulent transaction, then logs of those requests
- are not subject to this policy.
-
- b. If we encounter technical problems with our site, then, in rare
- circumstances, we may retain logs for longer than 10 days, if that is
- necessary to diagnose and fix those problems, but this practice will not be
- routinized and we will strive to delete such logs as soon as possible.
-
-4. AGGREGATION:
-
- a. We may retain and share anonymized datasets, such as aggregate records of
- readership patterns; statistical models of user behavior; graphs of system
- variables; data structures to count active users on monthly or yearly
- bases; database tables mapping authentication cookies to logged in
- accounts; non-unique data structures constructed within browsers for tasks
- such as ad frequency capping or conversion tracking; or logs with truncated
- and/or encrypted IP addresses and simplified User Agent strings.
-
- b. "Anonymized" means we have conducted risk mitigation to ensure
- that the dataset, plus any additional information that is in our
- possession or likely to be available to us, does not allow the
- reconstruction of reading habits, online or offline activity of groups of
- fewer than 5000 individuals or devices.
-
- c. If we generate anonymized datasets under this exception we will publicly
- document our anonymization methods in sufficient detail to allow outside
- experts to evaluate the effectiveness of those methods.
-
-5. ERRORS:
-
-From time to time, there may be errors by which user data is temporarily
-logged or retained in violation of this policy. If such errors are
-inadvertent, rare, and made in good faith, they do not constitute a breach
-of this policy. We will delete such data as soon as practicable after we
-become aware of any error and take steps to ensure that it is deleted by any
-third-party who may have had access to the data.
-
-ADDITIONAL DEFINITIONS
-
-"Fully Qualified Domain Name" means a domain name that addresses a computer
-connected to the Internet. For instance, example1.com; www.example1.com;
-ads.example1.com; and widgets.example2.com are all distinct FQDNs.
-
-"Supercookie" means any technology other than an HTTP Cookie which can be used
-by a server to associate identifiers with the clients that visit it. Examples
-of supercookies include Flash LSO cookies, DOM storage, HTML5 storage, or
-tricks to store information in caches or etags.
-
-"Risk mitigation" means an engineering process that evaluates the possibility
-and likelihood of various adverse outcomes, considers the available methods of
-making those adverse outcomes less likely, and deploys sufficient mitigations
-to bring the probability and harm from adverse outcomes below an acceptable
-threshold.
-
-"Reading habits" includes amongst other things lists of visited DNS names, if
-those domains pertain to specific topics or activities, but records of visited
-DNS names are not reading habits if those domain names serve content of a very
-diverse and general nature, thereby revealing minimal information about the
-opinions, interests or activities of the user.
diff --git a/doc/en/doco.bb b/doc/en/doco.bb
deleted file mode 100644
index 7ca64cfea..000000000
--- a/doc/en/doco.bb
+++ /dev/null
@@ -1,33 +0,0 @@
-[b]Creating Documentation[/b]
-
-To contribute documentation, simply put some words in a cunning order, and make their existence known to a developer. You can do this literally anywhere as long as a developer can see it. Once made aware, somebody will check it in for you. You should try to avoid proprietary formats, or locations that require authentication with methods other than Zot in order to make it easy for a developer to access, but even this is not a strict requirement.
-
-If you wish to contribute directly, that's fine too. To contribute directly, documentation should be in one of the following formats:
-
-[li]Markdown[/li]
-[li]BBCode[/li]
-[li]HTML[/li]
-[li]Plain Text[/li]
-
-Other formats are also allowed, but support for the format must be added to mod/help.php first.
-
-If editing a plain text file, please keep column width to 80. This is because plain text is used in instances where we may not have a working installation - the installation documentation, for example - and it should be easy to read these from a CLI text editor.
-
-The advantage of Markdown is that it is human readable.
-
-The advantage of BBCode is that it is identity aware.
-
-Therefore, if using BBCode, try to make the most of it:
-[li]Use ZRL links where appropriate to ensure a link to another site retains authentication and keeps identity based documentation working[/li]
-[li]Use baseurl or observer.baseurl tags where appropriate instead of example.com for authenticated viewers.[/li]
-[li]Support non-authenticated users with observer=0 tags. We presently do not do this due to historical oversights. This needs adding everywhere[/li]
-
-[b]Translations[/b]
-
-To translate documentation, or provided documentation in languages other than English:
-[li]Create a directory in doc/ with your two letter country code if it doesn't already exist (eg, doc/de/ for German or doc/fr/ for French)[/li]
-[li]Create a document with the same filename as the English version, but with content in your own language. This allows us to fallback to the English if the translation for a particular page is not provided[/li]
-
-To create documentation that has no equivalent file in English, you can create a new file with a name of your choosing - but you'll also need to provide a localised version of the index page (main.bb in English) to make it accessible from the menu.
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/encryption.bb b/doc/en/encryption.bb
deleted file mode 100644
index 9985f4b33..000000000
--- a/doc/en/encryption.bb
+++ /dev/null
@@ -1,18 +0,0 @@
-[size=large]Builtin Automatic Encryption[/size]
-
-Full disclosure: The encryption $Projectname uses per default is not absolutely waterproof. There [i]are[/i] known procedures to circumvent it. [i]But[/i] this takes a lot of effort and needs to be done individually for each channel. And to make this clear: Other services store your messages in plaintext, therefore we regard this approach as a [i]significant[/i] improvement for your privacy. Plus you are always free to use further encryption and password protection if you so desire.
-
-
-To explain this in more detail:
-
-- each channel has its key pair
-- every non-public post is automatically encrypted
-- optional password protect content via crypto-javascript browser-to-browser encryption (needs to be enabled in settings) Full disclosure: A rogue hub admin could injects malicious javascript-code (e.g. keylogging-abilities) into the code. Encrypt our stuff out of band with GPG, become a hub administrator yourself or use other means of communication if this worries you.
-
-So what is the scope of security? Full disclosure: This might be great, but it is not perfect.
-- every non-public post is automatically encrypted but persons who have access to the site's database and files [i]may[/i] be able to decrypt everything by using these keys which obviously need to be stored on the server. To be clear: The encrypion keys are different for every channel and it is [i]quite an effort[/i] to do this. And again: Other services store your messages in plain text unencrypted. So this [i]is[/i] quite a significant win for your privacy.
-
-We believe that the NSA-level dragnet plaintext extracting mass surveillance is probably not possible due to the design of the zot protocol. Dedicated attacks including hacking into one hub to obtain the server logs and database only partly reveal what is going on between people communication between different hubs. We believe that this makes it much more expensive for state-level attackers to access your content in $Projectname.
-
-
-We gladly accept help improving the security of the system and auditing it as well.
diff --git a/doc/en/external-resource-links.bb b/doc/en/external-resource-links.bb
deleted file mode 100644
index 338db8023..000000000
--- a/doc/en/external-resource-links.bb
+++ /dev/null
@@ -1,21 +0,0 @@
-[h2]External resource links[/h2]
-[h3]Third-Party Themes[/h3]
-[ul]
-[*][url=https://github.com/omigeot/redstrap3]Redstrap[/url]
-[*][url=https://bitbucket.org/tobiasd/red-clean]Clean[/url]
-[*][url=https://github.com/tonybaldwin/redmatrixthemes/]nubasic[/url]
-[*][url=https://github.com/deadsuperhero/redmatrix-themes]Sean Tilley's themes[/url]
-[/ul]
-[h3]Third-Party addons[/h3]
-[ul]
-[*][url=https://abcentric.net/git/abcjsplugin.git]ABCjs integration - display scores in posts (WIP)[/url]
-[/ul]
-[h3]Related projects[/h3]
-[ul]
-[*][url=https://addons.mozilla.org/en-US/firefox/addon/redshare/]Redshare for Firefox[/url]
-[*][url=https://github.com/cvogeley/red-for-android]Red for Android[/url]
-[*][url=https://github.com/zzottel/feed2red]feed2red.pl (posts Atom/RSS feeds to channel)[/url]
-[*][url=https://wordpress.org/plugins/hubzilla-wp/]WordPress gateway (combine with wppost addon for full features)[/url]
-[/ul]
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/extra_features.bb b/doc/en/extra_features.bb
deleted file mode 100644
index 17d85228e..000000000
--- a/doc/en/extra_features.bb
+++ /dev/null
@@ -1,98 +0,0 @@
-// multiple of these have been enabled by default. should we note this here somewhere, move it or remove them from this file?
-[b]Features[/b]
-
-The default interface of $Projectname was designed to be uncluttered. There are a huge number of extra features (some of which are extremely useful) which you can turn on and get the most of the application. These are found under the Extra Features link of your Settings page.
-
-[b]Content Expiration[/b]
-
-Remove posts/comments and/or private messages at a future time. An extra button is added to the post editor which asks you for an expiration. Typically this in &quot;yyyy-mm-dd hh:mm&quot; format, but in the English language you have a bit more freedom and can use most any recognisable date reference such as &quot;next Thursday&quot; or &quot;+1 day&quot;. At the specified time (give or take approximately ten minutes based on the remote system's checking frequency) the post is removed.
-
-[b]Multiple Profiles[/b]
-
-The ability to create multiple profiles which are visible only to specific persons or groups. Your default profile may be visible to anybody, but secondary profiles can all contain different or additional information and can only be seen by those to whom that profile is assigned.
-
-[b]Web Pages[/b]
-
-Provides the ability to use web page design feaures and create custom webpages from your own content and also to design the pages with page layouts, custom menus, and content blocks.
-
-[b]Private Notes[/b]
-
-On pages where it is available (your matrix page and personal web pages) provide a &quot;widget&quot; to create and store personal reminders and notes.
-
-[b]Extended Identity Sharing[/b]
-
-By default your identity travels with you as you browse the matrix to remote sites - and they know who you are and can show you content that only you can see. With Extended Identity Sharing you can provide this information to any website you visit from within the matrix.
-
-[b]Expert Mode[/b]
-
-This allows you to see some advanced configuration options that would confuse some people or cause support issues. In particular this can give you full control over theme features and colours - so that you can tweak a large number of settings of the display theme to your liking.
-
-[b]Premium Channel[/b]
-
-This allows you to set restrictions and terms on those that connect with your channel. This may be used by celebrities or anybody else who wishes to describe their channel to people who wish to connect with it. In certain cases you may be asked for payment in order to connect.
-
-[b]Post Preview[/b]
-
-Allows previewing posts and comments exactly as they would look on the page before publishing them.
-
-[b]Channel Sources[/b]
-
-Automatically import and re-publish channel content from other channels or feeds. This allows you to create sub-channels and super-channels from content provided elsewhere. The rules are that the content must be public, and the channel owner must give you permission to source their channel.
-
-[b]Even More Encryption[/b]
-
-Private messages are encrypted during transport and storage. In this day and age, this encyption may not be enough if your communications are extremely sensitive. This options lets you provide optional encryption of content &quot;end-to-end&quot; with a shared secret key. How the recipient learns the secret key is completely up to you. You can provide a hint such as &quot;the name of aunt Claire's first dog&quot;.
-
-[b]Search by Date[/b]
-
-This provides the ability to select posts by date ranges
-
-[b]Privacy Group Filter[/b]
-
-Enable widget to display stream posts only from selected privacy groups. This also toggles the outbound permissions while you are viewing a group. This is analogous to Google &quot;circles&quot; or Disapora &quot;aspects&quot;.
-
-[b]Saved Searches[/b]
-
-Provides a search widget on your matrix page which can save selected search terms for re-use.
-
-[b]Personal Tab[/b]
-
-Enable tab to display only matrix posts that you've interacted with in some way, as an author or a contributor to the conversation.
-
-[b]New Tab[/b]
-
-Enables a tab to display all new matrix activity as a firehose or timeline.
-
-[b]Affinity Tool[/b]
-
-Filter matrix stream activity by the depth of your relationships
-
-[b]Edit Sent Posts[/b]
-
-Edit and correct posts and comments after sending
-
-[b]Tagging[/b]
-
-Ability to tag existing posts, including those written by others.
-
-[b]Post Categories[/b]
-
-Add categories to your channel posts
-
-[b]Saved Folders[/b]
-
-Ability to file posts under folders or tags for later recall
-
-[b]Dislike Posts[/b]
-
-Ability to dislike posts/comments
-
-[b]Star Posts[/b]
-
-Ability to mark special posts with a star indicator
-
-[b]Tag Cloud[/b]
-
-Provide a personal tag cloud on your channel page
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/faq_admins.bb b/doc/en/faq_admins.bb
deleted file mode 100644
index 0b54a41de..000000000
--- a/doc/en/faq_admins.bb
+++ /dev/null
@@ -1,78 +0,0 @@
-[size=large][b]$Projectname FAQ[/b][/size]
-
-[toc]
-
-[h3]Is there a way to change the Admin account?[/h3]
-[h3]Is there a way to have multiple administrators?[/h3]
-Yes, but it's a bit messy at the moment as it is not yet exposed in the UI. To make an account an administrative account,
-one needs to add 4096 to the account_roles entry in the account table of the database. Likewise, to remove administrative permissions,
-one must subtract 4096 from the account roles.
-
-[h3]I can log in, but there are no posts or webpages[/h3]
-
-Most likely, your item table has crashed. Run the MySQL command [code]repair table item;[/code]
-
-[h3]Login doesn't work, immediately after login, the page reloads and I'm logged out[/h3]
-
-Most likely, your session table has crashed. Run the MySQL command [code]repair table session;[/code]
-
-[h3]When I switch theme, I sometimes get elements of one theme superimposed on top of the other[/h3]
-
-a) store/[data]/smarty3 isn't writeable by the webserver. Make it so.
-
-b) You're using Midori, or with certain themes, Konqueror in KHTML mode.
-
-[b]My network tab won't load, it appears to be caused by a photo or video[/h3]
-
-Your PHP memory limit is too low. Increase the size of the memory_limit directive in your php.ini
-
-Contrary to popular belief, the number of users on a hub doesn't make any difference to the required memory limit, rather, the content
-of an individuals matrix counts. Streams with lots of photos and video require more memory than streams with lots of text.
-
-[h3]I have no communication with anybody[/h3]
-
-You're listening on port 443, but do not have a valid SSL certificate. Note this applies even if your baseurl is http.
-Don't listen on port 443 if you cannot use it. It is strongly recommended to solve this problem by installing a browser
-valid SSL certificate rather than disabling port 443.
-
-[h3]How do I update a non-Git install?[/h3]
-1) Backup .htconfig.php
-2) Backup everything in store/
-3) Backup any custom changes in mod/site/ and view/site
-3) Delete your existing installation
-4) Upload the new version.
-5) Upload the new version of themes and addons.
-6) Restore everything backed up earlier.
-
-[h3]What do I need to do when moving my hub to a different server[/h3]
-
-1) Git clone on the new server. Repeat the process for any custom themes, and addons.
-2) Rsync .htconfig.php
-3) Rsync everything in store/
-4) Rsync everything in mod/site/ and view/site (these will only exist if you have custom modules)
-5) Dump and restore DB.
-
-[h3]How do I reinstall an existing hub on the same server?[/h3]
-
-1) [code]git reset --hard HEAD[/code] will reset all files to their upstream defaults. This will not reset any local files that do not also exist upstream. Eg, if you have local changes to mod/channel.php, this will reset them - but will not reset any changes in mod/site/channel.php
-2) If you absolutely must reinstall - for example, if you need to upgrade operating system - follow the steps for moving to a different server, but instead of using rsync, backup and restore some other way.
-
-Do not reinstall a hub with a fresh database and fresh .htconfig.php unless as a very last resort. Creating a temporary account and ask for help via a support channel for non-trivial reinstalls is preferable to reinstalling fresh.
-
-[h3]How do I set the default homepage for logged out viewers?[/h3]
-
-Use the custom_home addon available in the main addons repository.
-
-[h3]What do the different directory mode settings mean?[/h3]
-[code]// Configure how we communicate with directory servers.
-// DIRECTORY_MODE_NORMAL = directory client, we will find a directory (all of your member's queries will be directed elsewhere)
-// DIRECTORY_MODE_SECONDARY = caching directory or mirror (keeps in sync with realm primary [adds significant cron execution time])
-// DIRECTORY_MODE_PRIMARY = main directory server (you do not want this unless you are operating your own realm. one per realm.)
-// DIRECTORY_MODE_STANDALONE = "off the grid" or private directory services (only local site members in directory)
-[/code]
-- The default is NORMAL. This off-loads most directory services to a different server. The server used is the config:system/directory_server. This setting MAY be updated by the code to one of the project secondaries if the current server is unreachable. You should either be in control of this other server, or should trust it to use this setting.
-- SECONDARY. This allows your local site to act as a directory server without exposing your member's queries to another server. It requires extra processing time during the cron polling, and is not recommended to be run on a shared web host.
-- PRIMARY. This allows you to run a completely separate 'Network' of directory servers with your own Realm. By default, all servers are on the RED_GLOBAL realm unless the config:system/directory_realm setting is overridden. [i]Do not use this unless you have your own directory_realm.[/i]
-- STANDALONE. This is like primary, except it's a 'Network' all on it's own without talking to any other servers. Use this if you have only one server and want to be segregated from the Red#Matrix directory listings.
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/feature/access_tokens.bb b/doc/en/feature/access_tokens.bb
deleted file mode 100644
index eb5c03717..000000000
--- a/doc/en/feature/access_tokens.bb
+++ /dev/null
@@ -1,47 +0,0 @@
-Feature: Zot Access Tokens
-Status: Draft
-Date: 15 July 2016
-
-
-Purpose:
-
-In order to facilitate sharing of private resources with non-members or members of federation nodes with limited identification discovery, Hubzilla should provide members with a mechanism to create and manage temporary ("throwaway") logins, aka "Zot Access Tokens". These tokens/credentials may be used to authenticate to a hubzilla site for the sole purpose of accessing privileged or access controlled resources (files, photos, posts, webpages, chatrooms, etc.).
-
-
-Scope:
-
-Zot Access Tokens do not convey membership in the site or network. In particular, they do not provide an account or channel; which may be necessary to interact with the hub owner or with others in the network or federation of networks. In most cases they can only be used to consume restricted resources and do not have an ability to create those resources, however this ability may be provided by custom configurations or in future releases or addons.
-
-For instance the ability for a temporary login to access a chatroom may provide suitable permission to create chat messages inside that chatroom.
-
-
-Implementation:
-
-Zot Access Tokens are managed through a "tab" of the settings page. Access to this tab may be controlled by site configuration. On this page, channels may create, edit, list, and remove any access tokens under their control.
-
-The form to create/edit accepts three parameters, a human readable name, a password or access token, and an optional expiration. Once expired, the access token is no longer valid, may no longer be used, and will be automatically purged from the list of temporary accounts. The password field in the create/edit forms displays the text of the access token and not an obscured password. By default we will create a token using the autoname() function, which generally produces a random character sequence which is "pronounceable", hence easy to convey or remember. This can be changed to any other character sequence which is acceptable to the site password complexity policy. (In most Hubzilla installations this imposes a minimum of three characters, but may be extended by plugin or site policy).
-
-
-Usage:
-
-We do not specify mechanisms for sharing these tokens with others. Any communication method may be used. Any tokens you have created are added to the Access Control List selector and may be used anywhere that Access Control Lists are provided.
-
- Example: A visitor arrives at your site. She has an access token you have provided, and attempts to visit one of your photo albums (which is restricted to be viewed only by yourself and one temporary identity). Permission is denied.
-
-The visitor now selects "Login" from the menu navigation bar. This presents a login page. She enters the name and password you have provided her, and she can now view the restricted photo album.
-
-
-Alternatively, you may share a link to a protected file by adding a parameter "&zat=abc123" to the URL, where the string "abc123" is the access token or password for the temporary login. No further negotiation is required, and the file is presented.
-
-Zot Acess Tokens are represented internally as an authenticated "observer". Querying the observer in code should return a pseudo or system generated xchan with an unknown protocol and a default profile photo. It will match (successfully) any access control rule which allows authenticated observers.
-
-Security Considerations:
-
-The URL form of authentication is inherently less secure than using a login, but may be preferable for some uses of this feature. It probably should not be transmitted over non-SSL links.
-
-
-Future development:
-
-It might be desirable for future implementations to provide an options for single-use, where the access token is removed promptly following first use.
-
- \ No newline at end of file
diff --git a/doc/en/feature/additional/access.md b/doc/en/feature/additional/access.md
deleted file mode 100644
index b7f0df717..000000000
--- a/doc/en/feature/additional/access.md
+++ /dev/null
@@ -1,41 +0,0 @@
-## Access Control and Permissions
-
-
-### Privacy Groups
-
-Enable management and selection of privacy groups.
-<!-- TODO: full description for Privacy Groups -->
-
-Minimum required technical skill level to see this feature: 0
-
-
-### Multiple Profiles
-
-Ability to create multiple profiles.
-<!-- TODO: full description for Multiple Profiles -->
-
-Minimum required technical skill level to see this feature: 3
-
-
-### Permission Categories
-
-Provide alternate connection permission limits.
-<!-- TODO: full description for Permission Categories -->
-
-Minimum required technical skill level to see this feature: 2
-
-
-### OAuth Clients
-
-Manage authenticatication tokens for mobile and remote apps.
-<!-- TODO: full description for OAuth Clients -->
-
-Minimum required technical skill level to see this feature: 1
-
-
-### Access Tokens
-
-Create access tokens so that non-members can access private content.
-<!-- TODO: full description for Access Tokens -->
-
-Minimum required technical skill level to see this feature: 2
diff --git a/doc/en/feature/additional/composition.md b/doc/en/feature/additional/composition.md
deleted file mode 100644
index 1256f7501..000000000
--- a/doc/en/feature/additional/composition.md
+++ /dev/null
@@ -1,67 +0,0 @@
-## Post Composition Features
-
-
-### Large Photos
-
-Include large (1024px) photo thumbnails in posts.
-If not enabled, use small (640px) photo thumbnails
-<!-- TODO: full description for Large Photos -->
-
-Minimum required technical skill level to see this feature: 1
-
-
-### Channel Sources
-
-Automatically import channel content from other channels or feeds
-<!-- TODO: full description for Channel Sources -->
-
-Minimum required technical skill level to see this feature: 3
-
-
-### Even More Encryption
-
-Allow optional encryption of content end-to-end with a shared secret key
-<!-- TODO: full description for Even More Encryption -->
-
-Minimum required technical skill level to see this feature: 3
-
-
-### Enable Voting Tools
-
-Provide a class of post which others can vote on
-<!-- TODO: full description for Enable Voting Tools -->
-
-Minimum required technical skill level to see this feature: 3
-
-
-### Disable Comments
-
-Provide the option to disable comments for a post
-<!-- TODO: full description for Disable Comments -->
-
-Minimum required technical skill level to see this feature: 2
-
-
-### Delayed Posting
-
-Allow posts to be published at a later date
-<!-- TODO: full description for Delayed Posting -->
-
-Minimum required technical skill level to see this feature: 2
-
-
-### Content Expiration
-
-Remove posts/comments and/or private messages at a future time
-<!-- TODO: full description for Content Expiration -->
-
-Minimum required technical skill level to see this feature: 1
-
-
-### Suppress Duplicate Posts/Comments
-
-Prevent posts with identical content to be published
-with less than two minutes in between submissions.
-<!-- TODO: full description for Suppress Duplicate Posts/Comments -->
-
-Minimum required technical skill level to see this feature: 1
diff --git a/doc/en/feature/additional/filtering.md b/doc/en/feature/additional/filtering.md
deleted file mode 100644
index ba8e1e29f..000000000
--- a/doc/en/feature/additional/filtering.md
+++ /dev/null
@@ -1,57 +0,0 @@
-## Network and Stream Filtering
-
-
-### Search by Date
-
-Ability to select posts by date ranges
-<!-- TODO: full description for Search by Date -->
-
-Minimum required technical skill level to see this feature: 1
-
-
-### Saved Searches
-
-Save search terms for re-use
-<!-- TODO: full description for Saved Searches -->
-
-Minimum required technical skill level to see this feature: 2
-
-
-### Network Personal Tab
-
-Enable tab to display only Network posts that you've interacted on
-<!-- TODO: full description for Network Personal Tab -->
-
-Minimum required technical skill level to see this feature: 1
-
-
-### Network New Tab
-
-Enable tab to display all new Network activity
-<!-- TODO: full description for Network New Tab -->
-
-Minimum required technical skill level to see this feature: 2
-
-
-### Affinity Tool
-
-Filter stream activity by depth of relationships
-<!-- TODO: full description for Affinity Tool -->
-
-Minimum required technical skill level to see this feature: 1
-
-
-### Suggest Channels
-
-Show friend and connection suggestions
-<!-- TODO: full description for Suggest Channels -->
-
-Minimum required technical skill level to see this feature: 1
-
-
-### Connection Filtering
-
-Filter incoming posts from connections based on keywords/content
-<!-- TODO: full description for Connection Filtering -->
-
-Minimum required technical skill level to see this feature: 3
diff --git a/doc/en/feature/additional/general.md b/doc/en/feature/additional/general.md
deleted file mode 100644
index a1b712b12..000000000
--- a/doc/en/feature/additional/general.md
+++ /dev/null
@@ -1,130 +0,0 @@
-## General Features
-
-
-### New Member Links
-
-Display new member quick links menu.
-<!-- TODO: full description for New Member Links -->
-
-Minimum required technical skill level to see this feature: 1
-
-
-### Advanced Profiles
-
-Additional profile sections and selections
-<!-- TODO: full description for Advanced Profiles -->
-
-Minimum required technical skill level to see this feature: 1
-
-
-### Profile Import/Export
-
-Save and load profile details across sites/channels
-<!-- TODO: full description for Profile Import/Export -->
-
-Minimum required technical skill level to see this feature: 3
-
-
-### Web Pages
-
-Provide managed web pages on your channel
-<!-- TODO: full description for Web Pages -->
-
-Minimum required technical skill level to see this feature: 3
-
-
-### Wiki
-
-Provide a wiki for your channel
-<!-- TODO: full description for Wiki -->
-
-Minimum required technical skill level to see this feature: 2
-
-
-### Private Notes
-
-Enables a tool to store notes and reminders (note: not encrypted)
-<!-- TODO: full description for Private Notes -->
-
-Minimum required technical skill level to see this feature: 1
-
-
-### Cards
-
-Create personal planning cards
-<!-- TODO: full description for Cards -->
-
-Minimum required technical skill level to see this feature: 1
-
-
-### Articles
-
-Create interactive articles
-<!-- TODO: full description for Articles -->
-
-Minimum required technical skill level to see this feature: 1
-
-
-### Navigation Channel Select
-
-Change channels directly from within the navigation dropdown menu
-<!-- TODO: full description for Navigation Channel Select -->
-
-Minimum required technical skill level to see this feature: 3
-
-
-### Photo Location
-
-If location data is available on uploaded photos, link this to a map.
-<!-- TODO: full description for Photo Location -->
-
-Minimum required technical skill level to see this feature: 2
-
-
-### Access Controlled Chatrooms
-
-Provide chatrooms and chat services with access control.
-<!-- TODO: full description for Access Controlled Chatrooms -->
-
-Minimum required technical skill level to see this feature: 1
-
-
-### Smart Birthdays
-
-Make birthday events timezone aware in case your friends are scattered across the planet.
-<!-- TODO: full description for Smart Birthdays -->
-
-Minimum required technical skill level to see this feature: 2
-
-
-### Event Timezone Selection
-
-Allow event creation in timezones other than your own.
-<!-- TODO: full description for Event Timezone Selection -->
-
-Minimum required technical skill level to see this feature: 2
-
-
-### Premium Channel
-
-Allows you to set restrictions and terms
-on those that connect with your channel
-<!-- TODO: full description for Premium Channel -->
-
-Minimum required technical skill level to see this feature: 4
-
-
-### Advanced Directory Search
-
-Allows creation of complex directory search queries
-<!-- TODO: full description for Advanced Directory Search -->
-
-Minimum required technical skill level to see this feature: 4
-
-
-### Advanced Theme and Layout Settings
-
-Allows fine tuning of themes and page layouts
-<!-- TODO: full description for Advanced Theme and Layout Settings -->
-
-Minimum required technical skill level to see this feature: 4
diff --git a/doc/en/feature/additional/overview.md b/doc/en/feature/additional/overview.md
deleted file mode 100644
index b51f60bcd..000000000
--- a/doc/en/feature/additional/overview.md
+++ /dev/null
@@ -1,33 +0,0 @@
-[chset]: /settings "Channel Settings"
-[ftset]: /settings/features "Additional Features Settings"
-[ftgen]: /help/feature/additional/general "General Features"
-[ftacc]: /help/feature/additional/access "Access Control and Permissions"
-[ftcom]: /help/feature/additional/composition "Post Composition Features"
-[ftfil]: /help/feature/additional/filtering "Network and Stream Filtering"
-[ftpos]: /help/feature/additional/posts "Post/Comment Tools"
-
-
-# Additional Features
-
-<!-- TODO: Introduction to additional features -->
-
-<!-- TODO: Short info and crosslink on techlevels -->
-
-You can switch the features on and off from the
-[Additional Features][ftset] link in the [Channel Settings][chset].
-
-<!-- TODO: Infos about feature visibility and causes/dependencies -->
-
-The following pages decribe all the available features
-grouped in the same way as they are with the accordion tabs on the
-[Additional Features][ftset] settings page:
-
-[General Features][ftgen]
-
-[Access Control and Permissions][ftacc]
-
-[Post Composition Features][ftcom]
-
-[Network and Stream Filtering][ftfil]
-
-[Post/Comment Tools][ftpos]
diff --git a/doc/en/feature/additional/posts.md b/doc/en/feature/additional/posts.md
deleted file mode 100644
index d3f6b37db..000000000
--- a/doc/en/feature/additional/posts.md
+++ /dev/null
@@ -1,57 +0,0 @@
-## Post/Comment Tools
-
-
-### Community Tagging
-
-Ability to tag existing posts
-<!-- TODO: full description for Community Tagging -->
-
-Minimum required technical skill level to see this feature: 1
-
-
-### Post Categories
-
-Add categories to your posts
-<!-- TODO: full description for Post Categories -->
-
-Minimum required technical skill level to see this feature: 1
-
-
-### Emoji Reactions
-
-Add emoji reaction ability to posts
-<!-- TODO: full description for Emoji Reactions -->
-
-Minimum required technical skill level to see this feature: 1
-
-
-### Saved Folders
-
-Ability to file posts under folders
-<!-- TODO: full description for Saved Folders -->
-
-Minimum required technical skill level to see this feature: 2
-
-
-### Dislike Posts
-
-Ability to dislike posts/comments
-<!-- TODO: full description for Dislike Posts -->
-
-Minimum required technical skill level to see this feature: 1
-
-
-### Star Posts
-
-Ability to mark special posts with a star indicator
-<!-- TODO: full description for Star Posts -->
-
-Minimum required technical skill level to see this feature: 1
-
-
-### Tag Cloud
-
-Provide a personal tag cloud on your channel page
-<!-- TODO: full description for Tag Cloud -->
-
-Minimum required technical skill level to see this feature: 2
diff --git a/doc/en/federate.bb b/doc/en/federate.bb
deleted file mode 100644
index 9137ec160..000000000
--- a/doc/en/federate.bb
+++ /dev/null
@@ -1,71 +0,0 @@
-[h2]Creating protocol federation services[/h2]
-
-There are three main components to writing federation plugins. These are:
-
-[1] Channel discovery and making connections
-[2] Sending posts/messages
-[3] Receiving posts/messages
-
-In addition, federation drivers must handle
-
-[4] differences in privacy policies (and content formats)
-
-
-[h3]Making connections[/h3]
-
-The core application provides channel discovery in the following sequence:
-
-[1] Zot channel discovery
-[2] Webfinger (channel@hub) lookup
- [2.1] RFC7033 webfinger
- [2.2] XRD based webfinger (old style)
-[3] URL discovery (currently only used to discover RSS feeds)
-[4] If all of these fail, the network is "unknown" and we are unable to communicate with or connect with the channel. An 'xchan' record [i]may[/i] still be created [b]if[/b] there is enough information known to identify a unique channel.
-
-Any of the lookup services may be bound to a plugin and extended. When a channel is discovered, we create an 'xchan' record which is a platform neutral representation of the channel identity. If we need to send information to the channel, a 'hubloc' (hub location) record is also generally required. A 'follow' plugin hook is provided to bypass webfinger and this discovery sequence completely.
-
-The final step in gluing this together is to create an 'abook' record, which attaches an xchan in a relationship to a local channel with a given set of permissions.
-
-For networks which do not support nomadic identity, your plugin must also set "abook_instance" which is a comma-separated list of local URLs that the remote channel is connected with. For instance if your member was connected to my channel clone at https://example.com, the abook_instance for that connection would read 'https://example.com'. If you also connected to my clone at https://abc.example.com, the string would be changed to 'https://example.com,https://abc.example.com'. This allows local channels to differentiate which instance a given remote channel is connected with and avoid delivery failures to those channels from other clone instances.
-
-A federation plugin for a webfinger based system needs only to examine the webfinger or XRD results and identify a protocol stack which can be used to connect or communicate. Then create an xchan record with the given 'xchan_network' type and a hubloc record with the same 'hubloc_network' with the information given. Currently the plugin will need to create the entire record, but in the future this will be extended so that the plugin only need identify a network name and the record will be populated with all other known values.
-
-An xchan record is always required in order to connect. To connect, create an abook record with the desired permissions.
-
-Additional information that your plugin requires for communication can be stored in the xconfig table and/or abconfig table if there is no convenient or appropriate table column in the xchan or hubloc tables.
-
-When a connection is made, we generally call the notifier (include/notifier.php) to send a message to the remote channel. This is bound to the hook 'permissions_create'. Your plugin will need to handle this in order to send a "follow" or "make friends" message to the other network.
-
-Notes: The first stage zot lookup will be replaced with a webfinger lookup. This work is in progress. A separate lookup was required initially as webfinger does not allow non-SSL connections. We will provide non-SSL zot lookups (usually test and development sites) via the "old" XRD based webfinger to avoid this limitation.
-
-The core application will attempt to create xchan records for projects identified as members of the "open web"; currently Hubzilla, Friendica, Diaspora, GNU-Social and Pump.io. This is so that comments can be passed amongst project sites and the network correctly identified. A federation plugin is required to fully federate with other networks, but comments may be passed to sites without such a plugin installed so that there are no unexplained holes in conversations.
-
-The core application must also provide signing ability for Diaspora comments since they require a special signing format and must be signed by the comment author regardless of whether that channel federates with Diaspora. The owner of the conversation may federate with Diaspora so the comments must be signed. This is unfortunate but necessary.
-
-
-[h3]Sending Messages[/h3]
-
-Whenever any message is sent (with the sole exception of directory communications), we invoke the notifier (include/notifier.php), and pass it the type of message and generally an identifier to lookup the information being sent from the database (items or conversational things, private mail, permissions updates, etc.).
-
-The notifier has several hooks which may be used by plugins in different ways, depending on how their delivery loop works. For different message types and complex delivery situations you may need to tie into multiple hooks. The 'permissions_create' hook was mentioned in the first section. There is also a 'permissions_update' message if permissions have changed and the other end of the link needs to be advised. Few services will provide or handle this (as their permissions are static), but it is also used for instance to send profile and profile photo update messages and you may wish to handle this.
-
-The next plugin hook is 'notifier_process'. It is passed an array providing the complete state of the notifier and is called once per notifier invocation. It contains the complete list of recipients (with xchan_network set for each).
-
-There is also 'notifier_hub' which like 'notifier_process' is passed the complete state of the notifier, but the difference is that it is called for each individual hub or distinct URL delivery and may be matched on the hubloc_network type. Hub delivery is much more efficient than recipient delivery but may not be suitable for all protocol stacks.
-
-
-Your plugin will be required to understand the message state and recipients and translate the sent item to the desired format. You will also be required to check privacy and block communication to anybody but the intended recipients - *if* it is a private communication. The plugin will often at this point stick the message into the queue and return the queue id to the notifier.
-
-
-Queue handlers exist already for simple posted data. If you create a queue entry with 'post' type we'll open an HTTP POST request and post the data provided and acknowledge success or failure. You can create other forms of communication by providing a different outq_driver type and handling the processing of queue requests yourself. Delivery reporting is available if you wish to acknowledge different error conditions, or anything beyond success/failure. Advanced delivery reporting will also require a custom queue type. The basic 'post' type only deals with success (communication successful with the remote site) and failure.
-
-
-
-[h3]Receiving Messages[/h3]
-
-
-Receiving messages from the remote network will probably require a 'receive' endpoint or module dedicated to your network communication protocol. This is a URL route that your plugin may need to register with the'module_loaded' hook. You module will then take responsibility for importing any data which arrives at that endpoint and translating it to the format required for this project and storing the resulting data. The basic structure we use is an extensible activitystream item but with slightly different field names and several optional fields. It can be easily mapped to an activitystream. Additional data can be stored in the "iconfig" table. item_store() and item_store_update() are generally used to store the data and send appropriate notifications. Similiar facilities are available for private mail and profile information.
-
-
-
-
diff --git a/doc/en/filesync.md b/doc/en/filesync.md
deleted file mode 100644
index 4c64bdb09..000000000
--- a/doc/en/filesync.md
+++ /dev/null
@@ -1,61 +0,0 @@
-File Sync and Clone
-===================
-
-
-
-File cloning across multiple instances of a channel is a very hard problem, due to the nature of PHP memory allocation. This needs to be handled dramatically differently than cloning or syncing of other information. (Processing one large video file or 40-50 photos could exhaust memory). Therefore we can't easily just dump all the data to a dump file and sequentially process it. Loading the dump file itself is likely to exhaust memory.
-
-There are also two primary operations we are considering. The first is the hardest - saving and then importing all your channel information into a new channel clone. The second is synchronising file changes as they occur across two or more "active" clones.
-
-For the first cut at this tool we will concentrate on the second case, while trying to maintain some measure of compatibility with the first case so that we can re-use the same tools.
-
-Meta Data
-=========
-
-
-First we need the metadata for the file in order to precisely re-construct its structure on another site. This requires the following information:
-
-'attach' structure (without file contents - which is the default) for the file itself **and** its parent directories so that we can re-create its precise place in the file system, since we do not know if the parent directory has been imported previously or ever.
-
-'photo' structure for any photo elements which were created as a result of uploading this file into the system. This typically contains several different 'scales' or thumbnail images, some of which may be cropped for profile photo use or cover photo use. We need to retain the cropping information which is not present in the metadata, but only in the stored data. The actual thumbnail image data may or may not be included in the metadata. A cover photo of large scale (scale #7) could potentially cause memory issues. Not as bad as a 100M video, but if you have several of these they could add up.
-
-'item' entries which are linked to this file. These can be file share activities, the "parent item" linked to photos, and any attached conversation items (photo likes, comments, etc.)
-
-All of these items will require URL replacement and re-signing of the item as they are relocated to another site.
-
-
-File Data
-=========
-
-Then we have the actual file data we need to reconstruct the file. This needs to be stored separately from the meta-data to avoid memory exhaustion when processing. The actual file data can be used to reconstruct the attach structure and the first four photo scales. If this is a photo, we need access to the "#4 scale" (profile photo) and the #7 scale (cover photo) as they were originally cropped. All other thumbnails can be generated from these.
-
-
-
-File Sync
-=========
-
-
-We will consider this operation first because it is probably the most straightforward to implement. When a photo is added to or removed or changed from the source system, we will send a clone sync packet to all known clones containing the metadata - but **no file data** . We can only send one sync packet per file operation that needs to be synced.
-
-The receiving end will create and perform URL translation on all the metadata structures and store them. Then it will need to fetch the actual data. Assuming CURL supports streaming, an authenticated request is sent to the original site and the original file is requested and streamed directly to disk (bypassing all processing). If photo scale #4 or scale #7 is required, these are requested and stored into their respective structures. We're assuming in this case that the cover photo large scale will not exhaust memory. If CURL cannot be made to support streaming, request packets need to be queued and sent to the origination site to obtain "chunks" of the file and re-assembled once all chunks have been retrieved.
-
-The authenticated request depends on the mechanism. For CURL streaming, some signed secret with a timestamp will probably need to be generated and posted to the file origination site. Then the data can be retrieved with minimal internal processing and dumped directly to disk using stdio buffering. In the case of a zot request, the zot request packet will be validated, however scheduling chunk batches and re-assembling them could be tricky.
-
-
-File Backup/Restore
-===================
-
-This is much more complicated as we do not have an authenticate web server to request data from. The metadata can be mostly the same, but we need some form of signalling that we will not be fetching the file via the web. This will likely require a client side process to parse each metadata file and locate a file on disk which it is associated with. Then the data would need to be streamed to the destination server with a special endpoint designed for this task. A java app might be the best option here to retain platform neutrality.
-
-Another option would be to use WebDAV for this step. The metadata files would be uploaded first, and then the data files. If a data file corresponded to an existing metadata file, the metadata would be processed; the file stored appropriately, and the metadata file then removed. In this case, photos of scales 4 and 7 would need to be provided in the metadata.
-
-
-Optionally, this step could also be performed with a filesystem local to the destination server. This would be the highest performance, and a suite of shell-based tools (in the case of Linux) could perform the "client-side" of the task.
-
-The complexity of this task mandates careful planning into how the data is organised and stored and if necessary backed up remotely or transmitted for backup by the source website.
-
-
-Backward Compatibility
-======================
-
-There are some obvious issues with making data available for backup or cloning which existed on the system prior to the existence of restore/sync tools. To keep the tools themselves relatively uncomplicated (to the extent possible given the constraints) backward compatibility may have to be preformed by dedicated plugin or addon. \ No newline at end of file
diff --git a/doc/en/first-post.bb b/doc/en/first-post.bb
deleted file mode 100644
index cf6ed5b49..000000000
--- a/doc/en/first-post.bb
+++ /dev/null
@@ -1,3 +0,0 @@
-[size=large]Your first posting[/size]
-
-... to be written ...
diff --git a/doc/en/functions.md b/doc/en/functions.md
new file mode 100644
index 000000000..93da03c06
--- /dev/null
+++ b/doc/en/functions.md
@@ -0,0 +1,163 @@
+### Functions
+
+The following are some of the main features of Hubzilla that are included in the official version. Hubzilla is a highly extensible platform, so more features and capabilities can be added through additional themes and plugins.
+
+#### Affinity slider
+
+When adding connections in Hubzilla, members have the option to assign ‘affinity levels’ (how close your friendship is) to the new connection. For example, if you add someone whose blog you follow, you can assign their channel an ‘Acquaintance’ affinity level.
+
+On the other hand, if you add a friend's channel, you could assign them the ‘Friends’ affinity level.
+
+At this point, the Hubzilla *affinity slider tool*, which usually appears at the top of your ‘Stream’ page, will adjust the content of the page to display channels within the desired affinity range. Channels outside this range will not be displayed unless you adjust the slider to include them.
+
+The affinity slider allows for instant filtering of large amounts of content, grouped by degree of proximity.
+
+#### Filtering connections
+
+The optional ‘connection filter’ gives you the ability to control exactly what is displayed in your stream. When enabled, the connection editor provides inputs to select criteria that must be met to include or exclude a specific post from a specific channel. Once a post has been allowed, all comments on that post are allowed, regardless of whether they meet the selection criteria. You can select words that, if present, will block the post or ensure that it is included in your stream. Regular expressions can be used for even finer control, as well as hashtags or even the recognised language of the post.
+
+#### Access control lists
+
+When sharing content, members have the option to restrict who can see the content. By clicking on the padlock below the share field, you can select the desired recipients of the post by clicking on their name.
+
+Once sent, the message can only be viewed by the sender and the selected recipients. In other words, the message will not appear on public walls.
+
+Access control lists can be applied to content and posts, photos, events, websites, chat rooms and files.
+
+#### Single sign-on
+
+Access control lists work for all channels in the grid thanks to our unique single sign-on technology. Most internal links provide an identity token that can be verified on other Hubzilla sites and used to control access to private resources. You log in to your home hub once. After that, authentication is ‘magic’ for all Hubzilla resources.
+
+#### WebDAV-enabled file storage
+
+Files can be uploaded to your personal storage area using your operating system's utilities (in most cases by drag & drop). You can protect these files with access control lists for any combination of Hubzilla members (including some third-party network members) or make them publicly accessible.
+
+#### Photo albums
+
+Store photos in albums. All your photos can be protected by access control lists.
+
+#### Events calendar
+
+Create and manage events and tasks, which can also be protected with access control lists. Events can be imported/exported to other software using the industry standard vcalendar/iCal format and shared in posts. Birthday events are automatically added by your friends and converted to your correct time zone so you know exactly when the birthday takes place - regardless of where in the world you are in relation to the birthday person. Events are usually created with attendance counters so that your friends and connections can instantly approve.
+
+#### Chat rooms
+
+You can create any number of personal chat rooms and allow access via access control lists. These are usually more secure than XMPP, IRC and other instant messaging transports, although we also allow the use of these other services via plugins.
+
+#### Website creation
+
+Hubzilla has numerous ‘content management’ tools for creating web pages, including layout editing, menus, blocks, widgets and page/content areas. All of these tools can be access controlled so that the resulting pages are only accessible to the intended audience.
+
+#### Apps
+
+Apps can be created and distributed by members. These differ from traditional ‘vendor lockin’ apps in that they are completely controlled by the author - who can control access to the app's target pages and pay for that access accordingly. Most of the apps in Hubzilla are free and can be easily created by people without programming skills.
+
+#### Layout
+
+The page layout is based on a description language called Comanche. Hubzilla itself is written in Comanche layouts, which you can change. This allows a level of customisation not normally found in so-called ‘multi-user environments’.
+
+#### Bookmarks
+
+Share and save/manage bookmarks of links provided in conversations.
+
+#### Encryption of private messages and data protection aspects
+
+Private messages are saved in an encrypted format. Although this is not 100% secure, it usually prevents the occasional spying by the site administrator or ISP.
+
+Each Hubzilla channel has its own set of private and associated public RSA 4096-bit keys, which are generated when the channel is first created. This is used to protect private messages and posts during transmission.
+
+Additionally, messages can be created with ‘end-to-end encryption’ that cannot be read by Hubzilla operators, ISPs, or anyone else who does not know the passcode.
+
+Public messages are generally not encrypted during transmission or storage.
+
+Private messages can be withdrawn (not sent), although there is no guarantee that the recipient has not yet read them.
+
+Posts and messages can be given an expiry date, after which they are deleted/removed from the recipient's page.
+
+#### Service federation
+
+As well as additional ‘cross-post connectors’ to a variety of alternative networks, there is native support for importing content from RSS/Atom feeds and using them to create specialised channels. Plugins are also available to communicate with others via the Diaspora and GNU-Social (OStatus) protocols. These networks do not support nomadic identity or cross-domain access control; however, basic communication to/from Diaspora, Friendica, GNU-Social, Mastodon and other providers using these protocols is supported.
+
+There is also experimental support for OpenID authentication that can be used in access control lists. This is a project that is being worked on. Your Hubzilla hub can be used as an OpenID provider to authenticate you to external services that use this technology.
+
+Channels can be authorised to become ‘derivative channels’, where two or more existing channels are combined to create a new thematic channel.
+
+#### Privacy groups
+
+Our implementation of privacy groups is similar to Google's ‘Circles’ and Diaspora's ‘Aspects’. This allows you to filter your incoming stream by selected groups and automatically restrict the outgoing access control list to members of that group when you post. You can undo this at any time (before sending the post).
+
+#### Directory services
+
+We provide easy access to a member directory and provide decentralised tools that can make ‘suggestions’ to friends. The directories are normal Hubzilla sites that have decided to take on the role of directory server. This requires more resources than most typical sites and is therefore not the default setting. The directories are synchronised and mirrored so that they all contain up-to-date information across the network (subject to normal propagation delays).
+
+#### TLS/SSL
+
+For Hubzilla hubs that use TLS/SSL, communication between client and server is encrypted via TLS/SSL. In light of recent media revelations about widespread global surveillance and encryption circumvention by the NSA and GCHQ, it is reasonable to assume that HTTPS-protected communications can be compromised in a number of ways. Private communication is therefore encrypted at a higher level before it is sent to the outside world.
+
+#### Channel settings
+
+When a channel is created, a role is selected that applies a set of pre-configured security and privacy settings. These are selected according to best practice to maintain the desired level of data protection.
+
+If you select a ‘custom’ privacy role, you can set fine-grained permissions for different aspects of communication for each channel. For example, under the heading ‘Security and Privacy Settings’, there are six (6) possible display/access options for each aspect on the left-hand side, which can be selected by clicking on the drop-down menu. There are also a number of other privacy settings that you can edit.
+
+The options are:
+
+\- No one but yourself. - Only those you explicitly authorise. - Anyone in your address book. - Everyone on this website. - Everyone on this network. - Everyone who is authenticated. - Anyone who is on the internet.
+
+#### Public and private forums
+
+Forums are usually channels in which several authors can participate. There are currently two ways to publish posts in forums: 1) ‘wall-to-wall’ posts and 2) via @mention tags in the forum. Forums can be created by anyone and used for any purpose. The directory includes an option to search for public forums. Private forums can only contain posts that are often only seen by members.
+
+#### Cloning accounts
+
+Accounts in Hubzilla are referred to as *nomadic identities* because a member's identity is not tied to the hub where the identity was originally created. For example, if you create a Facebook or Gmail account, it is tied to those services. You cannot function without Facebook.com or Gmail.com.
+
+Let's say you created a Hubzilla identity called **`tina@hubzillahub.com`**. You can clone it to another Hubzilla hub by choosing the same or a different name: **`liveForever@somehubzillahub.info`**
+
+Both channels will now be synchronised, i.e. all your contacts and settings will be duplicated on your clone. It does not matter whether you send a post from your original hub or from the new hub. The posts will be mirrored on both accounts.
+
+This is a pretty revolutionary feature if we consider a few scenarios:
+
+- What happens if the hub an identity is in suddenly goes offline? Without cloning, a member is unable to communicate until the hub is back online (no doubt many of you have seen and cursed Twitter's ‘Fail Whale’). With cloning, you simply log into your cloned account and life goes merrily on.
+- The administrator of your hub can no longer afford to pay for his free and public Hubzilla hub. He announces that the hub will be shut down in a fortnight. This gives you plenty of time to clone your identity(ies) and preserve your Hubzilla relationships, friends and content.
+- What if your identity is subject to government censorship? Your Hub provider may be forced to delete your account and all identities and associated data. With cloning, Hubzilla offers **resistance to censorship**. If you want, you can have hundreds of clones, each with a different name, scattered across many different hubs on the Internet.
+
+Hubzilla offers interesting new possibilities for privacy. For more information, see the [Tips for protecting your privacy](./usermanual/protection_of_privacy.md) page.
+
+Some caveats apply. A full explanation of identity cloning can be found on the [Clone](./usermanual/clone.md) page.
+
+#### Multiple profiles
+
+Any number of profiles can be created with different information that can be made visible to certain of your connections/friends. A ‘standard’ profile can be viewed by anyone and can contain limited information, while more information is accessible to selected groups or individuals. This means that the profile (and site content) that your beer-drinking friends see may be different from what your peers see, and also completely different from what is visible to the general public.
+
+#### Account backup
+
+Hubzilla offers a simple one-click account backup where you can download a full backup of your profile. The backups can then be used to clone or restore a profile.
+
+#### Account deletion
+
+Accounts can be deleted instantly by clicking on a link. That's it. All associated content will then be deleted from the network (including posts and any other content created by the deleted profile). Depending on the number of connections you have, the process of deleting removed content may take some time, but it will be done as quickly as possible.
+
+#### Deletion of content
+
+All content created in Hubzilla remains under the control of the member (or channel) who originally created it. A member can delete a message or a series of messages at any time. The deletion process ensures that the content is deleted regardless of whether it was posted on a channel's primary (home) hub or on another hub where the channel has been remotely authenticated via Nomad (Hubzilla communication and authentication protocol).
+
+#### Media
+
+Similar to any other modern blogging system, social network or micro-blogging service, Hubzilla supports uploading files, embedding videos and linking to websites.
+
+#### Preview/editing
+
+Posts and comments can be previewed before sending and edited after sending.
+
+#### Voting/polls
+
+Posts can be converted into ‘poll’ elements that allow readers to provide feedback summarised in ‘agree’, ‘disagree’ and ‘abstain’ counters. This allows you to gauge interest in ideas and create informal polls.
+
+#### Extending Hubzilla
+
+Hubzilla can be extended in various ways: website customisation, personalisation, option settings, themes and addons/plugins.
+
+#### API
+
+An API is available for use by third-party services. A plugin also provides a basic implementation of the Twitter API (for which there are hundreds of third-party tools). Access can be via login/password or OAuth, and client registration of OAuth applications is provided. \ No newline at end of file
diff --git a/doc/en/gdpr1.md b/doc/en/gdpr1.md
deleted file mode 100644
index daa401a3d..000000000
--- a/doc/en/gdpr1.md
+++ /dev/null
@@ -1,114 +0,0 @@
-
-Privacy Notice May 2018
-
-How your information will be used
-=================================
-
-Information you provide to this website may be stored and used to provide services to you.
-
-We require an email address to idenitfy the account holder. This will not be shared with
-any other website or service. It is used to send you notifications about your account and
-perform administrative tasks such as resetting your password. You have the option to
-opt-out of all email notifications through your settings.
-
-Communication channels created on this website require a name and a photo or avatar. A
-default avatar will be chosen if you do not supply one. The name, avatar, and a link to
-the channel webpage will be shared with other servers and services in order to refer to
-this identity. The name does not need to be your real name and the photo or avatar does
-not need to resemble you.
-
-All other information you supply to this website is optional.
-
-As a social communication and cloud storage service, you will usually be using this website
-to share information with others. We provide a range of privacy options to allow you to
-restrict this sharing to only those you choose.
-
-
-Processing of your information
-==============================
-
-Our processing of your information is limited to storing it for you to use. We MAY keep logs
-of activity to help diagnose software issues and to maintain security of the system against
-intrusion. These logs are routinely deleted after a few days.
-
-We MAY (if you have provided this permission) try to suggest frendships or connections based
-on analysing publicly available information about your connections. This is currently the most
-advanced data "processing" performed at this website. You may restrict access to this information
-if this processing is undesired.
-
-If supplied, we MAY use your gender to formulate text messages in your native language, for
-instance "Bob commented on HIS post."
-
-In all other cases, your data is stored under your desired privacy policy and to the best
-of our ability is only shared with those who you have elected to share it with.
-
-We do not share your private information with third parties or analyse your behaviour or personal
-characteristics. We have no advertisements or business relationships with advertisers.
-
-We MAY be asked or forced to divulge information provided by you in response to legitimate
-criminal and legal proceedings. Where possible we will notify you if this happens.
-
-
-Access to your information
-==========================
-
-Some communications are shared with other websites. Those using the same software will usually
-have similar privacy policies.
-
-
-You may be shown embedded videos and provided links to visit other websites as part of your
-day-to-day activities using this website. This MAY expose you to monitoring by external services, such
-as (but not limited to) Facebook, Twitter, and Google. Each website operator is allowed to configure
-whether or not embedded content is permitted.
-
-Further access to your personal data and stored files is under your control.
-
-Our storage of your data is provided under your implied consent through your continued use of
-the service. You may withdraw this consent at any time and on account deletion we will remove
-all data which belongs to you. The process of deletion may take several days as we also make a good faith
-effort to delete it from any internetworked websites that have been provided a copy.
-
-
-All data and files stored for a communications channel are available for you to
-download for either archival puposes or to transfer to another compatible website.
-
-
-
-Your rights
-===========
-
-Under the General Data Protection Regulation
-(GDPR) and The Data Protection Act 2018
-(DPA) you have a number of rights with regard to your personal data.
-You have the right to request from us access to and rectification or erasure of your personal data,
-the right to restrict processing, object to processing as well as in certain circumstances the right
-to data portability.
-
-If you have provided consent for the processing of your data you have the right (in certain
-circumstances) to withdraw that consent at any time which will not affect the lawfulness of
-the processing before your consent was withdrawn.
-
-You have the right to lodge a complaint to the Information Commissioners’ Office if you
-believe that we have not complied with the requirements of the GDPR or DPA 18 with regard
-to your personal data.
-
-Identity and contact details of controller and data protection officer
-
-[NAME OF COMPANY]
-is the controller
-[and processor]
-of data for the purposes of the DPA 18 and GDPR. 3
-
-If you have any concerns as to how your data is processed you can contact:
-
-[
-[NAME]
-Data Protection Offer at
-[EMAIL ADDRESS]
-]
-[NAME] [JOB TITLE]
-at
-[EMAIL ADDRESS]
-or you can write to these
-individuals using the address of
-[]
diff --git a/doc/en/general.bb b/doc/en/general.bb
deleted file mode 100644
index 0b80db756..000000000
--- a/doc/en/general.bb
+++ /dev/null
@@ -1,18 +0,0 @@
-[h2]Project and site information[/h2]
-[h3]$Projectname[/h3]
-[zrl=[baseurl]/help/Privacy]Privacy Policy[/zrl]
-[zrl=[baseurl]/help/project/governance]Project Governance[/zrl]
-[zrl=[baseurl]/help/contributor/convenant]Project Covenant and Code of Conduct[/zrl]
-
-[h3]External resources[/h3]
-[zrl=[baseurl]/help/external-resource-links]List of external resources[/zrl]
-[url=https://framagit.org/hubzilla/core/]Main Website[/url]
-[url=https://framagit.org/hubzilla/addons]Addon Website[/url]
-[url=[baseurl]/help/credits]$Projectname Credits[/url]
-[h3]About this $Projectname hub[/h3]
-[zrl=[baseurl]/help/TermsOfService]Terms of Service For This Hub[/zrl]
-[zrl=[baseurl]/siteinfo]Hub Information (/siteinfo)[/zrl]
-[zrl=[baseurl]/siteinfo/json]Detailed Technical Hub Information in JSON format(/siteinfo/json)[/zrl]
-
-#include doc/macros/main_footer.bb;
-
diff --git a/doc/en/git_for_non_developers.bb b/doc/en/git_for_non_developers.bb
deleted file mode 100644
index 5fba17439..000000000
--- a/doc/en/git_for_non_developers.bb
+++ /dev/null
@@ -1,71 +0,0 @@
-[b]Git For Non-Developers[/b]
-
-So you're handling a translation, or you're contributing to a theme, and every time you make a pull request you have to talk to one of the developers before your changes can be merged in?
-
-Chances are, you just haven't found a quick how-to explaining how to keep things in sync on your end. It's really very easy.
-
-After you've created a fork of the repo (just click &quot;fork&quot; at github), you need to clone your own copy.
-
-For the sake of examples, we'll assume you're working on a theme called redexample (which does not exist).
-
-[code]git clone https://github.com/username/red.git[/code]
-
-Once you've done that, cd into the directory, and add an upstream.
-
-[code]
-cd red
-git remote add upstream https://framagit.org/hubzilla/core/
-[/code]
-
-From now on, you can pull upstream changes with the command
-[code]git fetch upstream[/code]
-
-Before your changes can be merged automatically, you will often need to merge upstream changes.
-
-[code]
-git merge upstream/master
-[/code]
-
-You should always merge upstream before pushing any changes, and [i]must[/i] merge upstream with any pull requests to make them automatically mergeable.
-
-99% of the time, this will all go well. The only time it won't is if somebody else has been editing the same files as you - and often, only if they have been editing the same lines of the same files. If that happens, that would be a good time to request help until you get the hang of handling your own merge conflicts.
-
-Then you just need to add your changes [code]git add view/theme/redexample/[/code]
-
-This will add all the files in view/theme/redexample and any subdirectories. If your particular files are mixed throughout the code, you should add one at a time. Try not to do git add -a, as this will add everything, including temporary files (we mostly, but not always catch those with a .gitignore) and any local changes you have, but did not intend to commit.
-
-Once you have added all the files you have changed, you need to commit them. [code]git commit[/code]
-
-This will open up an editor where you can describe the changes you have made. Save this file, and exit the editor.
-
-Finally, push the changes to your own git
-[code]git push[/code]
-
-And that's it, your repo is up to date!
-
-All you need to do now is actually create the pull request. There are two ways to do this.
-
-The easy way, if you're using Github is to simply click the green button at the top of your own copy of the repository, enter a description of the changes, and click 'create pull request'. The
-main repository, themes, and addons all have their main branch at Github, so this method can be used most of the time.
-
-Most people can stop here.
-
-Some projects in the extended RedMatrix ecosphere have no Github presence, to pull request these is a bit different - you'll have to create your pull request manually. Fortunately, this isn't
-much harder.
-
-[code]git request-pull -p <start> <url>[/code]
-
-Start is the name of a commit to start at. This must exist upstream. Normally, you just want master.
-
-URL is the URL of [i]your[/i] repo.
-
-One can also specify <end>. This defaults to HEAD.
-
-Example:
-[code]
-git request-pull master https://example.com/project
-[/code]
-
-And simply send the output to the project maintainer.
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/glossary.md b/doc/en/glossary.md
new file mode 100644
index 000000000..4086f48ba
--- /dev/null
+++ b/doc/en/glossary.md
@@ -0,0 +1,20 @@
+### Glossary
+
+- **Hub**
+ An instance of this software running on a standard web server
+- **Grid**
+ The global network of hubs that exchange information with each other using the Zot protocol.
+- **Channel**
+ The basic identity in the grid. A channel can represent a person, a blog or a forum, to name but a few. Channels can connect to other channels to share information with very detailed permissions.
+- **Cloning**
+ Channels can have clones that are connected to separate and otherwise unconnected accounts on independent hubs. Communication shared with a channel is synchronised between the channel clones, allowing a channel to send and receive messages and access shared content from multiple hubs. This provides resilience in the event of network and hardware failures, which can be a major problem for self-hosted or resource-constrained web servers. Cloning allows you to completely move a channel from one hub to another, taking your data and connections with you. See nomadic identity.
+- **nomadic identity**
+ The ability to authenticate and easily migrate an identity across independent hubs and web domains. Nomadic identity provides true ownership of an online identity, as the identities of the channels controlled by an account on a hub are not tied to the hub itself. A hub is more of a ‘host’ for channels. With Hubzilla, you don't have an ‘account’ on a server like you do with typical websites, but you own an identity that you can take with you across the web by using clones.
+- **Nomad**
+ The new JSON-based protocol for implementing secure decentralised communication and services. It differs from many other communication protocols in that it builds communication on a decentralised identity and authentication framework. The authentication component is similar to the OpenID concept, but is isolated from DNS-based identities. As far as possible, remote authentication is silent and invisible. This provides a mechanism for distributed access control on the Internet that is unobtrusive.
+
+ The protocol was originally called Zot. In 2021 it was renamed Nomad by Mike Macgirvin. A distinction is now made between the protocol and the implementation (software) of the protocol. The implementation is still called Zot at Hubzilla (more precisely Zot6, because it continues the implementation of the then identically named protocol in version 6).
+
+ In general, the protocol is now only referred to as Nomad. When the term Zot or Zot6 (usually in the form ‘Nomad/Zot6’) is used, the Nomad protocol is meant when referring to the protocol. Zot or Zot6 only appear independently in the area of software development at Hubzilla, because the routines and the programme library that implement Nomad in practice bear this name.
+
+ <u>Important:</u> The implementations of Nomad in Hubzilla and in (streams) are partially incompatible with each other. This applies in particular to the nomadic identity. It is therefore not possible to clone a Hubzilla channel (Nomad v. Zot6) on a (streams) instance (Nomad v, Zot12), and vice versa. \ No newline at end of file
diff --git a/doc/en/hidden_configs.bb b/doc/en/hidden_configs.bb
deleted file mode 100644
index 4eac1aa6e..000000000
--- a/doc/en/hidden_configs.bb
+++ /dev/null
@@ -1,127 +0,0 @@
-[h1]Advanced Configurations for Administrators[/h1]
-
-[i]This document assumes you're an administrator.[/i]
-
-$Projectname contains many configuration options hidden from the main admin panel. These are generally options considered too niche, advanced or prone do confusion.
-
-These settings can be modified through the shell, from the the top level web directory, with the syntax:
-
-[code]util/config cat key value[/code]
-for a site configuration, or
-
-[code]util/pconfig channel_id cat key value[/code]
-for a member configuration.
-
-For a site configuration, another option is to add a line to .htconfig.php, with the syntax:
-[code]App::$config['cat']['key'] = 'value';[/code]
-
-
-[h2]Member configuration (pconfig)[/h2]
-
-[dl terms="mb"]
- [*= system.always_my_theme ] Always use your own theme when viewing channels on the same hub. This will break in some quite imaginative ways when viewing channels with theme dependent Comanche.
- [*= system.blocked ] An array of xchans blocked by this channel. Technically, this is a hidden config and does belong here, however, addons (notably superblock) have made this available in the UI.
- [*= system.default_cipher ] Set the default cipher used for E2EE items.
- [*= system.display_friend_count ] Set the number of connections to display in the connections profile widget.
- [*= system.do_not_track ] As the browser header. This will break many identity based features. You should really just set permissions that make sense.
- [*= system.forcepublicuploads ] Force uploaded photos to be public when uploaded as wall items. It makes far more sense to just set your permissions properly in the first place. Do that instead.
- [*= system.network_page_default ] Set default params when viewing the network page. This should contain the same querystring as manual filtering.
- [*= system.paranoia ] Sets the security level of IP checking. If the IP address of a logged-in session changes apply this level to determine if the account should be logged out as a security breach.
-Options are:
- 0 &mdash; no IP checking
- 1 &mdash; check 3 octets
- 2 &mdash; check 2 octets
- 3 &mdash; check for any difference at all
-
- [*= system.prevent_tag_hijacking ] Prevent foreign networks hijacking hashtags in your posts and directing them at its own resources.
- [*= system.startpage ] Another of those technically hidden configs made available by addons. Sets the default page to view when logging in. This is exposed to the UI by the startpage addon.
- [*= system.taganyone ] Requires the config of the same name to be enabled. Allow the @mention tagging of anyone, whether you are connected or not. This doesn't scale.
- [*= system.anonymous_comments ] By default or if set to 1, custom permissions can be set to allow anonymous (moderated) comments like WordPress, moderated by the channel owner. If set to 0, no member of your site can select or enable this.
- [*= system.user_scalable ] Determine if the app is scalable on touch screens. Defaults to on, to disable, set to zero - real zero, not just false.
-[/dl]
-
-[h2]Site configuration[/h2]
-
-[dl terms="mb"]
- [*= randprofile.check ] When requesting a random profile, check that it actually exists first
- [*= randprofile.retry ] Number of times to retry getting a random profile
- [*= system.admin_email ] Specifies the administrator's email for this site. This is initially set during install.
- [*= system.authlog ] Logfile to use for logging auth errors. Used to plug in to server side software such as fail2ban. Auth failures are still logged to the main logs as well.
- [*= system.auto_channel_create ] Add the necessary form elements to create the first channel on the account registration page, and create it (possibly following email validation or administrator approval). This precludes the ability to import a channel from another site as the first channel created on this site for a new account. Use with system.default_permissions_role to streamline registration.
- [*= system.auto_follow ] Make the first channel of an account auto-follow channels listed here - comma separated list of webbies (member@hub addresses).
- [*= system.blacklisted_sites ] An array of specific hubs to block from this hub completely.
- [*= system.block_public_search ] Similar to block_public, except only blocks public access to search features. Useful for sites that want to be public, but keep getting hammered by search engines.
- [*= system.cron_hour ] Specify an hour in which to run cron_daily. By default with no config, this will run at midnight UTC.
- [*= system.default_permissions_role ] If set to a valid permissions role name, use that role for the first channel created by a new account and don't ask for the "Channel Type" on the channel creation form. Examples of valid names are: 'social', 'social_restricted', 'social_private', 'forum', 'forum_restricted' and 'forum_private'. Read more about permissions roles [zrl=[baseurl]/help/roles]here[/zrl].
- [*= system.default_profile_photo ] Set the profile photo that new channels start with. This should contain the name of a directory located under [font=courier]images/default_profile_photos/[/font], or be left unset. If not set then 'rainbow_man' is assumed.
- [*= system.directorytags ] Set the number of keyword tags displayed on the directory page. Default is 50 unless set to a positive integer.
- [*= system.disable_directory_keywords ] If '1', do not show directory keywords. If the hub is a directory server, prevent returning tags to any directory clients. Please do not set this for directory servers in the RED_GLOBAL realm.
- [*= system.disable_discover_tab ] This allows you to completely disable the ability to discover public content from external sites.
- [*= system.disable_dreport ] If '1', don't store or link to delivery reports
- [*= system.dlogfile ] Logfile to use for logging development errors. Exactly the same as logger otherwise. This isn't magic, and requires your own logging statements. Developer tool.
- [*= system.email_notify_icon_url ] URL of image (32x32) to display in email notifications (HTML bodies).
- [*= system.expire_delivery_reports ] Expiration in days for delivery reports - default 10
- [*= system.expire_limit ] Don't expire any more than this number of posts per channel per expiration run to keep from exhausting memory. Default 5000.
- [*= system.photo_storage_type] If '1', use filesystem instead SQL database to store thumbnails. Default is '0'. Introduced in 4.2
- [*= system.hidden_version_siteinfo ] If true, do not report the software version on siteinfo pages (system.hide_version also hides the version on these pages, this setting *only* hides the version on siteinfo pages).
- [*= system.hide_help ] Don't display help documentation link in nav bar
- [*= system.hide_in_statistics ] Tell the red statistics servers to completely hide this hub in hub lists.
- [*= system.hide_version ] If true, do not report the software version on webpages and tools. (*) Must be set in .htconfig.php
- [*= system.ignore_imagick ] Ignore imagick and use GD, even if imagick is installed on the server. Prevents some issues with PNG files in older versions of imagick.
- [*= system.max_daily_registrations ] Set the maximum number of new registrations allowed on any day. Useful to prevent oversubscription after a bout of publicity for the project.
- [*= system.max_import_size ] If configured, the maximum length of an imported text message. This is normally left at 200Kbytes or more to accomodate Friendica private photos, which are embedded.
- [*= system.max_tagged_forums ] Spam prevention. Limits the number of tagged forums which are recognised in any post. Default is 2. Only the first 'n' tags will be delivered as forums, the others will not cause any delivery.
- [*= system.minimum_feedcheck_minutes ] The minimum interval between polling RSS feeds. If this is lower than the cron interval, feeds will be polled with each cronjob. Defaults to 60 if not set. The site setting can also be over-ridden on a channel by channel basis by a service class setting aptly named 'minimum_feedcheck_minutes'.
- [*= system.no_age_restriction ] Do not restrict registration to people over the age of 13. This carries legal responsibilities in many countries to require that age be provided and to block all personal information from minors, so please check your local laws before changing.
- [*= system.object_cache_days] Set how long is cached embedded content can be used without refetching. Default is 30 days.
- [*= system.openssl_conf_file ] Specify a file containing OpenSSL configuration. Needed in some Windows installations to locate the openssl configuration file on the system. Read the code first. If you can't read the code, don't play with it.
- [*= system.openssl_encrypt ] Use openssl encryption engine, default is false (uses mcrypt for AES encryption)
- [*= system.optimize_items ] Runs optimise_table during some tasks to keep your database nice and defragmented. This comes at a performance cost while the operations are running, but also keeps things a bit faster while it's not. There also exist CLI utilities for performing this operation, which you may prefer, especially if you're a large site.
- [*= system.override_poll_lockfile ] Ignore the lock file in the poller process to allow more than one process to run at a time.
- [*= system.paranoia ] As the pconfig, but on a site-wide basis. Can be overwritten by member settings.
- [*= system.pin_types ] Array of allowed item types for pinning. Defaults depend on module but can be redifined here.
- [*= system.photo_cache_time ] How long to cache photos, in seconds. Default is 86400 (1 day). Longer time increases performance, but it also means it takes longer for changed permissions to apply.
- [*= system.platform_name ] What to report as the platform name in webpages and statistics. (*) Must be set in .htconfig.php
- [*= system.rating_enabled ] Distributed reputation reporting and data collection. This feature is currently being re-worked.
- [*= system.poke_basic ] Reduce the number of poke verbs to exactly 1 ("poke"). Disable other verbs.
- [*= system.proc_run_use_exec ] If 1, use the exec system call in proc_run to run background tasks. By default we use proc_open and proc_close. On some (currently rare) systems this does not work well.
- [*= system.projecthome ] Display the project page on your home page for logged out viewers.
- [*= system.projecthome ] Set the project homepage as the homepage of your hub. (Obsolete)
- [*= system.pubstream_ordering ] Set pubstream ordering. Possible values 'commented' (default), 'created' and 'edited'.
- [*= system.register_link ] path to direct to from the "register" link on the login form. On closed sites this will direct to 'pubsites'. For open sites it will normally redirect to 'register' but you may change this to a custom site page offering subscriptions or whatever.
- [*= system.reserved_channels ] Don't allow members to register channels with this comma separated list of names (no spaces)
- [*= system.sellpage ] A URL shown in the public sites list to sell your hub - display service classes, etc.
- [*= system.startpage ] Set the default page to be taken to after a login for all channels at this website. Can be overwritten by user settings.
- [*= system.sys_expire_days ] How many days to keep discovered public content from other sites
- [*= system.taganyone ] Allow the @mention tagging of anyone whether you are connected or not.
- [*= system.tempdir ] Place to store temporary files (currently unused), default is defined in the PHP configuration.
- [*= system.tos_url ] Set an alternative link for the ToS location.
- [*= system.transport_security_header ] if non-zero and SSL is being used, include a strict-transport-security header on webpages
- [*= system.uploaddir ] Location to upload files (default is system.tempdir, currently used only by js_upload plugin)
- [*= system.workflow_channel_next ] The page to direct new members to immediately after creating a channel.
- [*= system.workflow_register_next ] The page to direct members to immediately after creating an account (only when auto_channel_create or UNO is enabled).
-[/dl]
-
-
-[h3]Directory config[/h3]
-
-[h4]Directory search defaults[/h4]
-
-[dl terms="mb"]
- [*= directory.globaldir ] 0 or 1. Default 0. If you visit the directory on a site you'll just see the members of that site by default. You have to go through an extra step to see the people in the rest of the network; and by doing so there's a clear delineation that these people *aren't* members of that site but of a larger network.
- [*= directory.pubforums ] 0 or 1. Public forums [i]should[/i] be default 0.
- [*= directory.safemode ] 0 or 1.
-[/dl]
-
-[h4]Directory server configuration[/h4][i](see [zrl=[baseurl]/help/directories]help/directories[/zrl])[/i]
-
-[dl terms="mb"]
- [*= system.directory_mode ]
- [*= system.directory_primary ]
- [*= system.directory_realm ]
- [*= system.directory_server ]
- [*= system.realm_token ]
-[/dl]
-
-#include doc/macros/main_footer.bb;
-
diff --git a/doc/en/hooklist.bb b/doc/en/hooklist.bb
deleted file mode 100644
index 7231cd185..000000000
--- a/doc/en/hooklist.bb
+++ /dev/null
@@ -1,698 +0,0 @@
-[h2]Hooks[/h2]
-
-Hooks allow plugins/addons to "hook into" the code at many points and alter the behaviour or otherwise perform independent actions when an activity takes place or when certain data structures are accessed. There are many hooks which allow you to tie into the software at most any point and do something slightly different than the default thing. These hooks are passed two variables. The first is the App structure which contains details about the entire state of the page request as we build the resulting page. The second is unique to the specific hook that is called and provides specific detail about what is happening in the software at the time the hook is invoked.
-
-[zrl=[baseurl]/help/hooks]Generated index of all hooks and the files which call them[/zrl]
-
-[zrl=[baseurl]/help/hook/module_mod_aftercontent]module_mod_aftercontent[/zrl]
- General purpose hook for any module, executed after mod_content(). Replace 'module' with module name, e.g. 'photos_mod_aftercontent'.
-
-[zrl=[baseurl]/help/hook/module_mod_content]module_mod_content[/zrl]
- General purpose hook for any module, executed before mod_content(). Replace 'module' with module name, e.g. 'photos_mod_content'.
-
-[zrl=[baseurl]/help/hook/module_mod_init]module_mod_init[/zrl]
- General purpose hook for any module, executed before mod_init(). Replace 'module' with module name, e.g. 'photos_mod_init'.
-
-[zrl=[baseurl]/help/hook/module_mod_post]module_mod_post[/zrl]
- General purpose hook for any module, executed before mod_post(). Replace 'module' with module name, e.g. 'photos_mod_post'.
-
-[zrl=[baseurl]/help/hook/about_hook]about_hook[/zrl]
- Called from the siteinfo page
-
-[zrl=[baseurl]/help/hook/accept_follow]accept_follow[/zrl]
- Called when accepting a connection (friend request)
-
-[zrl=[baseurl]/help/hook/account_downgrade]account_downgrade[/zrl]
- Called when an account has expired, indicating a potential downgrade to "basic" service class
-
-[zrl=[baseurl]/help/hook/account_settings]account_settings[/zrl]
- Called when generating the account settings form
-
-[zrl=[baseurl]/help/hook/account_settings_post]account_settings_post[/zrl]
- Called when posting from the account settings form
-
-[zrl=[baseurl]/help/hook/activity_filter]activity_filter[/zrl]
- Called when generating the list of filters for the network page
-
-[zrl=[baseurl]/help/hook/activity_filter]activity_mapper[/zrl]
- Called when determining the activity type for transmission.
-
-[zrl=[baseurl]/help/hook/activity_filter]activity_decode_mapper[/zrl]
- Called when determining the activity type for transmission.
-
-[zrl=[baseurl]/help/hook/activity_filter]activity_obj_mapper[/zrl]
- Called when determining the object type for transmission.
-
-[zrl=[baseurl]/help/hook/activity_filter]activity_obj_decode_mapper[/zrl]
- Called when determining the object type for transmission.
-
-[zrl=[baseurl]/help/hook/activity_order]activity_order[/zrl]
- Called when generating the list of order options for the network page
-
-[zrl=[baseurl]/help/hook/addon_app_installed_filter]addon_app_installed_filter[/zrl]
- Called when determining whether an addon_app is installed
-
-[zrl=[baseurl]/help/hook/activity_received]activity_received[/zrl]
- Called when an activity (post, comment, like, etc.) has been received from a zot source
-
-[zrl=[baseurl]/help/hook/admin_aside]admin_aside[/zrl]
- Called when generating the admin page sidebar widget
-
-[zrl=[baseurl]/help/hook/affinity_labels]affinity_labels[/zrl]
- Used to generate alternate labels for the affinity slider.
-
-[zrl=[baseurl]/help/hook/api_perm_is_allowed]api_perm_is_allowed[/zrl]
- Called when perm_is_allowed() is executed from an API call.
-
-[zrl=[baseurl]/help/hook/app_destroy]app_destroy[/zrl]
- Called when an app is deleted
-
-[zrl=[baseurl]/help/hook/app_installed_filter]app_installed_filter[/zrl]
- Called when determining whether an app is installed
-
-[zrl=[baseurl]/help/hook/app_menu]app_menu[/zrl]
- Called when generating the app_menu dropdown (may be obsolete)
-
-[zrl=[baseurl]/help/hook/attach_delete]attach_delete[/zrl]
- Called when attachments are deleted from the attach table
-
-[zrl=[baseurl]/help/hook/atom_author]atom_author[/zrl]
- Called when generating an author or owner element for an Atom ActivityStream feed
-
-[zrl=[baseurl]/help/hook/atom_entry]atom_entry[/zrl]
- Called when generating each item entry of an Atom ActivityStreams feed
-
-[zrl=[baseurl]/help/hook/atom_feed]atom_feed[/zrl]
- Called when generating an Atom ActivityStreams feed
-
-[zrl=[baseurl]/help/hook/atom_feed_end]atom_feed_end[/zrl]
- Called when generation of an Atom ActivityStreams feed is completed
-
-[zrl=[baseurl]/help/hook/attach_upload_file]attach_upload_file[/zrl]
- Called when uploading a file
-
-[zrl=[baseurl]/help/hook/authenticate]authenticate[/zrl]
- Can provide alternate authentication mechanisms
-
-[zrl=[baseurl]/help/hook/author_is_pmable]author_is_pmable[/zrl]
- Called from the thread action menu to determine if we can send private mail to the post author
-
-[zrl=[baseurl]/help/hook/bb2diaspora]bb2diaspora[/zrl]
- called when converting bbcode to markdown
-
-[zrl=[baseurl]/help/hook/bbcode]bbcode[/zrl]
- Called at end of converting bbcode to HTML
-
-[zrl=[baseurl]/help/hook/bbcode_filter]bbcode_filter[/zrl]
- Called when beginning to convert bbcode to HTML
-
-[zrl=[baseurl]/help/hook/bb_translate_video]bb_translate_video[/zrl]
- Called when extracting embedded services from bbcode video elements (rarely used)
-
-[zrl=[baseurl]/help/hook/build_pagehead]build_pagehead[/zrl]
- Called when creating the HTML page header
-
-[zrl=[baseurl]/help/hook/can_comment_on_post]can_comment_on_post[/zrl]
- Called when deciding whether or not to present a comment box for a post
-
-[zrl=[baseurl]/help/hook/change_channel]change_channel[/zrl]
- Called when logging in to a channel (either during login or afterward through the channel manager)
-
-[zrl=[baseurl]/help/hook/channel_remove]channel_remove[/zrl]
- Called when removing a channel
-
-[zrl=[baseurl]/help/hook/channel_links]channel_links[/zrl]
- Called when generating the Link: HTTP header for a channel
-
-[zrl=[baseurl]/help/hook/channel_settings]channel_settings[/zrl]
- Called when displaying the channel settings page
-
-[zrl=[baseurl]/help/hook/chat_message]chat_message[/zrl]
- Called to create a chat message.
-
-[zrl=[baseurl]/help/hook/chat_post]chat_post[/zrl]
- Called when a chat message has been posted
-
-[zrl=[baseurl]/help/hook/check_account_email]check_account_email[/zrl]
- Validate the email provided in an account registration
-
-[zrl=[baseurl]/help/hook/check_account_invite]check_account_invite[/zrl]
- Validate an invitation code when using site invitations
-
-[zrl=[baseurl]/help/hook/check_account_password]check_account_password[/zrl]
- Used to provide policy control over account passwords (minimum length, character set inclusion, etc.)
-
-[zrl=[baseurl]/help/hook/check_channelallowed]check_channelallowed[/zrl]
- Used to over-ride or bypass the channel black/white block lists
-
-[zrl=[baseurl]/help/hook/check_siteallowed]check_siteallowed[/zrl]
- Used to over-ride or bypass the site black/white block lists
-
-[zrl=[baseurl]/help/hook/collect_public_recipients]collect_public_recipients[/zrl]
- Used to establish a list of recipients to send a public message to.
-
-[zrl=[baseurl]/help/hook/comment_buttons]comment_buttons[/zrl]
- Called when rendering the edit buttons for comments
-
-[zrl=[baseurl]/help/hook/comments_are_now_closed]comments_are_now_closed[/zrl]
- Called when deciding whether or not to present a comment box for a post
-
-[zrl=[baseurl]/help/hook/connect_premium]connect_premium[/zrl]
- Called when connecting to a premium channel
-
-[zrl=[baseurl]/help/hook/connection_remove]connection_remove[/zrl]
- Called when deleting/removing a connection
-
-[zrl=[baseurl]/help/hook/connector_settings]connector_settings[/zrl]
- Called when posting to the features/addon settings page
-
-[zrl=[baseurl]/help/hook/construct_page]construct_page[/zrl]
- General purpose hook to provide content to certain page regions. Called when constructing the Comanche page.
-
-[zrl=[baseurl]/help/hook/contact_block_end]contact_block_end[/zrl]
- Called when generating the sidebar "Connections" widget
-
-[zrl=[baseurl]/help/hook/contact_edit]contact_edit[/zrl]
- Called when editing a connection via connedit
-
-[zrl=[baseurl]/help/hook/contact_edit_post]contact_edit_post[/zrl]
- Called when posting to connedit
-
-[zrl=[baseurl]/help/hook/contact_select_options]contact_select_options[/zrl]
- Deprecated/unused
-
-[zrl=[baseurl]/help/hook/content_security_policy]content_security_policy[/zrl]
- Called prior to output of the Content-Security-Policy header
-
-[zrl=[baseurl]/help/hook/conversation_start]conversation_start[/zrl]
- Called in the beginning of rendering a conversation (message or message collection or stream)
-
-[zrl=[baseurl]/help/hook/cover_photo_content_end]cover_photo_content_end[/zrl]
- Called after a cover photo has been uplaoded
-
-[zrl=[baseurl]/help/hook/create_identity]create_identity[/zrl]
- Called when creating a channel
-
-[zrl=[baseurl]/help/hook/cron]cron[/zrl]
- Called when scheduled tasks (poller) is executed
-
-[zrl=[baseurl]/help/hook/cron_daily]cron_daily[/zrl]
- Called when daily scheduled tasks are executed
-
-[zrl=[baseurl]/help/hook/cron_weekly]cron_weekly[/zrl]
- Called when weekly scheduled tasks are executed
-
-[zrl=[baseurl]/help/hook/crypto_methods]crypto_methods[/zrl]
- Called when generating a list of crypto algorithms in the locally preferred order
-
-[zrl=[baseurl]/help/hook/daemon_addon]daemon_addon[/zrl]
- Called when invoking the extensible background daemon
-
-[zrl=[baseurl]/help/hook/daemon_master_release]daemon_master_release[/zrl]
- Called at the start of processing \Zotlabs\Daemon\Master::Release()
-
-[zrl=[baseurl]/help/hook/directory_item]directory_item[/zrl]
- Called when generating a directory listing for display
-
-[zrl=[baseurl]/help/hook/discover_channel_webfinger]discover_channel_webfinger[/zrl]
- Called when performing a webfinger lookup
-
-[zrl=[baseurl]/help/hook/display_item]display_item[/zrl]
- Called for each item being displayed in a conversation thread
-
-[zrl=[baseurl]/help/hook/display_settings]display_settings[/zrl]
- Called from settings module when displaying the 'display settings' section
-
-[zrl=[baseurl]/help/hook/display_settings_post]display_settings_post[/zrl]
- Called when posting from the settings module 'display settings' form
-
-[zrl=[baseurl]/help/hook/donate_contributors]donate_contributors[/zrl]
- called by the 'donate' addon when generating a list of donation recipients
-
-[zrl=[baseurl]/help/hook/donate_plugin]donate_plugin[/zrl]
- called by the 'donate' addon
-
-[zrl=[baseurl]/help/hook/donate_sponsors]donate_sponsors[/zrl]
- called by the 'donate' addon
-
-[zrl=[baseurl]/help/hook/dreport_is_storable]dreport_is_storable[/zrl]
- called before storing a dreport record to determine whether to store it
-
-[zrl=[baseurl]/help/hook/dreport_process]dreport_process[/zrl]
- called for each valid delivery report
-
-[zrl=[baseurl]/help/hook/dropdown_extras]dropdown_extras[/zrl]
- Add additional items to the dropdown cog when item/threads are displayed.
-
-[zrl=[baseurl]/help/hook/drop_item]drop_item[/zrl]
- called when an 'item' is removed
-
-[zrl=[baseurl]/help/hook/encode_object]encode_object[/zrl]
- called when encoding an object for transmission.
-
-[zrl=[baseurl]/help/hook/enotify]enotify[/zrl]
- called before any notification
-
-[zrl=[baseurl]/help/hook/enotify_mail]enotify_mail[/zrl]
- called when sending a notification email
-
-[zrl=[baseurl]/help/hook/enotify_store]enotify_store[/zrl]
- called when storing a notification record
-
-[zrl=[baseurl]/help/hook/enotify_store_end]enotify_store_end[/zrl]
- called after a notification record has been stored
-
-[zrl=[baseurl]/help/hook/event_created]event_created[/zrl]
- called when an event record is created
-
-[zrl=[baseurl]/help/hook/event_store_event]event_store_event[/zrl]
- called when an event record is created or updated
-
-[zrl=[baseurl]/help/hook/event_updated]event_updated[/zrl]
- called when an event record is modified
-
-[zrl=[baseurl]/help/hook/externals_url_select]externals_url_select[/zrl]
- called when generating a list of random sites to pull public posts from
-
-[zrl=[baseurl]/help/hook/feature_enabled]feature_enabled[/zrl]
- called when 'feature_enabled()' is used
-
-[zrl=[baseurl]/help/hook/feature_settings]feature_settings[/zrl]
- called from settings page when visiting 'addon/feature settings'
-
-[zrl=[baseurl]/help/hook/feature_settings_post]feature_settings_post[/zrl]
- called from settings page when posting from 'addon/feature settings'
-
-[zrl=[baseurl]/help/hook/fetch_and_store]fetch_and_store[/zrl]
- called to allow filtering of 'decoded' items before storage.
-
-[zrl=[baseurl]/help/hook/file_thumbnail]file_thumbnail[/zrl]
- called when generating thumbnail images for cloud page in 'view tiles' mode
-
-[zrl=[baseurl]/help/hook/follow]follow[/zrl]
- called when a follow operation takes place
-
-[zrl=[baseurl]/help/hook/follow_from_feed]follow_from_feed[/zrl]
- called when a follow operation takes place on an RSS feed
-
-[zrl=[baseurl]/help/hook/follow_allow]follow_allow[/zrl]
- called before storing the results of a follow operation
-
-[zrl=[baseurl]/help/hook/gender_selector]gender_selector[/zrl]
- called when creating the 'gender' drop down list (advanced profile)
-
-[zrl=[baseurl]/help/hook/gender_selector_min]gender_selector_min[/zrl]
- called when creating the 'gender' drop down list (normal profile)
-
-[zrl=[baseurl]/help/hook/generate_map]generate_map[/zrl]
- called to generate the HTML for displaying a map location by coordinates
-
-[zrl=[baseurl]/help/hook/generate_named_map]generate_named_map[/zrl]
- called to generate the HTML for displaying a map location by text location
-
-[zrl=[baseurl]/help/hook/get_all_api_perms]get_all_api_perms[/zrl]
- Called when retrieving the permissions for API uses
-
-[zrl=[baseurl]/help/hook/get_all_perms]get_all_perms[/zrl]
- called when get_all_perms() is used
-
-[zrl=[baseurl]/help/hook/get_best_language]get_best_language[/zrl]
- called when choosing the preferred language for the page
-
-[zrl=[baseurl]/help/hook/get_default_export_sections]get_default_export_sections[/zrl]
- Called to get the default list of functional data groups to export in identity_basic_export()
-
-[zrl=[baseurl]/help/hook/get_features]get_features[/zrl]
- Called when get_features() is called
-
-[zrl=[baseurl]/help/hook/get_photo]get_photo[/zrl]
- Called when photo content (except for profile photos) is fetched in mod_photo
-
-[zrl=[baseurl]/help/hook/get_profile_photo]get_profile_photo[/zrl]
- Called when local profile photo content is fetched in mod_photo
-
-[zrl=[baseurl]/help/hook/get_role_perms]get_role_perms[/zrl]
- Called when get_role_perms() is called to obtain permissions for named permission roles
-
-[zrl=[baseurl]/help/hook/global_permissions]global_permissions[/zrl]
- Called when the global permissions list is generated
-
-[zrl=[baseurl]/help/hook/home_content]home_content[/zrl]
- Called from mod_home to replace the content of the home page
-
-[zrl=[baseurl]/help/hook/home_init]home_init[/zrl]
- Called from the home page home_init() function
-
-[zrl=[baseurl]/help/hook/hostxrd]hostxrd[/zrl]
- Called when generating .well-known/hosts-meta for "old webfinger" (used by Diaspora protocol)
-
-[zrl=[baseurl]/help/hook/html2bb_video]html2bb_video[/zrl]
- Called when using the html2bbcode translation to handle embedded media
-
-[zrl=[baseurl]/help/hook/html2bbcode]html2bbcode[/zrl]
- Called when using the html2bbcode translation
-
-[zrl=[baseurl]/help/hook/identity_basic_export]identity_basic_export[/zrl]
- Called when exporting a channel's basic information for backup or transfer
-
-[zrl=[baseurl]/help/hook/import_author_xchan]import_author_xchan[/zrl]
- Called when looking up an author of a post by xchan_hash to ensure they have an xchan record on our site
-
-[zrl=[baseurl]/help/hook/import_channel]import_channel[/zrl]
- Called when importing a channel from a file or API source
-
-[zrl=[baseurl]/help/hook/import_directory_profile]import_directory_profile[/zrl]
- Called when processing delivery of a profile structure from an external source (usually for directory storage)
-
-[zrl=[baseurl]/help/hook/import_xchan]import_xchan[/zrl]
- Called when processing the result of zot_finger() to store the result
-
-[zrl=[baseurl]/help/hook/item_photo_menu]item_photo_menu[/zrl]
- Called when generating the list of actions associated with a displayed conversation item
-
-[zrl=[baseurl]/help/hook/item_store]item_store[/zrl]
- Called when item_store() stores a record of type item
-
-[zrl=[baseurl]/help/hook/item_stored]item_stored[/zrl]
- Called after item_store() has stored a record of type item in the database.
-
-[zrl=[baseurl]/help/hook/item_custom]item_custom[/zrl]
- Called before item_store() stores a record of type item (allowing addons to process ITEM_TYPE_CUSTOM items).
-
-[zrl=[baseurl]/help/hook/item_store_update]item_store_update[/zrl]
- Called when item_store_update() is called to update a stored item.
-
-[zrl=[baseurl]/help/hook/item_stored_update]item_stored_update[/zrl]
- Called after item_store_update() has updated a stored item.
-
-[zrl=[baseurl]/help/hook/item_translate]item_translate[/zrl]
- Called from item_store and item_store_update after the post language has been autodetected
-
-[zrl=[baseurl]/help/hook/jot_networks]jot_networks[/zrl]
- Called to generate the list of additional post plugins to enable from the ACL form
-
-[zrl=[baseurl]/help/hook/jot_tool]jot_tool[/zrl]
- Deprecated and possibly obsolete. Allows one to add action buttons to the post editor.
-
-[zrl=[baseurl]/help/hook/jot_tpl_filter]jot_tpl_filter[/zrl]
- Called to filter template vars before replacement in jot.tpl.
-
-[zrl=[baseurl]/help/hook/jot_header_tpl_filter]jot_header_tpl_filter[/zrl]
- Called to filter template vars before replacement in jot_header.tpl.
-
-[zrl=[baseurl]/help/hook/legal_webbie]legal_webbie[/zrl]
- Called to validate a channel address
-
-[zrl=[baseurl]/help/hook/legal_webbie_text]legal_webbie_text[/zrl]
- Provides an explanation of text/character restrictions for legal_webbie()
-
-[zrl=[baseurl]/help/hook/load_pdl]load_pdl[/zrl]
- Called when we load a PDL file or description
-
-[zrl=[baseurl]/help/hook/local_dir_update]local_dir_update[/zrl]
- Called when processing a directory update from a channel on the directory server
-
-[zrl=[baseurl]/help/hook/location_move]location_move[/zrl]
- Called when a new location has been provided to a UNO channel (indicating a move rather than a clone)
-
-[zrl=[baseurl]/help/hook/logged_in]logged_in[/zrl]
- Called when authentication by any means has succeeeded
-
-[zrl=[baseurl]/help/hook/logger]logger[/zrl]
- Called when making an entry to the application logfile
-
-[zrl=[baseurl]/help/hook/logging_out]logging_out[/zrl]
- Called when logging out
-
-[zrl=[baseurl]/help/hook/login_hook]login_hook[/zrl]
- Called when generating the login form
-
-[zrl=[baseurl]/help/hook/magic_auth]magic_auth[/zrl]
- Called when processing a magic-auth sequence
-
-[zrl=[baseurl]/help/hook/markdown_to_bb]markdown_to_bb[/zrl]
- Called when processing markdown conversion
-
-[zrl=[baseurl]/help/hook/match_webfinger_location]match_webfinger_location[/zrl]
- Called when processing webfinger requests
-
-[zrl=[baseurl]/help/hook/magic_auth_openid_success]magic_auth_openid_success[/zrl]
- Called when a magic-auth was successful due to openid credentials
-
-[zrl=[baseurl]/help/hook/magic_auth_success]magic_auth_success[/zrl]
- Called when a magic-auth was successful
-
-[zrl=[baseurl]/help/hook/main_slider]main_slider[/zrl]
- Called when generating the affinity tool
-
-[zrl=[baseurl]/help/hook/marital_selector]marital_selector[/zrl]
- Called when generating the list of choices for the 'marital status' profile dropdown (advanced profile)
-
-[zrl=[baseurl]/help/hook/marital_selector_min]marital_selector_min[/zrl]
- Called when generating the list of choices for the 'marital status' profile dropdown (normal profile)
-
-[zrl=[baseurl]/help/hook/module_loaded]module_loaded[/zrl]
- Called when a module has been successfully locate to server a URL request
-
-[zrl=[baseurl]/help/hook/mood_verbs]mood_verbs[/zrl]
- Called when generating the list of moods
-
-[zrl=[baseurl]/help/hook/nav]nav[/zrl]
- Called when generating the navigation bar
-
-[zrl=[baseurl]/help/hook/network_content_init]network_content_init[/zrl]
- Called when loading cntent for the network page
-
-[zrl=[baseurl]/help/hook/network_ping]network_ping[/zrl]
- Called during a ping request
-
-[zrl=[baseurl]/help/hook/network_to_name]network_to_name[/zrl]
- Deprecated
-
-[zrl=[baseurl]/help/hook/notifier_end]notifier_end[/zrl]
- Called when a delivery loop has completed
-
-[zrl=[baseurl]/help/hook/notifier_hub]notifier_hub[/zrl]
- Called when a hub is delivered
-
-[zrl=[baseurl]/help/hook/notifier_normal]notifier_normal[/zrl]
- Called when the notifier is invoked for a 'normal' delivery
-
-[zrl=[baseurl]/help/hook/notifier_process]notifier_process[/zrl]
- Called when the notifier is processing a message/event
-
-[zrl=[baseurl]/help/hook/obj_verbs]obj_verbs[/zrl]
- Called when creating the list of verbs available for profile "things".
-
-[zrl=[baseurl]/help/hook/oembed_action]oembed_action[/zrl]
- Called when deciding if an oembed url is to be filter, blocked, or approved
-
-[zrl=[baseurl]/help/hook/oembed_probe]oembed_probe[/zrl]
- Called when performing an oembed content lookup
-
-[zrl=[baseurl]/help/hook/other_encapsulate]other_encapsulate[/zrl]
- Called when encrypting content for which the algorithm is unknown (see also crypto_methods)
-
-[zrl=[baseurl]/help/hook/other_unencapsulate]other_unencapsulate[/zrl]
- Called when decrypting content for which the algorithm is unknown (see also crypto_methods)
-
-[zrl=[baseurl]/help/hook/page_content_top]page_content_top[/zrl]
- Called when we generate a webpage (before calling the module content function)
-
-[zrl=[baseurl]/help/hook/page_end]page_end[/zrl]
- Called after we have generated the page content
-
-[zrl=[baseurl]/help/hook/page_header]page_header[/zrl]
- Called when generating the navigation bar
-
-[zrl=[baseurl]/help/hook/page_header]page_meta[/zrl]
- Called when generating the meta data in the page header.
-
-[zrl=[baseurl]/help/hook/parse_atom]parse_atom[/zrl]
- Called when parsing an atom/RSS feed item
-
-[zrl=[baseurl]/help/hook/parse_link]parse_link[/zrl]
- Called when probing a URL to generate post content from it
-
-[zrl=[baseurl]/help/hook/pdl_selector]pdl_selector[/zrl]
- Called when creating a layout selection in a form
-
-[zrl=[baseurl]/help/hook/perm_is_allowed]perm_is_allowed[/zrl]
- Called during perm_is_allowed() to determine if a permission is allowed for this channel and observer
-
-[zrl=[baseurl]/help/hook/permissions_create]permissions_create[/zrl]
- Called when an abook entry (connection) is created
-
-[zrl=[baseurl]/help/hook/permissions_update]permissions_update[/zrl]
- Called when a permissions refresh is transmitted
-
-[zrl=[baseurl]/help/hook/permit_hook]permit_hook[/zrl]
- Called before a registered hook is actually executed to determine if it should be allowed or blocked
-
-[zrl=[baseurl]/help/hook/personal_xrd]personal_xrd[/zrl]
- Called when generating the personal XRD for "old webfinger" (Diaspora)
-
-[zrl=[baseurl]/help/hook/photo_post_end]photo_post_end[/zrl]
- Called after uploading a photo
-
-[zrl=[baseurl]/help/hook/photo_upload_begin]photo_upload_begin[/zrl]
- Called when attempting to upload a photo
-
-[zrl=[baseurl]/help/hook/photo_upload_end]photo_upload_end[/zrl]
- Called when a photo upload has been processed
-
-[zrl=[baseurl]/help/hook/photo_upload_file]photo_upload_file[/zrl]
- Called to generate alternate filenames for an upload
-
-[zrl=[baseurl]/help/hook/photo_upload_form]photo_upload_form[/zrl]
- Called when generating a photo upload form
-
-[zrl=[baseurl]/help/hook/photo_view_filter]photo_view_filter[/zrl]
- Called before the data is handed over to the photo_view template
-
-[zrl=[baseurl]/help/hook/poke_verbs]poke_verbs[/zrl]
- Called when generating the list of actions for "poke" module
-
-[zrl=[baseurl]/help/hook/post_local]post_local[/zrl]
- Called when an item has been posted on this machine via mod/item.php (also via API)
-
-[zrl=[baseurl]/help/hook/post_local_end]post_local_end[/zrl]
- Called after a local post operation has completed
-
-[zrl=[baseurl]/help/hook/post_local_start]post_local_start[/zrl]
- Called when a local post operation is commencing
-
-[zrl=[baseurl]/help/hook/post_mail]post_mail[/zrl]
- Called when a mail message has been composed
-
-[zrl=[baseurl]/help/hook/post_mail_end]post_mail_end[/zrl]
- Called when a mail message has been delivered
-
-[zrl=[baseurl]/help/hook/post_remote]post_remote[/zrl]
- Called when an activity arrives from another site
-
-[zrl=[baseurl]/help/hook/post_remote_end]post_remote_end[/zrl]
- Called after processing a remote post
-
-[zrl=[baseurl]/help/hook/post_remote_update]post_remote_update[/zrl]
- Called when processing a remote post that involved an edit or update
-
-[zrl=[baseurl]/help/hook/post_remote_update_end]post_remote_update_end[/zrl]
- Called after processing a remote post that involved an edit or update
-
-[zrl=[baseurl]/help/hook/prepare_body]prepare_body[/zrl]
- Called when generating the HTML for a displayed conversation item
-
-[zrl=[baseurl]/help/hook/prepare_body_final]prepare_body_final[/zrl]
- Called after generating the HTML for a displayed conversation item
-
-[zrl=[baseurl]/help/hook/prepare_body_init]prepare_body_init[/zrl]
- Called before generating the HTML for a displayed conversation item
-
-[zrl=[baseurl]/help/hook/privacygroup_extras]privacygroup_extras[/zrl]
- Called before generating the HTML for the Privacy Group edit options
-
-[zrl=[baseurl]/help/hook/privacygroup_extras_delete]privacygroup_extras_delete[/zrl]
- Called after privacy group is dropped.
-
-[zrl=[baseurl]/help/hook/privacygroup_extras_post]privacygroup_extras_post[/zrl]
- Called when privacy group edit form is submitted.
-
-[zrl=[baseurl]/help/hook/proc_run]proc_run[/zrl]
- Called when invoking PHP sub processes
-
-[zrl=[baseurl]/help/hook/process_channel_sync_delivery]process_channel_sync_delivery[/zrl]
- Called when accepting delivery of a 'sync packet' containing structure and table updates from a channel clone
-
-[zrl=[baseurl]/help/hook/profile_advanced]profile_advanced[/zrl]
- Called when generating an advanced profile page
-
-[zrl=[baseurl]/help/hook/profile_edit]profile_edit[/zrl]
- Called when editing a profile
-
-[zrl=[baseurl]/help/hook/profile_photo_content_end]profile_photo_content_end[/zrl]
- Called when changing a profile photo
-
-[zrl=[baseurl]/help/hook/profile_post]profile_post[/zrl]
- Called when posting an edited profile
-
-[zrl=[baseurl]/help/hook/profile_sidebar]profile_sidebar[/zrl]
- Called when generating the 'channel sidebar' or mini-profile
-
-[zrl=[baseurl]/help/hook/profile_sidebar_enter]profile_sidebar_enter[/zrl]
- Called before generating the 'channel sidebar' or mini-profile
-
-[zrl=[baseurl]/help/hook/queue_deliver]queue_deliver[/zrl]
- Called when delivering a queued message
-
-[zrl=[baseurl]/help/hook/register_account]register_account[/zrl]
- Called when an account has been created
-
-[zrl=[baseurl]/help/hook/render_location]render_location[/zrl]
- Called to generate an ineractive inline map
-
-[zrl=[baseurl]/help/hook/replace_macros]replace_macros[/zrl]
- Called before invoking the template processor
-
-[zrl=[baseurl]/help/hook/reverse_magic_auth]reverse_magic_auth[/zrl]
- Called before invoking reverse magic auth to send you to your own site to authenticate on this site
-
-[zrl=[baseurl]/help/hook/settings_account]settings_account[/zrl]
- Called when generating the account settings form
-
-[zrl=[baseurl]/help/hook/settings_form]settings_form[/zrl]
- Called when generating the channel settings form
-
-[zrl=[baseurl]/help/hook/settings_post]settings_post[/zrl]
- Called when posting from the channel settings form
-
-[zrl=[baseurl]/help/hook/sexpref_selector]sexpref_selector[/zrl]
- Called when generating a dropdown of sexual preference (advanced profile)
-
-[zrl=[baseurl]/help/hook/sexpref_selector_min]sexpref_selector_min[/zrl]
- Called when generating a dropdown of sexual preference (normal profile)
-
-[zrl=[baseurl]/help/hook/smilie]smilie[/zrl]
- Called when translating emoticons
-
-[zrl=[baseurl]/help/hook/status_editor]status_editor[/zrl]
- Called when generating the status_editor.
-
-[zrl=[baseurl]/help/hook/stream_item]stream_item[/zrl]
- Called for each item which is rendered for viewing via conversation()
-
-[zrl=[baseurl]/help/hook/system_app_installed_filter]system_app_installed_filter[/zrl]
- Called when determining whether a system app is installed
-
-[zrl=[baseurl]/help/hook/tagged]tagged[/zrl]
- Called when a delivery is processed which results in you being tagged
-
-[zrl=[baseurl]/help/hook/thumbnail]thumbnail[/zrl]
- Called when generating thumbnails for cloud storage 'tile' view
-
-[zrl=[baseurl]/help/hook/update_unseen]update_unseen[/zrl]
- Called prior to automatically marking items seen which were loaded in the browser
-
-[zrl=[baseurl]/help/hook/validate_channelname]validate_channelname[/zrl]
- Used to validate the names used by a channel
-
-[zrl=[baseurl]/help/hook/webfinger]webfinger[/zrl]
- Called when visiting the webfinger (RFC7033) service
-
-[zrl=[baseurl]/help/hook/well_known]well_known[/zrl]
- Called when accessing the '.well-known' special site addresses
-
-[zrl=[baseurl]/help/hook/wiki_preprocess]wiki_preprocess[/zrl]
- Called before markdown/bbcode processors are run for wiki pages
-
-[zrl=[baseurl]/help/hook/zot_best_algorithm]zot_best_algorithm[/zrl]
- Called when negotiating crypto algorithms with remote sites
-
-[zrl=[baseurl]/help/hook/zid]zid[/zrl]
- Called when adding the observer's zid to a URL
-
-[zrl=[baseurl]/help/hook/zid_init]zid_init[/zrl]
- Called when authenticating a visitor who has used zid
-
-[zrl=[baseurl]/help/hook/zot_finger]zot_finger[/zrl]
- Called when a zot-info packet has been requested (this is our webfinger discovery mechanism)
diff --git a/doc/en/intro_for_developers.bb b/doc/en/intro_for_developers.bb
deleted file mode 100644
index 172008b7f..000000000
--- a/doc/en/intro_for_developers.bb
+++ /dev/null
@@ -1,113 +0,0 @@
-[b]$Projectname Developer Guide[/b]
-
-[b]File system layout:[/b]
-
-[addon] optional addons/plugins
-
-[boot.php] Every process uses this to bootstrap the application structure
-
-[doc] Help Files
-
-[images] core required images
-
-[include] The &quot;model&quot; in MVC - (back-end functions), also contains PHP &quot;executables&quot; for background processing
-
-[index.php] The front-end controller for web access
-
-[install] Installation and upgrade files and DB schema
-
-[library] Third party modules (must be license compatible)
-
-[mod] Controller modules based on URL pathname (e.g. #^[url=http://sitename/foo]http://sitename/foo[/url] loads mod/foo.php)
-
-[mod/site/] site-specific mod overrides, excluded from git
-
-[util] translation tools, main English string database and other miscellaneous utilities
-
-[version.inc] contains current version (auto-updated via cron for the master repository and distributed via git)
-
-[view] theming and language files
-
-[view/(css,js,img,php,tpl)] default theme files
-
-[view/(en,it,es ...)] language strings and resources
-
-[view/theme/] individual named themes containing (css,js,img,php,tpl) over-rides
-
-[b]The Database:[/b]
-
- [li]abook - contact table, replaces Friendica 'contact'[/li]
- [li]account - service provider account[/li]
- [li]addon - registered plugins[/li]
- [li]app - peronal app data[/li]
- [li]attach - file attachments[/li]
- [li]auth_codes - OAuth usage[/li]
- [li]cache - OEmbed cache[/li]
- [li]channel - replaces Friendica 'user'[/li]
- [li]chat - chat room content[/li]
- [li]chatpresence - channel presence information for chat[/li]
- [li]chatroom - data for the actual chat room[/li]
- [li]clients - OAuth usage[/li]
- [li]config - main configuration storage[/li]
- [li]conv - Diaspora private messages[/li]
- [li]event - Events[/li]
- [li]fcontact - friend suggestion stuff[/li]
- [li]ffinder - friend suggestion stuff[/li]
- [li]fserver - obsolete[/li]
- [li]fsuggest - friend suggestion stuff[/li]
- [li]pgrp - privacy groups[/li]
- [li]pgrp_member - privacy groups[/li]
- [li]hook - plugin hook registry[/li]
- [li]hubloc - Red location storage, ties a location to an xchan[/li]
- [li]item - posts[/li]
- [li]item_id - other identifiers on other services for posts[/li]
- [li]likes - likes of 'things'[/li]
- [li]mail - private messages[/li]
- [li]menu - channel menu data[/li]
- [li]menu_item - items uses by channel menus[/li]
- [li]notify - notifications[/li]
- [li]notify-threads - need to factor this out and use item thread info on notifications[/li]
- [li]obj - object data for things (x has y)[/li]
- [li]outq - output queue[/li]
- [li]pconfig - personal (per channel) configuration storage[/li]
- [li]photo - photo storage[/li]
- [li]poll - data for polls[/li]
- [li]poll_elm - data for poll elements[/li]
- [li]profdef - custom profile field definitions[/li]
- [li]profext - custom profile field data[/li]
- [li]profile - channel profiles[/li]
- [li]profile_check - DFRN remote auth use, may be obsolete[/li]
- [li]register - registrations requiring admin approval[/li]
- [li]session - web session storage[/li]
- [li]shares - shared item information[/li]
- [li[sign - Diaspora signatures. To be phased out.[/li]
- [li]site - site table to find directory peers[/li]
- [li]source - channel sources data[/li]
- [li]spam - unfinished[/li]
- [li]sys_perms - extensible permissions for the sys channel[/li]
- [li]term - item taxonomy (categories, tags, etc.) table[/li]
- [li]tokens - OAuth usage[/li]
- [li]updates - directory sync updates[/li]
- [li]verify - general purpose verification structure[/li]
- [li]vote - vote data for polls[/li]
- [li]xchan - replaces 'gcontact', list of known channels in the universe[/li]
- [li]xchat - bookmarked chat rooms[/li]
- [li]xconfig - as pconfig but for channels with no local account[/li]
- [li]xlink - &quot;friends of friends&quot; linkages derived from poco[/li]
- [li]xprof - if this hub is a directory server, contains basic public profile info of everybody in the network[/li]
- [li]xtag - if this hub is a directory server, contains tags or interests of everybody in the network[/li]
-
-
-[b]How to theme $Projectname - by Olivier Migeot[/b]
-
-This is a short documentation on what I found while trying to modify $Projectname's appearance.
-
-First, you'll need to create a new theme. This is in /view/theme, and I chose to copy 'redbasic' since it's the only available for now. Let's assume I named it .
-
-Oh, and don't forget to rename the _init function in /php/theme.php to be _init() instead of redbasic_init().
-
-At that point, if you need to add javascript or css files, add them to /js or /css, and then &quot;register&quot; them in _init() through head_add_js('file.js') and head_add_css('file.css').
-
-Now you'll probably want to alter a template. These can be found in in /view/tpl OR view//tpl. All you should have to do is copy whatever you want to tweak from the first place to your theme's own tpl directory.
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/macros/addons_footer.bb b/doc/en/macros/addons_footer.bb
deleted file mode 100644
index 32814c59b..000000000
--- a/doc/en/macros/addons_footer.bb
+++ /dev/null
@@ -1,2 +0,0 @@
-Return to the [zrl=[baseurl]/help/addons]Addons documentation[/zrl]
-Return to the [zrl=[baseurl]/help/main]Main documentation[/zrl]
diff --git a/doc/en/macros/cloud_footer.bb b/doc/en/macros/cloud_footer.bb
deleted file mode 100644
index 798cc9ea6..000000000
--- a/doc/en/macros/cloud_footer.bb
+++ /dev/null
@@ -1,2 +0,0 @@
-Return to the [zrl=[baseurl]/help/cloud]Cloud documentation[/zrl]
-Return to the [zrl=[baseurl]/help/main]Main documentation page[/zrl]
diff --git a/doc/en/macros/de/addons_footer.bb b/doc/en/macros/de/addons_footer.bb
deleted file mode 100644
index 068bb7ec7..000000000
--- a/doc/en/macros/de/addons_footer.bb
+++ /dev/null
@@ -1,2 +0,0 @@
-Zurück zur [zrl=[baseurl]/help/addons]Addons-Hilfe[/zrl]
-Zurück zur [zrl=[baseurl]/help/main]Hilfe-Startseite[/zrl]
diff --git a/doc/en/macros/de/cloud_footer.bb b/doc/en/macros/de/cloud_footer.bb
deleted file mode 100644
index 921448a3c..000000000
--- a/doc/en/macros/de/cloud_footer.bb
+++ /dev/null
@@ -1,2 +0,0 @@
-Zurück zur [zrl=[baseurl]/help/cloud]Cloud-Hilfe[/zrl]
-Zurück zur [zrl=[baseurl]/help/main]Hilfe-Startseite[/zrl]
diff --git a/doc/en/macros/de/main_footer.bb b/doc/en/macros/de/main_footer.bb
deleted file mode 100644
index e71603626..000000000
--- a/doc/en/macros/de/main_footer.bb
+++ /dev/null
@@ -1 +0,0 @@
-Zurück zur [zrl=[baseurl]/help/main]Hilfe-Startseite[/zrl]
diff --git a/doc/en/macros/de/troubleshooting_footer.bb b/doc/en/macros/de/troubleshooting_footer.bb
deleted file mode 100644
index be8c13a28..000000000
--- a/doc/en/macros/de/troubleshooting_footer.bb
+++ /dev/null
@@ -1,2 +0,0 @@
-Zurück zur [zrl=[baseurl]/help/troubleshooting]Troubleshooting-Startseite[/zrl]
-Zurück zur [zrl=[baseurl]/help/troubleshooting]Hilfe-Startseite[/zrl]
diff --git a/doc/en/macros/main_footer.bb b/doc/en/macros/main_footer.bb
deleted file mode 100644
index 08f671ded..000000000
--- a/doc/en/macros/main_footer.bb
+++ /dev/null
@@ -1 +0,0 @@
-Return to the [zrl=[baseurl]/help/main]Main documentation page[/zrl]
diff --git a/doc/en/macros/pl/addons_footer.bb b/doc/en/macros/pl/addons_footer.bb
deleted file mode 100644
index 79ac0a71c..000000000
--- a/doc/en/macros/pl/addons_footer.bb
+++ /dev/null
@@ -1,2 +0,0 @@
-Powróć do [zrl=[baseurl]/help/addons]dokumentacji dodatków[/zrl]
-Powróć do [zrl=[baseurl]/help/main]głównej strony dokumentacji[/zrl]
diff --git a/doc/en/macros/pl/cloud_footer.bb b/doc/en/macros/pl/cloud_footer.bb
deleted file mode 100644
index 48628ae1a..000000000
--- a/doc/en/macros/pl/cloud_footer.bb
+++ /dev/null
@@ -1,2 +0,0 @@
-Powróć do [zrl=[baseurl]/help/cloud]dokumentacji chmury[/zrl]
-Powróć do [zrl=[baseurl]/help/main]głównej strony dokumentacji[/zrl]
diff --git a/doc/en/macros/pl/main_footer.bb b/doc/en/macros/pl/main_footer.bb
deleted file mode 100644
index 8bf25fed5..000000000
--- a/doc/en/macros/pl/main_footer.bb
+++ /dev/null
@@ -1 +0,0 @@
-Powróć do [zrl=[baseurl]/help/main]głównej strony dokumentacji[/zrl]
diff --git a/doc/en/macros/pl/troubleshooting_footer.bb b/doc/en/macros/pl/troubleshooting_footer.bb
deleted file mode 100644
index 19328ad64..000000000
--- a/doc/en/macros/pl/troubleshooting_footer.bb
+++ /dev/null
@@ -1,2 +0,0 @@
-[zrl=[baseurl]/help/troubleshooting]Dokumentacja dotycząca rozwiązywania problemów[/zrl]
-[zrl=[baseurl]/help/troubleshooting]Główna strona dokumentacji[/zrl]
diff --git a/doc/en/macros/troubleshooting_footer.bb b/doc/en/macros/troubleshooting_footer.bb
deleted file mode 100644
index c7603a62b..000000000
--- a/doc/en/macros/troubleshooting_footer.bb
+++ /dev/null
@@ -1,2 +0,0 @@
-[zrl=[baseurl]/help/troubleshooting]Troubleshooting documentation[/zrl]
-[zrl=[baseurl]/help/troubleshooting]Documentation Main Page[/zrl]
diff --git a/doc/en/member/AdvancedSearch.md b/doc/en/member/AdvancedSearch.md
new file mode 100644
index 000000000..86db5bc8b
--- /dev/null
+++ b/doc/en/member/AdvancedSearch.md
@@ -0,0 +1,50 @@
+### Advanced Directory Search
+
+
+Advanced Directory Search is enabled in "Expert Mode" from your Settings => Additional features page.
+
+On the Directory page an option named "Advanced" will apear in the "Find Channels" widget (typically in the sidebar). Clicking "Advanced" will open another search box for entering advanced search requests.
+
+Advanced requests include
+
+* name=xxx
+[Channel name contains xxx]
+
+* address=xxx
+[Channel address (webbie) contains xxx]
+
+* locale=xxx
+[Locale (typically 'city') contains xxx]
+
+* region=xxx
+[Region (state/territory) contains xxx]
+
+* postcode=xxx
+[Postcode or zip code contains xxx]
+
+* country=xxx
+[Country name contains xxx]
+
+* gender=xxx
+[Gender contains xxx]
+
+* marital=xxx
+[Marital status contains xxx]
+
+* sexual=xxx
+[Sexual preference contains xxx]
+
+* keywords=xxx
+[Keywords contain xxx]
+
+There are many reasons why a match may not return what you're looking for, as many channels do not provide detailed information in their default (public) profile, and many of these fields allow free-text input in several languages - and this may be difficult to match precisely. For instance you may have better results finding somebody in the USA with 'country=u' (along with some odd channels from Deutschland and Bulgaria and Australia) because this could be represented in a profile as US, U.S.A, USA, United States, etc...
+
+Future revisions of this tool may try to smooth over some of these difficulties.
+
+Requests may be joined together with 'and', 'or', and 'and not'.
+
+Terms containing spaces must be quoted.
+
+Example:
+ name="charlie brown" and country=canada and not gender=female
+
diff --git a/doc/en/member/NSFW.md b/doc/en/member/NSFW.md
new file mode 100644
index 000000000..df254dd6c
--- /dev/null
+++ b/doc/en/member/NSFW.md
@@ -0,0 +1,14 @@
+## Content warning/NSFW
+
+Content warnings and hiding certain content is done with Hubzilla using the ‘NSFW’ app.
+While with other services in the Fediverse you have to rely on the authors of posts possibly hiding ‘sensitive’ content behind a content warning (a content or trigger warning), with Hubzilla you have this functionality in your own hands as a recipient. With the NSFW app, you can create filters that ensure that posts that match the filter rules are collapsed. The content of the post is only displayed when you click on the button.
+
+![nsfw 01](/help/en/member/pic/nsfw01.png)
+
+Here you can enter keywords and even [regular expressions](https://en.wikipedia.org/wiki/Regular_expression) that the posting will be searched for. If one of the words or a text pattern is found, the content in the stream will be collapsed.
+It is also possible to filter by language (lang=xx or lang!=xx).
+If a phrase that matches one of the filters entered is found in a posting, the posting will initially be hidden from you behind a content warning.
+
+![nsfw 02](/help/en/member/pic/nsfw02.png)
+
+![nsfw 03](/help/en/member/pic/nsfw03.png)
diff --git a/doc/en/member/account_settings.md b/doc/en/member/account_settings.md
new file mode 100644
index 000000000..4bba247df
--- /dev/null
+++ b/doc/en/member/account_settings.md
@@ -0,0 +1,6 @@
+#### Account settings
+
+You can use the account settings to change your account details.
+
+![settings 03](/help/en/member/pic/settings03.png)
+
diff --git a/doc/en/member/accounts_profiles_channels_basics.md b/doc/en/member/accounts_profiles_channels_basics.md
new file mode 100644
index 000000000..c3252ea56
--- /dev/null
+++ b/doc/en/member/accounts_profiles_channels_basics.md
@@ -0,0 +1,31 @@
+## Accounts, Profiles and Channels
+
+Once you have registered an *account* at the grid you have also created a *profile* and a *channel*.
+
+**Account**
+
+You have *one* account. This consists of your email account and your password. With your account you access your profile and your channel.
+
+*Think of your account as the way you authenticate at one $Projectname site. It lets you do things, such as creating profiles and channels with which you can connect to other people.*
+
+**Profile**
+
+You have surely registered with some other internet services, such as forums or online communities. For all of them you provided some information about yourself, such as date of birth, country, age and the likes.
+
+If you like you can see your profile here: [baseurl]/profile/[webname] and edit it by clicking on the pencil icon next to your avatar image.
+
+Unlike other services hubzilla offers you the advantage of creating *many more profiles*. That way you are able to distinguish between profiles targeted specially at everyone (your public profile), your work mates, your family and your partner.
+
+*Think of your profile as the basic information about yourself you tell other people.*
+
+**Channel**
+
+During the registration you created your first *channel*. Yes, besides several profiles you are able to have several channels. This might be a bit confusing in the beginning, but let's clear things up. You already have created one channel. You can use this one for the public, to communicate with people about every day life. But perhaps you are an avid book reader and many people are bored by that. So you open a *second channel* just for the book lovers, where you all can talk about books as much as you like. Obviously this is a new stream of posts, with a new profile (... or new profile*s* ...) and completely different contacts. Some connections might exist in both channels, but there will be some that are exclusive to only one of both. You yourself just switch between both of them just like you would in real life switch when talking to people you meet on the street or people you meet specially to talk about books. You can even connect to yourself, or better: to your other channel. :)
+
+*Think of a channel as different spaces dedicated to different topics where you meet with different people.*
+
+
+#include doc/en/member/channels.md;
+#include doc/en/member/profiles.md;
+#include doc/en/member/settings.md;
+#include doc/en/member/connecting_with_channels.md;
diff --git a/doc/en/member/additional_features.md b/doc/en/member/additional_features.md
new file mode 100644
index 000000000..90aa0fecb
--- /dev/null
+++ b/doc/en/member/additional_features.md
@@ -0,0 +1,29 @@
+#### Additional features (hidden settings)
+
+The ‘Additional Features’ settings are not accessible in their entirety either via the menu or via an icon. However, all the individual feature settings can also be accessed in the respective app via the cogwheel next to the main menu (avatar picture).
+These are settings for additional functions in all possible areas of Hubzilla. To access the settings, you have to append the URL of the hub `/settings/features` to the browser, e.g. `https://klacker.org/settings/features`.
+
+![settings 14](/help/en/member/pic/settings14.png)
+
+The default settings for all of these options are set by the hub administrator. This default setting can be overridden by the user in the ‘Additional Functions’.
+For each option, the administrator also has the option of locking the default setting to prevent changes. The user can still flip the switch for the option, but the selection is not saved and the option is reset to the default setting.
+
+![settings 15](/help/en/member/pic/settings15.png)
+
+![settings 16](/help/en/member/pic/settings16.png)
+
+![settings 17](/help/en/member/pic/settings17.png)
+
+![settings 18](/help/en/member/pic/settings18.png)
+
+![settings 19](/help/en/member/pic/settings19.png)
+
+![settings 20](/help/en/member/pic/settings20.png)
+
+![settings 21](/help/en/member/pic/settings21.png)
+
+![settings 22](/help/en/member/pic/settings22.png)
+
+![settings 23](/help/en/member/pic/settings23.png)
+
+![settings 24](/help/en/member/pic/settings24.png)
diff --git a/doc/en/member/addressbook.md b/doc/en/member/addressbook.md
new file mode 100644
index 000000000..d939feae3
--- /dev/null
+++ b/doc/en/member/addressbook.md
@@ -0,0 +1,23 @@
+## Address book (CardDAV)
+
+Hubzilla offers you address management with the ‘CardDAV’ app. You can create as many address books as you like.
+
+![carddav01](/help/en/member/pic/carddav01.png)
+
+The entries are stored in vCards format.
+
+![carddav02](/help/en/member/pic/carddav02.png)
+
+![carddav03](/help/en/member/pic/carddav03.png)
+
+![carddav04](/help/en/member/pic/carddav04.png)
+
+![carddav05](/help/en/member/pic/carddav05.png)
+
+![carddav06](/help/en/member/pic/carddav06.png)
+
+The app also allows you to import address books or individual vCards from a file.
+
+![carddav07](/help/en/member/pic/carddav07.png)
+
+Address books are generally private and cannot be shared - not even via remote authorisation.
diff --git a/doc/en/member/apps.md b/doc/en/member/apps.md
new file mode 100644
index 000000000..68111e864
--- /dev/null
+++ b/doc/en/member/apps.md
@@ -0,0 +1,58 @@
+## Apps
+
+A freshly installed Hubzilla instance has a number of basic functionalities. However, there are many features that are not part of the basic installation and are not immediately available for a newly created channel.
+
+The majority of functions are realised as ‘applications’ (or ‘apps’ for short).
+You can access the apps using the ‘app menu’, which is symbolised by the ‘⋮’ button (and is located on the far right of the navigation bar on most hubs). You can also pin frequently used apps to the navigation bar so that you don't have to open the app menu every time you call them up.
+
+How many and which apps are available to you depends on how the administrator has configured the hub.
+
+### App management
+
+You can manage the apps for your channel using app management. You can also access this in the app menu under the lowest menu item ‘+ Apps’
+
+After calling up the app management, the apps already installed are displayed.
+
+You can now switch between ‘Installed apps’ and ‘Available apps’ in the left-hand sidebar.
+
+#### Available apps
+
+In the available apps, you will find all the apps available on your hub. Some of them are already installed. You can use the button next to the app to install apps or update apps that are already installed.
+
+![apps 01](/help/en/member/pic/apps01.png)
+
+#### Installed apps
+
+The Installed apps tab contains all the apps that are installed for your channel. To the right of each app there are two or three buttons with icons: a star icon, a pinhead icon and, if applicable, a cogwheel icon.
+
+![apps 02](/help/en/member/pic/apps02.png)
+
+The star symbol is used to make the app accessible as a menu item in the app menu.
+
+![apps 03](/help/en/member/pic/apps03.png)
+
+![apps 04](/help/en/member/pic/apps04.png)
+
+You can pin the app in the navigation bar using the pin symbol.
+
+![apps 05](/help/en/member/pic/apps05.png)
+
+If there is a dialogue for app-specific settings for an app, you can access this via the button with the cogwheel symbol.
+
+![apps 06](/help/en/member/pic/apps06.png)
+
+#### Manage apps
+
+There is also a button labelled ‘Manage apps’ at the top of the installed apps tab. This button takes you to the ‘Manage apps’ page, where you can uninstall apps and also edit installed apps within certain limits.
+
+![apps 07](/help/en/member/pic/apps07.png)
+
+It is also possible to create your own apps there (only for advanced users!).
+
+#### Sorting apps in the menu
+
+You can easily sort the apps you have added to the app menu using drag-and-drop and specify your preferred order.
+
+![apps 08](/help/en/member/pic/apps08.png)
+
+#include doc/en/member/important_apps.md;
diff --git a/doc/en/member/article.md b/doc/en/member/article.md
new file mode 100644
index 000000000..9d40bd4db
--- /dev/null
+++ b/doc/en/member/article.md
@@ -0,0 +1,25 @@
+## Article
+
+The article is a macroblogging post type at Hubzilla and is suitable for real blog posts, for example. Unlike normal posts, which are distributed throughout the entire network (including the Fediverse), articles remain on your own hub. They are only accessible to users of other instances and users who do not have an account in the Fediverse via their URL. Of course, the URL can be shared so that the article will still be known in the Fediverse and can be accessed.
+You can create an article using the app (app menu ⋮) ‘Article’. When you open it, all created articles are displayed and you have the option to create a new article (‘Add article’).
+
+![article 01](/help/en/member/pic/article01.png)
+
+Creating an article is similar to creating a normal post. However, the input form has an additional field: ‘Link to page’.
+
+Here you can enter an easy-to-read link name. If you leave this field empty, a name will be assigned automatically (longer and more ‘cryptic’).
+
+![article 02](/help/en/member/pic/article02.png)
+
+If you have filled in the optional ‘Summary’ field, only the summary of an article will initially be displayed, just like a normal post.
+
+![article 03](/help/en/member/pic/article03.png)
+
+If you click on ‘View article’, the article itself will be displayed.
+
+![article 04](/help/en/member/pic/article04.png)
+
+The direct link to the article is composed as follows:
+`URL-of-your-hub/articles/channel-name/link-to-page`
+
+![article 05](/help/en/member/pic/article05.png)
diff --git a/doc/en/member/assets/bookmarker-save-icon.png b/doc/en/member/assets/bookmarker-save-icon.png
deleted file mode 100644
index 6a7c10eb9..000000000
--- a/doc/en/member/assets/bookmarker-save-icon.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/member/assets/bookmarks-menu-dropdown.png b/doc/en/member/assets/bookmarks-menu-dropdown.png
deleted file mode 100644
index 137ae52ad..000000000
--- a/doc/en/member/assets/bookmarks-menu-dropdown.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/member/assets/privacy-group-tool-public.png b/doc/en/member/assets/privacy-group-tool-public.png
deleted file mode 100644
index 828cb6411..000000000
--- a/doc/en/member/assets/privacy-group-tool-public.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/member/assets/privacy-tool-3.png b/doc/en/member/assets/privacy-tool-3.png
deleted file mode 100644
index ba896403d..000000000
--- a/doc/en/member/assets/privacy-tool-3.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/member/assets/qr_text_to_post.png b/doc/en/member/assets/qr_text_to_post.png
deleted file mode 100644
index 887c85492..000000000
--- a/doc/en/member/assets/qr_text_to_post.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/member/assets/zat_dialog.png b/doc/en/member/assets/zat_dialog.png
deleted file mode 100644
index 892964e95..000000000
--- a/doc/en/member/assets/zat_dialog.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/member/bbcode.html b/doc/en/member/bbcode.html
deleted file mode 100644
index e3e079b4d..000000000
--- a/doc/en/member/bbcode.html
+++ /dev/null
@@ -1,337 +0,0 @@
-<style>
- section {
- display: inline-block;
- overflow-x: scroll;
- }
-</style>
-<h3>Text Decoration</h3>
-<table class="table table-responsive table-bordered">
- <tbody>
- <tr>
- <th>BBcode syntax</th><th>Rendered text</th>
- </tr>
- <tr>
- <td><code>[b]bold[/b]</code></td><td><strong>bold</strong></td>
- </tr>
- <tr>
- <td><code>[i]italic[/i]</code></td><td><em>italic</em></td>
- </tr>
- <tr>
- <td><code>[u]underlined[/u]</code></td><td><u>underlined</u></td>
- </tr>
- <tr>
- <td><code>[s]strike[/s]</code></td><td><strike>strike</strike></td>
- </tr>
- <tr>
- <td><code>[color=red]red[/color]</code></td><td><span style="color: red;">red</span></td>
- </tr>
- <tr>
- <td><code>[hl]highlighted[/hl]</code></td><td><span style="background-color: yellow;">highlighted</span></td>
- </tr>
- <tr>
- <td><code>[font=courier]some text[/font] </code></td><td><span style="font-family: courier;">some text</span></td>
- </tr>
- <tr>
- <td><code>[quote]quote[/quote]</code></td><td><blockquote>quote</blockquote></td>
- </tr>
- <tr>
- <td><code>[quote=Author]Author? Me? No, no, no...[/quote]</code></td><td><strong class="author">Author wrote:</strong><blockquote>Author? Me? No, no, no...</blockquote></td>
- </tr>
- <tr>
- <td><code>
- [size=small]small text[/size]<br>
- [size=xx-large]xx-large text[/size]<br>
- [size=20]20px exactly[/size]<br>
- </code>
- Size options include: <strong>xx-small, small, medium, large, xx-large</strong></td><td><span style="font-size: small;">small text</span><br><span style="font-size: xx-large;">xx-large text</span><br><span style="font-size: 20px;">20px exactly</span></td>
- </tr>
- <tr>
- <td><code>Add a horizontal bar
-[hr]
-Like this
- </code></td><td>
- Add a horizontal bar<br><hr><br>Like this
- </td>
- </tr>
- <tr>
- <td><code>This is
-[center]centered[/center]
-text</code></td><td>
- This is<br><div style="text-align:center;">centered</div><br>text
- </td>
- </tr>
- </tbody>
-</table>
-
-<h3>Code blocks</h3>
-Code can be rendered generically in a block or inline format (depending on if there are new line characters in the text), or you can specify a supported language for enhanced syntax highlighting. Syntax highlighting requires a suitable rendering plugin such as <strong>hilite</strong>. Supported languages with the hilite plugin include <strong>php, css, mysql, sql, abap, diff, html, perl, ruby, vbscript, avrc, dtd, java, xml, cpp, python, javascript, js, json, sh </strong>.
-<br><br>
-If a rendering plugin is not installed or an unsupported language is specified, the output for syntax highlighted code blocks is the same as the block format code tag.
-<br><br>
-<table class="table table-responsive table-bordered">
- <tbody>
- <tr>
- <th>BBcode syntax</th><th>Output</th>
- </tr>
- <tr>
- <td><code>[code]function bbcode() { }[/code]</code></td><td><code>function bbcode() { }</code></td>
- </tr>
- <tr>
- <td><code>[code=php]function bbcode() {<br>
- $variable = true;<br>
- if( $variable ) {<br>
- echo "true";<br>
- }<br>
-}[/code]</code></td><td><code><div class="hl-main"><ol class="hl-main"><li><span class="hl-code">&nbsp;</span><span class="hl-reserved">function</span><span class="hl-code"> </span><span class="hl-identifier">bbcode</span><span class="hl-brackets">(</span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"></span></li><li><span class="hl-code">&nbsp;&nbsp;&nbsp;</span><span class="hl-var">$variable</span><span class="hl-code"> = </span><span class="hl-reserved">true</span><span class="hl-code">;</span></li><li><span class="hl-code">&nbsp;&nbsp;&nbsp;</span><span class="hl-reserved">if</span><span class="hl-brackets">(</span><span class="hl-code"> </span><span class="hl-var">$variable</span><span class="hl-code"> </span><span class="hl-brackets">)</span><span class="hl-code"> </span><span class="hl-brackets">{</span><span class="hl-code"></span></li><li><span class="hl-code">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span class="hl-reserved">echo</span><span class="hl-code"> </span><span class="hl-quotes">"</span><span class="hl-string">true</span><span class="hl-quotes">"</span><span class="hl-code">;</span></li><li><span class="hl-code">&nbsp;&nbsp;&nbsp;</span><span class="hl-brackets">}</span><span class="hl-code"></span></li><li><span class="hl-code">&nbsp;</span><span class="hl-brackets">}</span></li></ol></div></code></td>
- </tr>
- <tr>
- <td><code>[nobb][nobb]This is how [i]you[/i] can
-[u]show[/u] how to use
-[hl]BBcode[/hl] syntax[/nobb][/nobb]</code></td><td>[nobb]This is how [i]you[/i] can [u]show[/u] how to use [hl]BBcode[/hl] syntax[/nobb]</td>
- </tr>
- </tbody>
-</table>
-
-<h3>Lists</h3>
-<table class="table table-responsive table-bordered">
- <tbody>
- <tr>
- <th>BBcode syntax</th><th>Rendered list</th>
- </tr>
- <tr>
- <td><code>[ul]<br>
-[*] First list element<br>
-[*] Second list element<br>
-[/ul]</code></td><td><ul class="listbullet" style="list-style-type: circle;"><li> First list element</li><li> Second list element<br></li></ul></td>
- </tr>
- <tr>
- <td><code>[ol]<br>
-[*] First list element<br>
-[*] Second list element<br>
-[/ol]</code></td><td><ul class="listdecimal" style="list-style-type: decimal;"><li> First list element</li><li> Second list element<br></li></ul></td>
- </tr>
- <tr>
- <td><code>[list=A]<br>
-[*] First list element<br>
-[*] Second list element<br>
-[/list]</code>
- The list type options are <code>1, i, I, a, A</code>.</td><td><ul class="listupperalpha" style="list-style-type: upper-alpha;"><li> First list element</li><li> Second list element</li></ul></td>
- </tr>
- <tr>
- <td><code>[dl terms="b"]<br>
-[*= First element term] First element description<br>
-[*= Second element term] Second element description<br>
-[/dl]</code>
- The <strong>terms</strong> style options can be any combination of:
- <dl class="bb-dl dl-horizontal">
- <dt>b</dt><dd>bold</dd>
- <dt>i</dt><dd>italic</dd>
- <dt>u</dt><dd>underline</dd>
- <dt>m</dt><dd>monospace</dd>
- <dt>l</dt><dd>large</dd>
- <dt>h</dt><dd>horizontal &mdash; like <em>this</em> defintion list</dd>
- </dl>
- </td><td><dl class="bb-dl dl-terms-bold">
-<dt> First element term</dt><dd> First element description<br></dd>
-<dt> Second element term</dt><dd> Second element description<br></dd></dl></td>
- </tr>
- </tbody>
-</table>
-
-<h3>Tables</h3>
-
-<table class="table table-responsive table-bordered">
- <tbody>
- <tr>
- <th>BBcode syntax</th><th>Rendered table</th>
- </tr>
- <tr>
- <td><code>[table border=0]<br>
-[tr]<br>
-[th]Header 1[/th][th]Header 2[/th]<br>
-[/tr]<br>
-[tr][td]Content[/td][td]Content[/td][/tr]<br>
-[tr][td]Content[/td][td]Content[/td][/tr]<br>
-[/table]</code></td><td><table class="table table-responsive"><tbody><tr><th>Header 1</th><th>Header 2</th></tr>
-<tr><td>Content</td><td>Content</td></tr><tr><td>Content</td><td>Content</td></tr></tbody></table></td>
- </tr>
- <tr>
- <td><code>[table border=1]<br>
-[tr]<br>
-[th]Header 1[/th][th]Header 2[/th]<br>
-[/tr]<br>
-[tr][td]Content[/td][td]Content[/td][/tr]<br>
-[tr][td]Content[/td][td]Content[/td][/tr]<br>
-[/table]</code></td><td><table class="table table-responsive table-bordered"><tbody><tr><th>Header 1</th><th>Header 2</th></tr>
-<tr><td>Content</td><td>Content</td></tr><tr><td>Content</td><td>Content</td></tr></tbody></table></td>
- </tr>
- <tr>
- <td><code>[table]<br>
-[tr]<br>
-[th]Header 1[/th][th]Header 2[/th]<br>
-[/tr]<br>
-[tr][td]Content[/td][td]Content[/td][/tr]<br>
-[tr][td]Content[/td][td]Content[/td][/tr]<br>
-[/table]</code></td><td><table><tbody><tr><th>Header 1</th><th>Header 2</th></tr><tr><td>Content</td><td>Content</td></tr><tr><td>Content</td><td>Content</td></tr></tbody></table>
-</td>
- </tr>
- </tbody>
-</table>
-
-<h3>Links and Embedded Content</h3>
-
-<table class="table table-responsive table-bordered">
- <tbody>
- <tr>
- <th>BBcode syntax</th><th>Output</th>
- </tr>
- <tr>
- <td><code>[video]video URL[/video]<br>
-[audio]audio URL[/audio]</code></td><td></td>
- </tr>
- <tr>
- <td><code>[url=https://hubzilla.org]Hubzilla[/url]</code></td><td><a href="https://hubzilla.org" target="_blank">Hubzilla</a></td>
- </tr>
- <tr>
- <td><code>An image [img]https://example.org/image.jpg[/img]
-in some text </code></td><td>
- An image <img src="/images/default_profile_photos/rainbow_man/48.jpg" alt="Image/photo"> in some text
- </td>
- </tr>
- </tbody>
-</table>
-
-
-<h3>$Projectname specific codes</h3>
-
-<table class="table table-responsive table-bordered">
- <tbody>
- <tr>
- <th>BBcode syntax</th><th>Output</th>
- </tr>
- <tr>
- <td>Magic-auth version of [url] tag
- <code>[zrl=https://hubzilla.org]Identity-aware link[/zrl]</code>
- </td><td><code>https://hubzilla.org/?zid=[observer=1][observer.address][/observer][observer=0]your_channel@your.home.hub[/observer]</code></td>
- </tr>
- <tr>
- <td>Magic-auth version of [img] tag
- <code>[zmg]https://hubzilla.org/some/photo.jpg[/zmg]</code>
- </td><td>Image is only viewable by those authenticated and with permission.</td>
- </tr>
- <tr>
- <td>Observer-dependent output:<code>
- [nobb][observer=1]Text to display if observer IS authenticated[/observer][/nobb]
- </code></td><td></td>
- </tr>
- <tr>
- <td>
- <code>
- [nobb][observer=0]Text to display if observer IS NOT authenticated[/observer][/nobb]
- </code>
- </td>
- <td></td>
- </tr>
- <tr>
- <td>
- <code>
- [nobb][observer.language=en]Text to display if observer language is English[/observer][/nobb]
- </code>
- </td>
- <td></td>
- </tr>
- <tr>
- <td>
- <code>
- [nobb][observer.language!=de]Text to display if observer language is not German[/observer][/nobb]
- </code>
- </td>
- <td></td>
- </tr>
- <tr>
- <td>
- <code>
- [nobb][observer.url][/nobb]
- </code>
- </td>
- <td>channel URL of observer</td>
- </tr>
- <tr>
- <td>
- <code>
- [nobb][observer.baseurl][/nobb]
- </code>
- </td>
- <td>website of observer</td>
- </tr>
- <tr>
- <td>
- <code>
- [nobb][observer.name][/nobb]
- </code>
- </td>
- <td>name of observer</td>
- </tr>
- <tr>
- <td>
- <code>
- [nobb][observer.webname][/nobb]
- </code>
- </td>
- <td>short name in the url of the observer</td>
- </tr>
- <tr>
- <td>
- <code>
- [nobb][observer.address][/nobb]
- </code>
- </td>
- <td>address (ZOT-id) of observer</td>
- </tr>
- <tr>
- <td>
- <code>
- [nobb][observer.photo][/nobb]
- </code>
- </td>
- <td>profile photo of observer</td>
- </tr>
- <tr>
- <td><code>What is a spoiler?<br>
- [spoiler]Text you want to hide.[/spoiler]</code></td><td>
- What is a spoiler? <div onclick="openClose('opendiv-1131603764'); return false;" class="fakelink">Click to open/close</div><blockquote id="opendiv-1131603764" style="display: none;">Text you want to hide.</blockquote>
- </td>
- </tr>
- <tr>
- <td><code>[toc data-toc='div.page-body' data-toc-headings='h1,h2']</code><br>
-Create a table of content in a webpage or wiki page. Please refer to the <a href="http://ndabas.github.io/toc/" target="_blank">original jQuery toc</a> to get more explanations.
- <ul>
- <li>Optional param: 'data-toc'. If omitted the default is 'body'</li>
- <li>Optional param: 'data-toc-headings'. If omitted the default is 'h1,h2,h3'</li>
- </ul></td><td></td>
- </tr>
- <tr>
- <td><code>[nobb][rpost=title]Text to post[/rpost][/nobb]</code><br>
-The observer will be returned to their home hub to enter a post with the specified title and body. Both are optional</td><td><a href="[baseurl]/rpost?f=&amp;title=title&amp;body=Text+to+post" target="_blank">[baseurl]/rpost?f=&amp;title=title&amp;body=Text+to+post</a></td>
- </tr>
- <tr>
- <td>This requires the <a href="https://framagit.org/hubzilla/addons/tree/master/qrator"><strong>qrator</strong></a> plugin.<br><code>[qr]text to post[/qr]</code></td><td><img src="/doc/member/assets/qr_text_to_post.png"></td>
- </tr>
- <tr>
- <td>This requires a suitable map plugin such as <strong><a href="https://framagit.org/hubzilla/addons/tree/master/openstreetmap">openstreetmap</a></strong>.
- <code>[map]</code></td><td>Generate an inline map using the current browser coordinates of the poster, if browser location is enabled</td>
- </tr>
- <tr>
- <td>This requires a suitable map plugin such as <strong><a href="https://framagit.org/hubzilla/addons/tree/master/openstreetmap">openstreetmap</a></strong>.
- <code>[map=latitude,longitude]</code></td><td>Generate a map using global coordinates.</td></tr>
- <tr>
- <td>This requires a suitable map plugin such as <strong><a href="https://framagit.org/hubzilla/addons/tree/master/openstreetmap">openstreetmap</a></strong>.
- <code>[map]Place Name[/map]</code></td><td>
-Generate a map for a given named location. The first matching location is returned. For instance "Sydney" will usually return Sydney, Australia and not Sydney, Nova Scotia, Canada unless the more precise location is specified. It is highly recommended to use the post preview utility to ensure you have the correct location before submitting the post.
-</td>
- </tr>
- <tr>
- <td><code>[&amp;copy;]</code></td><td> &copy; </td>
- </tr>
- </tbody>
-</table>
diff --git a/doc/en/member/bbcode.md b/doc/en/member/bbcode.md
new file mode 100644
index 000000000..b3855b11c
--- /dev/null
+++ b/doc/en/member/bbcode.md
@@ -0,0 +1,81 @@
+### bbCode
+
+##### Text Decoration
+
+| BBcode syntax | Rendered text |
+| ------------------------------------------------------------ | --------------------------------- |
+| `[b]bold[/b]` | **bold** |
+| `[i]italic[/i]` | *italic* |
+| `[u]underlined[/u]` | underlined |
+| `[s]strike[/s]` | ~~strike~~ |
+| `[color=red]red[/color]` | ![red](/help/en/member/pic/red.png) |
+| `[hl]highlighted[/hl]` | ![highlited](/help/en/member/pic/highlited.png) |
+| `[font=courier]some text[/font] ` | ![font](/help/en/member/pic/font.png) |
+| `[quote]quote[/quote]` | ![quote](/help/en/member/pic/quote.png) |
+| `[quote=Author]Author? Me? No, no, no...[/quote]` | ![author](/help/en/member/pic/author.png) |
+| ` [size=small]small text[/size]`<br />`[size=xx-large]xx-large text[/size]`<br />` [size=20]20px exactly[/size] `<br />Size options include: **xx-small, small, medium, large, xx-large** | ![size](/help/en/member/pic/size.png) |
+| `Add a horizontal bar [hr] Like this ` | ![hbar](/help/en/member/pic/hbar.png) |
+| `This is [center]centered[/center] text` | ![center](/help/en/member/pic/center.png) |
+
+##### Code blocks
+
+Code can be rendered generically in a block or inline format (depending on if there are new line characters in the text), or you can specify a supported language for enhanced syntax highlighting. Syntax highlighting requires a suitable rendering plugin such as **hilite**. Supported languages with the hilite plugin include **php, css, mysql, sql, abap, diff, html, perl, ruby, vbscript, avrc, dtd, java, xml, cpp, python, javascript, js, json, sh** .
+
+ If a rendering plugin is not installed or an unsupported language is specified, the output for syntax highlighted code blocks is the same as the block format code tag.
+
+| BBcode syntax | Output |
+| ------------------------------------------------------------ | ------------------------------------------------------------ |
+| `[code]function bbcode() { }[/code]` | `function bbcode() { }` |
+| `[code=php]function bbcode() { $variable = true; if( $variable ) { echo "true"; } }[/code]` | ![code](/help/en/member/pic/code.png) |
+| `[nobb][nobb]This is how [i]you[/i] can [u]show[/u] how to use [hl]BBcode[/hl] syntax[/nobb][/nobb]` | This is how [i]you[/i] can [u]show[/u] how to use [hl]BBcode[/hl] syntax |
+
+##### Lists
+
+| BBcode syntax | Rendered list |
+| ------------------------------------------------------------ | ------------------------------------------------------------ |
+| `[ul] [*] First list element [*] Second list element [/ul]` | - First list element<br />- Second list element |
+| `[ol] [*] First list element [*] Second list element [/ol]` | 1. First list element<br />2. Second list element |
+| `[list=A] [*] First list element [*] Second list element [/list]` The list type options are `1, i, I, a, A`. | A. First list element<br />B. Second list element |
+| `[dl terms="b"] [*= First element term] First element description [*= Second element term] Second element description [/dl]` The **terms** style options can be any combination of: bbold iitalic uunderline mmonospace llarge hhorizontal — like *this* defintion list | First element term<br />First element description <br />Second element term<br />Second element description |
+
+##### Tables
+
+| BBcode syntax | Rendered table |
+| ------------------------------------------------------------ | --------------------------- |
+| `[table border=0] [tr] [th]Header 1[/th][th]Header 2[/th] [/tr] [tr][td]Content[/td][td]Content[/td][/tr] [tr][td]Content[/td][td]Content[/td][/tr] [/table]` | ![table1](/help/en/member/pic/table1.png) |
+| `[table border=1] [tr] [th]Header 1[/th][th]Header 2[/th] [/tr] [tr][td]Content[/td][td]Content[/td][/tr] [tr][td]Content[/td][td]Content[/td][/tr] [/table]` | ![table2](/help/en/member/pic/table2.png) |
+| `[table] [tr] [th]Header 1[/th][th]Header 2[/th] [/tr] [tr][td]Content[/td][td]Content[/td][/tr] [tr][td]Content[/td][td]Content[/td][/tr] [/table]` | ![table3](/help/en/member/pic/table3.png) |
+
+##### Links and Embedded Content
+
+| BBcode syntax | Output |
+| ------------------------------------------------------------ | ------------------------------------------------------------ |
+| `[video]video URL[/video] [audio]audio URL[/audio]` | VIDEO<br />AUDIO |
+| `[video='URL_TO_POSTER']video_link[/video]` | <img src="./pic/video_poster.png" alt="image" style="zoom:100%;" /> |
+| `[url=https://hubzilla.org]Hubzilla[/url]` | [Hubzilla](https://hubzilla.org) |
+| `An image [img]https://example.org/image.jpg[/img] in some text ` | <img src="./pic/image.png" alt="image" style="zoom:80%;" /> |
+
+##### Hubzilla spezific codes
+
+| BBcode syntax | Output |
+| ------------------------------------------------------------ | ------------------------------------------------------------ |
+| Magic-auth version of [url] tag `[zrl=https://hubzilla.org]Identity-aware link[/zrl]` | ![mauth](/help/en/member/pic/mauth.png) |
+| Magic-auth version of [img] tag `[zmg]https://hubzilla.org/some/photo.jpg[/zmg]` | Image is only viewable by those authenticated and with permission. |
+| Observer-dependent output:`[observer=1]Text to display if observer IS authenticated[/observer]` | |
+| ` [observer=0]Text to display if observer IS NOT authenticated[/observer]` | |
+| `[observer.language=en]Text to display if observer language is English[/observer]` | |
+| `[observer.language!=de]Text to display if observer language is not German[/observer]` | |
+| `[observer.url]` | channel URL of observer |
+| `[observer.baseurl]` | website of observer |
+| `[observer.name]` | name of observer |
+| ` [observer.webname]` | short name in the url of the observer |
+| `[observer.address]` | address (Nomad/Zot-id) of observer |
+| `[observer.photo]` | profile photo of observer |
+| `What is a spoiler? [spoiler]Text you want to hide.[/spoiler]` | What is a spoiler? Click to open/close |
+| `[toc data-toc='div.page-body' data-toc-headings='h1,h2']` Create a table of content in a webpage or wiki page. Please refer to the [original jQuery toc](http://ndabas.github.io/toc/) to get more explanations. Optional param: 'data-toc'. If omitted the default is 'body' Optional param: 'data-toc-headings'. If omitted the default is 'h1,h2,h3' | |
+| `[rpost=title]Text to post[/rpost]` The observer will be returned to their home hub to enter a post with the specified title and body. Both are optional | [[baseurl\]/rpost?f=&title=title&body=Text+to+post](file:///home/daniel/Fediverse/orig doc/member/[baseurl]/rpost?f=&title=title&body=Text+to+post) |
+| This requires the [**qrator**](https://framagit.org/hubzilla/addons/tree/master/qrator) plugin. `[qr]text to post[/qr]` | ![qrcode](/help/en/member/pic/qrcode.png) |
+| This requires a suitable map plugin such as **[openstreetmap](https://framagit.org/hubzilla/addons/tree/master/openstreetmap)**. `[map]` | Generate an inline map using the current browser coordinates of the poster, if browser location is enabled |
+| This requires a suitable map plugin such as **[openstreetmap](https://framagit.org/hubzilla/addons/tree/master/openstreetmap)**. `[map=latitude,longitude]` | Generate a map using global coordinates. |
+| This requires a suitable map plugin such as **[openstreetmap](https://framagit.org/hubzilla/addons/tree/master/openstreetmap)**. `[map]Place Name[/map]` | Generate a map for a given named location. The first matching location is returned. For instance "Sydney" will usually return Sydney, Australia and not Sydney, Nova Scotia, Canada unless the more precise location is specified. It is highly recommended to use the post preview utility to ensure you have the correct location before submitting the post. |
+| `[©]` | © |
diff --git a/doc/en/member/blocking_channels.md b/doc/en/member/blocking_channels.md
new file mode 100644
index 000000000..d2ccee7d7
--- /dev/null
+++ b/doc/en/member/blocking_channels.md
@@ -0,0 +1,19 @@
+## Blocking/ignoring/archiving/hiding channels
+
+Channels in your address book can have the status ‘ *blocked*’, ‘ *ignored’,*‘ *archived’* or ‘ *hidden’*. There is a filter on your connection page that displays the channels with these statuses.
+
+![block-etc 01](/help/en/member/pic/block-etc01.png)
+
+You can change the status of a channel on the pages for editing connections.
+
+The meaning:
+
+**Blocked:** The channel cannot read your items, regardless of permissions, and it cannot write to your channel.
+
+**Ignored**: The channel can read your items if it is authorised to do so, but it cannot write to your channel.
+
+**Hidden:** The channel is not displayed in the connection list of your profile. No one can see that you are connected to it. Note: It is still visible to your other connections, for example in replies to posts.
+
+**Archived:** If a channel cannot be reached for 30 days, it is automatically marked as archived. This means that all data is retained, but the channel is no longer queried for new information and is removed from the auto-complete. If you find out later that the channel is online again, you can remove it manually from the archive.
+
+#include doc/en/member/superblock.md;
diff --git a/doc/en/member/bookmarks.md b/doc/en/member/bookmarks.md
new file mode 100644
index 000000000..60582ec5d
--- /dev/null
+++ b/doc/en/member/bookmarks.md
@@ -0,0 +1,27 @@
+[##](##) Bookmarks
+
+Bookmarks specify a link that can be saved in your bookmarks folder. They use the character string `#^` followed by the link. These are often generated automatically. If the administrator of the Hub has installed the ‘bookmarker’ add-on, this sequence is converted into a bookmark symbol when the post or comment is viewed online.
+
+![Bookmarks01](/help/en/member/pic/bookm01.png)
+
+![Bookmarks02](/help/en/member/pic/bookm02.png)
+
+If you click on the icon, the bookmark is saved. If the bookmark add-in is not installed, the post drop-down menu contains a link to save the bookmark or bookmarks.
+
+To use bookmarks, you must install the ‘Bookmarks’ app.
+
+The app will then list all the bookmarks you have set.
+
+To add a bookmark independently of a link in the stream or a post, you can call up the page `<URL_your_hub>`/rbmark, which provides you with a mask for manually entering a bookmark.
+
+![Bookmarks03](/help/en/member/pic/bookm03.png)
+
+You can also create a [bookmarklet](https://en.wikipedia.org/wiki/Bookmarklet) and place it in the bookmark bar of your web browser, for example:
+
+```javascript
+javascript:javascript:(function(){var%20url=location.href;var%20title=document.title||url;window.open('[observer.baseurl]/rbmark?&url='+encodeURIComponent(url)+'&title='+encodeURIComponent(title)+'&source=bookmarklet','_blank','menubar=no,height=390,width=600,toolbar=no,scrollbars=no,status=no,dialog=1');})();
+```
+
+<u>Important:</u> Replace the expression `[observer.baseurl]` with the URL of your hub, e.g. if you have your channel on Klackerhub, you simply have to enter `https://klacker.org` for `[observer.baseurl]`.
+
+Now you can add any website you visit to your channel's bookmarks by clicking on the bookmarklet.
diff --git a/doc/en/member/calendar.md b/doc/en/member/calendar.md
new file mode 100644
index 000000000..648dc9f83
--- /dev/null
+++ b/doc/en/member/calendar.md
@@ -0,0 +1,22 @@
+## Calendar
+
+The ‘Calendar’ app can be used to manage appointments.
+After opening the app, a calendar overview (one month) is displayed.
+
+![cal 01](/help/en/member/pic/cal01.png)
+
+Clicking on a day allows you to create an event. In the input mask (shortened view, can be expanded by clicking on ‘more’ ) you can now enter the essential contents.
+
+![cal 02](/help/en/member/pic/cal02.png)
+
+![cal 03](/help/en/member/pic/cal03.png)
+
+You may also define detailed [permissions](permissions_content.md) for these entries, so that you can record private appointments and public/shared appointments in one and the same calendar.
+
+![cal 04](/help/en/member/pic/cal04.png)
+
+### CalDAV access with Android
+
+You can synchronise your Android calendar with your hub.
+Use the ‘URL’ and ‘Username’ to log in. The base URL is `<your-hub-URL>/cdav`, and the username is your channel name (without the leading ‘@’ and without the hub address ‘`@<your-hub>`’).
+To share your calendar, visit `<your-hub-URL>/cdav/calendar`.
diff --git a/doc/en/member/channel_locations.md b/doc/en/member/channel_locations.md
new file mode 100644
index 000000000..5e17b38ca
--- /dev/null
+++ b/doc/en/member/channel_locations.md
@@ -0,0 +1,6 @@
+#### Manage Channel locations
+
+If the current channel has clones on other hubs, the menu will show an additional entry, ‘Manage Channel locations’. This allows you to specify the hub on which the main channel (primary channel) is located (this also determines the part of the handle after the ‘@’).
+You can also delete clones from here. However, for channels on third-party servers, it is recommended that you delete the channel on the actual hub. Deleting from the clone management should only be used if the clone's hub no longer exists.
+
+![settings 11](/help/en/member/pic/settings11.png)
diff --git a/doc/en/member/channel_roles.md b/doc/en/member/channel_roles.md
new file mode 100644
index 000000000..85852e8a0
--- /dev/null
+++ b/doc/en/member/channel_roles.md
@@ -0,0 +1,41 @@
+### Channel roles
+
+When you create a new channel, you will be asked to select an permission role depending on how you want to use this channel. The most popular permission roles are the social network roles. You have many more choices comparable to Facebook groups and pages, collaborative spaces, news feeds and more. These roles automatically configure various system variables, from the permissions granted to friends to the default privacy and visibility settings. Advanced configurations are available to customise each of these parameters to your needs, but our experience has been that most users prefer to set it and forget it. Below are some of the different roles that are currently available and how they affect your privacy and interaction options.
+
+**There are four channel roles:**
+
+- Public
+- Personal
+- Community Forum
+- Customised
+
+#### Public
+
+The channel is a very permissive social network profile that is compatible with other federated social networks. Privacy is a lower priority than ease of access and connection with others. Anyone on the network can comment on your public posts and send you private messages. By default, posts and published articles are public, but you can override and restrict this when you create the article. You are listed in the directory. Your online presence and connections are visible to others. This mode can increase your susceptibility to unsolicited messages and spam. The ‘classic’ social media account.
+
+#### Personal
+
+By default, posts and published items are public, but you can override and restrict this when creating the item. You are listed in the directory. Your online presence and connections are visible to others. Only your direct connections can comment on your public posts and send you private messages.
+
+#### Community Forum
+
+The channel is a typical forum. By default, posts and published articles are public. Members can post articles via !mention or wall-to-wall. The posting of photos and other published articles is blocked. The channel is visible in the directory. Members are added automatically.
+
+In order to be able to view media as a forum user, some of which may have restricted authorisations, it is necessary to activate the option ‘Enable OCAP access’ in your own user channel under Settings → Privacy settings (`<hub>/settings/privacy`).
+
+#### Custom
+
+This is the most precise setting for channel rights. All rights can be set in fine granularity. Caution: If you select the wrong settings here, you can render your channel unusable. Fortunately, the rights can also be changed again so that such malfunctions can be rectified. It makes sense to consider the effects of each individual permission for yourself as the channel owner, but also for other users.
+
+**The following settings are possible for each control point:**
+
+- Only me
+- Only those you explicitly authorise
+- Accepted connections
+- Any connections
+- Everyone on this website
+- All Hubzilla members
+- Anyone authenticated
+- Anyone on the Internet
+
+To edit the **custom role**, select ‘Privacy settings’ in the settings. At the bottom right you will find the button ‘Custom channel role configuration’. If you click on it, a warning dialogue will appear, drawing your attention to the risks of incorrect configuration. If you confirm that you want to edit the rights, the settings dialogue for the user-defined role rights opens.
diff --git a/doc/en/member/channel_settings.md b/doc/en/member/channel_settings.md
new file mode 100644
index 000000000..e0f07d42f
--- /dev/null
+++ b/doc/en/member/channel_settings.md
@@ -0,0 +1,15 @@
+#### Channel settings
+
+If you access the settings via the menu item, the channel settings are displayed by default. The basic settings are used to set the properties and functions of the currently selected (used) channel. In addition to the channel role, you can also define the standard for automatically created directories in the cloud (these are generated, for example, when you upload an image as an attachment in a post).
+
+It is also possible to delete the channel (red ‘Delete channel’ button).
+
+<u>Important note:</u> It is not possible to create a new channel on this hub under the name of the deleted channel (not even by cloning). This is to protect against ‘identity misuse’. If you still want to reinstall a channel with this name for important reasons, you can contact the admin of the hub, explain the reasons (so that they can be sure that you have a legitimate interest) and ask them to remove the block. Only he can do this with a few simple steps in the database.
+
+You can also set the expiry period for imported content from other channels and control this import using two filters.
+
+![settings 04](/help/en/member/pic/settings04.png)
+
+The notification settings allow you to specify exactly whether you want to be notified of certain events and actions. And whether you also want the notification to be sent by email.
+
+![settings 05](/help/en/member/pic/settings05.png)
diff --git a/doc/en/member/channels.md b/doc/en/member/channels.md
new file mode 100644
index 000000000..e1b123ee4
--- /dev/null
+++ b/doc/en/member/channels.md
@@ -0,0 +1,15 @@
+### Channels
+
+Channels are simply collections of content that are stored in one place. A channel can stand for anything. It can represent you, a website, a forum, photo albums, anything. For most people, their first channel is ‘Me’.
+The most important functions for a channel that represents ‘me’ are:
+
+- Secure and private ‘spam-free’ communication
+- Identity and ‘single sign-on’ across the entire network
+- Privacy controls and authorisations that extend to the entire network
+- Directory services (like a phone book)
+
+In short, a channel that represents you is ‘me on the Internet’.
+With one account at a hub, several different channels can be created and used, each with its own individual configuration.
+
+#include doc/en/member/create_channels.md;
+#include doc/en/member/channel_roles.md;
diff --git a/doc/en/member/chat_rooms.md b/doc/en/member/chat_rooms.md
new file mode 100644
index 000000000..6134bfb39
--- /dev/null
+++ b/doc/en/member/chat_rooms.md
@@ -0,0 +1,13 @@
+## Chat Rooms
+
+The ‘Chat Rooms’ app allows you to set up chat rooms for instant messaging within a hub and to chat with other channels within the hub.
+
+If you access the app from the app menu, your own chat rooms will be displayed.
+To create a new chat room, click on the ‘Add Room’ button.
+
+![chat 01](/help/en/member/pic/chat01.png)
+
+Here you have to give the chat a name and you can choose how many minutes the chat content will expire after. It is also possible to use the [privacy tool](permissions_content.md) (🔒) to determine who is authorised to see and use the chat room.
+The URL to the chat room is displayed in the browser and is also available via the link to the chat room in the left sidebar. Now you can share the URL with another user in your hub and start a chat with them.
+
+![chat 02](/help/en/member/pic/chat02.png)
diff --git a/doc/en/member/clone.md b/doc/en/member/clone.md
new file mode 100644
index 000000000..27acbfee7
--- /dev/null
+++ b/doc/en/member/clone.md
@@ -0,0 +1,47 @@
+## Clone
+
+Hubzilla channels have a so-called ‘nomadic identity’. This is a speciality of the Nomad protocol, on which Hubzilla is based and with which Hubzilla hubs communicate with each other.
+
+The nomadic identity makes it possible to create clones of your own channel, which greatly increases your resistance to censorship and outages.
+
+If you have cloned your channel, it is no problem if your ‘home hub’ fails or does not work correctly. You can seamlessly continue to participate in the Fediverse with a channel clone located on another hub.
+All channel clones are automatically synchronised in the background.
+
+To create a clone of your channel, you need an account on another Hubzilla hub.
+There are now several ways to create a clone of your channel on this other hub.
+
+You can use the ‘Channel Export’ app to export the channel on your ‘home hub’.
+
+![clone 01](/help/en/member/pic/clone01.png)
+
+By clicking on the ‘Export channel’ button, you can export your identity and your social graph to a file that you can download.
+Since files, websites, wikis, calendars and chat rooms are always restricted to your own hub (i.e. the respective server), you can also use the ‘Channel export’ app to download archives of this data locally.
+Now log in to the new hub and either select the ‘Channels’ menu item in the main menu (your profile picture; top left) and then the ‘+ Create new’ button on the channel selection page that appears, or go directly to the channel creation page at `<URL-of-your-hub>/new_channel`.
+
+![clone 02](/help/en/member/pic/clone02.png)
+
+On this page, however, you do not enter any information for creating a new channel, but instead select the link ‘import an existing channel from another server’ at the bottom of the dialogue.
+
+![clone 03](/help/en/member/pic/clone03.png)
+
+The dialogue for importing the channel now opens.
+
+![clone 04](/help/en/member/pic/clone04.png)
+
+Click on the ‘File to upload: Browse...’ button to open a file dialogue where you can select the previously saved channel file.
+
+As an alternative to this method, you can also clone your channel directly from the source, i.e. the originating hub. To do this, you must enter the handle of the channel to be cloned, the e-mail address for logging in to the source hub, and the corresponding password in the channel import dialogue. In addition, you can use a switch to select whether files and objects from the source hub should also be imported (provided that your new hub allows this and the storage limit is sufficient).
+
+In the dialogue, you can also specify whether the new hub should be your ‘primary hub’. This means that the new channel (the one you are now creating) will be your primary channel. This affects the handle of your channel, which will now end with the URL of the new hub.
+
+As a rule, unless you want to move completely to a different hub, you leave the switch at ‘No’ and the primary hub remains the one it currently is (the handle remains unchanged).
+
+If you would like a different channel name (short name), you can enter it in an additional input field. If you leave the field empty, the channel short name remains unchanged.
+
+*Note: If the channel short name is already in use on the new hub (or has been blocked because an identical channel already existed there but was deleted), the system will automatically modify the short name.*
+
+Finally, click on ‘Submit’ and do NOT leave the page until the import is complete. Depending on the size of the source channel, this may take some time.
+
+You can manage your clones at any time via Settings → Manage clone addresses. You can define which is the ‘primary hub’ and you can delete clones, although it is recommended that you delete cloned channels directly on the respective hub.
+
+![clone 05](/help/en/member/pic/clone05.png)
diff --git a/doc/en/member/cloud_storage.md b/doc/en/member/cloud_storage.md
new file mode 100644
index 000000000..8b9ce0046
--- /dev/null
+++ b/doc/en/member/cloud_storage.md
@@ -0,0 +1,8 @@
+### Cloud storage
+
+Your files are visible to everyone who is allowed to view them on the internet at `<URL-of-your-hub>/cloud/<your-channel-name>`. If the viewer has sufficient rights, they can also create new files and folders/directories. This option should only be used for smaller files and photos (up to a few megabytes), as it uses the internal memory. Please use WebDAV to upload larger files (videos, music, etc.). These files can still be accessed via the web access.
+
+With WebDAV, you can copy files directly into or out of your computer's operating system, with your cloud files appearing like a virtual drive. This should be used to upload large files such as video and audio files.
+The URL for the cloud directory is `<URL-of-your-hub>/dav/`.
+
+If, depending on the DAV file system integration (depending on the operating system used and possibly the application), a username is required, this is the channel short name (i.e. without the leading ‘@’ and without the following ‘@hub address’). A possibly required password corresponds to your login password.
diff --git a/doc/en/member/comanche.md b/doc/en/member/comanche.md
new file mode 100644
index 000000000..57aa19902
--- /dev/null
+++ b/doc/en/member/comanche.md
@@ -0,0 +1,255 @@
+## Comanche page description language
+
+Comanche is a BBCode-like markup language that can be used to create elaborate and complex web pages by assembling them from a series of components, some of which are pre-built and others that can be defined on the fly. Comanche uses a page description language to create these pages.
+Comanche primarily selects which content should appear in the various areas of the page. The various areas have names, and these names may change depending on the layout template selected.
+
+### Page templates
+
+There are currently five layout templates, unless your website offers additional layouts.
+
+**Standard template**
+
+The default template defines a ‘nav’ area at the top, ‘aside’ as a sidebar with a fixed width, ‘content’ for the main content area and ‘footer’ for a page footer.
+
+**Full template**
+
+The full template corresponds to the default template except that there is no ‘aside’ area.
+
+**Choklet**
+
+The Choklet template offers a range of fluid layout styles that can be set to taste:
+
+- (default flavour) - a two-column layout similar to the default template, but more flexible
+- bannertwo - a two-column layout with a banner area, compatible with the default template on small displays
+- three - three-column layout (adds a ‘right_aside’ area to the standard template)
+- edgestwo - two-column layout with fixed margins
+- edgesthree - three-column layout with fixed margins
+- full - three-column layout with fixed margins and the addition of a ‘header’ area below the navigation bar
+
+**Redable**
+
+A template for reading longer texts in full screen mode (i.e. without a navigation bar). Three columns: aside, content and right_aside.
+For maximum readability, it is advisable to use only the middle content column.
+
+**Zen**
+
+Gives you the freedom to do everything yourself. Just a blank page with a content area.
+To select a layout template, use the ‘template’ tag.
+
+```
+[template]full[/template]
+```
+
+To select the template ‘choklet’ with the flavour ‘three’:
+
+```
+[template=three]choklet[/template]
+```
+
+The default template is used if no other template is specified. The template can use arbitrary names for the content regions. You will use ‘region’ tags to decide what content should be placed in which regions.
+Three ‘macros’ have been defined for your use.
+
+```
+$htmlhead - replaced with the site head content.
+$nav - replaced with the site navigation bar content.
+$content - replaced with the main page content.
+```
+
+By default, `$nav` is inserted into the ‘nav’ page area and `$content` into the ‘content’ area. You only need to use these macros if you want to change the order of the elements or move them to other areas.
+To select a theme for your page, use the ‘theme’ tag.
+
+```
+[theme]suckerberg[/theme]
+```
+
+This selects the theme ‘suckerberg’. By default, the theme preferred by your channel is used.
+
+```
+[theme=passion]suckerberg[/theme]
+```
+
+This selects the theme named ‘suckerberg’ and chooses the ‘passion’ scheme (theme variant). Alternatively, it is also possible to use compressed theme notation.
+
+```
+[theme]suckerberg:passion[/theme]
+```
+
+The compressed notation is not part of Comanche itself, but it is recognised by the Hubzilla platform as a theme specifier.
+
+**Navbar**
+
+```
+[navbar]tucson[/navbar]
+```
+
+Use the ‘tucson’ template for the navigation bar and CSS rules. By default, the ‘default’ template is used for the navigation bar.
+
+**Regions**
+
+Each region has a name, as mentioned above. You specify the region you are interested in with a ‘region’ tag containing the name. Any content you want to place in that region should be placed between the opening region tag and the closing tag.
+
+```
+[region=htmlhead]....content goes here....[/region]
+[region=aside]....content goes here....[/region]
+[region=nav]....content goes here....[/region]
+[region=content]....content goes here....[/region]
+```
+
+**CSS and Javascript**
+
+We have the option of including Javascript and CSS libraries in the htmlhead section. Currently we use jquery (js), bootstrap (css/js) and foundation (css/js).
+This overwrites the htmlhead of the selected theme.
+
+```
+[region=htmlhead]
+ [css]bootstrap[/css]
+ [js]jquery[/js]
+ [js]bootstrap[/js]
+[/region]
+```
+
+**Menus and blocks**
+
+The website creation tools allow you to create menus and blocks in addition to page content. These provide a set of existing content that can be placed in the areas and order you specify. Each of these elements has a name that you set when you create the menu or block.
+
+```
+[menu]mymenu[/menu]
+```
+
+This places the menu ‘mymenu’ at this point on the page, which must be within an area.
+
+```
+[menu=horizontal]mymenu[/menu]
+```
+
+This places the menu named ‘mymenu’ at this point on the page, which must be within an area. It also assigns the class ‘horizontal’ to the menu. The class ‘horizontal’ is defined in the redbasic theme. It may or may not be available in other themes.
+
+```
+[menu][var=wrap]none[/var]mymenu[/menu]
+```
+
+The `[var=wrap]none[/var]` variable in a block removes the enclosing div element from the menu.
+
+```
+[block]contributors[/block]
+```
+
+This places a block named ‘contributors’ in this region.
+
+```
+[block=someclass]contributors[/block]
+```
+
+This places a block named ‘contributors’ in this region. In addition, the class ‘someclass’ is applied to the block. This replaces the default block classes ‘bblock widget’.
+
+```
+[block][var=wrap]none[/var]contributors[/block]
+```
+
+The variable `[var=wrap]none[/var]` in a block removes the enclosing div element from the block.
+
+**Widgets**
+
+Widgets are executable applications provided by the system that you can place on your page. Some widgets require arguments that you can use to customise the widget to your purpose. System widgets are listed here. Widgets can also be created by plugins, themes or your website administrator to provide additional functions.
+Widgets and arguments are specified with the tags ‘widget’ and ‘var’.
+
+```
+[widget=recent_visitors][var=count]24[/var][/widget]
+```
+
+This loads the ‘recent_visitors’ widget and sets the ‘count’ argument to ‘24’.
+
+**Comments**
+
+The ‘comment’ tag is used to delimit comments. These comments are not displayed on the rendered page.
+
+```
+[comment]This is a comment[/comment]
+```
+
+**Conditional execution**
+
+You can use an ‘if’ construct to make decisions. These are currently based on the system configuration variable or the current observer.
+
+```
+[if $config.system.foo]
+ ... the configuration variable system.foo evaluates to ‘true’.
+[else]
+ ... the configuration variable system.foo evaluates to ‘false’.
+[/if]
+
+[if $observer]
+ ... this content will only be show to authenticated viewers
+[/if]
+```
+
+The ‘else’ clause is optional.
+In addition to the Boolean evaluation, several tests are supported.
+
+```
+[if $config.system.foo == bar]
+ ... the configuration variable system.foo is equal to the string ‘bar’
+[/if]
+[if $config.system.foo != bar]
+ ... the configuration variable system.foo is not equal to the string ‘bar’
+[/if]
+[if $config.system.foo {} bar ]...
+ the configuration variable system.foo is a simple array containing a value ‘bar’
+[/if]
+[if $config.system.foo {*} bar]...
+ the configuration variable system.foo is a simple array containing a key named ‘bar’
+[/if]
+```
+
+**Complex example**
+
+```
+[comment]use an existing page template which provides a banner region plus 3 columns beneath it[/comment]
+
+[template]3-column-with-header[/template]
+
+[comment]Use the "darknight" theme[/comment]
+
+[theme]darkknight[/theme]
+
+[comment]Use the existing site navigation menu[/comment]
+
+[region=nav]$nav[/region]
+
+[region=side]
+
+ [comment]Use my chosen menu and a couple of widgets[/comment]
+
+ [menu]myfavouritemenu[/menu]
+
+ [widget=recent_visitors]
+ [var=count]24[/var]
+ [var=names_only]1[/var]
+ [/widget]
+
+ [widget=tagcloud][/widget]
+ [block]donate[/block]
+
+[/region]
+
+
+
+[region=middle]
+
+ [comment]Show the normal page content[/comment]
+
+ $content
+
+[/region]
+
+
+
+[region=right]
+
+ [comment]Show my condensed channel "wall" feed and allow interaction if the observer is allowed to interact[/comment]
+
+ [widget]channel[/widget]
+
+[/region]
+```
+
diff --git a/doc/en/member/commenting.md b/doc/en/member/commenting.md
new file mode 100644
index 000000000..f41ad37e7
--- /dev/null
+++ b/doc/en/member/commenting.md
@@ -0,0 +1,8 @@
+### Commenting
+
+If you want to comment on a post, i.e. reply to it, click in the field at the bottom of the post (‘Comment’). This opens the comment editor, which is similar to the post editor. However, there are no fields for a title, a summary or categories. Below the input field for the content of the comment there are again buttons for certain formatting (not all that are available in the post editor, because not everything is possible in a comment) and on the right again a button for a preview, as well as a ‘Submit’ button to publish the comment.
+
+![Comment](/help/en/member/pic/comment01.png)
+
+You can also mark up the text in the comment content field with Markdown, bbCode and HTML.
+As there is no field for a summary, it is not possible to use this for a content warning regarding the comment. However, this can be achieved using bbCode by inserting the content warning in `[summary][/summary]`. This initially hides all of the following content, which can then be displayed by clicking on it.
diff --git a/doc/en/member/connecting_with_channels.md b/doc/en/member/connecting_with_channels.md
new file mode 100644
index 000000000..323a2af88
--- /dev/null
+++ b/doc/en/member/connecting_with_channels.md
@@ -0,0 +1,31 @@
+### Connecting with channels
+
+Connections in Hubzilla can have many different meanings. A connection is more precisely defined as a set of permissions that you have granted to another person. In traditional social networks, all connections are given the same permissions or at most two levels (friends and ‘followers’). In Hubzilla, a separate set of permissions can be set/customised depending on the situation and the relationship you have with the other channel. You can allow someone to see your posts, but not your photos. You can also deny them permission to comment on your posts or send private messages to you. But let's make it simple: you want to be friends with someone you know from social networks. How do you do that?
+
+You can view the directory. The directory is available on all Hubzilla sites, so if you search from your own site, you'll get results from across the network. You can search by name, interest, location and keyword.
+If you already know someone's ‘handle’, you can contact them directly. A handle looks just like an email address (e.g. `bob@example.com)` but refers to a person in the open social network. In order to establish a connection, a compatible network protocol must be used. By default, this software supports the Nomad protocol, but other protocols can be provided via plugins/add-ons. For more information on connecting to channels on other networks, see below.
+
+#### How to connect to other Hubzilla channels:
+
+Visit the desired channel's profile by clicking on their photo in the directory, stream or comments and it will open their channel homepage in the channel viewer. On the left side of the screen you will normally see a link labelled ‘Connect’. Click on it and you're done. Depending on the settings of the channel you want to connect to, you may have to wait for the channel to approve your connection, but no further action is required on your part. Once you have initiated the connection, you will be redirected to the connection editor. Here you can assign specific authorisations for this channel if you want to make changes.
+
+You can also create a connection to any channel by going to the ‘Connections’ page of your website or directory and entering the ‘Handle’ in the ‘Add new connection’ field. Use this method if someone tells you their handle and you want to connect to them. The process is the same as when connecting via the ‘Connect’ button - you will then be redirected to the connection editor to set the authorisations.
+
+#### This is how you establish a connection to channels in other networks:
+
+The process for connecting to ‘channels’ on other networks (such as GNU Social, Mastodon, Misskey, Pleroma and Diaspora) is similar - enter their ‘handle’ in the ‘+Add’ field on the ‘Connections’ page. However, before you do this, please visit the App Management in the app menu and make sure that the appropriate protocol (Diaspora, GNU-Social/OStatus or ActivityPub) is deployed in your hub and ***enabled*** **for your channel**. These networks/protocols do not support account migration and location independence. So if you change location or clone your channel elsewhere, communication with these connections may fail. For this reason, these protocols are not enabled by default, but only with your consent. Enabling these protocols is an important decision between communicating with friends on these networks and account resilience in case your server goes down.
+
+Some communication networks offer more than one protocol. For example, you can connect to someone who uses both the ‘ostatus’ and ‘activitypub’ protocols for communication. In general, the ‘activitypub’ protocol provides a better experience than the ‘ostatus’ protocol, but Hubzilla often chooses the first protocol it detects, and that may not be what you want. You can connect to someone using a specific protocol by putting the protocol name in square brackets before their ‘handle’. For example
+
+`[activitypub]https://foo.bar/foobar`
+
+`[ostatus]foobar@foo.bar`
+
+`[diaspora]foobar@foo.bar[zot]foobar@foo.bar`
+
+`[feed]https://foo.bar/foobar`
+
+#### How to connect to RSS feeds:
+
+Your hub administrator can allow you to connect to RSS feeds. The procedure for connecting to an RSS feed is the same, except that you enter (or paste) the URL of the feed into the ‘Add new connection’ field. The options may be restricted by your hub administrator because connections to feeds can sometimes cause high system loads.
+
diff --git a/doc/en/member/connection_editor.md b/doc/en/member/connection_editor.md
new file mode 100644
index 000000000..8a8fdde23
--- /dev/null
+++ b/doc/en/member/connection_editor.md
@@ -0,0 +1,31 @@
+### Connection editor
+
+If you click on the ‘Edit’ button for a contact in the ‘Connections’ app, the connection editor opens.
+You can use the editor to assign a specific contact role to a contact. If required, you can use the ‘Contact Roles’ button to display the existing roles and also create new roles. Another button (Compare authorisations) allows you to compare the assigned authorisations with those of the standard authorisation role.
+
+![conn 02](/help/en/member/pic/conn02.png)
+
+You can use the ‘Privacy groups’ tab to assign a contact to one or more privacy groups.
+
+![conn 03](/help/en/member/pic/conn03.png)
+
+You can use the ‘Profiles’ tab to specify which of your profiles (if you have created several) is displayed to the contact.
+
+![conn 04](/help/en/member/pic/conn04.png)
+
+With the content filters, you can filter out posts from a contact with certain content or use filters to specify that only posts with defined content end up in the stream.
+
+![conn 05](/help/en/member/pic/conn05.png)
+
+You can use the contact tools to
+
+- block the contact,
+- ignore,
+- archive,
+- hide
+
+or
+
+- delete.
+
+![conn 06](/help/en/member/pic/conn06.png)
diff --git a/doc/en/member/connections.md b/doc/en/member/connections.md
new file mode 100644
index 000000000..5176836ff
--- /dev/null
+++ b/doc/en/member/connections.md
@@ -0,0 +1,23 @@
+## Connections
+
+You can use the ‘Connections’ app to display all your connections.
+
+![conn 01](/help/en/member/pic/conn01.png)
+
+In the overview, for each connection
+
+- the channel name
+- the date of the connection
+- the channel address (handle)
+- the network of the contact (ActivityPub, Zot (Nomad), RSS, Diaspora...)
+ - you can use a filter next to it to display the channel's recent activities in the stream
+- the profile picture
+- and a coloured dot (traffic light colours) indicates the rights granted by the contact (if you rest the mouse pointer over the coloured dot, the rights granted are displayed)
+
+can be seen.
+
+A label is also displayed which shows the type of connection or warns that (in the case of clones) there is no connection at this location.
+There is an ‘Edit’ button on the right of each contact entry, with which you can edit the connection using the connection editor.
+
+#include doc/en/member/connection_editor.md;
+#include doc/en/member/diaspora_compat.md;
diff --git a/doc/en/member/conversation_features.md b/doc/en/member/conversation_features.md
new file mode 100644
index 000000000..991d4e60e
--- /dev/null
+++ b/doc/en/member/conversation_features.md
@@ -0,0 +1,8 @@
+### Conversation features
+
+You can use this dialogue to define certain conversation features for a post:
+
+- Allow emoji reactions
+- Allow dislikes
+- Allow local marking (asterisks)
+- Allow replies to comments
diff --git a/doc/en/member/create_channels.md b/doc/en/member/create_channels.md
new file mode 100644
index 000000000..7792b75a7
--- /dev/null
+++ b/doc/en/member/create_channels.md
@@ -0,0 +1,9 @@
+#### Create channels
+
+Once you have created your account, you will be presented with the ‘Add Channel’ screen. Normally, your first channel will be one that represents you - so it's a good idea to use your own name (or a pseudonym) as your channel name. The channel name should be considered the title or short description of your channel. The ‘Choose a short nickname’ field is similar to a ‘Username’. With what you enter here, you create a channel address (also known as a ‘handle’ in Fediverse) that other people can use to connect to you and that you can use to log in to other websites. This address looks like an email address and has the form `<nickname>@<your_hub>.`
+
+**Note**: ***In other Fediverse services, the handle is preceded by an ‘@’.*** ***With Hubzilla, this character must be omitted if you want to connect to another user or search for a handle, as an example.***
+
+You can create additional channels via the ‘Channel manager’ link.
+
+As soon as you have done this, your channel is ready for use. Under `<your_hub>/channel/<nickname>` you will find your channel ‘Stream’. Your most recent activities are displayed here in reverse chronological order.
diff --git a/doc/en/member/delete.md b/doc/en/member/delete.md
new file mode 100644
index 000000000..1da26a5e2
--- /dev/null
+++ b/doc/en/member/delete.md
@@ -0,0 +1,3 @@
+### Delete
+
+This function can be used to delete posts from the stream. A normal user does not have this option in the global stream. Only an administrator can remove posts from this stream.
diff --git a/doc/en/member/delete_account.md b/doc/en/member/delete_account.md
new file mode 100644
index 000000000..eaaebe661
--- /dev/null
+++ b/doc/en/member/delete_account.md
@@ -0,0 +1,7 @@
+## Deleting your account
+
+If you wish to delete your account, i.e. all access to the hub, use the settings in the main menu (top left; profile picture): Settings → Account settings.
+
+At the top of the page, you will see a button labelled ‘Remove account’. Click on it and your account (account) will be deleted, including all content, after you enter your account password (for security purposes).
+
+![delacc 01](/help/en/member/pic/delacc01.png)
diff --git a/doc/en/member/deleting_channel.md b/doc/en/member/deleting_channel.md
new file mode 100644
index 000000000..fa8bb0892
--- /dev/null
+++ b/doc/en/member/deleting_channel.md
@@ -0,0 +1,9 @@
+## Deleting a channel
+
+If you want to delete your channel, use the settings in the main menu (top left; profile picture): Settings → Channel settings.
+At the top of the page, you will see a button labelled ‘Remove channel’. Click on it and your channel, including all content, will be deleted after you enter your account password (for security purposes).
+
+![delchan 01](/help/en/member/pic/delchan01.png)
+
+***Note:*** It is no longer possible to create a new channel with the same name on this hub. This is because the channel nickname is locked in the database to prevent impersonation. However, if you need to restore the old channel (by cloning) on the hub, please ask the administrator to delete the locked nickname from the Hubzilla database.
+
diff --git a/doc/en/member/diaspora_compat.md b/doc/en/member/diaspora_compat.md
new file mode 100644
index 000000000..ec8398d70
--- /dev/null
+++ b/doc/en/member/diaspora_compat.md
@@ -0,0 +1,37 @@
+### Diaspora Compatibility
+
+The Diaspora Protocol addon allows a site to communicate using the Diaspora protocol, which allows communications and connections to be made with Diaspora members (and also Friendica members, since that network also provides the Diaspora Protocol).
+
+This addon is available in the 'basic' and 'standard' server configurations. It is not available with and the plugin is disabled completely when you are using the 'pro' server configuration. The reason for this is that the Diaspora protocol is not very sophisticated and many $projectname features do not work well with it.
+
+Members will have to be aware of limitations of the protocol or limit their own activities to those which are compatible with Diaspora. The 'pro' server configuration is free from these limitations and you may use all of the project features and abilities without regard for how they translate to other networks. Many features are unique to Hubzilla and are supported by the Nomad protocol, which is our native communications language between servers/hubs.
+
+If you are using a configuration which allows direct Diaspora communications you should be aware of the limitations presented here.
+
+- Private mail retraction (unsend) is not possible for Diaspora connections.
+- Private posts and their associated comments are sent in plaintext email notifications in Diaspora and Friendica. This is a major privacy issue and affects any private communications you have where *any* member of the conversation is on another network. Be aware of it.
+- Access control only works on posts and comments. Diaspora members will get permission denied trying to access any other access controlled hubzilla objects such as files, photos, webpages, chatrooms, etc. In the case of private photos that are linked to posts, they will see a "prohibited sign" instead of the photo. Diaspora has no concept of private media and provides an illusion of photo privacy by using obscured URLs rather than protecting the photo from snooping by unauthorised viewers.
+
+There is no workaround except to make your media resources public (to everybody on the internet).
+
+- Edited posts will not be delivered. Diaspora members will see the original post/comment without edits. There is no mechanism in the protocol to update an existing post. We cannot delete it and submit another invisibly because the message-id will change and we need to keep the same message-id on our own network. The only workaround is to delete the post/comment and do it over. (If this is a post, this will delete any existing likes/comments). We may eventually provide a way to delete the out of date copy only from Diaspora and keep it intact on networks that can handle edits.
+- Nomadic identity ($projectname 'standard' only) will not work with Diaspora. We may eventually provide an **option** which will allow you to "start sharing" from all of your clones when you make the first connection. The Diaspora person does not have to accept this, but it will allow your communications to continue if they accept this connection. Without this option, if you go to another server from where you made the connection originally or you make the connection before creating the clone, you will need to connect with them again from the new location.
+- Post expiration is not supported on Diaspora. We may provide you an option to not send expiring posts to that network. In the future this may be provided with a remote delete request.
+- End-to-end encryption is not supported. We will translate these posts into a lock icon, which can never be unlocked from the Diaspora side.
+- Message verification will eventually be supported.
+- Multiple profiles are not supported. Diaspora members can only see your default profile.
+- Birthday events will not appear in Diaspora. Other events will be translated and sent as a post, but all times will either be in the origination channel's timezone or in GMT. We do not know the recipient's timezone because Diaspora doesn't have this concept.
+- We currently allow tags to be hijacked by default. An option is provided to allow you to prevent the other end of the network from hijacking your tags and point them at its own resources.
+- Community tags will not work. We will send a tagging activity as a comment. It won't do anything.
+- Privacy tags (@!somebody) will not be available to Diaspora members. These tags may have to be stripped or obscured to prevent them from being hijacked - which could result in privacy issues.
+- Plus-tagged hubzilla forums should work from Diaspora.
+- You cannot use Diaspora channels as channel sources.
+- Dislikes of posts will be converted to comments and you will have the option to send these as comments or not send them to Diaspora (which does not provide dislike). Currently they are not sent.
+- We will do the same for both likes and dislikes of ***comments***. They can either be sent as comments or you will have the ability to prevent them from being transmitted to Diaspora. Currently they are not sent.
+- Emojis are currently untranslated.
+- "observer tags" will be converted to empty text.
+- Embedded apps will be translated into links.
+- Embedded page design elements (work in progress) will be either stripped or converted to an error message.
+- Diaspora members will not appear in the directory.
+- There are differences in oembed compatibility between the networks. Some embedded resources will turn into a link on one side or the other.
+
diff --git a/doc/en/member/direct_messages.md b/doc/en/member/direct_messages.md
new file mode 100644
index 000000000..233d7864b
--- /dev/null
+++ b/doc/en/member/direct_messages.md
@@ -0,0 +1,9 @@
+## Direct messages
+
+Direct messages are messages that are addressed to one or more individual connections. They are accessible via the network stream. A filter for direct messages has been added to the stream filter widget for quick access.
+
+If you want to send a direct message to one (or more) other users (direct messages can only be read by the recipients and the sender), you write a normal post and address it exclusively to the recipient(s) using a special mention. This is done with the private mention (privacy tag). A privacy tag is a name that is preceded by the two characters `@!` and which, in addition to marking these channels, also changes the data protection authorisations of the post so that only these are taken into account.
+
+You do not need to use a privacy tag to reply to a DN ‘privately’, i.e. as a DN. You simply reply directly to the incoming DN, which distributes the reply to all original recipients.
+
+As an alternative to the privacy tag, you can also select channels or privacy groups from the [privacy tool](permissions_content.md) (🔒). This is the more complicated way, but it also works. However, the use of a privacy tag overwrites any selection made in the privacy tool. So if you write a post that is to be sent as a direct message, you can omit the privacy tag and instead click on the padlock symbol next to the ‘Share’ button to access the authorisation settings.
diff --git a/doc/en/member/directory.md b/doc/en/member/directory.md
new file mode 100644
index 000000000..8871e6c3f
--- /dev/null
+++ b/doc/en/member/directory.md
@@ -0,0 +1,15 @@
+## The Directory
+
+Hubzilla offers a channel directory via the app (in the app menu ⋮). The Hubzilla Grid channels are listed in the directory.
+
+![directory](/help/en/member/pic/directory.png)
+
+The directory options in the left sidebar can be used to filter the scope of the directory. For example, you can restrict the listing to channels from your own hub.
+
+There is also a keyword cloud in the left sidebar, which you can use to find channels with corresponding interests/focal points.
+
+There is also a search field in the left sidebar to find channels by name / name components or interests (keywords).
+
+You can then connect directly to a channel found by clicking on the corresponding button.
+
+#include doc/en/member/AdvancedSearch.md;
diff --git a/doc/en/member/display_settings.md b/doc/en/member/display_settings.md
new file mode 100644
index 000000000..a500a7864
--- /dev/null
+++ b/doc/en/member/display_settings.md
@@ -0,0 +1,18 @@
+#### Display settings
+
+The display settings can be used to customise the design of the channel. In addition, you can determine which content is displayed, within certain limits.
+In the design settings, you can select from the installed themes and set your design scheme for the topic.
+
+![settings 07](/help/en/member/pic/settings07.png)
+
+With the custom design settings, it is possible to adjust the colour scheme to your own preferences and to define corner rounding, standard sizes and standard dimensions for avatars. The default settings are initially displayed in simplified form, and only allow you to set the dark mode, choose a narrow navigation bar, set the width of the content area and adjust the font size.
+
+![settings 08](/help/en/member/pic/settings08.png)
+
+If you set the ‘Show advanced settings’ switch to ‘Yes’ and submit the selection, the advanced settings will be displayed, where you can set colours, avatar dimensions and background images.
+
+![settings 09](/help/en/member/pic/settings09.png)
+
+The content settings allow you to select various parameters (e.g. the time until the view is updated) and to switch off the display of the ‘links for new members’ that are displayed when new channels are created.
+
+![settings 10](/help/en/member/pic/settings10.png)
diff --git a/doc/en/member/encryption.md b/doc/en/member/encryption.md
new file mode 100644
index 000000000..68ef7532d
--- /dev/null
+++ b/doc/en/member/encryption.md
@@ -0,0 +1,18 @@
+## Builtin Automatic Encryption
+
+Full disclosure: The encryption hubzilla uses per default is not absolutely waterproof. There *are* known procedures to circumvent it. *But* this takes a lot of effort and needs to be done individually for each channel. And to make this clear: Other services store your messages in plaintext, therefore we regard this approach as a *significant* improvement for your privacy. Plus you are always free to use further encryption and password protection if you so desire.
+
+
+To explain this in more detail:
+
+- each channel has its key pair
+- every non-public post is automatically encrypted
+- optional password protect content via crypto-javascript browser-to-browser encryption (needs to be enabled in settings) Full disclosure: A rogue hub admin could injects malicious javascript-code (e.g. keylogging-abilities) into the code. Encrypt our stuff out of band with GPG, become a hub administrator yourself or use other means of communication if this worries you.
+
+So what is the scope of security? Full disclosure: This might be great, but it is not perfect.
+- every non-public post is automatically encrypted but persons who have access to the site's database and files *may* be able to decrypt everything by using these keys which obviously need to be stored on the server. To be clear: The encrypion keys are different for every channel and it is *quite an effort* to do this. And again: Other services store your messages in plain text unencrypted. So this *is* quite a significant win for your privacy.
+
+We believe that the NSA-level dragnet plaintext extracting mass surveillance is probably not possible due to the design of the Nomad protocol. Dedicated attacks including hacking into one hub to obtain the server logs and database only partly reveal what is going on between people communication between different hubs. We believe that this makes it much more expensive for state-level attackers to access your content in hubzilla.
+
+
+We gladly accept help improving the security of the system and auditing it as well.
diff --git a/doc/en/member/files.md b/doc/en/member/files.md
new file mode 100644
index 000000000..642884fd0
--- /dev/null
+++ b/doc/en/member/files.md
@@ -0,0 +1,28 @@
+## Files
+
+Hubzilla provides cloud functionality. This means that you have a directory for each channel in which you can create further subdirectories and store files. You can define precise access rights for each directory, and even for each individual file. These can range from visibility for the general public, to visibility for members of certain groups, to individual approval for individual members of your own connections. It is even possible to share files with people who do not have a Hubzilla identity. This is done using guest access tokens.
+
+Creating and deleting directories and creating and deleting files is really easy.
+You can access your cloud storage via the ‘app menu’ (⋮) → Files. You can also upload images via the files section, which is also possible via the ‘Photos’ section.
+
+The files can be displayed in a list view
+
+![files 01](/help/en/member/pic/files01.png)
+
+or in a tile view
+
+![files 02](/help/en/member/pic/files02.png)
+
+You can create new directories/folders with the ‘Create’ button. When creating a folder, you can also immediately define the [permissions for](permissions_content.md) the new folder (🔓).
+
+With the ‘+ Add files’ button, you can upload files to your cloud. Here, too, you can set the access permissions directly.
+
+To set or change access rights (permissions) for directories or files at a later time, click on the context menu (︙) of the file or directory. You can also perform various file operations here.
+
+![files 03](/help/en/member/pic/files03.png)
+
+If you attach a file using the ‘📎’ button when creating a post or comment, this file will be stored in a new directory (sample: `year-month`) in the cloud, if one is created.
+
+#include doc/en/member/photos.md;
+#include doc/en/member/gallery.md;
+#include doc/en/member/cloud_storage.md;
diff --git a/doc/en/member/follow_conversation.md b/doc/en/member/follow_conversation.md
new file mode 100644
index 000000000..55edff774
--- /dev/null
+++ b/doc/en/member/follow_conversation.md
@@ -0,0 +1,3 @@
+### Follow / unfollow conversation
+
+With Follow conversation / Unfollow conversation you can switch whether you want to follow a thread, i.e. whether you want to receive notifications about comments/replies, likes, dislikes, emoji reactions or that the post has been shared or re-shared.
diff --git a/doc/en/member/gallery.md b/doc/en/member/gallery.md
new file mode 100644
index 000000000..11cd31c7d
--- /dev/null
+++ b/doc/en/member/gallery.md
@@ -0,0 +1,6 @@
+### Gallery
+
+The ‘Gallery’ app is a simple photo gallery that displays all your images from the cloud storage.
+The displayed images are scaled appropriately, which can lead to a slightly blurred display for smaller images. It is more practical to use the ‘[Photos](fotos.md)’ app.
+
+![gallery 01](/help/en/member/pic/gal01.png)
diff --git a/doc/en/member/guest_access.md b/doc/en/member/guest_access.md
new file mode 100644
index 000000000..0efce5ab5
--- /dev/null
+++ b/doc/en/member/guest_access.md
@@ -0,0 +1,15 @@
+## Guest access
+
+If you would like to share private content (i.e. content that is not accessible to the public) with people who do not have a Hubzilla account, you have the option of realising this using guest access.
+
+With guest access, you create a (possibly temporary) access that enables the user who logs in with this data to access your publicly accessible content, but also non-public content that you specifically release for guest access.
+
+If you call up the ‘Guest access’ app, a web form is displayed with which you can set up such guest access. You enter a login name of your choice. Hubzilla has already automatically generated a password for the guest access. You can now give these two pieces of information to the person you want to give access to content.
+
+In the ‘Expires’ field, you can also enter an expiry date after which the guest access will be automatically deleted. If you leave this field empty, the guest access will be created without a time limit. It will then never expire automatically and may have to be deleted manually.
+
+You can also define a [contact role](/help/member/permissions_contact_roles.md) for the guest access.
+
+All guest accounts are listed in the left-hand sidebar. If you select a guest account there, you can edit or delete it again (even before the deadline expires).
+
+As soon as a guest account has been created, it also appears in the ‘[Authorisation settings](/help/member/permissions_content.md)’ (privacy tool) under ‘User-defined selection’. You can use this to explicitly authorise individual private content for the guest account (but of course also for other contacts) so that the guest can access the content.
diff --git a/doc/en/member/important_apps.md b/doc/en/member/important_apps.md
new file mode 100644
index 000000000..63214f2e7
--- /dev/null
+++ b/doc/en/member/important_apps.md
@@ -0,0 +1,16 @@
+### Important apps
+
+If you want to use your channel mainly for social networking, there are some apps that are not installed or activated by default, some of which are essential.
+
+To participate in the entire Fediverse, you need to install the ‘**ActivityPub Protocol**’ app.
+
+To make it easier to find worthwhile contacts and find out what is happening in the Fediverse, you can install and use the ‘**[Public stream](/help/en/member/public_stream.md)**’ app.
+
+The ‘[**NSFW**](inhaltswarnung_NSFW.md)’ app, with which you can create and use your own content warning filters, is also useful and recommended.
+
+You should also install the ‘[**Superblock**](superblock.md)’ app, which allows you to completely block certain users.
+The ‘[**Privacy Groups**](privacy_gruppen.md)’ app is also important. This makes it possible to create contact groups and filter them, as well as to communicate with the contacts in the groups.
+
+----
+
+**<u>Please note:</u>** The ‘Public stream’ app is only available for installation if the hub administrator has activated this stream.
diff --git a/doc/en/member/insert_images.md b/doc/en/member/insert_images.md
new file mode 100644
index 000000000..d8be83428
--- /dev/null
+++ b/doc/en/member/insert_images.md
@@ -0,0 +1,50 @@
+### Insert images
+
+There are various ways to insert images into posts and comments.
+
+##### In the post editor
+
+There are two buttons in the post editor that allow you to insert images: ‘Embed image’ and ‘Attach/upload file’.
+
+With ‘Embed image’, you can insert an image that already exists in the cloud into the post. The image must therefore either be available or you can upload it for precisely this purpose, e.g. using the ‘Files’ app.
+
+![picture01](/help/en/member/pic/picture01.png)
+
+If you select this button, an overview of the available image files opens and you only have to select and click on the appropriate image. Please note: After selecting the image to be inserted, the selection window must be closed again. It does not close automatically after inserting an image so that you have the option of inserting several images in one go.
+
+Once selected, the image, scaled to its original size, is inserted as a clickable link to the source file using the bbCode tags `[zrl][zmg][/zmg][/zrl]` at the end of the previous post (not at the current text cursor position).
+
+![picture03](/help/en/member/pic/picture03.png)
+
+With ‘Attach/upload file’ an image can be uploaded directly from your own device and embedded at the end of the post.
+
+![picture02](/help/en/member/pic/picture02.png)
+
+A file selection dialogue opens where you can select and upload the image on your own device. It is uploaded to an automatically created subdirectory in the cloud and inserted at the end of the previous post using the bbCode tags `[zrl][zmg][/zmg][/zrl]`.
+
+<u>Please note:</u> With this method, the image is not scaled to its original size but displayed as a smaller preview image if there is no continuous text. As a result, you may see a chequered background around the image.
+
+![picture04](/help/en/member/pic/picture04.png)
+
+##### In the comment editor
+
+The comment editor only offers the option of uploading and embedding an image. The button for inserting an existing image does not exist there.
+
+![picture05](/help/en/member/pic/picture05.png)
+
+If you still want to insert an existing image, you must do this manually using the tags `[img][/img]` or `[img=URL][/img]`. To do this, you need to know the URL of the image. In this way, you can also insert images from external sources on the web.
+
+If you use `[img=WIDTHxHEIGHT][/img]`, you can scale the image.
+
+##### Alt text
+
+If you want an alternative text (alt text) to be displayed when the image cannot be displayed or - as a pop-up - when you move the mouse pointer over the image, you can place it between the two tags `[img=URL]ALT_TEXT[/img]`. If you have embedded the image in a post using the first method (‘Embed image’), you have to do it yourself. You have to replace the scaling that was inserted in the opening zmg tag with the URL of the image and replace the automatically inserted image URL between the opening and closing zmg tag with the alt text.
+
+Example:
+
+The image was embedded and the code
+`[zrl=https://klacker.org/photos/tutorial01/image/cd747cd9-3f05-42cd-94cc-91c7368c5a18][zmg=520x520]https://klacker.org/photo/cd747cd9-3f05-42cd-94cc-91c7368c5a18-2.png[/zmg][/zrl]`
+was created automatically, then it must be changed to insert the alt text ‘Hubzilla icon on red background’, for example:
+`[zrl=https://klacker.org/photos/tutorial01/image/cd747cd9-3f05-42cd-94cc-91c7368c5a18][zmg=https://klacker.org/photo/cd747cd9-3f05-42cd-94cc-91c7368c5a18-2.png520x520]Hubzilla icon on red background[/zmg][/zrl]`
+
+![picture06](/help/en/member/pic/picture06.png)
diff --git a/doc/en/member/interact.md b/doc/en/member/interact.md
new file mode 100644
index 000000000..9dec01a2e
--- /dev/null
+++ b/doc/en/member/interact.md
@@ -0,0 +1,24 @@
+## Interact with posts
+
+You can interact with posts that you see in the stream. This is one of the purposes of social networks. You can comment on such a post, but there are also other ways of interacting.
+The buttons for this can be found at the bottom right of the post.
+
+![interact 01](/help/en/member/pic/interact01.png)
+
+You can ‘like’ 🖒 or ‘dislike’ a post 🖓 or you can react to a post with an emoji:
+
+![interact 02](/help/en/member/pic/interact02.png)
+
+Further functions can be found in the ⚙ menu.
+
+![interact 03](/help/en/member/pic/interact03.png)
+
+#include doc/en/member/repeat.md;
+#include doc/en/member/share.md;
+#include doc/en/member/link_to_source.md;
+#include doc/en/member/save_to_folder.md;
+#include doc/en/member/toggle_star_status.md;
+#include doc/en/member/show_source_code.md;
+#include doc/en/member/follow_conversation.md;
+#include doc/en/member/delete.md;
+#include doc/en/member/conversation_features.md;
diff --git a/doc/en/member/link_to_source.md b/doc/en/member/link_to_source.md
new file mode 100644
index 000000000..fdb7faf29
--- /dev/null
+++ b/doc/en/member/link_to_source.md
@@ -0,0 +1,3 @@
+### Link to source
+
+Link to source’ takes you to the actual source of a post. You end up at the original post on the instance of the post creator.
diff --git a/doc/en/member/member_faq.bb b/doc/en/member/member_faq.bb
deleted file mode 100644
index 9533cb557..000000000
--- a/doc/en/member/member_faq.bb
+++ /dev/null
@@ -1,10 +0,0 @@
-[h3]$Projectname FAQ[/h3]
-[h4]I am able to edit a post's text after I saved it, but is there a way to change the permissions?[/h4]
-Short anser: No, there isn't. There are reasons. You are able to change permissons to your files, photos and the likes, but not to posts after you have saved them. The main reason is: Once you have saved a post it is being distributed either to the public channel and from there to other $Projectname servers or to those you intended it to go. Just like you cannot reclaim something you gave to another person, you cannot change permissions to $Projectname posts. We would need to track everywhere your posting goes, keep track of everyone you allowed to see it and then keep track of from whom to delete it.
-If a posting is public this is even harder, as $Projectname is a global network and there is no way to follow a post, let alone reclaim it reliably. Other networks that may receive your post have no reliable way to delete or reclaim the post.
-[h4]I downloaded my channel and imported it (cloned my identity) to another site but there is no content, no posts, no photos. What is wrong???[/h4]
-Posts and photos/files are provided separately from the channel basic information. This is due to memory limitations dealing with years of conversations and photo archives. Posts and conversations can be synced separately from the basic channel information. Photos and file archives can be transferred using a plugin tool such as 'redfiles', which is currently listed as "experimental". When creating this feature we thought that keeping all your contacts was the most important task. Your friends have already seen your old content. Posts/conversations were next in priority and these may now be synced. Files and photos are the last bit to get completely working. Once we find someone willing to finish implementing this, it will be done. :)
-[h4]I can't see private resources[/h4]
-You have probably disabled third party cookies. You need to enable them for remote authentication to work.
-[h4]There are a lot of foreign language posts. Let's auto-translate them.[/h4]
-There are also a lot of [b]private[/b] foreign language posts and auto-translation services would require us to transmit these private messages to the translation service; and we don't know what they will do with them on their servers. Actually we do know thanks to Edward Snowden. Our best bet is a project called [b][i]Apertium[/i][/b] which is an open source translator we can install locally. It is currently missing German translations - which are the most requested translation in the matrix. Once again, this will be implemented when we find somebody who really wants to make it happen.
diff --git a/doc/en/member/member_guide.bb b/doc/en/member/member_guide.bb
deleted file mode 100644
index fd18f2f32..000000000
--- a/doc/en/member/member_guide.bb
+++ /dev/null
@@ -1,1087 +0,0 @@
-[h3]Overview[/h3]
-
-While many features and capabilities of $Projectname are familiar to people who have used social networking sites and blogging software, there are also quite a few new concepts and features that most people have not encountered before. Some of the new ideas are related to the decentralized nature of the grid; others are associated with the advanced permissions system that is necessary to protect your data privacy. The purpose of this guide is to help you understand how to create, configure, and use your nomadic identity.
-
-[h3]Registration[/h3]
-
-Not all $Projectname sites allow open registration. If registration is allowed, you will see a &quot;Register&quot; link immediately below the login prompts on the site home page. Following this link will take you to the site Registration page. On some sites it may redirect you to another site which allow registrations. As all $Projectname sites are linked, it does not matter where your account resides.
-
-[b]Your Email Address[/b]
-
-Please provide a valid email address. Your email address is never published. This address will be used to activate your account, to (optionally) send email notifications for incoming messages or items, [i]and to recover lost passwords[/i].
-
-[b]Password[/b]
-
-Enter a password of your choice, and repeat it in the second box to ensure it was typed correctly. As $Projectname offers a decentralised identity, your account can log you in to many other websites.
-
-[b]Terms Of Service[/b]
-
-Click the link to read the site's [zrl=[baseurl]/help/TermsOfService]Terms of Service[/zrl]. Once you've read them, tick the box in the register form to confirm.
-
-[b]Register[/b]
-
-Once you have provided the necessary details, click the 'Register' button. Some sites may require administrator approval before the registration is processed, and you will be alerted if this is the case. Please watch your email (including spam folders) for your registration approval.
-
-
-
-[h3]Channels[/h3]
-
-[h4]What are channels?[/h4]
-
-Channels are simply collections of content stored in one place. A channel can represent anything. It could represent you, a website, a forum, photo albums, anything. For most people, their first channel with be &quot;Me&quot;.
-
-The most important features for a channel that represents &quot;me&quot; are:
-[ul]
-[*]Secure and private &quot;spam free&quot; communications
-
-[*]Identity and &quot;single-signon&quot; across the entire network
-
-[*]Privacy controls and permissions which extend to the entire network
-
-[*]Directory services (like a phone book)
-[/ul]
-In short, a channel that represents yourself is &quot;me, on the internet&quot;.
-
-
-
-[h3]Channel Permission Roles[/h3]
-
-When you create a new channel, you will be asked to select a permission role based on how you envision using this channel. The most popular permission roles are the Social Networking roles. You have many other choices, providing options which are analagous to Facebook Groups and Pages, collaborative spaces, newsfeeds, and more. These roles automatically configure several different system variables ranging from what permissions are granted to friends, to your default privacy settings and visibility choices. Advanced configurations are available to let you adjust each of these parameters to your needs, but we've found that most people prefer to "set it and forget it". Thew following describes some of the different roles which are currently available and how they impact your privacy and ability to interact.
-
-[h4]Social[/h4]
-
-[b]Federation[/b]
-
-The channel is a very permissive social networking profile which is compatible with other federated social networks. The permission policies are similar to Twitter and mostly compatible with Diaspora and Mastodon. Privacy is a lower priority than ease of access and connecting with others. Anybody in the network can comment on your public posts and send you private mail. By default posts and published items are public, but you can over-ride this when creating the item and restrict it. You are listed in the directory. Your online presence and connections are visible to others. This mode [i]may[/i] increase your exposure to undesired communications and spam. This role is not generally recommended [i]unless[/i] you need to interact regularly with members of other networks.
-
-[b]Mostly Public[/b]
-
-The channel is a typical social networking profile. By default posts and published items are public, but you can over-ride this when creating the item and restrict it. You are listed in the directory. Your online presence and connections are visible to others. Only your immediate connections can comment on your public posts and send you private mail. The permission policies are similar to Facebook.
-
-
-[b]Restricted[/b]
-
-By default all posts and published items are sent to your 'Friends' privacy group and not made public. New friends are added to this privacy group. You can over-ride this and create a public post or published item if you desire. You are listed in the directory. Your online presence (for chat) and your connections (friends) are visible to your profile viewers.
-
-[b]Private[/b]
-
-By default all posts and published items are sent to your 'Friends' privacy group. New friends are added to this privacy group. You can over-ride this and create a public post or public item if you desire. You are NOT listed in the directory. Only your connections can see your other connections. Your online presence is hidden.
-
-
-[h4]Forum[/h4]
-
-[b]Mostly Public[/b]
-
-The channel is a typical forum. By default posts and published items are public. Members may post by !mention or wall-to-wall post. Posting photos and other published items is blocked. The channel is visible in the directory. Members are added automatically.
-
-
-[b]Restricted[/b]
-
-By default all posts and published items are sent to the channel's 'Friends' privacy group. New friends are added to this privacy group. Members may post by !mention or wall-to-wall post, but posts and replies may also be seen by other receipients of the top-level post who are not members. The channel is visible in the directory. Members must be manually added by the forum owner.
-
-[b]Private[/b]
-
-By default all posts and published items are sent to your 'Friends' privacy group. New friends are added to this privacy group. The owner can over-ride this and create a public post or public item if desired. Members cannot. You are NOT listed in the directory. Only your connections can see your other connections. Your online presence is hidden. Members must be manually added by the forum owner. Posting by !mention is disabled. Posts can only be made via wall-to-wall posts, and sent to members of the 'Friends' privacy group. They are not publicly visible.
-
-
-[h4]Feed[/h4]
-
-
-[b]Public[/b]
-
-Similiar to Social - Mostly Public, but tailored for RSS feed sources. Items may be freely republished and sourced. Online presence is meaningless, therefore hidden. New connections are automatically approved.
-
-
-[b]Restricted[/b]
-
-Not listed in directory. Online presence is meaningless, therefore hidden. Feed is published only to members of the 'Friends' privacy group. New connections are automatically added to this privacy group. Members must be manually approved by the channel owner.
-
-
-[h4]Special[/h4]
-
-[b]Celebrity/Soapbox[/b]
-
-Listed in directory. Communications are by default public. Online presence is hidden. No commenting or feedback of any form is allowed, though connections have the ability to "like" your profile.
-
-
-[b]Group Repository[/b]
-
-A public forum which allows members to post files/photos/webpages.
-
-
-[h4]Custom/Expert Mode[/h4]
-
-Set all the privacy and permissions manually to suit your specific needs.
-
-
-[h3]Creating channels[/h3]
-
-
-After creating your account, you will be presented with the &quot;Add a channel&quot; screen. Normally, your first channel will be one that represents you - so using your own name (or psuedonym) as the channel name is a good idea. The channel name should be thought of as a title, or brief description of your channel. The &quot;choose a short nickname&quot; box is similar to a &quot;username&quot; field. We will use whatever you enter here to create a channel address, which other people will use to connect to you, and you will use to log in to other sites. This looks like an email address, and takes the form nickname@siteyouregisteredat.xyz
-
-See Also
-[zrl=[baseurl]/help/accounts_profiles_channels_basics]The Basics about Identities within $Projectname[/zrl]
-[zrl=[baseurl]/help/accounts]Accounts[/zrl]
-[zrl=[baseurl]/help/profiles]Profiles[/zrl]
-[zrl=[baseurl]/help/permissions]Permissions[/zrl]
-[zrl=[baseurl]/help/remove_account]Remove Account[/zrl]
-
-
-You can create additonal channels from the &quot;Channel Manager&quot; link.
-
-Once you have done this, your channel is ready to use. At [observer=1][observer.url][/observer][observer=0][baseurl]/channel/username[/observer] you will find your channel &quot;stream&quot;. This is where your recent activity will appear, in reverse chronological order. If you post in the box marked &quot;share&quot;, the entry will appear at the top of your stream. You will also find links to all the other communication areas for this channel here. The "hamburger" menu in most themes will provide you with navigation to other system components and apps. The Photos page contain photo albums, and the Events page contains events share by both yourself and your contacts.
-
-[b]Profiles[/b]
-
-$Projectname has unlimited profiles. You may use different profiles to show different &quot;sides of yourself&quot; to different audiences. This is different to having different channels. Different channels allow for completely different sets of information. You may have a channel for yourself, a channel for your sports team, a channel for your website, or whatever else. A profile allows for finely graded &quot;sides&quot; of each channel. For example, your default public profile might say &quot;Hello, I'm Fred, and I like laughing&quot;. You may show your close friends a profile that adds &quot;and I also enjoy dwarf tossing&quot;.
-
-You always have a profile known as your &quot;default&quot; or &quot;public&quot; profile. This profile is always available to the general public and cannot be hidden (there may be rare exceptions on privately run or disconnected sites). You may, and probably should restrict the information you make available on your public profile.
-
-That said, if you want other friends to be able to find you, it helps to have the following information in your public profile...
-
-[ul][*]Your real name or at least a nickname everybody knows
-[*]A photo of you
-[*]Your location on the planet, at least to a country level.[/ul]
-
-In addition, if you'd like to meet people that share some general interests with you, please take a moment and add some &quot;Keywords&quot; to your profile. Such as &quot;music, linux, photography&quot; or whatever. You can add as many keywords as you like.
-
-To create an alternate profile, first go to [zrl=[baseurl]/settings/features]Settings &gt; Additional Features[/zrl] and enable &quot;Multiple Profiles&quot; there, otherwise you won't have the ability to use more than just your default profile.
-
-Then select &quot;Edit Profiles&quot; from the menu of your $Projectname site. You may edit an existing profile, change the profile photo, add things to a profile or create a new profile. You may also create a &quot;clone&quot; of an existing profile if you only wish to change a few items but don't wish to enter all the information again. To do that, click on the profile you want to clone and choose &quot;Clone this profile&quot; there.
-
-In the list of your profiles, you can also choose the contacts who can see a specific profile. Just click on &quot;Edit visibility&quot; next to the profile in question (only available for the profiles that are not your default profile) and then click on specific connections to add them to or remove them from the group of people who can see this profile.
-
-Once a profile has been selected, when the person views your profile, they will see the private profile you have assigned. If they are not authenticated, they will see your public profile.
-
-There is a setting which allows you to publish your profile to a directory and ensure that it can be found by others. You can change this setting on the &quot;Settings&quot; page.
-
-If you do not wish to be found be people unless you give them your channel address, you may leave your profile unpublished.
-
-[b]Keywords and Directory Search[/b]
-
-On the directory page, you may search for people with published profiles. Currently, only the name field and the keywords are searched. You may also include such keywords in your default profile - which may be used to search for common interests with other members. Keywords are used in the channel suggestion tool and although they aren't visible in the directory, they are shown if people visit your profile page.
-
-On your Connnections page and in the directory there is a link to &quot;Suggestions&quot; or &quot;Channel Suggestions&quot;, respectively. This will find channels who have matching and/or similar keywords. The more keywords you provide, the more relevant the search results that are returned. These are sorted by relevance.
-
-See Also
-
-[zrl=[baseurl]/help/AdvancedSearch]Advanced Searching[/zrl]
-
-
-[h4]The grid, permissions and delegation[/h4]
-
-The &quot;Grid&quot; page contains all recent posts from across $Projectname network, again in reverse chronologial order. The exact posts that appear here depend largely on your permissions. At their most permissive, you will receive posts from complete strangers. At the other end of the scale, you may see posts from only your friends - or if you're feeling really anti-social, only your own posts.
-
-As mentioned at the start, many other kinds of channel are possible, however, the creation procedure is the same. The difference between channels lies primarily in the permissions assigned. For example, a channel for sharing documents with colleagues at work would probably want more permissive settings for &quot;Can write to my &quot;public&quot; file storage&quot; than a personal account. For more information, see the [zrl=[baseurl]/help/roles]permissions section[/zrl].
-
-You can also delegate control of your channels' posts and connections, but not its configurations, to another channel. That is done by editing a connection and assigning it the permission to administer your channel's resources.
-
-[h3]Connecting To Channels[/h3]
-
-Connections in $Projectname can take on a great many different meanings. A connection is more accurately defined as a set of permissions that you have granted to somebody else. In traditional social network applications, all connections are granted the same permissions; or at most there two levels (friends and 'followers'). In $Projectname, a range of separate permissions may be set/adjusted depending on the siutation and relationship you have with the other channel. You can allow somebody to view your posts but not your photos. You can also deny them permission to comment on your posts or send private mail to you. But let's keep it simple, you want to be friends with somebody like you are familiar with from social networking. How do you do it?
-
-First, you need to find some channels to connect to. There are two primary ways of doing this. Firstly, setting the &quot;Can send me their channel stream and posts&quot; permission to &quot;Anybody in this network&quot; will bring posts from complete strangers to your matrix. This will give you a lot of public content and should hopefully help you find interesting, entertaing people, forums, and channels.
-
-The next thing you can do is look at the Directory. The directory is available on every $Projectname website which means searching from your own site will bring in results from the entire network. You can search by name, interest, location, and keyword.
-
-If you already know somebody's 'webbie' you can connect with them directly. A webbie looks just like an email address (for instance bob@example.com) but refers to somebody in the open social web. In order to connect they must be using a compatible network protocol. By default, this software supports the 'zot' protocol, however additional protocols may be provided through plugins/addons. See below for more information on connecting to channels on other networks.
-
-To connect with other $Projectname channels:
-
-Visit their profile by clicking their photograph in the directory, matrix, or comments, and it will open their channel home page in the channel viewer. At the left hand side of the screen, you will usually see a link called &quot;connect&quot;. Click it, and you're done. Depending on the settings of the channel you are connecting to, you may need to wait for them to approve your connection, but no further action is needed on your part. Once you've initiated the connection, you will be taken to the connection editor. This allows you to assign specific permissions for this channel if you wish to make any changes.
-
-You may also connect with any channel by visiting the &quot;Connections&quot; page of your site or the Directory and typing their &quot;webbie&quot; into the &quot;Add New Connection&quot; field. Use this method if somebody tells you their webbie and you wish to connect with them. The process is the same as connecting via the &quot;Connect&quot; button - you will then be taken to the connection editor to set permissions.
-
-To connect with channels on other networks:
-
-The process for connecting to channels on other networks (such as GNU-Social, Mastodon, and Diaspora) is similar - type their &quot;webbie&quot; into the &quot;Add New Connections&quot; box on the &quot;Connections&quot; page. Before you do this however, please visit your Settings page (Feature/Addon Settings) and ensure that the relevant protocol (Diaspora, GNU-Social/OStatus, or ActivityPub) is provided on your hub and [b][i]activated[/i] for your channel[/b]. These networks/protocols do not support account migration and location independence so if you move location or clone your channel elsewhere, communications with these connections may fail. For this reason these protocols are not activated by default, but only through your consent. Activating these protocols involves an important decision between communicating with friends on these networks or providing fail-safe account resilience if your server fails.
-
-Some communications offer more than one protocol. If you wish to connect with somebody on Mastodon (for instance) they can use either the 'ostatus' or the 'activitypub' protocol for communication. Generally the 'activitypub' protocol will provide a better experience than 'ostatus', but $Projectname will often choose the first protocol it discovers and this may not be the one you want. You may connect with somebody over a specific protocol by prepending the protocol name in square brackets to their &quot;webbie&quot;. For example
-
-[code]
-[activitypub]https://foo.bar/foobar
-[ostatus]foobar@foo.bar
-[diaspora]foobar@foo.bar
-[zot]foobar@foo.bar
-[feed]https://foo.bar/foobar
-[/code]
-
-
-To connect with RSS feeds:
-
-Your hub admin may allow connecting to RSS feeds. The process for connecting to an RSS feed is the same, exept type (or paste) the URL of the feed into the &quot;Add New Connection&quot; box. Feeds are only processed once or twice per day and your hub admin may impose limits on how many feeds you may add.
-
-[h4]Block/Ignore/Archive/Hide channels [/h4]
-
-Channels in your address book can have statuses such as [i]blocked[/i], [i]ignored[/i], [i]archived[/i] and [i]hidden[/i]. From your connections page you can see tabs that display the channels with those statuses. From your edit connection pages you can change the statuses of a channel.
-
-Here's their meaning:
-
-[b]Blocked:[/b] the channel can't read your items regardless of permissions, nor can it write to your channel.
-
-[b]Ignored:[/b] the channel can read your items if it has permission, but can't write to your channel.
-
-[b]Hidden:[/b] the channel does not show up in your profile's connections list, noone can see you're connected, but beware they may still show up to your other connections, for example in post replies.
-
-[b]Archived:[/b] if a channel can't be reached for 30 days, it is automatically marked as archived. This keeps all the data but stops polling the channel for new information and removes it from autocomplete. If later you learn the channel has come back online, you may manually unarchive it.
-
-[h4]Premium Channels[/h4]
-
-Some channels are designated &quot;Premium Channels&quot; and may require some action on your part before a connection can be established. The Connect button will for these channels will take you to a page which lists in detail what terms the channel owner has set. If the terms are accepted, the connection will then proceed normally. In some cases, such as with celebrities and world-reknowned publishers, this may involve payment. If you do not agree to the terms, the connection will not proceed, or it may proceed but with reduced permissions allowed on your interactions with that channel.
-
-[h3]Permissions[/h3]
-Permissions in $Projectname are more complete than you may be used to. This allows us to define more fine graded relationships than the black and white &quot;this person is my friend, so they can do everything&quot; or &quot;this person is not my friend, so they can't do anything&quot; permissions you may find elsewhere.
-
-[h4]Permission Roles[/h4]
-
-When you create a channel we allow you to select different 'roles' for that channel. These create an entire family of permissions and privacy settings that are appropriate for that role. Typical roles are "Social - mostly public", "Social - mostly private", "Forum - public" and many others. These bring a level of simplicity to managing permissions. Just choose a role and appropriate permissions are automatically applied. You can also choose 'Custom/Expert mode' and change any individual permission setting in any way you desire.
-
-
-[h4]Default Permission Limits[/h4]
-
-There are a large number of individual permissions. These control everything from the ability to view your stream to the ability to chat with you. Every permission has a limit. The scope of these permissions varies from &quot;Only me&quot; to &quot;Everybody on the internet&quot; - though some scopes may not be available for some permissions. The limit applies to any published thing you create which has no privacy or access control. For example if you publish a photo and didn't select a specific audience with permission to view it, we apply the limit. These limits apply to everything within that permission rule, so you cannot apply a limit to one photo. The limit applies to all your photos. If all your photos are visible to everybody on the internet and you reduce the limit only to friends, [b]all[/b] of your photos will now be visible only to friends.
-
-[h4]Access Control[/h4]
-
-Access Control is the preferred method of managing privacy in [i]most[/i] cases, rather than using permission limits. This creates lists of either connections or privacy groups (or both) and uses the access list to decide if a permission is allowed. An access list is attached to everything you publish. Unlike permission limits, if you change the access control list on a single photo, it doesn't affect any of your other photos. You can use privacy groups and a "default access control list" to create and automate the management of access control lists to provide any level of privacy you desire on anything you publish.
-
-We highly recommend that you use the "typical social network" settings when you create your first channel, as it allows others to communicate with you and help you out if you have difficulty. You will find that these settings allow you as much privacy as you desire - when you desire it; but also allow you to communicate in public if you choose to. You are free to use much more private settings once you have learned your way around.
-
-
-[dl terms="l"]
-[*= The scopes of permissions are:]
-[dl terms="i"]
- [*= Nobody Except Yourself ] This is self explanatory. Only you will be allowed access.
-
- [*= Only those you specifically allow ] By default, people you are not connected to, and all new contacts will have this permission denied. You will be able to make exceptions for individual channels on their contact edit screen.
-
- [*= Anybody in your address book ] Anybody you do not know will have this permission denied, but anybody you accept as a contact will have this permission approved. This is the way most legacy platforms handle permissions.
-
- [*= Anybody On This Hub ] Anybody with a channel on the same hub/website as you will have permission approved. Anybody who is registered at a different hub will have this permission denied.
-
- [*= Anybody in this network ] Anybody in $Projectname will have this permission approved. Even complete strangers. However, anybody not logged in/authenticated will have this permission denied.
-
- [*= Anybody authenticated ] This is similar to "anybody in this network" except that it can include anybody who can authenticate by any means - and therefore [i]may[/i] include visitors from other networks.
-
- [*=Guest Access Token] This allows you to share a file, folder, photo, album, or channel with a specific person or group of people. They don't need to be $Projectname members. You can set an expiration for the Access Token.
-
- [*= Anybody on the internet ] Completely public. This permission will be approved for anybody at all.
-[/dl]
-[*= The individual permissions are:]
-[dl terms="i"]
- [*= Can view my &quot;public&quot; stream and posts. ] This permision determines who can view your channel &quot;stream&quot; that is, the non-private posts that appear on the &quot;home&quot; tab when you're logged in.
-
- [*= Can view my &quot;public&quot; channel profile. ] This permission determines who can view your channel's profile. This refers to the &quot;about&quot; tab
-
- [*= Can view my &quot;public&quot; photo albums. ] This permission determines who can view your photo albums. Individual photographs may still be posted to a more private audience.
-
- [*= Can view my &quot;public&quot; address book. ] This permission determines who can view your contacts. These are the connections displayed in the &quot;View connections&quot; section.
-
- [*= Can view my &quot;public&quot; file storage. ] This permission determines who can view your public files stored in your cloud.
-
- [*= Can view my &quot;public&quot; pages. ] This permission determines who can view your public web pages.
-
- [*= Can send me their channel stream and posts. ] This permission determines whose posts you will view. If your channel is a personal channel (ie, you as a person), you would probably want to set this to &quot;anyone in my address book&quot; at a minimum. A personal notes channel would probably want to choose &quot;nobody except myself&quot;. Setting this to &quot;Anybody in the network&quot; will show you posts from complete strangers, which is a good form of discovery.
-
- [*= Can post on my channel page (&quot;wall&quot;). ] This permission determines who can write to your wall when clicking through to your channel.
-
- [*= Can comment on my posts. ] This permission determines who can comment on posts you create. Normally, you would want this to match your &quot;can view my public stream and posts&quot; permission
-
- [*= Can send me private mail messages. ] This determines who can send you private messages (zotmail).
-
- [*= Can post photos to my photo albums. ] This determines who can post photographs in your albums. This is very useful for forum-like channels where connections may not be connected to each other.
-
- [*= Can forward to all my channel contacts via post tags. ] Using @- mentions will reproduce a copy of your post on the profile specified, as though you posted on the channel wall. This determines if people can post to your channel in this way.
-
- [*= Can chat with me (when available). ] This determines who can join the public chat rooms created by your channel.
-
- [*= Can write to my &quot;public&quot; file storage. ] This determines who can upload files to your public file storage, or 'cloud'.
-
- [*= Can edit my &quot;public&quot; pages. ] This determines who can edit your webpages. This is useful for wikis or sites with multiple editors.
-
- [*= Can administer my channel resources. ] This determines who can have full control of your channel. This should normally be set to &quot;nobody except myself&quot;.
-[/dl][/dl]
-[i]Note:[/i]
-Plugins/addons may provide special permission settings, so you may be offered additional permission settings beyond what is described here.
-
-If you have set any of these permissions to &quot;only those I specifically allow&quot;, you may specify individual permissions on the connnection edit screen.
-
-[h4]Affinity[/h4]
-
-The connection edit screen offers a slider to select a degree of friendship with the connnection (this tool is enabled through the &quot;Extra Features&quot; tab of your Settings page). Think of this as a measure of how much you like or dislike them. 1 is for people you like, whose posts you want to see all the time. 99 is for people you don't care for, and whose posts you might only wish to look at occasionally. Once you've assigned a value here, you can use the affinity tool on the matrix page to filter content based on this number.
-
-The slider on the matrix page has both a minimum and maximum value. Posts will only be shown from people who fall between this range. Affinity has no relation to permissions, and is only useful in conjunction with the affinity tool feature.
-
-[h3]Guest Access Tokens[/h3]
-Guest access tokens (sometimes called "Zot access tokens") allow you to share a file, folder, photo, album, or channel with a specific person or group of people who are not $Projectname members. These tokens allow you to share individual items by sending a link that includes the token in the URL; alternatively, people can actually [i]log in[/i] using the token credentials, after which they can seamlessly view whatever content has been shared with that token.
-
-To create and manage guest tokens, open the [zrl=[baseurl]/settings/tokens/]Guest Access Tokens[/zrl] settings page. A random token is generate with each page load, allowing you to create one by inputting an associated login name and optionally specifying an expiration date. Existing tokens are listed below the dialog and can be edited by selecting them or deleted by pressing the trash icon.
-
-Additional permissions may be granted to the guest token by expanding the [b]Individual Permissions[/b] options and selecting privacy settings such as [b]Can view my channel stream and posts[/b] or [b]Can chat with me[/b].
-
-[url=[baseurl]/help/feature/access_tokens]More details can be found here...[/url]
-
-[img][baseurl]/doc/member/assets/zat_dialog.png[/img]
-
-
-[h3]Markup Languages[/h3]
-$Projectname supports several markup languages for advanced formatting of content. The default markup language is a [url=[baseurl]/help/member/bbcode]custom variant of BBcode[/url], tailored for use in $Projectname. [url=[baseurl]/help/member/bbcode]BBcode[/url] is supported for posts, wiki pages, and web page elements. Wiki pages and webpage elements may also be written using standard Markdown.
-[table border=0]
-[tr][th]Content Type[/th][th]Supported Markup[/th][/tr]
-[tr][td]Post[/td][td][url=[baseurl]/help/member/bbcode]BBcode[/url][/td][/tr]
-[tr][td]Wiki[/td][td][url=[baseurl]/help/member/bbcode]BBcode[/url], Markdown[/td][/tr]
-[tr][td]Webpage element[/td][td][url=[baseurl]/help/member/bbcode]BBcode[/url], Markdown, HTML[/td][/tr]
-[/table]
-
-[h3]Tags And Mentions[/h3]
-
-[h4]Mentions[/h4]
-You can mention a channel by simply preceding their name with the [code]@[/code] character, like this:
-
-[code]
-@Jack
-[/code]
-
-If the channel mentioned is in the list of recipients for the post or comment, they will receive a notification, bringing it to their attention. If they're [i]not[/i] among the recipients, the tag will simply work as a link to their profile.
-
-When you begin to mention somebody, it will create an auto-complete box to select from [u]your immediate connections[/u](?). Select one as appropriate.
-
-
-[h4]Private Mentions[/h4]
-If you wish to restrict a post to a single—or only a few—channels you can do this by selecting channels or privacy groups from the privacy tool.
-
-[img=[baseurl]/help/en/member/assets/privacy-tool-3.png]Two views of the permissions dialogue. To the left: Selecing a single privacy group or forum as recipients for the post. To the right: Using the 'Custom selection' view to gain fine grained control over the recipients.[/img]
-
-You can also just tag a channel with a [i]privacy tag[/i].
-
-A privacy tag is a channel name preceded by the two characters [code]@![/code]. This will also change the privacy permissions of the post to only include the tagged channel. You can use more than one privacy tag, for instance [code]@!bob and @!linda[/code] will send the post only to Bob and Linda. This mechanism over-rides the privacy selector.
-
-[h4]Mentioning forums[/h4]
-
-Forums may be mentioned in the same way. [code]@!dogs[/code] will post to the dogs forum privately. The forum will redeliver the post to all the forum members, but your own followers will not see the post unless they are also members of the dogs forum.
-
-[size=small][b]Note:[/b] In previous releases you could mention a forum by prefixing the forum name with the characters [code]!![/code]. This is no longer supported. The correct way to send to a forum is by using the [code]@![/code] tag.[/size]
-
-[h4]Mentioning privacy groups[/h4]
-
-You may also tag privacy groups which are "public".
-
-When you create or edit a privacy group, there is a checkbox to allow the group members to be seen by others. If this box is checked for a group and you tag (for instance) [code]@!Friends[/code] - the post will be restricted to the Friends group. Check that the group is public before doing this - as there is no way to take back a post except to delete it. The group name will appear in the post and will alert members of that group that they are members of it.
-
-Set the privacy group visible to others to make it public:
-[img=[baseurl]/help/en/member/assets/privacy-group-tool-public.png]The privacy group tool with 'Members are visible to other channels' set to 'yes'.[/img]
-
-To add or edit privacy groups, you need to have the [url=[baseurl]/group]Privacy Groups app[/url] installed.
-
-[h4]Mentions and Comments[/h4]
-The above mechanisms only apply to "top-level" posts you create. Mentioning a channel with any of the above mechanisms has no effect in comments, except that the mentioned channel may receive a notification if they were already included as a recipient in the conversation.
-
-[h4]Topical Tags (also known as Hashtags)[/h4]
-Topical tags are indicated by preceding the tag name with the [code]#[/code] character. This will create a link in the post to a generalised site search for the term provided. For example, #[zrl=[baseurl]/search?tag=cars]cars[/zrl] will provide a search link for all posts tagged with '[code]#cars[/code]' on your site. Topical tags are generally a minimum of three characters in length. Shorter search terms are not likely to yield any search results, although this depends on the database configuration.
-
-Topical tags are also not normally linked if they are purely numeric, e.g. [code]#1[/code]. If you wish to use a numeric hashtag, please add some descriptive text such as [code]#2012-elections[/code] or enclose the entire tag in double quotes (for example [code]#"2012"[/code]). Doubles quotes are also required if the tag contains spaces ([code]#"My Tag"[/code]) and may be required if the tag contains punctuation characters ([code]#"EndsWithPeriod."[/code] or [code]#"Exciting!!!"[/code]).
-
-[h4]Bookmarks[/h4]
-Bookmarks indicate a link which can be saved to your bookmark folder. They use the sequence [code]#^[/code] followed by the link. Often these are generated automatically.
-
-[img=[baseurl]/help/en/member/assets/bookmarker-save-icon.png]Saving a bookmark by clicking the bookmark icon when the 'bookmarker' addon is enabled.[/img]
-
-If the site administrator has enabled the [url=[baseurl]/admin/addons/bookmarker]bookmarker[/url] addon for the site, this sequence will be converted to a bookmark icon when viewing the post or comment online. Clicking the icon will save the bookmark.
-
-Otherwise if the [url=[baseurl]/bookmarks]Bookmarks app[/url] is installed for the channel, the post dropdown menu contains a link for saving the bookmark or bookmarks.
-
-[img=[baseurl]/help/en/member/assets/bookmarks-menu-dropdown.png]A post with a bookmark, showing the dropdown menu.[/img]
-
-[h4]Manual Mentions[/h4]
-Where possible please use the auto-complete window to select tag and mention recipients, because it will generate a coded tag which uniquely identifies one channel. Names are sometimes ambiguous. However, you can "manually" tag a channel by matching the channel name or address.
-
-[code]
-@billy
-[/code]
-
-will tag a connection whose name or network address is 'billy' (exact match). If you have two connections with a name or network address of billy, for instance [code]billy@server1.hubzilla.org[/code] and [code]billy@server2.hubzilla2.org[/code], you will need to supply the complete address or the results will be ambiguous and the wrong person may be tagged.
-
-[code]
-@"Robert Johnson"
-[/code]
-
-will tag Robert Johnson. The double quotes are required if the tagged name contains space characters.
-
-
-[h3]Web Pages[/h3]
-
-$Projectname allows members and allowed connections to create static webpages. To activate this feature, enable the [b]Web Pages[/b] feature in your [b][url=[baseurl]/settings/features/]Additional Features[/url][/b] section.
-
-Once enabled, a new tab will appear on your channel page labeled &quot;Webpages&quot;. Clicking this link will take you to the webpage editor. Pages will be accessible at [b][baseurl]/page/[observer=1][observer.webname][/observer][observer=0]channelname[/observer]/pagelinktitle[/b]
-
-The &quot;page link title&quot; box allows you to specify the &quot;pagelinktitle&quot; of this URL. If no page link title is set, we will set one for you automatically, using the message ID of the item.
-
-Beneath the page creation box, a list of existing pages will appear with an &quot;edit&quot; link. Clicking this will take you to an editor, similar to that of the post editor, where you can make changes to your webpages.
-
-[h4]Using Blocks[/h4]
-
-Blocks can be parts of webpages. The basic HTML of a block looks like this
-[code]
- <div>
- Block Content
- </div>
-
-[/code]
-
-If a block has text/html content type it can also contain menu elements. Sample content of
-[code]
- <p>HTML block content</p>
- [menu]menuname[/menu]
-
-[/code]
-will produce HTML like this
-[code]
- <div>
- <p>HTML block content</p>
- <div>
- <ul>
- <li><a href="#">Link 1</a></li>
- <li><a href="#">Link 2</a></li>
- <li><a href="#">Link 3</a></li>
- </ul>
- </div>
- </div>
-
-[/code]
-
-Via the $content macro a block can also contain the actual webpage content. For this create a block with only
-[code]
- $content
-
-[/code]as content.
-
-To make a block appear in the webpage it must be defined in the page layout inside a region.
-[code]
- [region=aside]
- [block]blockname[/block]
- [/region]
-
-[/code]
-
-The block appearance can be manipulated in the page layout.
-
-Custom classes can be assigned
-[code]
- [region=aside]
- [block=myclass]blockname[/block]
- [/region]
-
-[/code]
-will produce this HTML
-[code]
- <div class="myclass">
- Block Content
- </div>
-
-[/code]
-
-Via the wrap variable a block can be stripped off its wrapping <div></div> tag
-[code]
- [region=aside]
- [block][var=wrap]none[/var]blockname[/block]
- [/region]
-
-[/code]
-will produce this HTML
-[code]
- Block Content
-[/code]
-
-[h4]Webpage element import tool[/h4]
-
-There are two methods of importing webpage elements: uploading a zip file or referencing a local cloud files folder. Both methods require that the webpage elements are specified using a specific folder structure. The import tool makes it possible to import all the elements necessary to construct an entire website or set of websites. The goal is to accommodate external development of webpages as well as tools to simplify and automate deployment on a hub.
-
-[h5] Folder structure [/h5]
-Element definitions must be stored in the repo root under folders called
-[code]
- /pages/
- /blocks/
- /layouts/
-[/code]
-
-Each element of these types must be defined in an individual subfolder using two files: one JSON-formatted file for the metadata and one plain text file for the element content.
-
-[h5] Page elements [/h5]
-Page element metadata is specified in a JSON-formatted file called [code]page.json[/code] with the following properties:
-[list]
-[*] title
-[*] pagelink
-[*] mimetype
-[*] layout
-[*] contentfile
-[/list]
-[b]Example[/b]
-
-Files:
-[code]
- /pages/my-page/page.json
- /pages/my-page/my-page.bbcode
-[/code]
-Content of [code]page.json[/code]:
-[code]
- {
- "title": "My Page",
- "pagelink": "mypage",
- "mimetype": "text/bbcode",
- "layout": "my-layout",
- "contentfile": "my-page.bbcode"
- }
-[/code]
-[h5] Layout elements [/h5]
-
-Layout element metadata is specified in a JSON-formatted file called [code]layout.json[/code] with the following properties:
-[list]
-[*] name
-[*] description
-[*] contentfile
-[/list]
-[b]Example[/b]
-
-Files:
-[code]
- /layouts/my-layout/layout.json
- /layouts/my-layout/my-layout.bbcode
-[/code]
-Content of [code]layout.json[/code]:
-[code]
- {
- "name": "my-layout",
- "description": "Layout for my project page",
- "contentfile": "my-layout.bbcode"
- }
-[/code]
-
-[h5] Block elements [/h5]
-
-Block element metadata is specified in a JSON-formatted file called [code]block.json[/code] with the following properties:
-[list]
-[*] name
-[*] title
-[*] mimetype
-[*] contentfile
-[/list]
-[b]Example[/b]
-
-Files:
-[code]
- /blocks/my-block/block.json
- /blocks/my-block/my-block.html
-[/code]
-Content of [code]block.json[/code]:
-
-[code]
- {
- "name": "my-block",
- "title": "",
- "mimetype": "text/html",
- "contentfile": "my-block.html"
- }
-[/code]
-
-[h3]Comanche Page Description Language[/h3]
-
-Comanche is a markup language similar to [url=[baseurl]/help/member/bbcode]BBcode[/url] with which to create elaborate and complex web pages by assembling them from a series of components - some of which are pre-built and others which can be defined on the fly. Comanche uses a Page Decription Language to create these pages.
-
-Comanche primarily chooses what content will appear in various regions of the page. The various regions have names and these names can change depending on what layout template you choose.
-
-[h4]Page Templates[/h4]
-Currently there are five layout templates, unless your site provides additional layouts.
-
-[dl terms="b"]
-[*= default]
-The default template defines a &quot;nav&quot; region across the top, &quot;aside&quot; as a fixed width sidebar,
-&quot;content&quot; for the main content region, and &quot;footer&quot; for a page footer.
-
-[*= full]
-The full template defines the same as the default template with the exception that there is no &quot;aside&quot; region.
-
-[*= choklet]
-The choklet template provides a number of fluid layout styles which can be specified by flavour:
-[list]
-[*] (default flavour) - a two column layout similar to the "default" template, but more fluid
-[*] bannertwo - a two column layout with a banner region, compatible with the "default" template on small displays
-[*] three - three column layout (adds a "right_aside" region to the default template)
-[*] edgestwo - two column layout with fixed side margins
-[*] edgesthree - three column layout with fixed side margins
-[*] full - three column layout with fixed side margins and adds a "header" region beneath the navigation bar
-[/list]
-
-[*= redable]
-A template for reading longer texts full screen (so without navigation bar). Three columns: aside, content and right_aside.
-For maximum readability it is advised to only use the middle content column.
-
-[*= zen]
-Gives you the freedom to do everything yourself. Just a blank page with a content region.
-[/dl]
-
-To choose a layout template, use the 'template' tag.
-
-[code]
- [template]full[/template]
-
-[/code]
-
-To choose the "choklet" template with the "three" flavour:
-
-[code]
- [template=three]choklet[/template]
-
-[/code]
-
-The default template will be used if no other template is specified. The template can use any names it desires for content regions. You will be using 'region' tags to decide what content to place in the respective regions.
-
-Three &quot;macros&quot; have been defined for your use.
-[code]
- $htmlhead - replaced with the site head content.
- $nav - replaced with the site navigation bar content.
- $content - replaced with the main page content.
-
-[/code]
-
-By default, $nav is placed in the &quot;nav&quot; page region and $content is placed in the &quot;content&quot; region. You only need to use these macros if you wish to re-arrange where these items appear, either to change the order or to move them to other regions.
-
-
-To select a theme for your page, use the 'theme' tag.
-[code]
- [theme]suckerberg[/theme]
-
-[/code]
-This will select the theme named &quot;suckerberg&quot;. By default your channel's preferred theme will be used.
-
-[code]
- [theme=passion]suckerberg[/theme]
-
-[/code]
-This will select the theme named &quot;suckerberg&quot; and select the &quot;passion&quot; schema (theme variant). Alternatively it may be possible to use a condensed theme notation for this.
-
-[code]
- [theme]suckerberg:passion[/theme]
-
-[/code]
-
-The condensed notation isn't part of Comanche itself but is recognised by $Projectname platform as a theme specifier.
-
-[h4]Navbar[/h4]
-
-[code]
- [navbar]tucson[/navbar]
-[/code]
-
-Use the 'tucson' navbar template and CSS rules. By default the 'default' navbar template will be used.
-
-
-[h4]Regions[/h4]
-Each region has a name, as noted above. You will specify the region of interest using a 'region' tag, which includes the name. Any content you wish placed in this region should be placed between the opening region tag and the closing tag.
-
-[code]
- [region=htmlhead]....content goes here....[/region]
- [region=aside]....content goes here....[/region]
- [region=nav]....content goes here....[/region]
- [region=content]....content goes here....[/region]
-
-[/code]
-
-[h4]CSS and Javascript[/h4]
-We have the possibility to include javascript and css libraries in the htmlhead region. At present we make use of jquery (js), bootstrap (css/js) and foundation (css/js).
-This will overwrite the selected themes htmlhead.
-
-[code]
- [region=htmlhead]
- [css]bootstrap[/css]
- [js]jquery[/js]
- [js]bootstrap[/js]
- [/region]
-
-[/code]
-
-[h4]Menus and Blocks[/h4]
-Your webpage creation tools allow you to create menus and blocks, in addition to page content. These provide a chunk of existing content to be placed in whatever regions and whatever order you specify. Each of these has a name which you define when the menu or block is created.
-
-[code]
- [menu]mymenu[/menu]
-
-[/code]
-This places the menu called &quot;mymenu&quot; at this location on the page, which must be inside a region.
-
-[code]
- [menu=horizontal]mymenu[/menu]
-
-[/code]
-This places the menu called &quot;mymenu&quot; at this location on the page, which must be inside a region. Additionally it applies the "horizontal" class to the menu. "horizontal" is defined in the redbasic theme. It may or may not be available in other themes.
-
-[code]
- [menu][var=wrap]none[/var]mymenu[/menu]
-
-[/code]
-The variable [var=wrap]none[/var] in a block removes the wrapping div element from the menu.
-
-[code]
- [block]contributors[/block]
-[/code]
-This places a block named &quot;contributors&quot; in this region.
-
-[code]
- [block=someclass]contributors[/block]
-
-[/code]
-This places a block named &quot;contributors&quot; in this region. Additionally it applies the &quot;someclass&quot; class to the block. This replaces the default block classes &quot;bblock widget&quot;.
-
-[code]
- [block][var=wrap]none[/var]contributors[/block]
-
-[/code]
-The variable [var=wrap]none[/var] in a block removes the wrapping div element from the block.
-
-[h4]Widgets[/h4]
-Widgets are executable apps provided by the system which you can place on your page. Some widgets take arguments which allows you to tailor the widget to your purpose. System widgets are listed [url=help/Widgets]here[/url]. Widgets can also ve created by plugins, themes, or your site administrator to provide additional functionality.
-
-
-Widgets and arguments are specified with the 'widget' and 'var' tags.
-[code]
- [widget=recent_visitors][var=count]24[/var][/widget]
-
-[/code]
-
-This loads the &quot;recent_visitors&quot; widget and supplies it with the argument &quot;count&quot; set to &quot;24&quot;.
-
-[h4]Comments[/h4]
-The 'comment' tag is used to delimit comments. These comments will not appear on the rendered page.
-
-[code]
- [comment]This is a comment[/comment]
-
-[/code]
-
-[h4]Conditional Execution[/h4]
-You can use an 'if' construct to make decisions. These are currently based on system configuration variable or the current observer.
-
-[code]
- [if $config.system.foo]
- ... the configuration variable system.foo evaluates to 'true'.
- [else]
- ... the configuration variable system.foo evaluates to 'false'.
- [/if]
-
- [if $observer]
- ... this content will only be show to authenticated viewers
- [/if]
-
-[/code]
-
- The 'else' clause is optional.
-
- Several tests are supported besides boolean evaluation.
-
-[code]
- [if $config.system.foo == bar]
- ... the configuration variable system.foo is equal to the string 'bar'
- [/if]
- [if $config.system.foo != bar]
- ... the configuration variable system.foo is not equal to the string 'bar'
- [/if]
- [if $config.system.foo {} bar ]
- ... the configuration variable system.foo is a simple array containing a value 'bar'
- [/if]
- [if $config.system.foo {*} bar]
- ... the configuration variable system.foo is a simple array containing a key named 'bar'
- [/if]
-[/code]
-
-[h4]Complex Example[/h4]
-[code]
- [comment]use an existing page template which provides a banner region plus 3 columns beneath it[/comment]
-
- [template]3-column-with-header[/template]
-
- [comment]Use the &quot;darknight&quot; theme[/comment]
-
- [theme]darkknight[/theme]
-
- [comment]Use the existing site navigation menu[/comment]
-
- [region=nav]$nav[/region]
-
- [region=side]
-
- [comment]Use my chosen menu and a couple of widgets[/comment]
-
- [menu]myfavouritemenu[/menu]
-
- [widget=recent_visitors]
- [var=count]24[/var]
- [var=names_only]1[/var]
- [/widget]
-
- [widget=tagcloud][/widget]
- [block]donate[/block]
-
- [/region]
-
-
-
- [region=middle]
-
- [comment]Show the normal page content[/comment]
-
- $content
-
- [/region]
-
-
-
- [region=right]
-
- [comment]Show my condensed channel &quot;wall&quot; feed and allow interaction if the observer is allowed to interact[/comment]
-
- [widget]channel[/widget]
-
- [/region]
-
-[/code]
-
-
-[h3]Personal Cloud Storage[/h3]
-
-$Projectname provides an ability to store privately and/or share arbitrary files with friends.
-
-You may either upload files from your computer into your storage area, or copy them directly from the operating system using the WebDAV protocol.
-
-On many public servers there may be limits on disk usage.
-
-[h4]File Attachments[/h4]
-
-The quickest and easiest way to share files is through file attachments. In the row of icons below the status post editor is a tool to upload attachments. Click the tool, select a file and submit. After the file is uploaded, you will see an attachment code placed inside the text region. Do not edit this line or it may break the ability for your friends to see the attachment. You can use the post permissions dialogue box or privacy hashtags to restrict the visibility of the file - which will be set to match the permissions of the post your are sending.
-
-To delete attachments or change the permissions on the stored files, visit [observer=1][baseurl]/cloud/[observer.webname][/observer][observer=0][baseurl]/cloud/username replacing username with the nickname you provided during channel creation[/observer].
-
-[h4]Web Access[/h4]
-
-Your files are visible on the web at the location [observer=1][baseurl]/cloud/[observer.webname][/observer][observer=0][baseurl]/cloud/username[/observer] to anybody who is allowed to view them. If the viewer has sufficient privileges, they may also have the ability to create new files and folders/directories. This should only be used for smaller files and photos (up to a few megabytes) as it uses internal memory. For larger files (videos, music, etc.), please upload using WebDAV. These files may still be retrieved via web access.
-
-[h4]WebDAV access[/h4]
-
-WebDAV provides a way to copy files directly to or from your computer's operating system, where your cloud files appear as a virtual disk drive. This should be used to upload large files such as video and audio; as it is not limited to available memory. See [zrl=help/member/member_guide#Cloud_Desktop_Clients]Cloud Desktop Clients[/zrl] below.
-
-[h4]CalDAV and CardDAV access on Android[/h4]
-
-You can sync you calendar and contacts on Android with your Hub.
-
-The following steps where tested for [url=https://f-droid.org/en/packages/at.bitfire.davdroid/]DAVdroid[/url]
-[list]
-[*] install DAVdroid
-[*] add account
-[*] use "URL" and "user name" to login
-[list]
-[*] base url is [baseurl]/cdav
-[*] user name is [observer=1][observer.webname][/observer][observer=0]username[/observer]
-[/list]
-[/list]
-
-To share your calendar visit [observer.baseurl]/cdav/calendar
-
-
-[h4]Permissions[/h4]
-
-When using WebDAV, the file is created with your channel's default file permissions and this cannot be changed from within the operating system. It also may not be as restrictive as you would like. What we've found is that the preferred method of making files private is to first create folders or directories; then visit [observer=1][baseurl]/cloud/[observer.webname][/observer][observer=0][baseurl]/cloud/username[/observer] select the directory and change the permissions. Do this before you put anything into the directory. The directory permissions take precedence so you can then put files or other folders into that container and they will be protected from unwanted viewers by the directory permissions. It is common for folks to create a &quot;personal&quot; or &quot;private&quot; folder which is restricted to themselves. You can use this as a personal cloud to store anything from anywhere on the web or any computer and it is protected from others. You might also create folders for &quot;family&quot; and &quot;friends&quot; with permission granted to appropriate privacy groups.
-
-[h3]Cloud Desktop Clients[/h3]
-
-
-[h4]Cloud Desktop Clients - Windows[/h4]
-
-WebDAV using Windows 7 graphical user interface wizard:
-1. Left-click the Start-button to open the start menu.
-2. Right-click the My computer icon to access its menu.
-3. Left-click Map network drive... to open the connection dialog wizard.
-4. Type '[baseurl]/dav/nickname' in the textbox (replace nickname with your channel nickname) and click the Complete button.
-5. Type your $Projectname channel nickname. IMPORTANT - NO at-sign or domain name.
-6. Type your $Projectname password
-
-[h4]Cloud Desktop Clients - Linux[/h4]
-
-Nautilus
-1. Click on "Network".
-2. In the textfield type davs://my-hub.org/dav/[observer=1][observer.webname][/observer][observer=0]nickname[/observer]
-3. Press "Connect".
-4. User name is [observer=1][observer.webname][/observer][observer=0]your channels nickname[/observer]
-
-[h5]Mount as a filesystem[/h5]
-
-[b]Mounting As A Filesystem[/b]
-
-To install your cloud directory as a filesystem, you first need davfs2 installed. 99% of the time, this will be included in your distributions repositories. In Debian
-
-[code]apt-get install davfs2[/code]
-
-If you want to let normal users mount the filesystem
-
-[code] dpkg-reconfigure davfs2[/code]
-
-and select &quot;yes&quot; at the prompt.
-
-Now you need to add any user you want to be able to mount dav to the davfs2 group
-
-[code]usermod -aG davfs2 &lt;DesktopUser&gt;[/code]
-
-[b]Note:[/b] on some systems the user group may be different, i.e. - "network"
-on Arch Linux. If in doubt, check the davfs documentation for your
-particular OS.
-
-Edit /etc/fstab
-
-[code]nano /etc/fstab[/code]
-
- to include your cloud directory by adding
-
-[code]
-[baseurl]/dav/ /mount/point davfs user,noauto,uid=&lt;DesktopUser&gt;,file_mode=600,dir_mode=700 0 1
-[/code]
-
-Where [baseurl] is the URL of your hub, /mount/point is the location you want to mount the cloud, and &lt;DesktopUser&gt; is the user you log in to one your computer. Note that if you are mounting as a normal user (not root) the mount point must be in your home directory.
-
-For example, if I wanted to mount my cloud to a directory called 'cloud' in my home directory, and my username was bob, my fstab would be
-
-[code][baseurl]/dav/ /home/bob/cloud davfs user,noauto,uid=bob,file_mode=600,dir_mode=700 0 1[/code]
-
-Now, create the mount point.
-
-[code]mkdir /home/bob/cloud[/code]
-
-and also create a directory file to store your credentials
-
-[code]mkdir /home/bob/.davfs2[/code]
-
-Create a file called 'secrets'
-
-[code]nano /home/bob/.davfs2/secrets[/code]
-
-and add your cloud login credentials
-
-[code]
-[baseurl]/dav &lt;username&gt; &lt;password&gt;
-[/code]
-
-Where &lt;username&gt; and &lt;password&gt; are the username and password [i]for your hub[/i].
-
-Don't let this file be writeable by anyone who doesn't need it with
-
-[code]chmod 600 /home/bob/.davfs2/secrets[/code]
-
-Finally, mount the drive.
-
-[code]mount [baseurl]/dav[/code]
-
-You can now find your cloud at /home/bob/cloud and use it as though it were part of your local filesystem - even if the applications you are using have no dav support themselves.
-
-[b]Troubleshooting[/b]
-
-With some webservers and certain configurations, you may find davfs2 creating files with 0 bytes file size where other clients work just fine. This is generally caused by cache and locks. If you are affected by this issue, you need to edit your davfs2 configuration.
-
-[code]nano /etc/davfs2/davfs2.conf[/code]
-
-Your distribution will provide a sample configuration, and this file should already exist, however, most of it will be commented out with a # at the beginning of the line.
-
-First step is to remove locks.
-
-Edit the use_locks line so it reads [code]use_locks 0[/code].
-
-Unmount your file system, remount your file system, and try copying over a file from the command line. Note you should copy a new file, and not overwrite an old one for this test. Leave it a minute or two then do [code]ls -l -h[/code] and check the file size of your new file is still greater than 0 bytes. If it is, stop there, and do nothing else.
-
-If that still doesn't work, disable the cache. Note that this has a performance impact so should only be done if disabling locks didn't solve your problem. Edit the cache_size and set it to [code]cache_size 0[/code] and also set file_refresh to [code]file_refresh 0[/code]. Unmount your filesystem, remount your file system, and test it again.
-
-If it [i]still[/i] doesn't work, there is one more thing you can try. (This one is caused by a bug in older versions of dav2fs itself, so updating to a new version may also help). Enable weak etag dropping by setting [code]drop_weak_etags 1[/code]. Unmount and remount your filesystem to apply the changes.
-
-
-[h5]Dolphin[/h5]
-Visit webdavs://example.com/dav where &quot;example.com&quot; is the URL of your hub.
-
-When prompted for a username and password, enter your channel name (the first part of your webbie - no @ or domain name) and password for your normal account.
-
-Note, if you are already logged in to the web interface via Konqueror, you will not be prompted for further authentication.
-
-[h5]Konqueror[/h5]
-
-Simply visit webdavs://example.com/dav after logging in to your hub, where &quot;example.com&quot; is the URL of your hub.
-
-No further authentication is required if you are logged in to your hub in the normal manner.
-
-Additionally, if one has authenticated at a different hub during their normal browser session, your identity will be passed to the cloud for these hubs too - meaning you can access any private files on any server, as long as you have permissions to see them, as long as you have visited that site earlier in your session.
-
-This functionality is normally restricted to the web interface, and is not available to any desktop software other than KDE.
-
-[h5]Nautilus[/h5]
-
-1. Open a File browsing window (that's Nautilus)
-2. Select File &gt; Connect to server from the menu
-3. Type davs://&lt;domain_name&gt;/dav/&lt;your_channelname&gt; and click Connect
-4. You will be prompted for your channel name (same as above) and password
-5. Your personal DAV directory will be shown in the window
-
-[h5]Nemo[/h5]
-
-For (file browser) Nemo 1.8.2 under Linux Mint 15, Cinnamon 1.8.8. Nemo ist the standard file browser there.
-
-1st way
-type &quot;davs://&lt;domain_name&gt;/dav/&lt;your_channelname&gt;&quot; in the address bar.
-
-2nd way
-Menu &gt; file &gt; connect to server
-Fill the dialog
-- Server: hubzilla_domain_name
-- Type: Secure WebDAV (https)
-- Folder: /dav
-- Username: yourchannelname
-- Password: yourpassword
-
-Once open you can set a bookmark.
-
-[h5]Server Notes[/h5]
-
-Note: There have been reported issues with clients that use "chunked transfer encoding", which includes Apple iOS services, and also the "AnyClient" and "CyberDuck" tools. These work fine for downloads, but uploads often end up with files of zero size. This is caused by an incorrect implemention of chunked encoding in some current FCGI (fast-cgi) implementations. Apache running with PHP as a module does not have these issues, but when running under FCGI you may need to use alternative clients or use the web uploader. At the time of this writing the issue has been open and no updates provided for at least a year. If you encounter zero size files with other clients, please check the client notes; as there are occasional configuration issues which can also produce these symptoms.
-
-[h3]Saved Searches[/h3]
-
-In order to quickly find information, the 'saved search' widget may be used. This widget may be presented as a sidebar tool on your network page and possibly from your channel page. It is differentiated from the 'navigation bar' search tool in that it does not search the entire site, but only the subset of information available to your channel.
-
-Additionally the search terms you provide may activate a one-time search or be saved in a list for re-use. Saving the search item also invokes the search in addition to adding it to the saved list (which is displayed below the search text entry box). Any item in the list may be discarded if it is no longer needed.
-
-The saved search widget will provide autocompletion of channels (the results are prefixed with '@'), and hashtags (prefixed with '#'). You do not need to enter these tags; although entering the desired tag will reduce the autocomplete results to only hold the relevant information. The behaviour maps as follows:
-
-[list]
-[*]@name - search your network stream for posts or comments written by 'name'. This will also change the post editor permissions to include only 'name'; as if this was a privacy group.
-[*]#hashtag - search you network stream for posts containing #hashtag.
-[*]text - search your network stream for posts containing 'text'.
-[/list]
-
-
-[h3]Remove Channel or Account[/h3]
-
-[h4]Remove Channel[/h4]
-
-Select the 'Remove Channel' link on your channel settings page or visit the URL:
-
- [baseurl]/removeme
-
-You will need to confirm your password and the channel you are currently logged into will be removed.
-
-[hl][i][b]This is irreversible.[/b][/i][/hl]
-
-If you have identity clones on other hubs this only removes by default the channel instance which exists on this hub.
-
-[h4]Remove Account[/h4]
-
-Select 'Remove Account' from your account settings page or visit the URL:
-
- [baseurl]/removeaccount
-
-You will need to confirm your password and the account you are currently logged into will be removed.
-
-[hl][i][b]This is irreversible.[/b][/i][/hl]
-
-All your channels will be deleted. If you have identity clones on other hubs this only removes by default the channels instances which exists on this hub.
-
diff --git a/doc/en/member/mentions.md b/doc/en/member/mentions.md
new file mode 100644
index 000000000..ac668c46d
--- /dev/null
+++ b/doc/en/member/mentions.md
@@ -0,0 +1,8 @@
+## Mentions
+
+Channels (users) are labelled by simply prefixing their name (handle) with the @ sign. `@Jack`
+When you mention someone, an autocomplete field is created from which you can select your immediate connections. Select accordingly.
+
+If the contact is authorised to receive your posting, they will receive a tag notification.
+
+If the addressee is not in your contact list, you must write out the handle after the ‘@’ sign. If the recipient also authorises mentions of third parties, they will also be notified of the mention.
diff --git a/doc/en/member/overview.md b/doc/en/member/overview.md
new file mode 100644
index 000000000..cba453093
--- /dev/null
+++ b/doc/en/member/overview.md
@@ -0,0 +1,3 @@
+## Overview
+
+While many of Hubzilla's features and capabilities will be familiar to those who have used social networking sites and blogging software before, there are also some new concepts and features that most people have not yet encountered. Some of the new ideas are related to the decentralised nature of the grid, others to the advanced permission system needed to protect your data. This guide will help you understand how to create, configure and use your nomadic identity.
diff --git a/doc/en/member/permissions.md b/doc/en/member/permissions.md
new file mode 100644
index 000000000..f5704e5f1
--- /dev/null
+++ b/doc/en/member/permissions.md
@@ -0,0 +1,7 @@
+## Permissions
+
+Permissions are a core element of Hubzilla. They allow very fine-grained options for making content accessible, hiding it or restricting its use. They are also used to make direct messages possible by using authorisations to determine who can see the post (direct messages are nothing else) and who cannot.
+
+#include doc/en/member/permissions_content.md;
+#include doc/en/member/permissions_channel_roles.md;
+#include doc/en/member/permissions_contact_roles.md;
diff --git a/doc/en/member/permissions_channel_roles.md b/doc/en/member/permissions_channel_roles.md
new file mode 100644
index 000000000..b968f6ab5
--- /dev/null
+++ b/doc/en/member/permissions_channel_roles.md
@@ -0,0 +1,45 @@
+### Permissions - User-defined channel roles
+
+[Channel roles](channel_roles.md) define which rights are granted when interacting with a channel. They can be accessed under ‘Settings’ → ‘Channel settings’.
+
+The role for a channel can be defined here. Channel roles also have an influence on contact roles because individual rights that are specified and inherited from the channel roles overwrite your own settings there.
+To truly customise the role permissions of your channel, you must select ‘User-defined’ as the channel role.
+
+The other roles (‘Public’, ‘Personal’, ‘Community Forum’) are predefined authorisation roles (see: [Channel roles](channel_roles.md)).
+
+With the customised channel roles, you can define who can perform the following interactions and how:
+
+- Can see my channel stream and my posts
+- Can send me the posts from their channel
+- Can see my default profile
+- Can see my connections
+- Can see my file and image folders
+- Can upload/modify my file and image folders
+- Can see the web pages of my channel
+- Can see my wiki pages
+- Can create/edit web pages in my channel
+- Can edit my wiki pages
+- Can publish posts on my channel page (‘wall’)
+- Can send me direct messages
+- Can like/dislike profiles and profile stuff
+- Can chat with me
+- Can quote/mirror my public posts in other channels
+- Can administer my channel
+
+The following authorisations are then available for these interactions:
+
+- Only me
+- Only those you explicitly allow
+- Accepted connections
+- Any connections
+- Everyone on this website
+- All Hubzilla members
+- Anyone authenticated
+- Anyone on the Internet
+
+To edit the **custom role**, select ‘Privacy settings’ in the settings. At the bottom right you will find the button ‘Custom channel role configuration’. If you click on it, a warning dialogue appears, which draws your attention to the risks of incorrect configuration. If you confirm that you want to edit the rights, the settings dialogue for the user-defined role rights opens.
+
+----
+
+**Important note:**
+The user-defined roles should be set with caution and harbour the risk that the channel will no longer behave as desired with certain configurations.
diff --git a/doc/en/member/permissions_contact_roles.md b/doc/en/member/permissions_contact_roles.md
new file mode 100644
index 000000000..b340930a1
--- /dev/null
+++ b/doc/en/member/permissions_contact_roles.md
@@ -0,0 +1,17 @@
+### Permissions - Contact roles
+
+Contact roles are used to create roles (i.e. a collection of rights and options) for contacts. These roles can then be assigned to a contact or all contacts in a privacy group (not the group itself). This restricts or extends the possibilities of contacts.
+
+The ‘Contact roles’ app can be used to create roles that correspond to the [channel roles](permissions_channel_roles.md). This permission role can then be assigned to individual contacts or all contacts in a privacy group in the contact editor or privacy group editor.
+
+After creation, each channel automatically has the ‘Standard’ contact role (‘System role - not editable’). New contacts are automatically assigned this contact role (unless you create your own contact role, change this default setting and assign the new, customised role to new contacts in future). The default contact role includes authorisations based on the selected channel role. In addition to the rights granted by the channel role, some other rights are granted so that the channel behaves as you would expect based on the channel role (e.g. ‘Public’ is most similar to a ‘normal’ social network channel).
+
+![croles1](/help/en/member/pic/croles1.png)
+
+Note: Some of the rights of a channel role (whether standard or self-created) are inherited from the channel role. These rights cannot be revoked in the contact role. The contact role is a whitelist in which only additional rights can be granted.
+
+You can assign a contact role to a contact in the [connection editor](connection_editor.md). This dialogue also appears when you add a new contact. By default, the contact role for which the ‘Automatically assign this role to new contacts’ switch has been activated is selected here.
+
+![croles2](/help/en/member/pic/croles2.png)
+
+For channels without self-defined contact roles, this is always ‘Standard’. There is also a ‘Contact Roles’ button in the connection editor which takes you to the contact role editor if you want to create a new contact role for the contact.
diff --git a/doc/en/member/permissions_content.md b/doc/en/member/permissions_content.md
new file mode 100644
index 000000000..83d51922c
--- /dev/null
+++ b/doc/en/member/permissions_content.md
@@ -0,0 +1,32 @@
+### Permissions for content
+
+If you share content on Hubzilla, i.e. publish posts, upload images or texts, enter appointments in the calendar, you can define exactly who has access to this content.
+You can access the permission settings for content via a button ("Privacy Tool") with a padlock symbol 🔒or 🔓.
+
+For sharing posts: ![perm 01](/help/en/member/pic/perm01.png)
+
+For creating folders/directories in the cloud storage: ![perm 02](/help/en/member/pic/perm02.png)
+
+For uploading files: ![perm 03](/help/en/member/pic/perm03.png)
+
+For sharing dates/events: ![perm 04](/help/en/member/pic/perm04.png)
+
+There are also corresponding permission setting options for websites, wiki pages and various other content.
+
+If you click on the icon, the permissions dialogue opens, which you can use to set the permissions for other users (this is usually about the visibility of content).
+
+![perm 05](/help/en/member/pic/perm05.png)
+
+You have the choice between
+
+- **Public** - As the name suggests, the content is visible to everyone on the Internet. So even for users who do not use a Fediverse service.
+- **Only me** - Here, only the user who created the content can see it. They ‘share’ it with themselves.
+- **Privacy groups** - The content is visible to all users who are in one of your privacy groups.
+- **Customised selection** - Here you can specify exactly who can see the content. It is also possible to combine privacy groups and individual contacts by selecting ‘Allow’ or ‘Deny’ for the respective entry.
+
+![perm 06](/help/en/member/pic/perm06.png)
+
+----
+
+**Important note:**
+Once permissions for postings have been set, they can no longer be changed! A posting is immediately distributed to an indeterminable number of other servers, so that permissions cannot be subsequently granted or withdrawn, whereas permissions for other content such as files, images, etc. can be subsequently edited because this content is only stored on your own instance (hub) and only the reference to the content is passed on when it is shared.
diff --git a/doc/en/member/photos.md b/doc/en/member/photos.md
new file mode 100644
index 000000000..5a25fa9c3
--- /dev/null
+++ b/doc/en/member/photos.md
@@ -0,0 +1,17 @@
+### Photos
+
+The Photos app is a special management/viewing tool for images uploaded to your own cloud. It displays thumbnails instead of tiles or file names, which makes it easier to find specific images.
+
+![photos 01](/help/en/member/pic/photos01.png)
+
+Clicking on the image will take you to the image view. There are two control icons here to switch to the next or previous photo.
+
+![photos 02](/help/en/member/pic/photos02.png)
+
+There is also a ‘Photo tools’ button that allows you to set the image as a profile picture or banner and to edit the image using a menu.
+
+![photos 03](/help/en/member/pic/photos03.png)
+
+Clicking on the image again will open a full-size view.
+
+![photos 04](/help/en/member/pic/photos04.png)
diff --git a/doc/en/member/pic/apps01.png b/doc/en/member/pic/apps01.png
new file mode 100644
index 000000000..5330c148c
--- /dev/null
+++ b/doc/en/member/pic/apps01.png
Binary files differ
diff --git a/doc/en/member/pic/apps02.png b/doc/en/member/pic/apps02.png
new file mode 100644
index 000000000..ef003e66d
--- /dev/null
+++ b/doc/en/member/pic/apps02.png
Binary files differ
diff --git a/doc/en/member/pic/apps03.png b/doc/en/member/pic/apps03.png
new file mode 100644
index 000000000..65495c445
--- /dev/null
+++ b/doc/en/member/pic/apps03.png
Binary files differ
diff --git a/doc/en/member/pic/apps04.png b/doc/en/member/pic/apps04.png
new file mode 100644
index 000000000..4c219fb6c
--- /dev/null
+++ b/doc/en/member/pic/apps04.png
Binary files differ
diff --git a/doc/en/member/pic/apps05.png b/doc/en/member/pic/apps05.png
new file mode 100644
index 000000000..9fee0ed74
--- /dev/null
+++ b/doc/en/member/pic/apps05.png
Binary files differ
diff --git a/doc/en/member/pic/apps06.png b/doc/en/member/pic/apps06.png
new file mode 100644
index 000000000..566b0a0d6
--- /dev/null
+++ b/doc/en/member/pic/apps06.png
Binary files differ
diff --git a/doc/en/member/pic/apps07.png b/doc/en/member/pic/apps07.png
new file mode 100644
index 000000000..eb9f9029a
--- /dev/null
+++ b/doc/en/member/pic/apps07.png
Binary files differ
diff --git a/doc/en/member/pic/apps08.png b/doc/en/member/pic/apps08.png
new file mode 100644
index 000000000..6f787d357
--- /dev/null
+++ b/doc/en/member/pic/apps08.png
Binary files differ
diff --git a/doc/en/member/pic/article01.png b/doc/en/member/pic/article01.png
new file mode 100644
index 000000000..50788249f
--- /dev/null
+++ b/doc/en/member/pic/article01.png
Binary files differ
diff --git a/doc/en/member/pic/article02.png b/doc/en/member/pic/article02.png
new file mode 100644
index 000000000..0f5c5f504
--- /dev/null
+++ b/doc/en/member/pic/article02.png
Binary files differ
diff --git a/doc/en/member/pic/article03.png b/doc/en/member/pic/article03.png
new file mode 100644
index 000000000..3c9c8330c
--- /dev/null
+++ b/doc/en/member/pic/article03.png
Binary files differ
diff --git a/doc/en/member/pic/article04.png b/doc/en/member/pic/article04.png
new file mode 100644
index 000000000..afb88b9c7
--- /dev/null
+++ b/doc/en/member/pic/article04.png
Binary files differ
diff --git a/doc/en/member/pic/article05.png b/doc/en/member/pic/article05.png
new file mode 100644
index 000000000..e54844b49
--- /dev/null
+++ b/doc/en/member/pic/article05.png
Binary files differ
diff --git a/doc/en/member/pic/author.png b/doc/en/member/pic/author.png
new file mode 100644
index 000000000..75ec9e636
--- /dev/null
+++ b/doc/en/member/pic/author.png
Binary files differ
diff --git a/doc/en/member/pic/block-etc01.png b/doc/en/member/pic/block-etc01.png
new file mode 100644
index 000000000..b04d34203
--- /dev/null
+++ b/doc/en/member/pic/block-etc01.png
Binary files differ
diff --git a/doc/en/member/pic/bookm01.png b/doc/en/member/pic/bookm01.png
new file mode 100644
index 000000000..97cdcab11
--- /dev/null
+++ b/doc/en/member/pic/bookm01.png
Binary files differ
diff --git a/doc/en/member/pic/bookm02.png b/doc/en/member/pic/bookm02.png
new file mode 100644
index 000000000..b57aa435e
--- /dev/null
+++ b/doc/en/member/pic/bookm02.png
Binary files differ
diff --git a/doc/en/member/pic/bookm03.png b/doc/en/member/pic/bookm03.png
new file mode 100644
index 000000000..54ada0db9
--- /dev/null
+++ b/doc/en/member/pic/bookm03.png
Binary files differ
diff --git a/doc/en/member/pic/cal01.png b/doc/en/member/pic/cal01.png
new file mode 100644
index 000000000..16bbce37b
--- /dev/null
+++ b/doc/en/member/pic/cal01.png
Binary files differ
diff --git a/doc/en/member/pic/cal02.png b/doc/en/member/pic/cal02.png
new file mode 100644
index 000000000..217468d57
--- /dev/null
+++ b/doc/en/member/pic/cal02.png
Binary files differ
diff --git a/doc/en/member/pic/cal03.png b/doc/en/member/pic/cal03.png
new file mode 100644
index 000000000..a64a10a1f
--- /dev/null
+++ b/doc/en/member/pic/cal03.png
Binary files differ
diff --git a/doc/en/member/pic/cal04.png b/doc/en/member/pic/cal04.png
new file mode 100644
index 000000000..8dca0419c
--- /dev/null
+++ b/doc/en/member/pic/cal04.png
Binary files differ
diff --git a/doc/en/member/pic/cal05.png b/doc/en/member/pic/cal05.png
new file mode 100644
index 000000000..b13d30f86
--- /dev/null
+++ b/doc/en/member/pic/cal05.png
Binary files differ
diff --git a/doc/en/member/pic/carddav01.png b/doc/en/member/pic/carddav01.png
new file mode 100644
index 000000000..10b02c040
--- /dev/null
+++ b/doc/en/member/pic/carddav01.png
Binary files differ
diff --git a/doc/en/member/pic/carddav02.png b/doc/en/member/pic/carddav02.png
new file mode 100644
index 000000000..eea60f355
--- /dev/null
+++ b/doc/en/member/pic/carddav02.png
Binary files differ
diff --git a/doc/en/member/pic/carddav03.png b/doc/en/member/pic/carddav03.png
new file mode 100644
index 000000000..c711bf5a5
--- /dev/null
+++ b/doc/en/member/pic/carddav03.png
Binary files differ
diff --git a/doc/en/member/pic/carddav04.png b/doc/en/member/pic/carddav04.png
new file mode 100644
index 000000000..56c70c93b
--- /dev/null
+++ b/doc/en/member/pic/carddav04.png
Binary files differ
diff --git a/doc/en/member/pic/carddav05.png b/doc/en/member/pic/carddav05.png
new file mode 100644
index 000000000..59aa8cfa6
--- /dev/null
+++ b/doc/en/member/pic/carddav05.png
Binary files differ
diff --git a/doc/en/member/pic/carddav06.png b/doc/en/member/pic/carddav06.png
new file mode 100644
index 000000000..d38ddd34e
--- /dev/null
+++ b/doc/en/member/pic/carddav06.png
Binary files differ
diff --git a/doc/en/member/pic/carddav07.png b/doc/en/member/pic/carddav07.png
new file mode 100644
index 000000000..9869dcb95
--- /dev/null
+++ b/doc/en/member/pic/carddav07.png
Binary files differ
diff --git a/doc/en/member/pic/center.png b/doc/en/member/pic/center.png
new file mode 100644
index 000000000..c3ce7b125
--- /dev/null
+++ b/doc/en/member/pic/center.png
Binary files differ
diff --git a/doc/en/member/pic/chat01.png b/doc/en/member/pic/chat01.png
new file mode 100644
index 000000000..93bc9a77a
--- /dev/null
+++ b/doc/en/member/pic/chat01.png
Binary files differ
diff --git a/doc/en/member/pic/chat02.png b/doc/en/member/pic/chat02.png
new file mode 100644
index 000000000..0d699f2e8
--- /dev/null
+++ b/doc/en/member/pic/chat02.png
Binary files differ
diff --git a/doc/en/member/pic/clone01.png b/doc/en/member/pic/clone01.png
new file mode 100644
index 000000000..b58ae4948
--- /dev/null
+++ b/doc/en/member/pic/clone01.png
Binary files differ
diff --git a/doc/en/member/pic/clone02.png b/doc/en/member/pic/clone02.png
new file mode 100644
index 000000000..fb4d69541
--- /dev/null
+++ b/doc/en/member/pic/clone02.png
Binary files differ
diff --git a/doc/en/member/pic/clone03.png b/doc/en/member/pic/clone03.png
new file mode 100644
index 000000000..2e97842de
--- /dev/null
+++ b/doc/en/member/pic/clone03.png
Binary files differ
diff --git a/doc/en/member/pic/clone04.png b/doc/en/member/pic/clone04.png
new file mode 100644
index 000000000..16b2d5298
--- /dev/null
+++ b/doc/en/member/pic/clone04.png
Binary files differ
diff --git a/doc/en/member/pic/clone05.png b/doc/en/member/pic/clone05.png
new file mode 100644
index 000000000..5ac750c8b
--- /dev/null
+++ b/doc/en/member/pic/clone05.png
Binary files differ
diff --git a/doc/en/member/pic/code.png b/doc/en/member/pic/code.png
new file mode 100644
index 000000000..2bed8e01a
--- /dev/null
+++ b/doc/en/member/pic/code.png
Binary files differ
diff --git a/doc/en/member/pic/comment01.png b/doc/en/member/pic/comment01.png
new file mode 100644
index 000000000..c84079ba4
--- /dev/null
+++ b/doc/en/member/pic/comment01.png
Binary files differ
diff --git a/doc/en/member/pic/conn01.png b/doc/en/member/pic/conn01.png
new file mode 100644
index 000000000..142b048b3
--- /dev/null
+++ b/doc/en/member/pic/conn01.png
Binary files differ
diff --git a/doc/en/member/pic/conn02.png b/doc/en/member/pic/conn02.png
new file mode 100644
index 000000000..06cfc042c
--- /dev/null
+++ b/doc/en/member/pic/conn02.png
Binary files differ
diff --git a/doc/en/member/pic/conn03.png b/doc/en/member/pic/conn03.png
new file mode 100644
index 000000000..72fb98e6f
--- /dev/null
+++ b/doc/en/member/pic/conn03.png
Binary files differ
diff --git a/doc/en/member/pic/conn04.png b/doc/en/member/pic/conn04.png
new file mode 100644
index 000000000..e2d88c251
--- /dev/null
+++ b/doc/en/member/pic/conn04.png
Binary files differ
diff --git a/doc/en/member/pic/conn05.png b/doc/en/member/pic/conn05.png
new file mode 100644
index 000000000..87fa312be
--- /dev/null
+++ b/doc/en/member/pic/conn05.png
Binary files differ
diff --git a/doc/en/member/pic/conn06.png b/doc/en/member/pic/conn06.png
new file mode 100644
index 000000000..085d85480
--- /dev/null
+++ b/doc/en/member/pic/conn06.png
Binary files differ
diff --git a/doc/en/member/pic/croles1.png b/doc/en/member/pic/croles1.png
new file mode 100644
index 000000000..a70a2376b
--- /dev/null
+++ b/doc/en/member/pic/croles1.png
Binary files differ
diff --git a/doc/en/member/pic/croles2.png b/doc/en/member/pic/croles2.png
new file mode 100644
index 000000000..2c3451501
--- /dev/null
+++ b/doc/en/member/pic/croles2.png
Binary files differ
diff --git a/doc/en/member/pic/delacc01.png b/doc/en/member/pic/delacc01.png
new file mode 100644
index 000000000..83a4c5fae
--- /dev/null
+++ b/doc/en/member/pic/delacc01.png
Binary files differ
diff --git a/doc/en/member/pic/delchan01.png b/doc/en/member/pic/delchan01.png
new file mode 100644
index 000000000..e86103194
--- /dev/null
+++ b/doc/en/member/pic/delchan01.png
Binary files differ
diff --git a/doc/en/member/pic/directory.png b/doc/en/member/pic/directory.png
new file mode 100644
index 000000000..4bbc8447a
--- /dev/null
+++ b/doc/en/member/pic/directory.png
Binary files differ
diff --git a/doc/en/member/pic/files01.png b/doc/en/member/pic/files01.png
new file mode 100644
index 000000000..cc6998ae4
--- /dev/null
+++ b/doc/en/member/pic/files01.png
Binary files differ
diff --git a/doc/en/member/pic/files02.png b/doc/en/member/pic/files02.png
new file mode 100644
index 000000000..7fc9d76b7
--- /dev/null
+++ b/doc/en/member/pic/files02.png
Binary files differ
diff --git a/doc/en/member/pic/files03.png b/doc/en/member/pic/files03.png
new file mode 100644
index 000000000..4e57ae850
--- /dev/null
+++ b/doc/en/member/pic/files03.png
Binary files differ
diff --git a/doc/en/member/pic/font.png b/doc/en/member/pic/font.png
new file mode 100644
index 000000000..598448ce7
--- /dev/null
+++ b/doc/en/member/pic/font.png
Binary files differ
diff --git a/doc/en/member/pic/gal01.png b/doc/en/member/pic/gal01.png
new file mode 100644
index 000000000..b3b1cec56
--- /dev/null
+++ b/doc/en/member/pic/gal01.png
Binary files differ
diff --git a/doc/en/member/pic/hbar.png b/doc/en/member/pic/hbar.png
new file mode 100644
index 000000000..10594f231
--- /dev/null
+++ b/doc/en/member/pic/hbar.png
Binary files differ
diff --git a/doc/en/member/pic/highlited.png b/doc/en/member/pic/highlited.png
new file mode 100644
index 000000000..4dc0224f1
--- /dev/null
+++ b/doc/en/member/pic/highlited.png
Binary files differ
diff --git a/doc/en/member/pic/image.png b/doc/en/member/pic/image.png
new file mode 100644
index 000000000..ecb300cd2
--- /dev/null
+++ b/doc/en/member/pic/image.png
Binary files differ
diff --git a/doc/en/member/pic/interact01.png b/doc/en/member/pic/interact01.png
new file mode 100644
index 000000000..e73f0a789
--- /dev/null
+++ b/doc/en/member/pic/interact01.png
Binary files differ
diff --git a/doc/en/member/pic/interact02.png b/doc/en/member/pic/interact02.png
new file mode 100644
index 000000000..81b2c84d5
--- /dev/null
+++ b/doc/en/member/pic/interact02.png
Binary files differ
diff --git a/doc/en/member/pic/interact03.png b/doc/en/member/pic/interact03.png
new file mode 100644
index 000000000..5b3985f2d
--- /dev/null
+++ b/doc/en/member/pic/interact03.png
Binary files differ
diff --git a/doc/en/member/pic/mauth.png b/doc/en/member/pic/mauth.png
new file mode 100644
index 000000000..5e624124b
--- /dev/null
+++ b/doc/en/member/pic/mauth.png
Binary files differ
diff --git a/doc/en/member/pic/nsfw01.png b/doc/en/member/pic/nsfw01.png
new file mode 100644
index 000000000..4a4eec074
--- /dev/null
+++ b/doc/en/member/pic/nsfw01.png
Binary files differ
diff --git a/doc/en/member/pic/nsfw02.png b/doc/en/member/pic/nsfw02.png
new file mode 100644
index 000000000..4d344fcd7
--- /dev/null
+++ b/doc/en/member/pic/nsfw02.png
Binary files differ
diff --git a/doc/en/member/pic/nsfw03.png b/doc/en/member/pic/nsfw03.png
new file mode 100644
index 000000000..5bc63d258
--- /dev/null
+++ b/doc/en/member/pic/nsfw03.png
Binary files differ
diff --git a/doc/en/member/pic/perm01.png b/doc/en/member/pic/perm01.png
new file mode 100644
index 000000000..178990892
--- /dev/null
+++ b/doc/en/member/pic/perm01.png
Binary files differ
diff --git a/doc/en/member/pic/perm02.png b/doc/en/member/pic/perm02.png
new file mode 100644
index 000000000..ced652bb9
--- /dev/null
+++ b/doc/en/member/pic/perm02.png
Binary files differ
diff --git a/doc/en/member/pic/perm03.png b/doc/en/member/pic/perm03.png
new file mode 100644
index 000000000..d7cb84dcc
--- /dev/null
+++ b/doc/en/member/pic/perm03.png
Binary files differ
diff --git a/doc/en/member/pic/perm04.png b/doc/en/member/pic/perm04.png
new file mode 100644
index 000000000..ad9575630
--- /dev/null
+++ b/doc/en/member/pic/perm04.png
Binary files differ
diff --git a/doc/en/member/pic/perm05.png b/doc/en/member/pic/perm05.png
new file mode 100644
index 000000000..6f923df62
--- /dev/null
+++ b/doc/en/member/pic/perm05.png
Binary files differ
diff --git a/doc/en/member/pic/perm06.png b/doc/en/member/pic/perm06.png
new file mode 100644
index 000000000..7ba94f2a0
--- /dev/null
+++ b/doc/en/member/pic/perm06.png
Binary files differ
diff --git a/doc/en/member/pic/pgroups01.png b/doc/en/member/pic/pgroups01.png
new file mode 100644
index 000000000..642082311
--- /dev/null
+++ b/doc/en/member/pic/pgroups01.png
Binary files differ
diff --git a/doc/en/member/pic/pgroups02.png b/doc/en/member/pic/pgroups02.png
new file mode 100644
index 000000000..a144ca0ad
--- /dev/null
+++ b/doc/en/member/pic/pgroups02.png
Binary files differ
diff --git a/doc/en/member/pic/pgroups03.png b/doc/en/member/pic/pgroups03.png
new file mode 100644
index 000000000..a6096d836
--- /dev/null
+++ b/doc/en/member/pic/pgroups03.png
Binary files differ
diff --git a/doc/en/member/pic/pgroups04.png b/doc/en/member/pic/pgroups04.png
new file mode 100644
index 000000000..a2a992dbc
--- /dev/null
+++ b/doc/en/member/pic/pgroups04.png
Binary files differ
diff --git a/doc/en/member/pic/photos01.png b/doc/en/member/pic/photos01.png
new file mode 100644
index 000000000..a1cfd009f
--- /dev/null
+++ b/doc/en/member/pic/photos01.png
Binary files differ
diff --git a/doc/en/member/pic/photos02.png b/doc/en/member/pic/photos02.png
new file mode 100644
index 000000000..3d82c5660
--- /dev/null
+++ b/doc/en/member/pic/photos02.png
Binary files differ
diff --git a/doc/en/member/pic/photos03.png b/doc/en/member/pic/photos03.png
new file mode 100644
index 000000000..b5abc89a5
--- /dev/null
+++ b/doc/en/member/pic/photos03.png
Binary files differ
diff --git a/doc/en/member/pic/photos04.png b/doc/en/member/pic/photos04.png
new file mode 100644
index 000000000..509c77260
--- /dev/null
+++ b/doc/en/member/pic/photos04.png
Binary files differ
diff --git a/doc/en/member/pic/picture01.png b/doc/en/member/pic/picture01.png
new file mode 100644
index 000000000..7389e778d
--- /dev/null
+++ b/doc/en/member/pic/picture01.png
Binary files differ
diff --git a/doc/en/member/pic/picture02.png b/doc/en/member/pic/picture02.png
new file mode 100644
index 000000000..0b0f89ca0
--- /dev/null
+++ b/doc/en/member/pic/picture02.png
Binary files differ
diff --git a/doc/en/member/pic/picture03.png b/doc/en/member/pic/picture03.png
new file mode 100644
index 000000000..6efbfb4fb
--- /dev/null
+++ b/doc/en/member/pic/picture03.png
Binary files differ
diff --git a/doc/en/member/pic/picture04.png b/doc/en/member/pic/picture04.png
new file mode 100644
index 000000000..a837b69bf
--- /dev/null
+++ b/doc/en/member/pic/picture04.png
Binary files differ
diff --git a/doc/en/member/pic/picture05.png b/doc/en/member/pic/picture05.png
new file mode 100644
index 000000000..f180d8686
--- /dev/null
+++ b/doc/en/member/pic/picture05.png
Binary files differ
diff --git a/doc/en/member/pic/picture06.png b/doc/en/member/pic/picture06.png
new file mode 100644
index 000000000..d9bff1e54
--- /dev/null
+++ b/doc/en/member/pic/picture06.png
Binary files differ
diff --git a/doc/en/member/pic/posting01.png b/doc/en/member/pic/posting01.png
new file mode 100644
index 000000000..735bb0644
--- /dev/null
+++ b/doc/en/member/pic/posting01.png
Binary files differ
diff --git a/doc/en/member/pic/qrcode.png b/doc/en/member/pic/qrcode.png
new file mode 100644
index 000000000..0db383feb
--- /dev/null
+++ b/doc/en/member/pic/qrcode.png
Binary files differ
diff --git a/doc/en/member/pic/quote.png b/doc/en/member/pic/quote.png
new file mode 100644
index 000000000..cef0df619
--- /dev/null
+++ b/doc/en/member/pic/quote.png
Binary files differ
diff --git a/doc/en/member/pic/red.png b/doc/en/member/pic/red.png
new file mode 100644
index 000000000..227f8ccda
--- /dev/null
+++ b/doc/en/member/pic/red.png
Binary files differ
diff --git a/doc/en/member/pic/savefolder01.png b/doc/en/member/pic/savefolder01.png
new file mode 100644
index 000000000..dd95987ea
--- /dev/null
+++ b/doc/en/member/pic/savefolder01.png
Binary files differ
diff --git a/doc/en/member/pic/savefolder02.png b/doc/en/member/pic/savefolder02.png
new file mode 100644
index 000000000..d775ddae6
--- /dev/null
+++ b/doc/en/member/pic/savefolder02.png
Binary files differ
diff --git a/doc/en/member/pic/savefolder03.png b/doc/en/member/pic/savefolder03.png
new file mode 100644
index 000000000..896ec24b2
--- /dev/null
+++ b/doc/en/member/pic/savefolder03.png
Binary files differ
diff --git a/doc/en/member/pic/savefolder04.png b/doc/en/member/pic/savefolder04.png
new file mode 100644
index 000000000..7f21ed49c
--- /dev/null
+++ b/doc/en/member/pic/savefolder04.png
Binary files differ
diff --git a/doc/en/member/pic/search01.png b/doc/en/member/pic/search01.png
new file mode 100644
index 000000000..75cc8bce6
--- /dev/null
+++ b/doc/en/member/pic/search01.png
Binary files differ
diff --git a/doc/en/member/pic/search02.png b/doc/en/member/pic/search02.png
new file mode 100644
index 000000000..feaec66ad
--- /dev/null
+++ b/doc/en/member/pic/search02.png
Binary files differ
diff --git a/doc/en/member/pic/search03.png b/doc/en/member/pic/search03.png
new file mode 100644
index 000000000..b68d81fa7
--- /dev/null
+++ b/doc/en/member/pic/search03.png
Binary files differ
diff --git a/doc/en/member/pic/settings01.png b/doc/en/member/pic/settings01.png
new file mode 100644
index 000000000..7d54b766f
--- /dev/null
+++ b/doc/en/member/pic/settings01.png
Binary files differ
diff --git a/doc/en/member/pic/settings02.png b/doc/en/member/pic/settings02.png
new file mode 100644
index 000000000..dfd5d3df5
--- /dev/null
+++ b/doc/en/member/pic/settings02.png
Binary files differ
diff --git a/doc/en/member/pic/settings03.png b/doc/en/member/pic/settings03.png
new file mode 100644
index 000000000..a331b9306
--- /dev/null
+++ b/doc/en/member/pic/settings03.png
Binary files differ
diff --git a/doc/en/member/pic/settings04.png b/doc/en/member/pic/settings04.png
new file mode 100644
index 000000000..4a18df7f6
--- /dev/null
+++ b/doc/en/member/pic/settings04.png
Binary files differ
diff --git a/doc/en/member/pic/settings05.png b/doc/en/member/pic/settings05.png
new file mode 100644
index 000000000..beff5d823
--- /dev/null
+++ b/doc/en/member/pic/settings05.png
Binary files differ
diff --git a/doc/en/member/pic/settings06.png b/doc/en/member/pic/settings06.png
new file mode 100644
index 000000000..c66c4182e
--- /dev/null
+++ b/doc/en/member/pic/settings06.png
Binary files differ
diff --git a/doc/en/member/pic/settings07.png b/doc/en/member/pic/settings07.png
new file mode 100644
index 000000000..39c294ebe
--- /dev/null
+++ b/doc/en/member/pic/settings07.png
Binary files differ
diff --git a/doc/en/member/pic/settings08.png b/doc/en/member/pic/settings08.png
new file mode 100644
index 000000000..0dab2d1f2
--- /dev/null
+++ b/doc/en/member/pic/settings08.png
Binary files differ
diff --git a/doc/en/member/pic/settings09.png b/doc/en/member/pic/settings09.png
new file mode 100644
index 000000000..964a2e0c0
--- /dev/null
+++ b/doc/en/member/pic/settings09.png
Binary files differ
diff --git a/doc/en/member/pic/settings10.png b/doc/en/member/pic/settings10.png
new file mode 100644
index 000000000..939c43db8
--- /dev/null
+++ b/doc/en/member/pic/settings10.png
Binary files differ
diff --git a/doc/en/member/pic/settings11.png b/doc/en/member/pic/settings11.png
new file mode 100644
index 000000000..7a2c4d5ae
--- /dev/null
+++ b/doc/en/member/pic/settings11.png
Binary files differ
diff --git a/doc/en/member/pic/settings12.png b/doc/en/member/pic/settings12.png
new file mode 100644
index 000000000..6429464f4
--- /dev/null
+++ b/doc/en/member/pic/settings12.png
Binary files differ
diff --git a/doc/en/member/pic/settings13.png b/doc/en/member/pic/settings13.png
new file mode 100644
index 000000000..22a36dbd5
--- /dev/null
+++ b/doc/en/member/pic/settings13.png
Binary files differ
diff --git a/doc/en/member/pic/settings14.png b/doc/en/member/pic/settings14.png
new file mode 100644
index 000000000..bd8d0d074
--- /dev/null
+++ b/doc/en/member/pic/settings14.png
Binary files differ
diff --git a/doc/en/member/pic/settings15.png b/doc/en/member/pic/settings15.png
new file mode 100644
index 000000000..7e5378dac
--- /dev/null
+++ b/doc/en/member/pic/settings15.png
Binary files differ
diff --git a/doc/en/member/pic/settings16.png b/doc/en/member/pic/settings16.png
new file mode 100644
index 000000000..1a7c29809
--- /dev/null
+++ b/doc/en/member/pic/settings16.png
Binary files differ
diff --git a/doc/en/member/pic/settings17.png b/doc/en/member/pic/settings17.png
new file mode 100644
index 000000000..a8e690d0f
--- /dev/null
+++ b/doc/en/member/pic/settings17.png
Binary files differ
diff --git a/doc/en/member/pic/settings18.png b/doc/en/member/pic/settings18.png
new file mode 100644
index 000000000..88f05e28a
--- /dev/null
+++ b/doc/en/member/pic/settings18.png
Binary files differ
diff --git a/doc/en/member/pic/settings19.png b/doc/en/member/pic/settings19.png
new file mode 100644
index 000000000..d376d42e1
--- /dev/null
+++ b/doc/en/member/pic/settings19.png
Binary files differ
diff --git a/doc/en/member/pic/settings20.png b/doc/en/member/pic/settings20.png
new file mode 100644
index 000000000..2925de06c
--- /dev/null
+++ b/doc/en/member/pic/settings20.png
Binary files differ
diff --git a/doc/en/member/pic/settings21.png b/doc/en/member/pic/settings21.png
new file mode 100644
index 000000000..74b589177
--- /dev/null
+++ b/doc/en/member/pic/settings21.png
Binary files differ
diff --git a/doc/en/member/pic/settings22.png b/doc/en/member/pic/settings22.png
new file mode 100644
index 000000000..d18762634
--- /dev/null
+++ b/doc/en/member/pic/settings22.png
Binary files differ
diff --git a/doc/en/member/pic/settings23.png b/doc/en/member/pic/settings23.png
new file mode 100644
index 000000000..b10adab2b
--- /dev/null
+++ b/doc/en/member/pic/settings23.png
Binary files differ
diff --git a/doc/en/member/pic/settings24.png b/doc/en/member/pic/settings24.png
new file mode 100644
index 000000000..e55a08a96
--- /dev/null
+++ b/doc/en/member/pic/settings24.png
Binary files differ
diff --git a/doc/en/member/pic/size.png b/doc/en/member/pic/size.png
new file mode 100644
index 000000000..f87ab74cf
--- /dev/null
+++ b/doc/en/member/pic/size.png
Binary files differ
diff --git a/doc/en/member/pic/source.png b/doc/en/member/pic/source.png
new file mode 100644
index 000000000..bb1db07a1
--- /dev/null
+++ b/doc/en/member/pic/source.png
Binary files differ
diff --git a/doc/en/member/pic/star.png b/doc/en/member/pic/star.png
new file mode 100644
index 000000000..ed6206c1f
--- /dev/null
+++ b/doc/en/member/pic/star.png
Binary files differ
diff --git a/doc/en/member/pic/table1.png b/doc/en/member/pic/table1.png
new file mode 100644
index 000000000..99a4fb9a0
--- /dev/null
+++ b/doc/en/member/pic/table1.png
Binary files differ
diff --git a/doc/en/member/pic/table2.png b/doc/en/member/pic/table2.png
new file mode 100644
index 000000000..ed241a6d6
--- /dev/null
+++ b/doc/en/member/pic/table2.png
Binary files differ
diff --git a/doc/en/member/pic/table3.png b/doc/en/member/pic/table3.png
new file mode 100644
index 000000000..9b7acceb3
--- /dev/null
+++ b/doc/en/member/pic/table3.png
Binary files differ
diff --git a/doc/en/member/pic/video_poster.png b/doc/en/member/pic/video_poster.png
new file mode 100644
index 000000000..48b27fb91
--- /dev/null
+++ b/doc/en/member/pic/video_poster.png
Binary files differ
diff --git a/doc/en/member/pic/websites01.png b/doc/en/member/pic/websites01.png
new file mode 100644
index 000000000..f2eb31c29
--- /dev/null
+++ b/doc/en/member/pic/websites01.png
Binary files differ
diff --git a/doc/en/member/pic/websites02.png b/doc/en/member/pic/websites02.png
new file mode 100644
index 000000000..0e22c2232
--- /dev/null
+++ b/doc/en/member/pic/websites02.png
Binary files differ
diff --git a/doc/en/member/pic/websites03.png b/doc/en/member/pic/websites03.png
new file mode 100644
index 000000000..e061a2fcf
--- /dev/null
+++ b/doc/en/member/pic/websites03.png
Binary files differ
diff --git a/doc/en/member/pic/websites04.png b/doc/en/member/pic/websites04.png
new file mode 100644
index 000000000..5d28d9291
--- /dev/null
+++ b/doc/en/member/pic/websites04.png
Binary files differ
diff --git a/doc/en/member/pic/websites05.png b/doc/en/member/pic/websites05.png
new file mode 100644
index 000000000..c9efe1471
--- /dev/null
+++ b/doc/en/member/pic/websites05.png
Binary files differ
diff --git a/doc/en/member/pic/websites06.png b/doc/en/member/pic/websites06.png
new file mode 100644
index 000000000..d9da6f79a
--- /dev/null
+++ b/doc/en/member/pic/websites06.png
Binary files differ
diff --git a/doc/en/member/pic/wiki01.png b/doc/en/member/pic/wiki01.png
new file mode 100644
index 000000000..3fc603669
--- /dev/null
+++ b/doc/en/member/pic/wiki01.png
Binary files differ
diff --git a/doc/en/member/pic/wiki02.png b/doc/en/member/pic/wiki02.png
new file mode 100644
index 000000000..9921e2c5c
--- /dev/null
+++ b/doc/en/member/pic/wiki02.png
Binary files differ
diff --git a/doc/en/member/pic/wiki03.png b/doc/en/member/pic/wiki03.png
new file mode 100644
index 000000000..ab7126ab8
--- /dev/null
+++ b/doc/en/member/pic/wiki03.png
Binary files differ
diff --git a/doc/en/member/pic/wiki04.png b/doc/en/member/pic/wiki04.png
new file mode 100644
index 000000000..9ac2a29ff
--- /dev/null
+++ b/doc/en/member/pic/wiki04.png
Binary files differ
diff --git a/doc/en/member/posting.md b/doc/en/member/posting.md
new file mode 100644
index 000000000..3a10cde91
--- /dev/null
+++ b/doc/en/member/posting.md
@@ -0,0 +1,16 @@
+## Posting
+
+If you would like to write and share a post (publish, although the circle of recipients or those who can see the post may be restricted), you can usually do this via the ‘Share’ field located above the stream. Click on this field to open the post editor.
+
+At the top is the field for the post title (optional), below this is the field for the summary (also optional), if the administrator of your hub allows this function. The summary can also be used for the purpose of a content warning. Below the field for the summary is a field for categories (if activated by the admin).
+Below this is the text field in which you can create the post content. Depending on the Hub settings, you can use plain text, Markdown, bbCode or HTML for formatting the text.
+
+At the bottom of the post editor there are some buttons for easier formatting of the content and for inserting elements and using additional functions: bold, italic, underline, quote, code, attach/upload file, insert link, insert image (an image that already exists under Files), insert location, set expiry date for the post, set publication date, encrypt text, vote (poll) on/off, deactivate comments. To the right of this is another block with buttons. Here you can display a preview of the post, specify whether the post should be published on other networks, make the authorisation settings (who can see the post) and finally publish it using the ‘Share’ button.
+
+![Posting](/help/en/member/pic/posting01.png)
+
+You can also access the post editor by selecting the corresponding menu item in the app menu (top right ⋮) or the corresponding icon in the navigation bar (if you have pinned the ‘Write post’ app).
+
+#include doc/en/member/commenting.md;
+#include doc/en/member/insert_images.md;
+#include doc/en/member/bbcode.md;
diff --git a/doc/en/member/privacy_groups.md b/doc/en/member/privacy_groups.md
new file mode 100644
index 000000000..3b44d0a19
--- /dev/null
+++ b/doc/en/member/privacy_groups.md
@@ -0,0 +1,24 @@
+## Privacy Groups
+
+The ‘Privacy Groups’ app allows you to create groups to which you can assign contacts. On the one hand, they serve to filter the stream (so you can only display posts from users who are in a privacy group) and, on the other hand, they allow you to grant certain groups rights to content with regard to [permissions](permissions_contact_roles.md).
+
+The first function is easy to understand. If you have contacts (a contact can be in several groups) in a group and you select a specific group in the left sidebar in the stream view, only posts from contacts in that group will be displayed. This function thus acts as a stream filter.
+
+The second function is also easy to grasp, but rather unusual for many Fediverse users, since it only exists in this form in Hubzilla and related services (Streams, Friendica etc.). As the name ‘Privacy Groups’ suggests, this is also about restricted communication. If you select a group as the authorisation when composing a post, the post is only distributed to the contacts contained in that group and only they can see it. It is also not possible for the recipients (group members) to share such a post publicly. This allows for closed group communication.
+
+When you open the app, existing groups are displayed in the left sidebar and the input form for creating a new group is displayed in the main view.
+
+![pgrups 01](/help/en/member/pic/pgroups01.png)
+
+If you select one of the groups in the sidebar, you can edit it.
+
+![pgrups 02](/help/en/member/pic/pgroups02.png)
+
+![pgrups 03](/help/en/member/pic/pgroups03.png)
+
+Membership for contacts can also be set here. Clicking on an entry toggles the membership between ‘Not in group’ and ‘Group membership’. This way, you can remove members from a group or add users as group members.
+Adding a contact to a group can also be done in the ‘Connections’ app using the contacts tool:
+
+![pgrups 04](/help/en/member/pic/pgroups04.png)
+
+To add a new group, click on the ‘+ Add new group’ entry in the sidebar.
diff --git a/doc/en/member/privacy_settings.md b/doc/en/member/privacy_settings.md
new file mode 100644
index 000000000..ea03cc73a
--- /dev/null
+++ b/doc/en/member/privacy_settings.md
@@ -0,0 +1,5 @@
+#### Privacy settings
+
+In the privacy settings, you can determine whether your posts may be indexed by search engines, whether you accept contact requests automatically (without manual approval), whether all messages in which you are mentioned are automatically accepted, whether comments from users who are not among your contacts are submitted for moderation (approved/rejected) or deleted, and whether you allow OCAP access.
+
+![settings 06](/help/en/usermanual/pic/settings06.png)
diff --git a/doc/en/member/profiles.md b/doc/en/member/profiles.md
new file mode 100644
index 000000000..8f31061b4
--- /dev/null
+++ b/doc/en/member/profiles.md
@@ -0,0 +1,21 @@
+### Profiles
+
+Hubzilla has unlimited profiles. You can use different profiles to show different ‘sides of yourself’ to different target groups. This is not the same as having different channels. Different channels allow for completely different information. You can have a channel for yourself, a channel for your sports team, a channel for your website or something else. A profile allows for fine-grained ‘’sides‘’ of each channel. Different profiles could be compared to different business cards of a person. Depending on the purpose, different information is given on each business card. For example, your standard public profile could read: ‘Hi, I'm Fred and I like to laugh’. You can show your close friends a profile that says ‘and I also like to throw dwarfs’.
+
+You always have a profile that is referred to as your ‘standard’ or ‘public’ profile. This profile is always accessible to the general public and cannot be hidden (there may be rare exceptions on privately run or unaffiliated sites). You can and should limit the information you make available in your public profile.
+If you want your friends to be able to find you, it is helpful if you include the following information in your public profile...
+
+- Your real name or at least a nickname that everyone knows
+- A photo of you
+- Your location on earth, at least at country level.
+
+If you also want to meet people who share general interests with you, please take a moment to add some ‘keywords’ to your profile. For example, ‘music, linux, photography’ or something similar. You can add as many keywords as you like.
+
+Select ‘Edit profiles’ from the menu on your Hubzilla site. You can edit an existing profile, change the profile photo, add things to a profile or create a new profile. You can also create a ‘clone’ of an existing profile if you only want to change a few things but don't want to re-enter all the information. To do this, click on the profile you want to clone and select ‘Clone this profile’.
+
+In the list of your profiles, you can also select the contacts who can see a particular profile. Simply click on ‘Edit visibility’ next to the profile in question (only available for profiles that are not your default profile) and then click on specific connections to add them to or remove them from the group of people who can see this profile.
+
+Once a profile has been selected, the person viewing your profile will see the private profile you have assigned. If the person is not authenticated, they will see your public profile.
+There is a setting that allows you to publish your profile in a directory and ensure that it can be found by others. You can change this setting on the ‘Settings’ page.
+
+If you do not want others to find you without telling them your channel address, you can leave your profile unpublished.
diff --git a/doc/en/member/protection_of_privacy.md b/doc/en/member/protection_of_privacy.md
new file mode 100644
index 000000000..be4eee9e0
--- /dev/null
+++ b/doc/en/member/protection_of_privacy.md
@@ -0,0 +1,65 @@
+## Tips for protecting your privacy
+
+If you attach great importance to your privacy and still want to participate in Fediverse, you need to think carefully about what you want to reveal about yourself before and during the creation of a personal channel. This is the case with every Fediverse service. With Hubzilla, however, there is another important aspect. You not only have to ask yourself the question ‘What?’, but also ‘To whom?’ and ‘Which?’.
+
+With Hubzilla, you not only determine what you disclose about yourself, but also who you allow to see the information and content. And who you allow to interact with what.
+
+The advantage is that you are not dependent on a ‘rule set’, but can define different rules for different applications and different contacts.
+
+A typical use case would be that you want to participate in Fediverse in the normal way that you are familiar with from other social networks.
+
+When creating a channel, you must make the first relevant decision: **the channel role**.
+
+Here you can choose between ‘Public’, ‘Personal’, ‘Community Forum’ and ‘ Customised’.
+
+Apart from the ‘Community Forum’, which is intended for other applications, you have the choice between three roles.
+
+1. With the ‘Public’ role, you allow others to
+ 1. see your channel stream (i.e. the posts that you share publicly) and your posts in general,
+ 2. see your standard profile,
+ 3. see your connections,
+ 4. see your file and image folders,
+ 5. see the web pages of your channel,
+ 6. see the wiki pages of your channel,
+ 7. comment on, like or dislike your posts,
+ 8. send you direct messages,
+ 9. like or dislike your profiles and profile content and
+ 10. chat with you.
+
+These role-based rules reflect the ‘normal’ use of a social network quite well.
+
+1. The ‘Personal’ role is similar and only denies some of the permissions of the ‘Public’ role. It allows others to
+ 1. see your channel stream (i.e. the posts that you share publicly) and your posts in general,
+ 2. see your default profile,
+ 3. ./.
+ 4. see your file and image folders,
+ 5. see the web pages of your channel,
+ 6. see the wiki pages of your channel,
+ 7. ./.
+ 8. ./.
+ 9. ./.
+ 10. ./.
+
+Interaction by other users is restricted with this profile, as they are not allowed to comment on, like or dislike your posts (the latter also not in relation to your profile/profile content). They are also not allowed to send you direct messages or chat with you.
+
+The ‘User-defined’ role allows you to define all authorisations individually. Caution is advised here, as inappropriate rules can make a channel halfway ‘unusable’.
+
+For the intended use as a ‘typical social network account’, we recommend selecting the ‘Public’ or ‘Personal’ role.
+
+If you would rather opt for the ‘Personal’ role, but would still like to allow further interaction with certain users (friends, family, colleagues, etc.), you do not have to use the ‘Public’ role.
+
+Hubzilla works with whitelists (permission lists) for authorisations. The channel role therefore defines the basic authorisations. You cannot subsequently revoke these (apart from defining access rights in the specific individual case of a content) using other mechanisms.
+
+However, you can add various authorisations to the whitelist using different **contact roles**. For example, you could create a ‘Family’ contact role in which - in addition to the authorisations granted by the ‘Personal’ role - further authorisations (e.g. commenting, linking, disliking and sending direct messages) are granted. If you now assign this contact role to your contacts that you define as ‘Family’ in this example, your family members - unlike everyone else - can write comments, give you a thumbs up or thumbs down and communicate with you non-publicly (direct message).
+
+You can create as many contact roles as you like for different purposes and contacts and grant additional authorisations in addition to those of the channel role. But(!): You cannot revoke any authorisation from the channel role there.
+
+It is therefore advisable not to be too generous with the channel role and to select it accordingly, depending on the purpose of the channel. By selecting ‘User-defined’, for example, you could define an even more restrictive channel role than ‘Personal’ and then define further authorisations for certain users with the contact roles (only recommended if you are really familiar with the authorisation system).
+
+Another aspect of privacy is the **profile** information. For ‘typical’ use as a social network account, some information should be disclosed in the profile. Otherwise, other users will not have the idea of connecting with you. Or they want to connect with another user, but the other user refuses because they have no information about you (unless they know you and your channel name). So some information should go in there.
+
+As much as necessary, as little as possible.
+
+You should fill your standard profile, which every channel has, with information according to exactly this principle.
+
+However, Hubzilla allows you to create multiple profiles. In such profiles, you can then enter further information that may be of interest for certain connections. You then have the option of releasing such special profiles for certain connections. The information is therefore not visible and public to everyone, but is only available to the selected users.
diff --git a/doc/en/member/public_stream.md b/doc/en/member/public_stream.md
new file mode 100644
index 000000000..4fa015917
--- /dev/null
+++ b/doc/en/member/public_stream.md
@@ -0,0 +1,12 @@
+### Public Stream
+
+If the administrator of a hub has activated the public stream, you as a user can install and activate the ‘Public stream’ app.
+
+While all posts and activities from yourself and all your connections appear in the ‘normal’ stream, the public stream is more comprehensive.
+
+There are two options:
+
+1. If the administrator has restricted the public stream to their own hub, all public posts and activities from the streams of all users who have an account on this hub will appear there.
+2. If the administrator has not restricted the public stream to their own hub, all public posts from all channels of their own hub, public content that arrives at their own hub (e.g. comments on posts by hub users that originate from other instances) and randomly collected content from channels that are known to their own hub (i.e. all contacts from all channels on their own hub) will be displayed.
+
+The public stream is not unmoderated. The administrator of a hub has the option of deleting posts from the public stream (‘Admin delete’). These posts are then actually deleted from the public stream of their own hub. They also do not appear in the public stream view of other channels of the hub.
diff --git a/doc/en/member/registration.md b/doc/en/member/registration.md
new file mode 100644
index 000000000..879d89246
--- /dev/null
+++ b/doc/en/member/registration.md
@@ -0,0 +1,19 @@
+## Login / Registration
+
+Not all Hubzilla sites allow open registration. If registration is allowed, you will see a ‘Register’ link right next to the login prompt on the site's homepage. Following this link will take you to the site's registration page. On some sites, you may be redirected to another site that lists hubs where registration is allowed. Since all Hubzilla sites are connected, it does not matter where your account is located.
+
+**Your e-mail address**
+
+Please enter a valid e-mail address. Your email address will never be published. This address will be used to activate your account, to send (optional) email notifications for incoming messages or articles *and to recover lost passwords*.
+
+**Password**
+
+Enter a password of your choice and repeat it in the second field to ensure that it has been entered correctly. As Hubzilla offers a decentralised identity, you can use your account to log in to many other websites.
+
+**Terms of Use**
+
+Click on the link to read the website's terms of use. Once you have read them, confirm them by ticking the box in the registration form.
+
+**Log in**
+
+Once you have entered the required information, click on the ‘Register’ button. Some websites may require administrator approval before registration can be processed, in which case you will be notified. Please check your email (including your spam folder) for approval of your registration.
diff --git a/doc/en/member/repeat.md b/doc/en/member/repeat.md
new file mode 100644
index 000000000..d4a92f0a4
--- /dev/null
+++ b/doc/en/member/repeat.md
@@ -0,0 +1,5 @@
+### Repeat
+
+By repeating posts, the post is distributed to your own connections. Comments end up in the original post (in contrast to [shared posts](/help/en/usermanual/share)).
+
+This behaviour corresponds to ‘boosting’, as we know it from Mastodon or other Fediverse services, for example.
diff --git a/doc/en/member/save_to_folder.md b/doc/en/member/save_to_folder.md
new file mode 100644
index 000000000..088a6c539
--- /dev/null
+++ b/doc/en/member/save_to_folder.md
@@ -0,0 +1,22 @@
+### Save to folder
+
+If you want to remember postings for later, you can do this by [marking them](./toggle_star_status.md) (star). However, if you have a large number of postings marked in this way, it can easily become confusing.
+It is more practical to save such posts in different ‘folders’. These are categorised markings.
+If you select ‘Save in folder’, a dialogue window opens to select the folder.
+
+![savefolder 01](./pic/savefolder01.png)
+
+You can enter a folder name in the text field. If folders already exist, a double click in the text field will display a selection list of the existing folders so that you can select one of them.
+
+![savefolder 02](./pic/savefolder02.png)
+
+If you have placed a post in a folder, you can recognise this by the folder symbol (including folder name) at the bottom left of the post.
+
+![savefolder 03](./pic/savefolder03.png)
+
+This icon can also be used to remove a post from a folder by clicking on the ‘X’ in the icon.
+
+![savefolder 04](./pic/savefolder04.png)
+
+In the stream view, you will find the entry ‘Saved folders’ in the left sidebar. If you click on it, all existing folders are displayed. If you now select a folder, all the posts that you have saved in this folder will be displayed in (reverse) chronological order in the stream.
+This feature is comparable to the ‘clips’ (= categorised bookmarks) as known from Misskey and the Forkeys.
diff --git a/doc/en/member/search.md b/doc/en/member/search.md
new file mode 100644
index 000000000..6ab59a0fc
--- /dev/null
+++ b/doc/en/member/search.md
@@ -0,0 +1,14 @@
+## Search
+
+To quickly find information, you can use the search function.
+To do so, click on the icon in the navigation bar.
+
+![search 01](/help/en/member/pic/search01.png)
+
+![search 02](/help/en/member/pic/search02.png)
+
+This searches the entire hub. You can search for hashtags, handles and text.
+
+In the channel view, there is also a search field in the left sidebar. It only searches the stream of your own channel. Searches that have been performed in this widget can also be saved by clicking on the floppy disc symbol next to the search field. The saved search is then displayed in a list of search terms below the search field and can be repeated at any time with a single click.
+
+![search 03](/help/en/member/pic/search03.png)
diff --git a/doc/en/member/settings.md b/doc/en/member/settings.md
new file mode 100644
index 000000000..c27769ba2
--- /dev/null
+++ b/doc/en/member/settings.md
@@ -0,0 +1,34 @@
+### Settings
+
+Hubzilla allows a wide range of settings for behaviour, appearance, features, channels, etc.
+You can access most settings via the main menu, where you will find the Settings menu item.
+
+![settings 01](/help/en/usermanual/pic/settings01.png)
+
+![settings 02](/help/en/usermanual/pic/settings02.png)
+
+Various categories of settings are provided:
+
+- Account settings
+- Channel settings
+- Privacy settings
+- Display settings
+- Manage locations - if clones of your channel exist
+
+If you are in the stream view, you will see a small cogwheel (⚙) next to the main menu, which you can use to access the
+
+- stream settings
+
+There are also hidden settings
+
+- Additional functions
+
+which you cannot access via the menu or an icon.
+
+#include doc/en/usermanual/account_settings.md;
+#include doc/en/usermanual/channel_settings.md;
+#include doc/en/usermanual/privacy_settings.md;
+#include doc/en/usermanual/display_settings.md;
+#include doc/en/usermanual/channel_locations.md;
+#include doc/en/usermanual/stream_settings.md;
+#include doc/en/usermanual/additional_features.md;
diff --git a/doc/en/member/share.md b/doc/en/member/share.md
new file mode 100644
index 000000000..42478372e
--- /dev/null
+++ b/doc/en/member/share.md
@@ -0,0 +1,5 @@
+### Share
+
+When posts are shared (forwarded), a post by another user is posted again on your own channel. A new conversation is created in your own channel. Comments are added to the new conversation and not to the original one.
+
+Sharing posts only works from the stream or your own channel, but not from a ‘third-party’ channel.
diff --git a/doc/en/member/show_source_code.md b/doc/en/member/show_source_code.md
new file mode 100644
index 000000000..8f5aa4962
--- /dev/null
+++ b/doc/en/member/show_source_code.md
@@ -0,0 +1,13 @@
+### Show source code
+
+You can use this function to display the source code of a post. The content is therefore not rendered in formatted form, but includes the text including all markup tags (Markdown, bbCode, HTML).
+The function therefore seems to be more for advanced or very curious users.
+
+However, it does contain a feature that can be helpful for everyone.
+
+In addition to the internal post ID, there are two hyperlinks: ‘plink’ and ‘llink’.
+
+![source](/help/en/usermanual/pic/source.png)
+
+‘plink’ means “permalink” and corresponds to the [link to the source](link_to_source.md).
+‘llink’ means “local link” and refers to the location of the post on your own instance (hub). Clicking on it does not cause you to leave your own instance, but displays the post in the single view.
diff --git a/doc/en/member/stream_settings.md b/doc/en/member/stream_settings.md
new file mode 100644
index 000000000..429222e04
--- /dev/null
+++ b/doc/en/member/stream_settings.md
@@ -0,0 +1,9 @@
+#### Stream settings
+
+The stream settings are not accessed via Main Menu → Settings, but via the small cogwheel symbol (⚙) next to the main menu, which appears there as soon as you open the stream view.
+
+![settings 12](/help/en/usermanual/pic/settings12.png)
+
+The stream settings can be used to select the display of the stream and the features available there (e.g. stream filters, saving search queries, etc.).
+
+![settings 13](/help/en/usermanual/pic/settings13.png)
diff --git a/doc/en/member/superblock.md b/doc/en/member/superblock.md
new file mode 100644
index 000000000..34fa00752
--- /dev/null
+++ b/doc/en/member/superblock.md
@@ -0,0 +1,15 @@
+### Superblock
+
+The ‘Superblock’ app is a moderation method for your own stream. While Hubzilla's normal functionality only allows you to block users you are connected to using the contact tool, Superblock works regardless of whether you are connected to a contact or not.
+
+If you notice a user in the stream with whom you are not connected (because their posts are shared by a contact from your own address book) and - for whatever reason - you do not want to have any posts from this Fediverse user in the stream, you can achieve this with the ‘Superblock’ app.
+
+To do this, click on the small white triangle in the avatar of the user you want to block.
+
+A pull-down menu opens, which contains the menu item ‘Block completely’ at the bottom. Clicking on this menu item places the user in the superblock list. Posts from this user will no longer appear in their own stream. Affected posts are immediately hidden in the stream. In addition, this user will no longer be able to read your posts, regardless of their authorisations, nor will they be able to post to your channel.
+
+If you select the ‘Superblock’ app in the app menu (top right ⋮), a list of all blocked contacts is displayed.
+
+A ‘rubbish bin icon’ is displayed next to each contact. Click on this icon to remove the user from the block list. The user can then follow you again, see your posts and also comment on them and their posts will also appear in the stream again (e.g. by sharing a contact).
+
+Superblock is not installed and activated by default for new channels.
diff --git a/doc/en/member/tags.md b/doc/en/member/tags.md
new file mode 100644
index 000000000..d9a9f4b67
--- /dev/null
+++ b/doc/en/member/tags.md
@@ -0,0 +1,5 @@
+## Tags
+
+Tags (also called thematic tags, hashtags or topical tags) Tags are displayed by prefixing the tag name with the ‘#’ character. This creates a link in the post to a generalised website search for the specified term. For example, #cars will provide a search link for all posts that mention ‘cars’ on your website. Topical tags are usually at least three characters long. Shorter search terms are unlikely to return search results, but this depends on the database configuration.
+
+Thematic tags are also usually not linked if they are purely numeric, e.g. #1. If you wish to use a numeric hashtag, please include descriptive text such as #2012-elections or enclose the entire tag in double quotes (e.g. #"2012″). Double quotes are also required if the tag contains spaces (#"My Tag") and may be required if the tag contains punctuation (#"EndsWithPeriod." Or #"Exciting !!!").
diff --git a/doc/en/member/the_grid.md b/doc/en/member/the_grid.md
new file mode 100644
index 000000000..5f7b39665
--- /dev/null
+++ b/doc/en/member/the_grid.md
@@ -0,0 +1,3 @@
+## The grid
+
+The ‘Grid’ is the name given to the network of all Hubzilla hubs that communicate with each other via the Nomad protocol. The grid is effectively a subset of the Fediverse and includes all Hubzilla servers.
diff --git a/doc/en/member/the_stream.md b/doc/en/member/the_stream.md
new file mode 100644
index 000000000..964a11a7b
--- /dev/null
+++ b/doc/en/member/the_stream.md
@@ -0,0 +1,7 @@
+## The stream
+
+The stream is the list of posts, comments and boosts from users in the Fediverse. It is sorted in reverse chronological order (the most recent posts appear at the top). Exactly which posts are shown here depends largely on your authorisation settings.
+
+The stream (also known as the ‘timeline’ in other Fediverse services) can be filtered using various filters: by direct messages, events, polls, privacy groups, forums, flagged posts, your own posts, saved folders and names.
+
+The stream in the Fediverse is not created by algorithms that select supposedly interesting content for the user, but is determined exclusively by the user himself. In Fediverse, you, the user, are the algorithm for the stream.
diff --git a/doc/en/member/toggle_star_status.md b/doc/en/member/toggle_star_status.md
new file mode 100644
index 000000000..3d5a08c13
--- /dev/null
+++ b/doc/en/member/toggle_star_status.md
@@ -0,0 +1,8 @@
+### Toggle Star Status
+
+Switching the marking star should not be confused with ‘starring’ (= favouriting) other feed services (e.g. Mastodon). While ‘starring’ in these services represents a positive evaluation of the post, which corresponds most closely to a ‘link’, marking with a star in Hubzilla is comparable to setting a bookmark. By toggling (one click sets the star, another removes it again), the post is set as a kind of bookmark for the user.
+You can recognise a ‘starred’ post by the star symbol at the bottom left.
+
+![star](./pic/star.png)
+
+In the stream view, you will find the entry ‘Marked posts’ in the left sidebar. If you click on it, all marked posts are displayed in (reverse) chronological order in the stream.
diff --git a/doc/en/member/websites.md b/doc/en/member/websites.md
new file mode 100644
index 000000000..320d0e98d
--- /dev/null
+++ b/doc/en/member/websites.md
@@ -0,0 +1,139 @@
+## Websites
+
+The Websites app allows you to create static websites in your channel. Websites remain on your hub and are not federated. However, you can share the link to the website and enable all users in the Fediverse to visit your website.
+
+When you open the app, you will be taken to the website overview. The pages will be accessible at `<your-instance-URL>/page/<your-channel-name>/<page-link-title>`.
+
+![websites 01](/help/en/member/pic/websites01.png)
+
+In the left sidebar, there is a widget with the design tools for ‘blocks’, ‘menus’, ‘layouts’ and ‘pages’.
+Below that, there is another widget that allows you to export and import web pages.
+
+The centre section lists the existing web pages. You can edit, share and delete them. There is also a button to create a new web page: ‘Create’.
+
+When you click on this button, the web page editor opens.
+
+![websites 02](/help/en/member/pic/websites02.png)
+
+You now have the choice of how you want to design the website: with bbCode, with HTML, with Markdown, with plain text or with the Comanche layout language.
+
+You can also specify which layout (if you have created one or more using the layout design tool) should be used to display the web page.
+
+Next is the input field for the optional page title, as well as (also optional) a summary, and (mandatory) the page URL.
+
+Below that is the text editor for the content of the website.
+
+If you just want to create a very simple website with formatting and other markup elements, it is sufficient to create it in the website editor using plain text, HTML, bbCode or Markdown. This way you get a website without a special layout (without sidebars, without menus etc.).
+
+For more sophisticated websites, it is recommended that you work with blocks, layouts and menus.
+
+### Blocks
+
+Blocks can be parts of web pages. The basic HTML code of a block looks like this
+
+```
+ <div>
+ block content
+ </div>
+```
+
+If a block has the content type text/html, it can also contain menu items. The example content of
+
+```
+ <p>HTML block content</p>
+ [menu]menuname[/menu]
+```
+
+will produce HTML like this
+
+```
+ <div>
+ <p>HTML block content</p>
+<div>
+<ul>
+<li><a href=‘#’>Link 1</a></li>
+<li><a href=‘#’>Link 2</a></li>
+<li><a href=‘#’>Link 3</a></li>
+</ul>
+</div>
+</p>
+```
+
+A block can also contain the actual content of the website via the `$content` macro.
+To do this, create a block with only
+
+```
+ $content
+```
+
+as content.
+For a block to appear on the website, it must be defined within a region in the page layout.
+
+```
+ [region=aside]
+ [block]blockname[/block]
+ [/region]
+```
+
+The appearance of the block can be manipulated in the page layout.
+You can assign your own classes
+
+```
+ [region=aside]
+ [block=myclass]blockname[/block]
+ [/region]
+```
+
+will produce the following HTML
+
+```
+ <div class=‘myclass’>
+ Block Content
+ </div>
+```
+
+The wrap variable can be used to free a block from its enclosing `<div></div>`tag
+
+```
+ [region=aside]
+ [block][var=wrap]none[/var]blockname[/block]
+ [/region]
+```
+
+This HTML is generated
+
+```
+Block content
+```
+
+With the block editor, blocks can be created just as easily as web pages.
+
+![websites 03](/help/en/member/pic/websites03.png)
+
+### Menus
+
+The menu editor is used to easily create navigation menus.
+
+![websites 04](/help/en/member/pic/websites04.png)
+
+The menu must be assigned a unique name (this name can be used to reference it later in the website and in blocks). Entering a title is optional. You can also choose whether the menu is available for adding bookmarks. This feature makes it possible to add links marked as bookmarks from the stream to the menu with a single click.
+
+Click ‘Submit and continue’ to create the menu.
+
+![websites 05](/help/en/member/pic/websites05.png)
+
+The dialogue for adding a menu entry will now open. You must enter a name for the menu entry (‘Name of the link’) and the destination of the link. This can be a URL or the name of another menu (which is then integrated as a submenu).
+
+You can influence the sorting of the menu entries by entering a number at ‘Order in list’.
+
+If the URL is an external link to a source on another hub, you can ensure that you are authenticated at the target and that restricted content is available if necessary by setting the ‘Use Magic-Auth if available’ switch.
+
+You can also specify whether links should open in a new window or tab.
+
+Click on ‘Submit and proceed’ to create further entries. ‘Submit and finish’ ends the entry of menu items. Menus can, however, be edited and added to at any time.
+
+### Layouts
+
+Layouts are used to define the general structure of web pages. They are designed using the Comanche page description language, a variant of bbCode. You have to give the layout a name. The definition of the layout is then entered in the text field. This is also where you can define the contents of the various regions.
+
+![websites 06](/help/en/member/pic/websites06.png)
diff --git a/doc/en/member/wikis.md b/doc/en/member/wikis.md
new file mode 100644
index 000000000..37c6864e7
--- /dev/null
+++ b/doc/en/member/wikis.md
@@ -0,0 +1,31 @@
+## Wikis
+
+The ‘Wiki’ app makes it possible to create wikis in your own channel.
+Wiki pages are not federated and remain on your own hub.
+
+The wiki app offers simple, classic wiki functionality. Wiki posts can be created as plain text, Markdown text or BBcode text.
+
+![wiki 01](/help/en/member/pic/wiki01.png)
+
+To view (or edit) a wiki, select the corresponding wiki from the list on this page.
+If you want to create a new wiki, click on the button ‘+ Create new’.
+An input form will open in which you enter the name of the wiki and define the content type (as standard). You can use a switch to determine that only the selected content type (text, Markdown, BBcode) must be used for all wiki entries. You can also use a switch to turn the creation of a status post about wiki creation on or off.
+
+![wiki 02](/help/en/member/pic/wiki02.png)
+
+You can also set up detailed [permission rights](permissions_content.md) for a wiki.
+Click on ‘Submit’ to create the wiki and open the home page.
+The default view of a wiki page is always ‘View’, in which the text is rendered according to the source code. You can switch to the ‘Edit’ view using the tabs at the top to access the editor mode.
+If you have edited the page and then switch back to View, the changes will be displayed immediately.
+If you want to save the page, enter a suitable comment in the input field below the text and click on ‘Save’. The wiki page has been created.
+Using the third tab, labelled ‘History’, you can view the changes made to the wiki page and, if you wish, undo changes. This is a wiki-typical form of version control.
+
+![wiki 03](/help/en/member/pic/wiki03.png)
+
+![wiki 04](/help/en/member/pic/wiki04.png)
+
+As you create more wiki pages, they will be listed in the left sidebar, from where they can also be accessed.
+
+For collaborative editing of a wiki, it is necessary to grant the users who are allowed to work on the wiki appropriate rights. In the case of a Public, Personal or Community Forum channel, this is done by means of a corresponding contact role in which the editing of the wiki pages is authorised. This permission is not granted by default for the channel roles mentioned (permission can be granted for a user-defined channel role, but it then applies generally and cannot be withdrawn again using a contact role).
+
+If you want to exclude individual wikis from this, you must restrict their visibility via the permission settings of the wiki (padlock).
diff --git a/doc/en/pic/ui01.png b/doc/en/pic/ui01.png
new file mode 100644
index 000000000..1efeedd8f
--- /dev/null
+++ b/doc/en/pic/ui01.png
Binary files differ
diff --git a/doc/en/pic/ui02.png b/doc/en/pic/ui02.png
new file mode 100644
index 000000000..120d44cb5
--- /dev/null
+++ b/doc/en/pic/ui02.png
Binary files differ
diff --git a/doc/en/placeholder.md b/doc/en/placeholder.md
new file mode 100644
index 000000000..867e2c849
--- /dev/null
+++ b/doc/en/placeholder.md
@@ -0,0 +1 @@
+# Placeholder \ No newline at end of file
diff --git a/doc/en/plugins.bb b/doc/en/plugins.bb
deleted file mode 100644
index 3aecc458f..000000000
--- a/doc/en/plugins.bb
+++ /dev/null
@@ -1,312 +0,0 @@
-[b]Plugins[/b]
-
-So you want to make $Projectname do something it doesn't already do. There are lots of ways. But let's learn how to write a plugin or addon.
-
-
-In your $Projectname folder/directory, you will probably see a sub-directory called 'addon'. If you don't have one already, go ahead and create it.
-[code]
- mkdir addon
-[/code]
-Then figure out a name for your addon. You probably have at least a vague idea of what you want it to do. For our example I'm going to create a plugin called 'randplace' that provides a somewhat random location for each of your posts. The name of your plugin is used to find the functions we need to access and is part of the function names, so to be safe, use only simple text characters.
-
-Once you've chosen a name, create a directory beneath 'addon' to hold your working file or files.
-[code]
- mkdir addon/randplace
-[/code]
-Now create your plugin file. It needs to have the same name, and it's a PHP script, so using your favourite editor, create the file
-[code]
- addon/randplace/randplace.php
-[/code]
-The very first line of this file needs to be
-[code]
- &lt;?php
-[/code]
-Then we're going to create a comment block to describe the plugin. There's a special format for this. We use /* ... */ comment-style and some tagged lines consisting of
-[code]
- /**
- *
- * Name: Random Place (here you can use better descriptions than you could in the filename)
- * Description: Sample $Projectname plugin, Sets a random place when posting.
- * Version: 1.0
- * Author: Mike Macgirvin &lt;mike@zothub.com&gt;
- *
- */
-[/code]
-These tags will be seen by the site administrator when he/she installs or manages plugins from the admin panel. There can be more than one author. Just add another line starting with 'Author:'.
-
-The typical plugin will have at least the following functions:
-[code]
- pluginname_load()
- pluginname_unload()
-[/code]
-In our case, we'll call them randplace_load() and randplace_unload(), as that is the name of our plugin. These functions are called whenever we wish to either initialise the plugin or remove it from the current webpage. Also if your plugin requires things like altering the database schema before it can run for the very first time, you would likely place these instructions in the functions named
-[code]
- pluginname_install()
- pluginname_uninstall()
-[/code]
-
-Next we'll talk about [b]hooks[/b]. Hooks are places in $Projectname code where we allow plugins to do stuff. There are a [url=[baseurl]/help/hooklist]lot of these[/url], and they each have a name. What we normally do is use the pluginname_load() function to register a &quot;handler function&quot; for any hooks you are interested in. Then when any of these hooks are triggered, your code will be called.
-
-We register hook handlers with the 'Zotlabs\Extend\Hook::register()' function. It typically takes 3 arguments. The first is the hook we wish to catch, the second is the filename of the file to find our handler function (relative to the base of your $Projectname installation), and the third is the function name of your handler function. So let's create our randplace_load() function right now.
-
-[code]
- function randplace_load() {
- Zotlabs\Extend\Hook::register('post_local', 'addon/randplace/randplace.php', 'randplace_post_hook');
-
- Zotlabs\Extend\Hook::register('feature_settings', 'addon/randplace/randplace.php', 'randplace_settings');
- Zotlabs\Extend\Hook::register('feature_settings_post', 'addon/randplace/randplace.php', 'randplace_settings_post');
-
- }
-[/code]
-
-So we're going to catch three events, 'post_local' which is triggered when a post is made on the local system, 'feature_settings' to set some preferences for our plugin, and 'feature_settings_post' to store those settings.
-
-Next we'll create an unload function. This is easy, as it just unregisters our hooks. It takes exactly the same arguments.
-[code]
- function randplace_unload() {
- Zotlabs\Extend\Hook::unregister('post_local', 'addon/randplace/randplace.php', 'randplace_post_hook');
-
- Zotlabs\Extend\Hook::unregister('feature_settings', 'addon/randplace/randplace.php', 'randplace_settings');
- Zotlabs\Extend\Hook::unregister('feature_settings_post', 'addon/randplace/randplace.php', 'randplace_settings_post');
- }
-[/code]
-
-Hooks are always called with one argument which is specific to the hook you're calling. It contains information relevant to that particular place in the program, and often allows you to look at, and even change it. In order to change it, you need to add '&amp;' to the variable name so it is passed to your function by reference. Otherwise it will create a copy and any changes you make will be lost when the hook process returns. Usually (but not always) the passed data is a named array of data structures. Please see the &quot;hook reference&quot; (not yet written as of this date) for details on any specific hook. Occasionally you may need to view the program source to see precisely how a given hook is called and how the results are processed.
-
-Let's go ahead and add some code to implement our post_local hook handler.
-[code]
- function randplace_post_hook(&amp;$item) {
-
- /**
- *
- * An item was posted on the local system.
- * We are going to look for specific items:
- * - A status post by a profile owner
- * - The profile owner must have allowed our plugin
- *
- */
-
- logger('randplace invoked');
-
- if(! local_channel()) /* non-zero if this is a logged in user of this system */
- return;
-
- if(local_channel() != $item['uid']) /* Does this person own the post? */
- return;
-
- if(($item['parent']) || (! is_item_normal($item))) {
- /* If the item has a parent, or is not "normal", this is a comment or something else, not a status post. */
- return;
- }
-
- /* Retrieve our personal config setting */
-
- $active = get_pconfig(local_channel(), 'randplace', 'enable');
-
- if(! $active)
- return;
- /**
- *
- * OK, we're allowed to do our stuff.
- * Here's what we are going to do:
- * load the list of timezone names, and use that to generate a list of world cities.
- * Then we'll pick one of those at random and put it in the &quot;location&quot; field for the post.
- *
- */
-
- $cities = array();
- $zones = timezone_identifiers_list();
- foreach($zones as $zone) {
- if((strpos($zone,'/')) &amp;&amp; (! stristr($zone,'US/')) &amp;&amp; (! stristr($zone,'Etc/')))
- $cities[] = str_replace('_', ' ',substr($zone,strpos($zone,'/') + 1));
- }
-
- if(! count($cities))
- return;
- $city = array_rand($cities,1);
- $item['location'] = $cities[$city];
-
- return;
- }
-[/code]
-
-Now let's add our functions to create and store preference settings.
-[code]
- /**
- *
- * Callback from the settings post function.
- * $post contains the global $_POST array.
- * We will make sure we've got a valid user account
- * and that only our own submit button was clicked
- * and if so set our configuration setting for this person.
- *
- */
-
- function randplace_settings_post($post) {
- if(! local_channel())
- return;
- if($_POST['randplace-submit'])
- set_pconfig(local_channel(),'randplace','enable',intval($_POST['randplace']));
- }
-
-
-
- /**
- *
- * Called from the Feature Setting form.
- * The second argument is a string in this case, the HTML content region of the page.
- * Add our own settings info to the string.
- *
- * For uniformity of settings pages, we use the following convention
- * &lt;div class=&quot;settings-block&quot;&gt;
- * &lt;h3&gt;title&lt;/h3&gt;
- * .... settings html - many elements will be floated...
- * &lt;div class=&quot;clear&quot;&gt;&lt;/div&gt; &lt;!-- generic class which clears all floats --&gt;
- * &lt;input type=&quot;submit&quot; name=&quot;pluginnname-submit&quot; class=&quot;settings-submit&quot; ..... /&gt;
- * &lt;/div&gt;
- */
-
-
-
- function randplace_settings(&amp;$s) {
-
- if(! local_channel())
- return;
-
- /* Add our stylesheet to the page so we can make our settings look nice */
-
- head_add_css(/addon/randplace/randplace.css');
-
- /* Get the current state of our config variable */
-
- $enabled = get_pconfig(local_channel(),'randplace','enable');
-
- $checked = (($enabled) ? ' checked=&quot;checked&quot; ' : '');
-
- /* Add some HTML to the existing form */
-
- $s .= '&lt;div class=&quot;settings-block&quot;&gt;';
- $s .= '&lt;h3&gt;' . t('Randplace Settings') . '&lt;/h3&gt;';
- $s .= '&lt;div id=&quot;randplace-enable-wrapper&quot;&gt;';
- $s .= '&lt;label id=&quot;randplace-enable-label&quot; for=&quot;randplace-checkbox&quot;&gt;' . t('Enable Randplace Plugin') . '&lt;/label&gt;';
- $s .= '&lt;input id=&quot;randplace-checkbox&quot; type=&quot;checkbox&quot; name=&quot;randplace&quot; value=&quot;1&quot; ' . $checked . '/&gt;';
- $s .= '&lt;/div&gt;&lt;div class=&quot;clear&quot;&gt;&lt;/div&gt;';
-
- /* provide a submit button */
-
- $s .= '&lt;div class=&quot;settings-submit-wrapper&quot; &gt;&lt;input type=&quot;submit&quot; name=&quot;randplace-submit&quot; class=&quot;settings-submit&quot; value=&quot;' . t('Submit') . '&quot; /&gt;&lt;/div&gt;&lt;/div&gt;';
-
- }
-
-[/code]
-
-
-
-[h2]Advanced Plugins[/h2]
-
-Sometimes your plugins want to provide a range of new functionality which isn't provided at all or is clumsy to provide using hooks. In this case your plugin can also act as a 'module'. A module in our case refers to a structured webpage handler which responds to a given URL. Then anything which accesses that URL will be handled completely by your plugin.
-
-There are two ways to accomplish this. To create a module object use the following model:
-[code]
-<?php /* file: addon/randplace/Mod_Randplace.php */
-namespace Zotlabs\Module;
-
- // Your module will consist of the name of your addon with an uppercase first character, within the Zotlabs\Module namespace
- // To avoid namespace conflicts with your plugin, the convention we're using is to name the module file Mod_Addonname.php
- // In this case 'Mod_Randplace.php' and then include it from within your main plugin file 'randplace.php' with the line:
- //
- // require_once('addon/randplace/Mod_Randplace.php');
-
- class Randplace extends \Zotlabs\Web\Controller {
- function init() {
- // init method is always called first if it exists
- }
- function post() {
- // the post method is only called if there are $_POST variables present (e.g. the page request method is "post")
- }
- function get() {
- // The get method is used to display normal content on the page
- // whatever this function returns will be displayed in the page body
- }
- }
-[/code]
-
-The other option is to use a procedural interface. The $a argument to these function is obsolete, but must be present.
-The key to this is to create a simple function named pluginname_module() which does nothing. These lines and this interface
-can be used inside your addon file without causing a namespace conflict, as the object method will.
-
-[code]
- function randplace_module() { return; }
-[/code]
-Once this function exists, the URL #^[url=https://yoursite/randplace]https://yoursite/randplace[/url] will access your plugin as a module. Then you can define functions which are called at various points to return or process a structured webpage just like system modules. The typical functions and the order which they are called is
-[code]
- modulename_init($a) // (e.g. randplace_init($a);) called first - if you wish to emit json or xml,
- // you should do it here, followed by killme() which will avoid the default action of building a webpage
- modulename_post($a) // Called whenever the page is accessed via the &quot;post&quot; method
- modulename_content($a) // called to generate the central page content. This function should return a string
- // consisting of the central page content.
-[/code]
-Your module functions have access to the URL path as if they were standalone programs in the Unix operating system. For instance if you visit the page
-[code]
- https://yoursite/randplace/something/somewhere/whatever
-[/code]
-we will create an argc/argv list for use by your module functions
-[code]
- $x = argc(); // $x will be 4, the number of path arguments after the sitename
-
- for($x = 0; $x &lt; argc(); $x ++)
- echo $x . ' ' . argv($x);
-
-
- 0 randplace
- 1 something
- 2 somewhere
- 3 whatever
-[/code]
-
-[h3]Using class methods as hook handler functions[/h3]
-
-To register a hook using a class method as a callback, a couple of things need to be considered. The first is that the functions need to be declared static public so that they are available from all contexts, and they need to have a namespace attached because they can be called from within multiple namespaces. You can then register them as strings or arrays (using the PHP internal calling method).
-
-[code]
-<?php
-/*
- * plugin info block goes here
- */
-
-function myplugin_load() {
- Zotlabs\Extend\Hook::register('hook_name','addon/myplugin/myplugin.php','\\Myplugin::foo');
-[b]or[/b]
- Zotlabs\Extend\Hook::register('hook_name','addon/myplugin/myplugin.php',array('\\Myplugin','foo'));
-}
-
-class Myplugin {
-
- public static function foo($params) {
- // handler for 'hook_name'
- }
-}
-[/code]
-
-If you want to keep your plugin hidden from the siteinfo page, simply create a file called '.hidden' in your addon directory
-[code]
- touch addon/<addon name>/.hidden
-[/code]
-
-***Porting Friendica Plugins***
-
-$Projectname uses a similar plugin architecture to the Friendica project. The authentication, identity, and permissions systems are completely different. Many Friendica can be ported reasonably easily by renaming a few functions - and then ensuring that the permissions model is adhered to. The functions which need to be renamed are:
-
-[li] Friendica's pluginname_install() is pluginname_load()[/li]
-
-[li] Friendica's pluginname_uninstall() is pluginname_unload()[/li]
-
-$Projectname has _install and _uninstall functions but these are used differently.
-
-[li] Friendica's &quot;plugin_settings&quot; hook is called &quot;feature_settings&quot;[/li]
-
-[li] Friendica's &quot;plugin_settings_post&quot; hook is called &quot;feature_settings_post&quot;[/li]
-
-Changing these will often allow your plugin to function, but please double check all your permission and identity code because the concepts behind it are completely different in $Projectname. Many structured data names (especially DB schema columns) are also quite different.
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/problems-following-an-update.bb b/doc/en/problems-following-an-update.bb
deleted file mode 100644
index 7376d6163..000000000
--- a/doc/en/problems-following-an-update.bb
+++ /dev/null
@@ -1,38 +0,0 @@
-[b]Problems Following An Update[/b]
-
-A good 90% of all bugs encountered immediately after updating the code to the latest version are simple cache errors of one sort or another. If you update and find something very obvious is broken - like your matrix page doesn't load, notifications are missing, or comment boxes are missing - the chances are it's not a bug at all. Breaking basic functionality is the kind of thing developers tend to notice.
-
-If this happens to you, there are a few simple steps to take before resorting to the support forums:
-
-[b]Browser Cache[/b]
-
-Symptoms: Menus do not expand, ACL selector does not open, progress indicator does not display (or loops forever), Matrix and channel pages do not load.
-
-Force reload the page. Shift reload, or ctrl+f5. Occasionally, but very, very rarely, you will also need to clear the session data - which is achieved by restarting the browser.
-
-[b]FastCGI[/b]
-
-Symptoms: Incorrect variables. The basic UI mostly works, but displays incorrect content or is missing content entirely.
-
-If you're using php5-fpm, this problem is usually resolved with [code]service php5-fpm restart[/code]
-
-[b]Smarty Cache[/b]
-
-Symptoms:
-
-1) White Screen Of Death. This is most prevalent on the settings and admin pages.
-
-2) Missing icons, tabs, menus or features.
-
-We use the Smarty3 template engine to generate pages. These templates are compiled before they are displayed. Occasionally, a new or modified template will fail to overwrite the old compiled version. To clear the Smarty cache, delete all the files in store/[data]/smarty3/compiled [b]but do not delete the directory itself[/b]. Templates will then be recompiled on their next access.
-
-[b]Theme Issues[/b]
-
-There are many themes for $Projectname. Only Redbasic is officialy supported by the core developers. This applies [i]even if a core developer happens to support an additional theme[/i]. This means new features are only guaranteed to work in Redbasic.
-
-Redbasic uses a few javascript libraries that are done differently, or entirely absent in other themes. This means new features may only work properly in Redbasic. Before reporting an issue, therefore, you should switch to Redbasic to see if it exists there. If the issue goes away, this is not a bug - it's a theme that isn't up to date.
-
-Should you report an issue with the theme developers then? No. Theme developers use their themes. Chances are, they know. Give them two or three days to catch up and [i]then[/i] report the issue if it's still not fixed. There are two workarounds for this situation. Firstly, you can temporarily use Redbasic. Secondly, most themes are open source too - open a pull request and make yourself a friend.
-
-#include doc/macros/troubleshooting_footer.bb;
-
diff --git a/doc/en/red2pi.bb b/doc/en/red2pi.bb
deleted file mode 100644
index 8ae087fbf..000000000
--- a/doc/en/red2pi.bb
+++ /dev/null
@@ -1,342 +0,0 @@
-[b]How to install $Projectname on a Raspberry Pi[/b]
-
-You just bought a Raspberry Pi and want to run the RED Matrix with your own domain name?
-
-Then this page is for you! You will:
-[list=1]
-[*] Install Raspberry OS (Debian Linux) on a Raspberry
-[*] Install Apache Web Server, PHP, MaySQL, phpMyAdmin
-[*] Register a free domain (dynamic DNS) and use it for your hub
-[*] Install $Projectname
-[*] Keep your Raspberry Pi and $Projectname up-to-date
-[*] TODO Setting up SSL
-[*] TODO Running with SSL
-[*] TODO Make the webserver less vulnarable to attacks
-[/list]
-
-[size=large]1. Install Raspberry OS (Debian Linux)[/size]
-
-instructions under [url=http://www.raspberrypi.org/downloads]http://www.raspberrypi.org/downloads[/url]
-This page links to the quick start containing detailed instruction.
-
-[b]Format SD card[/b]
-
-using the program gparted under Linux Mint 15
-
-format as FAT32
-
-[b]Download NOOBS (offline and network install)[/b]
-
-[url=http://downloads.raspberrypi.org/noobs]http://downloads.raspberrypi.org/noobs[/url]
-
-unzip
-
-copy unzipped files to SD card
-
-[b]Install Raspbian as OS on the Rasperry Pi[/b]
-
-connect with keyboard via USB
-
-connect with monitor via HDMI
-
-Insert SD card into Rasperry Pi
-
-Connect with power supply to switch on the Rasperry
-
-choose Raspbian as OS (&gt; installs Raspbian....)
-
-wait for the coniguration program raspi-config (you can later start it by sudo raspi-config)
-
-[b]Configure Raspbian[/b]
-
-in raspi-config &gt; advanced &gt; choose to use ssh (!! You need this to connect to administrate your Pi from your PC !!)
-
-in raspi-config &gt; change the password (of default user &quot;pi&quot; from &quot;raspberry&quot; to your password)
-
-in raspi-config (optional) &gt; Internationalisation options &gt; Change Locale &gt; to de_DE.utf-8 utf-8 (for example)
-
-in raspi-config (optional) &gt; Internationalisation options &gt; Change Timezoe &gt; set your timezone
-
-in raspi-config (optional) &gt; Overlock &gt; medium
-
-(Source [url=http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#]http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#[/url])
-
-
-[b]More[/b]
-
-[code]sudo reboot[/code]
-
-Now its time to connect the Pi to the network.
-[ul]
-[*] pull out keyboard
-[*] pull out monitor
-[*] you even can pull out the power supply (USB)
-[*] plug-in the network cable to the router
-[*] plug-in the power supply again
-[*] wait for a minute or to give the Pi time to boot and start ssh...
-[/ul]
-
-On your PC connect to the Pi to administrate (here update it).
-Open the console on the PC (Window: Start &gt; cmd, Linux: Shell)
-
-Hint: use the router admin tool to find out the IP of your PI[code]ssh pi@192.168.178.37
-sudo apt-get update
-sudo apt-get dist-upgrade[/code]
-
-(Source [url=http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#]http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#[/url])
-
-
-
-[size=large]2. Install Apache Web Server, PHP, MaySQL, phpMyAdmin[/size]
-
-[b]Install Apache Webserver[/b]
-
-[code]sudo bash
-sudo groupadd www-data[/code] might exist already
-
-[code]sudo usermod -a -G www-data www-data
-sudo apt-get update
-sudo reboot[/code]
-
-wait...
-reconnect via ssh, example: [code]ssh pi@192.168.178.37
-sudo apt-get install apache2 apache2-doc apache2-utils[/code]
-
-Open webbrowser on PC and check [url=http://192.168.178.37]http://192.168.178.37[/url]
-Should show you a page like &quot;It works&quot;
-
-(Source [url=http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#]http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#[/url])
-
-
-[b]Install PHP, MaySQL, phpMyAdmin[/b]
-
-[code]sudo bash
-apt-get install libapache2-mod-php5 php5 php-pear php5-xcache php5-curl
-apt-get install php5-mysql
-apt-get install mysql-server mysql-client[/code] enter and note the mysql passwort
-
-[code]apt-get install phpmyadmin[/code]
-
-Configuring phpmyadmin
-- Select apache2
-- Configure database for phpmyadmin with dbconfig-common?: Choose Yes
-
-(Source [url=http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#]http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#[/url])
-
-
-[b]Test installation[/b]
-
-[code]cd /var/www[/code]
-
-create a php file to test the php installation[code]sudo nano phpinfo.php[/code]
-
-Insert into the file:[code]
-&lt;?php
- phpinfo();
-?&gt;
-[/code]
-(save CTRL+0, ENTER, CTRL+X)
-
-open webbrowser on PC and try [url=http://192.168.178.37/phpinfo.php]http://192.168.178.37/phpinfo.php[/url] (page shows infos on php)
-
-connect phpMyAdmin with MySQL database [code]nano /etc/apache2/apache2.conf[/code]
-- CTRL+V... to the end of the file
-- Insert at the end of the file: (save CTRL+0, ENTER, CTRL+X)[code]Include /etc/phpmyadmin/apache.conf[/code]
-
-restart apache[code]/etc/init.d/apache2 restart
-sudo apt-get update
-sudo apt-get upgrade
-sudo reboot[/code]
-
-(Source [url=http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#]http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#[/url])
-
-
-[b]phpMyAdmin[/b]
-
-open webbrowser on PC and try #^[url=http://192.168.178.37/phpmyadmin]http://192.168.178.37/phpmyadmin[/url]
-
-(Source [url=http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#]http://www.manfred-steger.de/tuts/20-der-eigene-webserver-mit-dem-raspberry-pi#[/url])
-
-
-[b]Create an empty database... that is later used by RED[/b]
-
-open webbrowser on PC and try #^[url=http://192.168.178.37/phpmyadmin]http://192.168.178.37/phpmyadmin[/url]
-
-Create an empty database
-
-Note the access details (hostname, username, password, database name).
-
-
-[size=large]3. Selfhost[/size]
-
-(Source: #^[url=http://www.techjawab.com/2013/06/setup-dynamic-dns-dyndns-for-free-on.html]http://www.techjawab.com/2013/06/setup-dynamic-dns-dyndns-for-free-on.html[/url])
-
-[url=http://freedns.afraid.org/signup/]http://freedns.afraid.org/signup/[/url]
-
-[b]Step 1[/b]
-Register for a Free domain at #^[url=http://freedns.afraid.org/signup/]http://freedns.afraid.org/signup/[/url]
-(We will take techhome.homenet.org in this guide)
-
-[b]Step 2[/b]
-
-Logon to FreeDNS (where you just registered) and goto #^[url=http://freedns.afraid.org/dynamic/]http://freedns.afraid.org/dynamic/[/url]
-Right click on &quot;Direct Link&quot; and copy the URL and paste it somewhere.
-You should notice a large and unique alpha-numeric key in the URL, make a note of it as shown below:
-[code]http://freedns.afraid.org/dynamic/update.php?alphanumeric-key[/code]
-
-
-[b]Step 3[/b]
-Install inadyn using the following command:[code]sudo apt-get install inadyn[/code]
-
-[b]Step 4[/b]
-Configure inadyn using the below steps:[code]sudo nano /etc/inadyn.conf[/code]
-And add the following contains in it replacing the actual values:
-[code]
---username [color=red]techhome[/color]
---password [color=red]mypassword[/color]
---update_period 3600
---forced_update_period 14400
---alias [color=red]techhome.homenet.org&lt;/b&gt;,[color=red]alphanumeric key[/color]
---background
---dyndns_system default@freedns.afraid.org
---syslog
- [/code]
-
-
-[b]Step 5[/b]
-
-Now, we need to ensure that the DNS updater (Inadyn) runs automatically after every re-boot[code]export EDITOR=gedit &amp;&amp; sudo crontab -e[/code]
-Add the following line:[code]@reboot /usr/sbin/inadyn[/code]
-
-
-[b]Step 6[/b]
-
-Reboot system and then run the following command to ensure inadyn is running:[code]
-sudo reboot
-ps -A | grep inadyn
-[/code]
-Now your host is ready and up for accessing from internet...
-You can trying ssh-ing from another computer over the internet
-[code]ssh username@techhome.homenet.org[/code]
-Or, if any web server is running, then simply browse to #^[url=http://techhome.homenet.org]http://techhome.homenet.org[/url]
-Or, you can just ping it to test ping techhome.homenet.org
-To check the logs you can use this:
-[code]more /var/log/messages |grep INADYN[/code]
-
-
-[size=large]4. Install $Projectname[/size]
-
-(Source: [zrl=[baseurl]/help/Install][baseurl]/help/Install[/zrl])
-
-Linux Appache document root is /var/www/
-Two files exist there (created by the steps above): index.html, phpinfo.php
-
-
-[b]Install $Projectname and its Addons[/b]
-
-Cleanup: Remove the directory www/ (Git will not create files and folders in directories that are not empty.) Make sure you are in directory var[code]pi@pi /var $ cd /var[/code]
-
-Remove directory[code]pi@pi /var $ sudo rm -rf www/[/code]
-
-Download the sources of $Projectname from GIT
-[code]pi@pi /var $ sudo git clone https://framagit.org/hubzilla/core.git www[/code]
-
-Download the sources of the addons from GIT
-[code]pi@pi /var/www $ sudo git clone https://framagit.org/hubzilla/addons.git addon[/code]
-
-Make user www-data the owner of the whole web directory (including subdirectories and files)
-(TODO: This step has to be proofed by the next installation.)
-[code]pi@pi /var $ chown -R www-data:www-data /var/www/[/code]
-
-Check if you can update the sources from git[code]
-pi@pi /var $ cd www
-pi@pi /var/www $ git pull
-[/code]
-
-Check if you can update the addons
-[code]pi@pi /var/www $ cd addon/
-pi@pi /var/www/addon $ sudo git pull[/code]
-
-Make sure folder store/[data]/smarty3 exists and is writable by the webserver
-[code]pi@pi /var/www $ sudo chmod ou+w "store/\[data\]/smarty3"[/code]
-
-Create .htconfig.php and is writable by the webserver
-[code]pi@pi /var/www $ sudo touch .htconfig.php
-pi@pi /var/www $ sudo chmod ou+w .htconfig.php[/code]
-
-[b]First start and initial configuration of your RED Matrix hub[/b]
-
-In browser open #^[zrl=http://einervonvielen.mooo.com/]http://einervonvielen.mooo.com/[/zrl]
-(Replace einervonvielen.mooo.com by your domain, see chapter selfhost. Be patient. It takes time.)
-(#^[zrl=http://einervonvielen.mooo.com/index.php?q=setup]http://einervonvielen.mooo.com/index.php?q=setup[/zrl])
-
-There might be errors like the following.
-
-Error: libCURL PHP module required but not installed.
-Solution:
-apt-get install php5-curl
-
-Error: Apache webserver mod-rewrite module is required but not installed.
-Solution
-(Source: [url=http://xmodulo.com/2013/01/how-to-enable-mod_rewrite-in-apache2-on-debian-ubuntu.html]http://xmodulo.com/2013/01/how-to-enable-mod_rewrite-in-apache2-on-debian-ubuntu.html[/url])
-The default installation of Apache2 comes with mod_rewrite installed. To check whether this is the case, verify the existence of /etc/apache2/mods-available/rewrite.load
-- pi@pi /var/www $ nano /etc/apache2/mods-available/rewrite.load
- (You should find the contendt: LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so)
-To enable and load mod_rewrite, do the rest of steps.
-Create a symbolic link in /etc/apache2/mods-enabled
-- pi@pi /var/www $ sudo a2enmod rewrite
-Then open up the following file, and replace every occurrence of &quot;AllowOverride None&quot; with &quot;AllowOverride all&quot;.
-- pi@pi /var/www $ sudo nano /etc/apache2/sites-available/default
-Finally, restart Apache2.
-- pi@pi /var/www $ sudo service apache2 restart
-
-Error store is writable (not checked)
-Solution:
-(TODO: Make writeable to group www-data only?)
-pi@pi /var/www $ sudo mkdir store
-pi@pi /var/www $ chown -R www-data:www-data /var/www/red/
-pi@pi /var/www $ sudo chmod ou+w view
-
-[b]More[/b]
-
-Set up a cron job to run the poller once every 15 minutes in order to perform background processing.
-- pi@pi /var/www $ which php
-Make sure you are in the document root directory of the webserver
-- pi@pi /var/www $ cd /var/www/
-Try to execute the poller in order to make sure it works
-- pi@pi /var/www $ /usr/bin/php include/poller.php
-Create the cronjob
-- pi@pi /var/www $ crontab -e
-Enter
-- */15 * * * * cd /var/www/; /usr/bin/php include/poller.php
-- Save and exit.
-
-Prevent search engines from indexing your search pages. Why? This can cause heavy resource use.
-
-[code]
-php util/config system block_public_search 1
-[/code]
-
-
-
-[size=large]5. Keep your Raspberry Pi and your $Projectname up-to-date[/size]
-
-Git update every day at 4 am and addons at 5 am every day
-Try if the command is working
-- pi@pi /var/www $ sudo git pull
-Create the cronjob
-- pi@pi /var/www $ crontab -e
-Enter the following to update at 4:01 am every day
-- 01 04 * * * cd /var/www/; sudo git pull
-Enter the following to update the addons at 5:01 am every day
-- 01 05 * * * cd /var/www/addon/; sudo git pull
-Enter the following to update the Raspberry Pi (Raspbian OS = Debian) at 6:01 am every day
-- 01 06 * * * sudo aptitude -y update &amp;&amp; sudo aptitude -y safe-upgrade
-Save and exit.
-
-[size=large]6. Running with SSL[/size]
-
-Follow the instructions here:
-[url=https://github.com/friendica/friendica/wiki/Running-Friendica-with-SSL]https://github.com/friendica/friendica/wiki/Running-Friendica-with-SSL[/url]
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/roadmap.bb b/doc/en/roadmap.bb
deleted file mode 100644
index dce19848a..000000000
--- a/doc/en/roadmap.bb
+++ /dev/null
@@ -1,28 +0,0 @@
-
-Roadmap
-
-
-Platform
-
- Convert E2EE and select Javascript resources to dynamic loading using jQuery.getScript() [or other methods].
-
-Webpages
-
- Make webpage building easy, with point-n-click selectors to build PDLs
-
-
-Events
-
- Recurring events
- Integrate Hubzilla events with CalDAV
-
-
-Connections
-
- CardDAV integration with abook and profiles
-
-
-Issues manager
-
- Provide easy(easier) channel move (as opposed to channel copy or clone)
-
diff --git a/doc/en/schema_development.bb b/doc/en/schema_development.bb
deleted file mode 100644
index 10832684a..000000000
--- a/doc/en/schema_development.bb
+++ /dev/null
@@ -1,78 +0,0 @@
-[b]Red Development - A Guide To The Schema System[/b]
-
-A schema, in a nutshell, is a collection of settings for a bunch of variables to define
-certain elements of a theme. A schema is loaded as though it were part of config.php
-and has access to all the same information. Importantly, this means it is identity aware,
-and can be used to do some interesting things. One could, for example, restrict options
-by service class, or present different options to different members.
-
-By default, we filter only by whether or not expert mode is enabled. If expert mode is
-enabled, all options are presented to the member. If it is not, only scheme, background
-image, font face, and iconset are available as choices.
-
-A schema is loaded *after* the member's personal settings. Therefore, to allow a member
-to overwrite a particular aspect of a schema you would use the following syntax:
-[code]
- if (! $foo)
- $foo = 'bar';
-[/code]
-However, there are circumstances - particularly with positional elements - where it
-may be desirable (or necessary) to override a member's settings. In this case, the syntax
-is even simpler:
-[code]
- $foo = 'bar';
-[/code]
-Members will not thank you for this, however, so only use it when it is required.
-
-If no personal options are set, and no schema is selected, we will first try to load a schema
-with the file name &quot;default.php&quot;. This file should never be included with a theme. If it
-is, merge conflicts will occur as people update their code. Rather, this should be defined
-by administrators on a site by site basis.
-default.php and default.css MUST be symlinks to existing scheme files.
-
-You schema does not need to - and should not - contain all of these values. Only the values
-that differ from the defaults should be listed. This gives you some very powerful options
-with very few lines of code.
-
-Note the options available differ with each theme. The options available with the Redbasic
-theme are as follows:
-
-[li] nav_colour
- The colour of the navigation bar. Options are red, black and silver. Alternatively,
- one can set $nav_bg_1, $nav_bg_2, $nav_bg_3 and $nav_bg_4 to provide gradient and
- hover effects.[/li]
-[li] banner_colour
- The font colour of the banner element. Accepts an RGB or Hex value.[/li]
-[li] bgcolour
- Set the body background colour. Accepts an RGB or Hex value.[/li]
-[li] background_image
- Sets a background image. Accepts a URL or path.[/li]
-[li] item_colour
- Set the background colour of items. Accepts an RGB or Hex value.[/li]
-[li] item_opacity
- Set the opacity of items. Accepts a value from 0.01 to 1[/li]
-[li] toolicon_colour
- Set the colour of tool icons. Accepts an RGB or Hex value.[/li]
-[li] toolicon_activecolour
- Set the colour of active or hovered icon tools.[/li]
-[li] font_size
- Set the size of fonts in items and posts. Accepts px or em.[/li]
-[li] body_font_size
- Sets the size of fonts at the body level. Accepts px or em.[/li]
-[li] font_colour
- Sets the font colour. Accepts an RGB or Hex value.[/li]
-[li] radius
- Set the radius of corners. Accepts a numeral, and is always in px.[/li]
-[li] shadow
- Set the size of shadows shown with inline images. Accepts a numerical
- value. Note shadows are not applied to smileys.[/li]
-[li] converse_width
- Set the maximum width of the content region in px.[/li]
-[li] nav_min_opacity[/li]
-[li] top_photo[/li]
-[li] reply_photo[/li]
-
-If a your_schema_name.css file is found, the content of this file will be attached to the end of style.css.
-This gives the schem developer the possiblity to override any style component.
-
-#include doc/macros/main_footer.bb;
diff --git a/doc/en/schemaspy_hubzilla/zot.meta.xml b/doc/en/schemaspy_hubzilla/zot.meta.xml
deleted file mode 100644
index 98a9d370e..000000000
--- a/doc/en/schemaspy_hubzilla/zot.meta.xml
+++ /dev/null
@@ -1,283 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<schemaMeta xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-xsi:noNamespaceSchemaLocation="http://schemaspy.sourceforge.net/xmlschema/2011/02/05/schemaspy.meta.xsd">
-<comments>
-2015-11-04 - Hubzilla database schema with some corrected cross-table relationships supporting decentralized publishing. Haakon Meland Eriksen.
-</comments>
-
- <tables>
- <table name="abook">
- <column name="abook_xchan">
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="account">
- </table>
- <table name="addon">
- </table>
- <table name="app">
- </table>
- <table name="attach">
- <column name="hash">
- <foreignKey table="channel" column="channel_hash"/>
- <foreignKey table="hubloc" column="hubloc_hash"/>
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="auth_codes">
- </table>
- <table name="cache">
- </table>
- <table name="channel">
- <column name="channel_hash">
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="chat">
- <column name="chat_xchan">
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="chatpresence">
- <column name="cp_xchan">
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="chatroom">
- </table>
- <table name="clients">
- </table>
- <table name="config">
- </table>
- <table name="conv">
- </table>
- <table name="dreport">
- <column name="dreport_xchan">
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="event">
- <column name="event_xchan">
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- <column name="event_hash">
- <foreignKey table="channel" column="channel_hash"/>
- <foreignKey table="hubloc" column="hubloc_hash"/>
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="fcontact">
- </table>
- <table name="ffinder">
- </table>
- <table name="fserver">
- </table>
- <table name="fsuggest">
- </table>
- <table name="group_member">
- <column name="xchan">
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="groups">
- <column name="hash">
- <foreignKey table="channel" column="channel_hash"/>
- <foreignKey table="hubloc" column="hubloc_hash"/>
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="hook">
- </table>
- <table name="hubloc">
- <column name="hubloc_hash">
- <foreignKey table="channel" column="channel_hash"/>
- <foreignKey table="hubloc" column="hubloc_hash"/>
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="issue">
- </table>
- <table name="item">
- <column name="owner_xchan">
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- <column name="author_xchan">
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- <column name="source_xchan">
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="item_id">
- </table>
- <table name="likes">
- </table>
- <table name="mail">
- <column name="from_xchan">
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- <column name="to_xchan">
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="manage">
- <column name="xchan">
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="menu">
- </table>
- <table name="menu_item">
- </table>
- <table name="notify">
- <column name="hash">
- <foreignKey table="channel" column="channel_hash"/>
- <foreignKey table="hubloc" column="hubloc_hash"/>
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="obj">
- </table>
- <table name="outq">
- <column name="outq_hash">
- <foreignKey table="channel" column="channel_hash"/>
- <foreignKey table="hubloc" column="hubloc_hash"/>
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="pconfig">
- </table>
- <table name="photo">
- <column name="xchan">
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="poll">
- </table>
- <table name="poll_elm">
- </table>
- <table name="profdef">
- </table>
- <table name="profext">
- <column name="hash">
- <foreignKey table="channel" column="channel_hash"/>
- <foreignKey table="hubloc" column="hubloc_hash"/>
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="profile">
- </table>
- <table name="profile_check">
- </table>
- <table name="register">
- <column name="hash">
- <foreignKey table="channel" column="channel_hash"/>
- <foreignKey table="hubloc" column="hubloc_hash"/>
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="session">
- </table>
- <table name="shares">
- <column name="share_xchan">
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="sign">
- </table>
- <table name="site">
- </table>
- <table name="source">
- <column name="src_channel_xchan">
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- <column name="src_xchan">
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="spam">
- </table>
- <table name="sys_perms">
- </table>
- <table name="term">
- <column name="term_hash">
- <foreignKey table="channel" column="channel_hash"/>
- <foreignKey table="hubloc" column="hubloc_hash"/>
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- <column name="parent_hash">
- <foreignKey table="channel" column="channel_hash"/>
- <foreignKey table="hubloc" column="hubloc_hash"/>
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="tokens">
- </table>
- <table name="updates">
-
- <column name="ud_hash">
- <foreignKey table="channel" column="channel_hash"/>
- <foreignKey table="hubloc" column="hubloc_hash"/>
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="verify">
- </table>
- <table name="vote">
- <column name="vote_xchan">
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="xchan">
- <column name="xchan_hash">
- <foreignKey table="channel" column="channel_hash"/>
- <foreignKey table="hubloc" column="hubloc_hash"/>
- </column>
- </table>
- <table name="xchat">
-
- <column name="xchat_xchan">
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="xconfig">
-
- <column name="xchan">
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="xign">
-
- <column name="xchan">
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
-
- </table>
- <table name="xlink">
-
- <column name="xlink_xchan">
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- <table name="xperm">
-
- </table>
- <table name="xprof">
- <column name="xprof_hash">
- <foreignKey table="channel" column="channel_hash"/>
- <foreignKey table="hubloc" column="hubloc_hash"/>
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
-
- </table>
- <table name="xtag">
-
- <column name="xtag_hash">
- <foreignKey table="channel" column="channel_hash"/>
- <foreignKey table="hubloc" column="hubloc_hash"/>
- <foreignKey table="xchan" column="xchan_hash" />
- </column>
- </table>
- </tables>
-
-</schemaMeta>
diff --git a/doc/en/sql_conventions.bb b/doc/en/sql_conventions.bb
deleted file mode 100644
index f6a4e251f..000000000
--- a/doc/en/sql_conventions.bb
+++ /dev/null
@@ -1,91 +0,0 @@
-[h1]SQL Conventions[/h1]
-[b]Intro[/b]
-The following common SQL conventions appear throughout the code in many places. We use a simple DBA (DataBase Abstraction layer) to handle differences between databases. Please be sure to use only standards-compliant SQL.
-
-[b]Rule One[/b]
-Worth Repeating: Don't use non-standard SQL. This goes for addons as well. If you do use non-standard SQL, and the dba funcs are insufficient, do a if()/switch() or similar for all currently supported databases. Currently nothing red# does requires non-standard SQL.
-
-[b]Using a format string[/b]
-[li]Uses sprintf()
-To be written
-[code]// Example
-$r = q("SELECT * FROM profile WHERE uid = %d",
- local_channel()
-);
-[/code][/li]
-
-[b]Checking bit flags in a where clause[/b]
-[li]You must explicitly convert integers to booleans. The easiest way to do this is to compare to 0.
-[code]// Example
-$r = q("SELECT abook_id, abook_flags, abook_my_perms, abook_their_perms, xchan_hash, xchan_photo_m, xchan_name, xchan_url from abook left join xchan on abook_xchan = xchan_hash where abook_channel = %d and not (abook_flags & %d)>0 ",
- intval($uid),
- intval(ABOOK_FLAG_SELF)
-);
-[/code]
-[/li]
-[li]Turning off a flag
-[code]$y = q("update xchan set xchan_flags = (xchan_flags & ~%d) where (xchan_flags & %d)>0 and xchan_hash = '%s'",
- intval(XCHAN_FLAGS_ORPHAN),
- intval(XCHAN_FLAGS_ORPHAN),
- dbesc($rr['hubloc_hash'])
-);[/code]
-[/li]
-[li]Turning on a flag
-[code]$y = q("update xchan set xchan_flags = (xchan_flags | %d) where xchan_hash = '%s'",
- intval(XCHAN_FLAGS_ORPHAN),
- dbesc($rr['hubloc_hash'])
-);[/code]
-[/li]
-
-[b]Using relative times (INTERVALs)[/b]
-[li]Sometimes you want to compare something, like less than x days old.
-[code]// Example
-$r = q("SELECT * FROM abook left join xchan on abook_xchan = xchan_hash
- WHERE abook_dob > %s + interval %s and abook_dob < %s + interval %s",
- db_utcnow(), db_quoteinterval('7 day'),
- db_utcnow(), db_quoteinterval('14 day')
-);[/code]
-[/li]
-[b]Paged results[/b]
-[li]To be written
-[code]// Example
-$r = q("SELECT * FROM mail WHERE uid=%d AND $sql_extra ORDER BY created DESC LIMIT %d OFFSET %d",
- intval(api_user()),
- intval($count), intval($start)
-);[/code][/li]
-
-[b]NULL dates[/b]
-[li]To be written
-[code]// Example
-$r = q("DELETE FROM mail WHERE expires != '%s' AND expires < %s ",
- dbesc(NULL_DATE),
- db_utcnow()
-);[/code][/li]
-
-[b]Storing binary data[/b]
-[li]To be written
-[code]// Example
-$x = q("update photo set data = '%s', height = %d, width = %d where resource_id = '%s' and uid = %d and scale = 0",
- dbescbin($ph->imageString()),
- intval($height),
- intval($width),
- dbesc($resource_id),
- intval($page_owner_uid)
-);[/code][/li]
-
-[b]Current timestamp[/b]
-[li][code]// Example
-$randfunc = db_getfunc('rand');
-$r = q("select xchan_url from xchan left join hubloc on hubloc_hash = xchan_hash where hubloc_connected > %s - interval %s order by $randfunc limit 1",
- db_utcnow(), db_quoteinterval('30 day')
-);[/code][/li]
-
-[b]SQL Function and Operator Abstraction[/b]
-[li]Sometimes the same function or operator has a different name/symbol in each database. You use db_getfunc('funcname') to look them up. The string is [i]not[/i] case-sensitive; do [i]not[/i] include parens.
-[code]// Example
-$randfunc = db_getfunc('rand');
-$r = q("select xchan_url from xchan left join hubloc on hubloc_hash = xchan_hash where hubloc_connected > %s - interval %s order by $randfunc limit 1",
- db_utcnow(), db_quoteinterval('30 day')
-);[/code][/li]
-
-#include doc/macros/main_footer.bb; \ No newline at end of file
diff --git a/doc/en/the_hubzilla_project.md b/doc/en/the_hubzilla_project.md
new file mode 100644
index 000000000..0b930b8fa
--- /dev/null
+++ b/doc/en/the_hubzilla_project.md
@@ -0,0 +1,78 @@
+## The Hubzilla project
+
+### Hubzilla Governance
+
+Governance refers to the management of a project and in particular how this relates to conflict resolution.
+
+#### Community management
+
+The project is managed and decisions are made by the ‘community’. The governance structure is still under development. Until the structure is finalised, decisions are made in the following order:
+
+1. Lazy consensus
+ If a project proposal is made in one of the community governance forums and no serious objections are raised within a ‘reasonable’ period of time from the date of the proposal (we usually give all interested parties 2 to 3 days to comment), no vote is required and the proposal is deemed accepted. Concerns may be raised at this stage, but if these are resolved during the discussion and possible solutions are offered, the proposal is still considered approved.
+2. Veto
+ Experienced developers who have already committed to many projects can veto any decision. The decision cannot proceed until the veto is lifted or an alternative proposal is submitted.
+3. Community vote
+ A decision for which there is no clear mandate or consensus, but which has not been vetoed, can be put to a Community vote. Currently, this is a simple referendum in one of the relevant community forums. At present, the people decide the outcome. This may change in the future if the community adopts a ‘council’ governance model. This document will be updated with the updated governance rules at that time.
+
+Community voting does not always lead to a pleasant outcome and can lead to polarised factions in the community (so other models are also being considered). If the proposal is rejected, there are still various ways to resubmit the proposal with slightly different parameters (turning it into an addon, turning it into an optional feature that is disabled by default, etc.). If there is a lot of interest in the feature and the vote is ‘close’, this can cause a lot of bad feelings for the losing voters. In such close votes, **it** is **strongly recommended** that the requester take steps to address any concerns and resubmit the request.
+
+#### Privacy
+
+Q: Who can see my content?
+A: By default, EVERYONE on the Internet, unless you restrict it. Hubzilla allows you to choose the level of privacy you want. Restricted content is NOT visible to ‘spy networks’ and advertisers. You are protected against eavesdropping by outsiders - as much as possible. Hub administrators with sufficient knowledge and patience may be able to intercept some private communications, but they must make an effort to do so. There are privacy modes within Hubzilla that are tap-proof even for experienced and determined hub administrators.
+
+Q: Can my content be censored?
+A: Hubzilla (the network) CANNOT censor your content. Server and hub administrators are subject to local laws and MAY remove objectionable content from their site/hub. Anyone CAN become a hub administrator, including you, and therefore post content that might otherwise be censored. You MAY still be subject to local laws.
+
+##### Definitions
+
+**Hubzilla**, also referred to as ‘the network’, is a collection of individual computers/servers (also called **hubs** ) that are connected together to form a larger co-operative network.
+
+**Hub**
+A single computer or server that is connected to Hubzilla. These are provided by a **hub administrator** and can be public or private, paid or free.
+
+**Hub administrator**
+The system operator of a single hub.
+
+##### **Policies**
+
+**Public information**
+
+Any information or content you post within Hubzilla MAY be public or visible to anyone on the Internet. Where possible, Hubzilla allows you to protect content and restrict who can see it.
+Your profile photo, channel name, and the location (URL or network address) of your channel are visible to anyone on the Internet, and privacy controls do not affect the display of these items.
+You MAY additionally provide other profile information. Any information you provide in your ‘standard’ or **public profile** MAY be transmitted to other hubs in Hubzilla and additionally displayed in the channel directory. You can restrict access to this profile information. You can restrict it to members of your Hub only, to connections (friends) only, or to other limited groups of viewers as you wish. If you want your profile to be restricted, you must make the appropriate privacy settings or simply NOT provide any additional information.
+
+**Content**
+
+The content you provide (status messages, photos, files, etc.) belongs to you. Hubzilla's default setting is that content is published openly and visible to everyone on the internet (PUBLIC). You CAN control this in your channel settings and restrict the default permissions or you CAN restrict the visibility of each published item separately (PRIVATE). Hubzilla developers ensure that restricted content is ONLY visible to those listed in the restriction list - as best they can.
+Content (especially status messages) that you share with other networks or that you have made visible to everyone on the internet (PUBLIC) cannot simply be taken back after it has been published. They MAY be shared with other networks and made available via RSS/Atom feeds. They can also be syndicated to other Hubzilla sites. They MAY appear on other networks and websites and be visible in web searches. If you do not want this default behaviour, please adjust your channel settings and restrict who can see your content.
+
+**Comments and forum posts**
+
+Comments on posts created by others and posts labelled as forum posts belong to you as the creator/author, but the distribution of these posts is not under your direct control and you relinquish SOME rights to these elements. These posts/comments MAY be shared with others and MAY be visible to anyone on the Internet. In the case of comments, the creator of the ‘first post’ in the thread (conversation) you are replying to controls the distribution of all comments and replies to that post. He is the ‘owner’ and therefore has certain rights over the entire conversation (including all comments contained therein). You can still edit or delete the comment, but the owner of the conversation also has the right to edit, delete, redistribute and save/restore the entire content of the conversation.
+
+**Private information**
+
+Hubzilla developers ensure that all content you provide that is labelled PRIVATE is protected against eavesdropping to the best of their ability. Private channel content MAY be seen in the database of any Hub administrator involved, but private messages are obscured in the database. The latter means that it is very difficult, but NOT impossible, for this content to be seen by a hub administrator. Private channel content and private messages are also removed from email notifications. End-to-end encryption is an optional feature that can NOT be seen even by a determined administrator.
+
+##### **Identity protection**
+
+Protecting your identity is another aspect. Since you have a decentralised identity in Hubzilla, your privacy extends beyond your home hub. If you want to have complete control over your privacy and security, you should run your own hub on your own server. For many people, this is complicated and can overwhelm their technical skills. So let's list some precautions you can take to protect your privacy as much as possible.
+A decentralised identity has many advantages and offers you many interesting features, but you should be aware of the fact that your identity is known to other hubs in the Hubzilla network. One of these benefits is that other channels can offer you customised content and allow you to see private things (such as private photos that others want to share with you). For this reason, these channels need to know who you are. But we understand that sometimes these other channels know more about you than you might want them to. For example, the Visage plug-in, which can tell a channel owner when you last visited their profile. You can easily opt out of this minor and, we think, harmless tracking.
+
+\* You can enable [Do Not Track (DNT)](#)[(http://donottrack.us/)?f=&zid=pepecyb@hub.hubzilla.hu)](http://donottrack.us/)?f=&zid=pepecyb@hub.hubzilla.hu)) in your web browser. We respect this new privacy proposal. All modern browsers support DNT. You can find it in the privacy settings of your browser or you can consult the manual of your web browser. This will not affect the functionality of Hubzilla. This setting is probably sufficient for most people.
+
+*You can [disable](Einstellungen) the publication of your channel in our channel directory. If you want others to find your channel, you should tell them your channel address directly. We think this is a good indication that you prefer extra privacy and will automatically enable ‘Do Not Track’ if this is the case.
+
+\* You can have a blocked hub. This means that all channels and content in this hub are not public and not visible to the outside world. Only your hub administrator can do this. We also respect this and automatically activate ‘Do Not Track’ if it is set.
+
+##### **Censorship**
+
+Hubzilla is a global network that includes all religions and cultures. This does not mean that every member of the network feels the same way you do about controversial topics, and some people may OBJECT to the content you post. If you want to post something that you know may not be accepted by everyone, it is best to limit the publicity to a small circle of friends using the privacy controls.
+
+Hubzilla as a network provider cannot censor content. Hub administrators CAN, however, censor content that appears on their hub to comply with local laws or even personal judgement. Their decision is final. If you have issues with a hub administrator, you can move your account and posts to another site that better meets your expectations. Please check your Hub's [Terms of Service](help/TermsOfService) (regularly) to be aware of any rules or guidelines. If your content consists of illegal or problematic material, we STRONGLY recommend that you run your own hub (become a hub administrator). Your content may be blocked on some hubs, but Hubzilla as a network cannot prevent it from being published.
+
+Hubzilla RECOMMENDS that hub administrators allow a 1-2 day grace period between warning an account owner of content to be removed and the physical removal or deactivation of the account. This gives the content owner the opportunity to export their channel metadata and import it to another site. In rare cases, the content may be of such a nature that immediate account cancellation is warranted. This is a Hub decision, not a Hubzilla decision.
+
+If you typically and regularly post content of a harmful or offensive nature, you are STRONGLY encouraged to flag your account as ‘NSFW’ (Not Safe For Work). This will prevent your profile photo from being displayed in the directory, except to viewers who have disabled ‘safe mode’. If your profile photo is deemed adult or offensive by the directory administrators, the directory administrator MAY flag your profile photo as NSFW. Currently, there is no official mechanism to appeal or reverse this decision. Therefore, you SHOULD flag your own account as NSFW if it may be inappropriate for a general audience. \ No newline at end of file
diff --git a/doc/en/toc.html b/doc/en/toc.html
index b1edef6d2..e9ef86fbc 100644
--- a/doc/en/toc.html
+++ b/doc/en/toc.html
@@ -2,73 +2,101 @@
<div class="mb-3">
<div class="">
<h3 class="panel-title">
- Tutorials
+ Members
</h3>
</div>
- <div id="tutorials" class="doco-section">
+ <div id="members" class="doco-section">
<div class="vstack">
- <a class="" href="/help/tutorials/personal_channel">Personal Channel</a>
+ <a href="/help/member/overview">Overview</a>
+ <a href="/help/member/registration">Login/Registration</a>
+ <a href="/help/member/accounts_profiles_channels_basics">Accounts, Profiles and Channels</a>
+ <a href="/help/member/posting">Posting</a>
+ <a href="/help/member/the_grid">The Grid</a>
+ <a href="/help/member/the_stream">The Stream</a>
+ <a href="/help/member/apps">Apps</a>
+ <a href="/help/member/connections">Connections</a>
+ <a href="/help/member/directory">The Directory</a>
+ <a href="/help/member/blocking_channels">Blocking Channels</a>
+ <a href="/help/member/permissions">Permissions</a>
+ <a href="/help/member/direct_messages">Direct Messages</a>
+ <a href="/help/member/mentions">Mentions</a>
+ <a href="/help/member/tags">Tags</a>
+ <a href="/help/member/bookmarks">Bookmarks</a>
+ <a href="/help/member/search">Search</a>
+ <a href="/help/member/article">Article</a>
+ <a href="/help/member/files">Files</a>
+ <a href="/help/member/chat_rooms">Chat Rooms</a>
+ <a href="/help/member/guest_access">Guest Access</a>
+ <a href="/help/member/privacy_groups">Privacy Groupd</a>
+ <a href="/help/member/calendar">Calendar</a>
+ <a href="/help/member/addressbook">Address Book</a>
+ <a href="/help/member/wikis">Wikis</a>
+ <a href="/help/member/NSFW">Content Warning/NSFW</a>
+ <a href="/help/member/clone">Clone</a>
+ <a href="/help/member/websites">Websites</a>
+ <a href="/help/member/comanche">Comanche Page Description Language</a>
+ <a href="/help/member/encryption">Encryption</a>
+ <a href="/help/member/protection_of_privacy">Tips for Protecting Your Privacy</a>
+ <a href="/help/member/deleting_channel">Deleting a Channel</a>
+ <a href="/help/member/delete_account">Deleting your account</a>
+ <hr>
+ <a href="/help/bugs">Reporting bugs</a>
+ <a href="/help/member/member_faq">FAQ</a>
</div>
</div>
</div>
<div class="mb-3">
<div class="">
<h3 class="panel-title">
- Members
+ Tutorials
</h3>
</div>
- <div id="members" class="doco-section">
+ <div id="tutorials" class="doco-section">
<div class="vstack">
- <a class="" href="/help/member/member_guide">Guide</a>
- <a class="" href="/help/member/bbcode">BBcode Reference</a>
- <a class="" href="/help/feature/additional/overview">Additional Features</a>
- <a class="" href="/help/bugs">Reporting Bugs</a>
- <a class="" href="/help/member/member_faq">FAQ</a>
+ <a href="/help/tutorials/step_with_hubzilla">Step by step into the Fediverse with Hubzilla</a>
+ <a href="/help/tutorials/customise_look">Customise the look of Hubzilla</a>
</div>
</div>
</div>
<div class="mb-3">
<div class="">
<h3 class="panel-title">
- Administrators
+ Administrators
</h3>
</div>
<div id="administrators" class="doco-section">
<div class="vstack">
- <a class="" href="/help/admin/administrator_guide">Guide</a>
- <a class="" href="/help/admin/hub_snapshots">Hub Snapshots</a>
- <a class="" href="/help/database">Database</a>
- <a class="" href="/help/hidden_configs">Extra configs</a>
+ <a class="" href="/help/adminmanual/manual_for_administrators">Guide</a>
</div>
</div>
</div>
<div class="mb-3">
<div class="">
<h3 class="panel-title">
- Developers
+ Developers
</h3>
</div>
<div id="developers" class="doco-section">
<div class="vstack">
- <a class="" href="/help/developer/developer_guide">Guide</a>
- <a class="" href="/help/developer/covenant">Code of Conduct</a>
- <a class="" href="/help/developer/zot_protocol">Zot Protocol</a>
- <a class="" href="/help/developer/api_zot">Zot API</a>
- <a class="" href="/help/hooklist">Hooks</a>
+ <a class="" href="/help/developer/developers_guide">Guide</a>
</div>
</div>
</div>
<div class="mb-3">
<div class="">
<h3 class="panel-title">
- About
+ About
</h3>
</div>
<div id="about" class="doco-section">
<div class="vstack">
- <a class="" href="/help/about/about">About</a>
- <a class="" href="/help/about/project">Project</a>
- <a class="" href="/help/about/about_hub">About this hub</a>
+ <a href="/help/about">About Hubzilla</a>
+ <a href="/help/functions">Functions</a>
+ <a href="/help/glossary">Glossary</a>
+ <a href="/help/ui">User interface</a>
+ <a href="/help/the_hubzilla_project">Project</a>
+ <a href="/help/credits">Credits</a>
+ <a href="/help/placeholder">About this hub</a>
</div>
</div>
</div>
diff --git a/doc/en/tutorials/DerivedTheme1.md b/doc/en/tutorials/DerivedTheme1.md
new file mode 100644
index 000000000..3858a335f
--- /dev/null
+++ b/doc/en/tutorials/DerivedTheme1.md
@@ -0,0 +1,96 @@
+### Creating a Derived Theme
+
+**Lesson 1**
+
+A derived theme takes most of the settings from its "parent" theme and lets you change a few things to your liking without creating an entire theme package.
+
+
+To create a derived theme, first choose a name. For our example we'll call our theme 'mytheme'. Hopefully you'll be a bit more creative. But throughout this document, wherever you see 'mytheme', replace that with the name you chose.
+
+**Directory Structure**
+
+First you need to create a theme directory structure. We'll keep it simple. We need a php directory and a css directory. Here are the Unix/Linux commands to do this. Assume that 'mywebsite' is your top level hubzilla folder.
+
+
+ cd mywebsite
+ mkdir view/theme/mytheme
+ mkdir view/theme/mytheme/css
+ mkdir view/theme/mytheme/php
+
+
+Great. Now we need a couple of files. The first one is your theme info file, which describes the theme.
+
+It will be called view/theme/mytheme/php/theme.php (clever name huh?)
+
+Inside it, put the following information - edit as needed
+
+ <?php
+
+ /**
+ * * Name: Mytheme
+ * * Description: Sample Derived theme
+ * * Version: 1.0
+ * * Author: Your Name
+ * * Compat: Red [*]
+ *
+ */
+
+ function mytheme_init(&$a) {
+
+ App::$theme_info['extends'] = 'redbasic';
+
+
+ }
+
+
+Remember to rename the mytheme_init function with your theme name. In this case we will be extending the theme 'redbasic'.
+
+
+Now create another file. We call this a PCSS file, but it's really a PHP file.
+
+The file is called view/theme/mytheme/php/style.php
+
+In it, put the following:
+
+ <?php
+
+ require_once('view/theme/redbasic/php/style.php');
+
+ echo @file_get_contents('view/theme/mytheme/css/style.css');
+
+
+
+That's it. This tells the software to read the PCSS information for the redbasic theme first, and then read our CSS file which will just consist of changes we want to make from our parent theme (redbasic).
+
+Now create the actual CSS file for your theme. Put it in view/theme/mytheme/css/style.css (where we just told the software to look for it). For our example, we'll just change the body background color so you can see that it works. You can use any CSS you'd like.
+
+
+ body {
+ background-color: #DDD;
+ }
+
+
+You've just successfully created a derived theme. This needs to be enabled in the admin "themes" panel, and then anybody on the site can use it by selecting it in Settings->Display Settings as their default theme.
+
+**Lesson 2**
+
+If you want to use the redbasic schemas for your derived theme, you have to do a bit more.
+
+Do everything as above, but don't create view/theme/mytheme/php/style.php, but copy instead view/theme/redbasic/php/style.php to view/theme/mytheme/php/style.php. Modify that file and remove (or comment out) these two lines:
+
+ if(local_channel() && App::$channel && App::$channel['channel_theme'] != 'redbasic')
+ set_pconfig(local_channel(), 'redbasic', 'schema', '---');
+
+Also add this line at the bottom:
+
+ echo @file_get_contents('view/theme/mytheme/css/style.css');
+
+To show the schema selector you have to copy view/theme/redbasic/tpl/theme_settings.tpl to view/theme/mytheme/tpl/theme_settings.tpl. Modify that file and replace the lines:
+
+ {{if $theme == redbasic}}
+ {{include file="field_select.tpl" field=$schema}}
+ {{/if}}
+
+with:
+
+ {{include file="field_select.tpl" field=$schema}}
diff --git a/doc/en/tutorials/assets/0965ace945f0c95ae38aa5bfedd230d2a7233d3915ac15d629f9dd845854.png b/doc/en/tutorials/assets/0965ace945f0c95ae38aa5bfedd230d2a7233d3915ac15d629f9dd845854.png
deleted file mode 100644
index d5cf1093f..000000000
--- a/doc/en/tutorials/assets/0965ace945f0c95ae38aa5bfedd230d2a7233d3915ac15d629f9dd845854.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/assets/1ebe02c205962dd25035c441631745d16acdb7a44e50d148256c8ad26a67.png b/doc/en/tutorials/assets/1ebe02c205962dd25035c441631745d16acdb7a44e50d148256c8ad26a67.png
deleted file mode 100644
index d613925aa..000000000
--- a/doc/en/tutorials/assets/1ebe02c205962dd25035c441631745d16acdb7a44e50d148256c8ad26a67.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/assets/2243e48ccea25bd907cce3dbd6fc9f7cd832a4c91a4c5dd294b7b219e7d8.png b/doc/en/tutorials/assets/2243e48ccea25bd907cce3dbd6fc9f7cd832a4c91a4c5dd294b7b219e7d8.png
deleted file mode 100644
index c403bf806..000000000
--- a/doc/en/tutorials/assets/2243e48ccea25bd907cce3dbd6fc9f7cd832a4c91a4c5dd294b7b219e7d8.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/assets/25eaad2435200f72a1dd3a00ba17a76ca6db4c246b3c4fa286b390cae7c8.png b/doc/en/tutorials/assets/25eaad2435200f72a1dd3a00ba17a76ca6db4c246b3c4fa286b390cae7c8.png
deleted file mode 100644
index ca8ba6fb9..000000000
--- a/doc/en/tutorials/assets/25eaad2435200f72a1dd3a00ba17a76ca6db4c246b3c4fa286b390cae7c8.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/assets/2b539d5a8474d6ec6dc91155b628d9be5f99ab04a78108ec404f53ec7bb5.png b/doc/en/tutorials/assets/2b539d5a8474d6ec6dc91155b628d9be5f99ab04a78108ec404f53ec7bb5.png
deleted file mode 100644
index 0da2d96e2..000000000
--- a/doc/en/tutorials/assets/2b539d5a8474d6ec6dc91155b628d9be5f99ab04a78108ec404f53ec7bb5.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/assets/31f42a02bdbae095e0329db6c3814e2975979aff12f873f43d81724c5e61.png b/doc/en/tutorials/assets/31f42a02bdbae095e0329db6c3814e2975979aff12f873f43d81724c5e61.png
deleted file mode 100644
index 2a209b2be..000000000
--- a/doc/en/tutorials/assets/31f42a02bdbae095e0329db6c3814e2975979aff12f873f43d81724c5e61.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/assets/324247680b605fd214fd61aecd8f216fa8f5dfa0f16a04c8e968fdbc43d0.png b/doc/en/tutorials/assets/324247680b605fd214fd61aecd8f216fa8f5dfa0f16a04c8e968fdbc43d0.png
deleted file mode 100644
index f992672b0..000000000
--- a/doc/en/tutorials/assets/324247680b605fd214fd61aecd8f216fa8f5dfa0f16a04c8e968fdbc43d0.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/assets/3656a67dce40a1fc2515e9089217f2e136d4fcf8babe77bac00ecaad43ce.png b/doc/en/tutorials/assets/3656a67dce40a1fc2515e9089217f2e136d4fcf8babe77bac00ecaad43ce.png
deleted file mode 100644
index b656192dc..000000000
--- a/doc/en/tutorials/assets/3656a67dce40a1fc2515e9089217f2e136d4fcf8babe77bac00ecaad43ce.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/assets/458a842c2ea0fbe3b7869bb14dfffe1e5be098d1cd6e590bbead25b4cc05.png b/doc/en/tutorials/assets/458a842c2ea0fbe3b7869bb14dfffe1e5be098d1cd6e590bbead25b4cc05.png
deleted file mode 100644
index 6129195b6..000000000
--- a/doc/en/tutorials/assets/458a842c2ea0fbe3b7869bb14dfffe1e5be098d1cd6e590bbead25b4cc05.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/assets/4aaaf1e124514c8d6999a5fe1d07be5af460cda4ba6cde9106ebc1564bb0.png b/doc/en/tutorials/assets/4aaaf1e124514c8d6999a5fe1d07be5af460cda4ba6cde9106ebc1564bb0.png
deleted file mode 100644
index 923403fe9..000000000
--- a/doc/en/tutorials/assets/4aaaf1e124514c8d6999a5fe1d07be5af460cda4ba6cde9106ebc1564bb0.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/assets/4cf326152797a8ecdf5630e921756f825ee00f8ee464d3ef9fed971d2852.png b/doc/en/tutorials/assets/4cf326152797a8ecdf5630e921756f825ee00f8ee464d3ef9fed971d2852.png
deleted file mode 100644
index f158ad5d9..000000000
--- a/doc/en/tutorials/assets/4cf326152797a8ecdf5630e921756f825ee00f8ee464d3ef9fed971d2852.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/assets/75d2927b7ad0d2043d4d3b6ba1364fac8ead173edd39340adaf78be11c9d.png b/doc/en/tutorials/assets/75d2927b7ad0d2043d4d3b6ba1364fac8ead173edd39340adaf78be11c9d.png
deleted file mode 100644
index edc8b01cc..000000000
--- a/doc/en/tutorials/assets/75d2927b7ad0d2043d4d3b6ba1364fac8ead173edd39340adaf78be11c9d.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/assets/7c976a06662a1357b3da8ed0680d1a721c85f2ae2bdd5739a8def466010e.png b/doc/en/tutorials/assets/7c976a06662a1357b3da8ed0680d1a721c85f2ae2bdd5739a8def466010e.png
deleted file mode 100644
index 5b259058b..000000000
--- a/doc/en/tutorials/assets/7c976a06662a1357b3da8ed0680d1a721c85f2ae2bdd5739a8def466010e.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/assets/99a6efda4df631dfb2d2a849412044cc6a0f8aebeac289d28786f2649d24.png b/doc/en/tutorials/assets/99a6efda4df631dfb2d2a849412044cc6a0f8aebeac289d28786f2649d24.png
deleted file mode 100644
index c03ffd18d..000000000
--- a/doc/en/tutorials/assets/99a6efda4df631dfb2d2a849412044cc6a0f8aebeac289d28786f2649d24.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/assets/9eae9fad774a4cd29e665961d35affbd053368056f562c58200fb41027b0.png b/doc/en/tutorials/assets/9eae9fad774a4cd29e665961d35affbd053368056f562c58200fb41027b0.png
deleted file mode 100644
index 65d4c5f0a..000000000
--- a/doc/en/tutorials/assets/9eae9fad774a4cd29e665961d35affbd053368056f562c58200fb41027b0.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/assets/b0bfdf02aef3710a37bb6092c3240b291eca8afa73133b3ac03b86f3302d.png b/doc/en/tutorials/assets/b0bfdf02aef3710a37bb6092c3240b291eca8afa73133b3ac03b86f3302d.png
deleted file mode 100644
index 45609a7bb..000000000
--- a/doc/en/tutorials/assets/b0bfdf02aef3710a37bb6092c3240b291eca8afa73133b3ac03b86f3302d.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/assets/b334915c03a665493915598c69c17a87c910a39db2cd3b5292e4623ea4c4.png b/doc/en/tutorials/assets/b334915c03a665493915598c69c17a87c910a39db2cd3b5292e4623ea4c4.png
deleted file mode 100644
index d239d6965..000000000
--- a/doc/en/tutorials/assets/b334915c03a665493915598c69c17a87c910a39db2cd3b5292e4623ea4c4.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/assets/b3eece28e8db67f1024af42055f0f24ed5e81ba622aca8cac576ccf5930e.png b/doc/en/tutorials/assets/b3eece28e8db67f1024af42055f0f24ed5e81ba622aca8cac576ccf5930e.png
deleted file mode 100644
index 45ed64d00..000000000
--- a/doc/en/tutorials/assets/b3eece28e8db67f1024af42055f0f24ed5e81ba622aca8cac576ccf5930e.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/assets/bdbcf0ffd9004657237f6b7b7863da5a8e39a5bc17d2c67fa160efef2056.png b/doc/en/tutorials/assets/bdbcf0ffd9004657237f6b7b7863da5a8e39a5bc17d2c67fa160efef2056.png
deleted file mode 100644
index fcaed8bef..000000000
--- a/doc/en/tutorials/assets/bdbcf0ffd9004657237f6b7b7863da5a8e39a5bc17d2c67fa160efef2056.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/assets/c4cad3e4c356dd2a227df79bd4dc6d47edf1b66ea243f005b6b452ec366b.png b/doc/en/tutorials/assets/c4cad3e4c356dd2a227df79bd4dc6d47edf1b66ea243f005b6b452ec366b.png
deleted file mode 100644
index 0ccfc8995..000000000
--- a/doc/en/tutorials/assets/c4cad3e4c356dd2a227df79bd4dc6d47edf1b66ea243f005b6b452ec366b.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/assets/c9a880cc82ffa1f7c2f460397bb083bf7dc2a2b8f065e64da598b45b4a2b.png b/doc/en/tutorials/assets/c9a880cc82ffa1f7c2f460397bb083bf7dc2a2b8f065e64da598b45b4a2b.png
deleted file mode 100644
index 1cb4d2d22..000000000
--- a/doc/en/tutorials/assets/c9a880cc82ffa1f7c2f460397bb083bf7dc2a2b8f065e64da598b45b4a2b.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/assets/d080e92d797af5e863fa39b2084c16a8410de1f7a6559633435817444aef.png b/doc/en/tutorials/assets/d080e92d797af5e863fa39b2084c16a8410de1f7a6559633435817444aef.png
deleted file mode 100644
index 22e4cb5d5..000000000
--- a/doc/en/tutorials/assets/d080e92d797af5e863fa39b2084c16a8410de1f7a6559633435817444aef.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/assets/e05248fdc5688d6d24bde52432fdc7b39692a094559aa504de99352940b1.png b/doc/en/tutorials/assets/e05248fdc5688d6d24bde52432fdc7b39692a094559aa504de99352940b1.png
deleted file mode 100644
index 5674f5207..000000000
--- a/doc/en/tutorials/assets/e05248fdc5688d6d24bde52432fdc7b39692a094559aa504de99352940b1.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/assets/e5d5674a34e848e2cce90a60fc416415271d9c51b81ad2a950fb0157222a.png b/doc/en/tutorials/assets/e5d5674a34e848e2cce90a60fc416415271d9c51b81ad2a950fb0157222a.png
deleted file mode 100644
index e6b4a9974..000000000
--- a/doc/en/tutorials/assets/e5d5674a34e848e2cce90a60fc416415271d9c51b81ad2a950fb0157222a.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/assets/ef78bc6aa3fafebd46f353514c907b3fdfe019918fc5553bb3f31388a36f.png b/doc/en/tutorials/assets/ef78bc6aa3fafebd46f353514c907b3fdfe019918fc5553bb3f31388a36f.png
deleted file mode 100644
index 8de042ae4..000000000
--- a/doc/en/tutorials/assets/ef78bc6aa3fafebd46f353514c907b3fdfe019918fc5553bb3f31388a36f.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/assets/facb0bdfdecb4c779de9048cd14b417c0d76de17af476be5f296b78d70e9.png b/doc/en/tutorials/assets/facb0bdfdecb4c779de9048cd14b417c0d76de17af476be5f296b78d70e9.png
deleted file mode 100644
index cec391fb4..000000000
--- a/doc/en/tutorials/assets/facb0bdfdecb4c779de9048cd14b417c0d76de17af476be5f296b78d70e9.png
+++ /dev/null
Binary files differ
diff --git a/doc/en/tutorials/customise_look.md b/doc/en/tutorials/customise_look.md
new file mode 100644
index 000000000..f8c5e3849
--- /dev/null
+++ b/doc/en/tutorials/customise_look.md
@@ -0,0 +1,214 @@
+## Customise the look of Hubzilla
+
+After creating a channel, its appearance is not particularly appealing.
+
+![hopt01](/help/en/tutorials/pic/hopt01.png)
+
+### Pin apps
+
+Firstly, pin the most important apps to the top navigation bar: ‘Write post’, ‘Channel’, ‘Stream’, ‘Connections’ and ‘Public post stream’.
+To do this, select the bottom menu item ‘+ Apps’ in the app menu (⋮ top right). This will take you to the app settings and immediately to the apps already installed. On this page, click on the small pin symbol for each desired app and you will immediately see the respective icon appear at the top of the navigation bar.
+
+![hopt02](/help/en/tutorials/pic/hopt02.png)
+
+### Display settings
+
+Now it's time for the display settings. To do this, click on ‘Settings’ in the main menu (top left, where you can see your avatar image). The various settings categories can now be seen in the left-hand sidebar. Select ‘Display settings’ here.
+
+![hopt03](/help/en/tutorials/pic/hopt03.png)
+
+![hopt04](/help/en/tutorials/pic/hopt04.png)
+
+The page for the display settings is displayed. Since you want to customise the design, the ‘Custom theme settings’ tab is the right choice.
+
+![hopt05](/help/en/tutorials/pic/hopt05.png)
+
+However, the input screen initially only has a few options (five settings). The last setting is the one for which you must switch on the switch first: ‘Show advanced settings’. Click on ‘Submit’ and call up the ‘Customised theme settings’ tab again. You will now see many more settings.
+
+![hopt06](/help/en/tutorials/pic/hopt06.png)
+
+![hopt07](/help/en/tutorials/pic/hopt07.png)
+
+As an example, the colours are changed here, the size of the avatars in the stream is adjusted and a background image is set.
+
+Under the main settings you will find the settings for the colours of the colour scheme. The default colour is displayed as a small circle under each input field. When choosing your own colour, it makes sense to use the standard colour as a guide, at least in terms of brightness. If you click in one of the input fields, a colour selection dialogue box opens, which you can use to specify the colour. As an example, here is a purple colour scheme in which the basic colours are changed: ‘Primary theme color’, i.e. the basic colour of the theme, ‘Success theme color’, which is the colour for clickable links, for example, ‘Info theme color’, which is displayed as the background colour for highlighted menu items, for example, and the ‘Background colour of the navigation bar’. All other colours are not changed in the example. The choice of colour is of course up to you.
+
+![hopt08](/help/en/tutorials/pic/hopt08.png)
+
+![hopt09](/help/en/tutorials/pic/hopt09.png)
+
+![hopt10](/help/en/tutorials/pic/hopt10.png)
+
+![hopt11](/help/en/tutorials/pic/hopt11.png)
+
+Schließlich die „Größe der Avatare von Themenstartern“ auf 48 Pixel festlegen.
+
+![hopt12](/help/en/tutorials/pic/hopt12.png)
+
+The background image is still missing. This should be relatively large, approximately the size of the main screen used. It is also advisable to use a rather light or pale image (if necessary, lighten it with the graphics programme and reduce the contrast) so that it does not ‘overwhelm’ the actual content.
+
+The background image must be accessible somewhere via a URL. It makes sense to upload the image to the files (cloud) of your own channel and use it from there.
+
+To do this, open the ‘Files’ app in the app menu. Here you can create an extra folder if you like (please note that the folder and the image uploaded there are publicly accessible... adjust the access rights with the small padlock, the privacy tool, if necessary) and then upload the image.
+
+![hopt13](/help/en/tutorials/pic/hopt13.png)
+
+![hopt14](/help/en/tutorials/pic/hopt14.png)
+
+![hopt15](/help/en/tutorials/pic/hopt15.png)
+
+![hopt16](/help/en/tutorials/pic/hopt16.png)
+
+![hopt17](/help/en/tutorials/pic/hopt17.png)
+
+![hopt18](/help/en/tutorials/pic/hopt18.png)
+
+![hopt19](/help/en/tutorials/pic/hopt19.png)
+
+![hopt20](/help/en/tutorials/pic/hopt20.png)
+
+![hopt21](/help/en/tutorials/pic/hopt21.png)
+
+After uploading, the image is displayed in the file list. Right-click on the entry and select to copy the URL to move the URL for the image to the clipboard.
+
+![hopt22](/help/en/tutorials/pic/hopt22.png)
+
+Back at ‘Customised theme settings’, you can now enter the URL in the ‘Background image’ input field.
+
+![hopt23](/help/en/tutorials/pic/hopt23.png)
+
+![hopt24](/help/en/tutorials/pic/hopt24.png)
+
+One last click and Hubzilla shines in its new look.
+
+![hopt25](/help/en/tutorials/pic/hopt25.png)
+
+### PDL Editor
+
+#### Basics
+
+Various Hubzilla apps and basic functions are based on specially designed websites. The user does not come into contact with the underlying mechanisms, they simply use these pages.
+If you call up the ‘Channel’ app, for example, your own channel is displayed.
+
+![hopt26](/help/en/tutorials/pic/hopt26.png)
+
+The channel banner can be seen at the top. The channel name and the channel address (handle) are embedded in this banner.
+
+Below this is the navigation bar with the main menu, the title of the hub, any pinned apps and the app menu.
+
+However, the area below the navigation bar is where things get interesting. This is where the biggest differences can be seen between the various apps. On the channel page, a card with the channel information (banner, profile picture, short description, profile information) can be found at the top of the left-hand sidebar (in the unchanged default state).
+
+In the centre, in the content area, the content created by this channel is displayed.
+Below the profile info card, in the left sidebar, there is a card with some of the connections (when calling up external channels, the shared contacts are displayed here).
+
+Below this is a card with the archives of the content (the top level is the years, one level below the months). If you select an archive, only the content published in the selected archive period is displayed in the content area.
+Below the archive card is the category card. All categories under which content was published are listed here. Clicking on such a category causes all content published by the channel under the corresponding category to be displayed in the content area.
+
+Below the archive card is the card with the keyword cloud, which displays the hashtags used and can be used to filter the content of the content area (channel articles).
+
+If there are unseen notifications, these are shown in another card in the right-hand sidebar.
+
+This is the ‘normal state’.
+
+#### Editing modules with the PDL editor
+
+The various pages that can be accessed via apps are also referred to as ‘modules’.
+
+As a user, you can now customise and design the appearance of these pages to a large extent. Internally, the structure of such a page is determined by a PDL file. These files are layout files that use the Comanche page description language.
+
+So that the user does not have to deal with such a language, there is the app ‘PDL Editor’, with which you can change / create the page structure with a GUI.
+
+The app must first be installed and activated. Then you can call it up from the app menu.
+If you call up the PDL Editor, the page structure of the HQ is displayed as standard.
+
+The main menu of the PDL Editor is located at the bottom centre of the screen. Here you will find the entries
+
+‘MODULES’, “TEMPLATES”, “ITEMS”, “SOURCE” and “APPLY”.
+
+From the ‘MODULES’ menu, you can select the module to be edited (this corresponds to the page to be edited).
+
+Assuming you want to customise the channel page (as it is displayed to you and visitors), select the ‘channel’ module here.
+
+![hopt27](/help/en/tutorials/pic/hopt27.png)
+
+The PDL file for the channel page is loaded and you can see the corresponding components (‘ITEMS’) of this page that have just been described.
+
+![hopt28](/help/en/tutorials/pic/hopt28.png)
+
+Assuming you would now like to ‘refine’ our channel page by displaying the time in the right-hand sidebar, select the ‘CLOCK’ item under ‘ITEMS’, ‘grab’ it with the mouse pointer at the cross arrow symbol and drag it to the right into the sidebar.
+
+![hopt29](/help/en/tutorials/pic/hopt29.png)
+
+![hopt30](/help/en/tutorials/pic/hopt30.png)
+
+To apply the changes, click on ‘APPLY’ in the main menu.
+
+![hopt31](/help/en/tutorials/pic/hopt31.png)
+
+If you now open the channel page, a card with the current time appears in the right-hand sidebar.
+
+![hopt32](/help/en/tutorials/pic/hopt32.png)
+
+In this way, you can customise all the pages found under ‘MODULES’ to your own taste.
+
+If you have customised your page and it is somehow ‘so completely chopped up’: No need to panic! In the main menu, you will find the additional entry ‘RESET’ for customised layout pages. Click on it to reset the page layout to the Hubzilla standard.
+
+However, not all items are presented here... Everyone can experiment a little. Most of them have an explanatory title.
+
+If you click on ‘SOURCE’ in the PDL main menu, the source code of the current layout is displayed. A look here will help you familiarise yourself with PDL. You can also make changes directly in the source code... if something is not accessible via the ‘ITEMS’. However, you should familiarise yourself with the page markup language, blocks and modules beforehand.
+
+![hopt33](/help/en/tutorials/pic/hopt33.png)
+
+#### **PDL editor for advanced users**
+
+Suppose you want to make some links accessible via a menu in the right-hand sidebar of the channel page.
+This is perfectly feasible.
+
+But first you need a menu. To create menus, however, you need to install and activate the ‘Websites’ app, because creating menus is part of the website functionality. So even if you don't want to create websites in your channel, you need the ‘Websites’ app to create menus. Although... that's not quite true. You can also access the menu editor in a different way than via the ‘Websites’ app. To do this, enter `<url-des-hub>/menu/<channel name>`. Now you also end up in the menu ‘app’. However, it is easier with the website app.
+
+![hopt34](/help/en/tutorials/pic/hopt34.png)
+
+Click on ‘Create’ to open the menu editor.
+
+![hopt35](/help/en/tutorials/pic/hopt35.png)
+
+Here you now have to enter a suitable name (which you can use to address the menu later) and (optionally) a title for the menu (this can be seen later on the website).
+
+Then click on ‘Submit and continue’.
+
+You will now be taken to the link editor for the menu you have just created. Here you enter the title of the menu item and the corresponding URL. You can also specify the order in which the menu items are sorted using the ‘Order in list’ field. Once you have completed the entry and clicked on ‘Submit and continue’, you can then enter another menu item. Clicking on ‘Submit and finalise’ adds the entry and closes the menu editor (you can of course also edit menus afterwards).
+
+![hopt36](/help/en/tutorials/pic/hopt36.png)
+
+![hopt37](/help/en/tutorials/pic/hopt37.png)
+
+![hopt38](/help/en/tutorials/pic/hopt38.png)
+
+The new menu now appears in the list of menus.
+
+![hopt39](/help/en/tutorials/pic/hopt39.png)
+
+Now return to the PDL editor and call up the channel module.
+
+Now there are again two possibilities. Either you open the source text editor ‘SOURCE’ and enter the entry for the menu card in the appropriate place by hand...
+
+If you want the menu to appear in the sidebar, select the ‘aside’ region and enter `[menu]mymenu[/menu]` as a new line.
+
+![hopt40](/help/en/tutorials/pic/hopt40.png)
+
+Now click on ‘Submit’ and the new map will appear in the visual PDL editor.
+Accept with ‘APPLY’... and then the menu is shown on the channel website.
+
+![hopt41](/help/en/tutorials/pic/hopt41.png)
+
+The second method (with which you don't have to search for the right place in the source code) is to simply drag any item in the PDL editor to the place where the menu should appear. Then click on the ‘Edit’ button for this item, change the existing entry to `[menu]mymenu[/menu]` and click on ‘Submit’. Then click on ‘APPLY’ and you have the same result.
+
+![hopt42](/help/en/tutorials/pic/hopt42.png)
+
+![hopt43](/help/en/tutorials/pic/hopt43.png)
+
+![hopt44](/help/en/tutorials/pic/hopt44.png)
+
+![hopt45](/help/en/tutorials/pic/hopt45.png)
+
+Have fun experimenting!
diff --git a/doc/en/tutorials/personal_channel.html b/doc/en/tutorials/personal_channel.html
deleted file mode 100644
index 3069cd44b..000000000
--- a/doc/en/tutorials/personal_channel.html
+++ /dev/null
@@ -1,162 +0,0 @@
-
-<p>This tutorial is intended to be followed in sequence as if you were setting up a
-channel for the first time. It introduces some of the tools and features related
-to a personal channel in a natural way.</p>
-
-<h3 id="Create_a_new_channel">Create a new channel</h3>
-
-<p>When you log in for the first time after registering, you must create a channel.
-(Alternatively you can visit https://your_website/new_channel)</p>
-
-<p><img class="img-responsive" src="/help/en/tutorials/assets/c9a880cc82ffa1f7c2f460397bb083bf7dc2a2b8f065e64da598b45b4a2b.png" alt="image"></p>
-
-<p>Enter your name and a nickname for the channel address, and select a "role".
-Typically if this is a personal channel that represents you, select a <strong>Social</strong> role
-with a level of default privacy that you are comfortable with. If you are unsure,
-select <strong>Social - Mostly public</strong> which allows easy interaction and provides privacy when you need it.
-Alternatively, <strong>Social - Restricted</strong> is very popular among privacy advocates, though it may require a bit more
-effort to meet people. Whichever setting you choose can be changed later if you decide you require more or less privacy than what is provided.</p>
-
-<!-- This section no longer applicable
-<h3 id="Configure_your_channel_features">Configure your channel features</h3>
-
-<p>When your new channel is created you are directed to the channel settings page.
-Take the time to look around at all the settings pages to familiarize yourself with
-your options, even if you don't understand everything you see right now.</p>
-
-<p>Navigate to the <strong>Additional Features</strong> settings and follow the screenshots below to
-enable various features. Remember to press the Submit button when you are done with
-your selections.</p>
-
-<p><img class="img-responsive" src="/help/en/tutorials/assets/3656a67dce40a1fc2515e9089217f2e136d4fcf8babe77bac00ecaad43ce.png" alt="image"><img class="img-responsive" src="/help/en/tutorials/assets/4aaaf1e124514c8d6999a5fe1d07be5af460cda4ba6cde9106ebc1564bb0.png" alt="image"><img class="img-responsive" src="/help/en/tutorials/assets/99a6efda4df631dfb2d2a849412044cc6a0f8aebeac289d28786f2649d24.png" alt="image"><img class="img-responsive" src="/help/en/tutorials/assets/e5d5674a34e848e2cce90a60fc416415271d9c51b81ad2a950fb0157222a.png" alt="image"></p>
--->
-
-<h3 id="Add_a_profile_photo">Add a profile photo</h3>
-
-<p>When your new channel is created, you will be directed to a page determined by your site admin. By default this is the <strong>Edit Profile</strong> page.
-
-<p>From the <strong>Profile Tools</strong> dropdown menu, select <strong>Change profile photo</strong> (or just click the profile photo).</p>
-
-<p><img class="img-responsive" src="/help/en/tutorials/assets/31f42a02bdbae095e0329db6c3814e2975979aff12f873f43d81724c5e61.png" alt="image"></p>
-
-<p>Upload your photo and size as necessary using the image editor.</p>
-
-<p><img class="img-responsive" src="/help/en/tutorials/assets/458a842c2ea0fbe3b7869bb14dfffe1e5be098d1cd6e590bbead25b4cc05.png" alt="image"></p>
-
-<p>When you press <strong>Done Editing</strong> you will be redirected back to the profile editor.
-(You might need to clear your browser cache if you have trouble seeing the new photo.)</p>
-
-<p><img class="img-responsive" src="/help/en/tutorials/assets/d080e92d797af5e863fa39b2084c16a8410de1f7a6559633435817444aef.png" alt="image"></p>
-
-<p>Returning to your channel home page you will see that a post notifying others of your new
-profile pic has been automatically posted.</p>
-
-<p><img class="img-responsive" src="/help/en/tutorials/assets/1ebe02c205962dd25035c441631745d16acdb7a44e50d148256c8ad26a67.png" alt="image"></p>
-
-<h3 id="Compose_a_post">Compose a post</h3>
-
-<p>Go to your channel home and open the post editor by pressing the <strong>Share</strong> textbox
-at the top of the channel "wall". Enter a message, and then drag-and-drop an image
-file into the post editor text area (alternatively you can use the <strong>Attach file</strong>
-tool at the bottom).</p>
-
-<p><img class="img-responsive" src="/help/en/tutorials/assets/b0bfdf02aef3710a37bb6092c3240b291eca8afa73133b3ac03b86f3302d.png" alt="image"></p>
-
-<p>Your image file will be automatically uploaded and stored in your cloud files, and
-a link will appear in the post window. Pressing the post preview button will allow you to preview your post before publishing it.</p>
-
-<p><img class="img-responsive" src="/help/en/tutorials/assets/7c976a06662a1357b3da8ed0680d1a721c85f2ae2bdd5739a8def466010e.png" alt="image"></p>
-
-<p>Pressing the lock button near the Submit button will open the <strong>Access Control List</strong>
-so you can specify exactly who can access this post.</p>
-
-<p><img class="img-responsive" src="/help/en/tutorials/assets/2b539d5a8474d6ec6dc91155b628d9be5f99ab04a78108ec404f53ec7bb5.png" alt="image"></p>
-
-<h3 id="Use_an_uploaded_image_as_a_channel_cover_photo">Use an uploaded image as a channel cover photo</h3>
-
-<p>One way to add some pizzazz your channel is to add a cover photo that visitors will
-see when they load your channel page. The integrated cloud file system
-allows you to choose an existing photo for this purpose.</p>
-
-<p>Visit your photos in the <strong>Photos</strong> app</p>
-
-<p><img class="img-responsive" src="/help/en/tutorials/assets/0965ace945f0c95ae38aa5bfedd230d2a7233d3915ac15d629f9dd845854.png" alt="image"></p>
-
-<p>Select the photo you wish to use and select <strong>Use as cover photo</strong> from the <strong>Photo Tools</strong>
-dropdown menu.</p>
-
-<p><img class="img-responsive" src="/help/en/tutorials/assets/9eae9fad774a4cd29e665961d35affbd053368056f562c58200fb41027b0.png" alt="image"></p>
-
-<p>Crop the image using the photo editor and save your changes.</p>
-
-<p><img class="img-responsive" src="/help/en/tutorials/assets/b3eece28e8db67f1024af42055f0f24ed5e81ba622aca8cac576ccf5930e.png" alt="image"></p>
-
-<p>When you load your channel home page, you will first see the cover photo, and your
-channel page will fade in as you scroll down.</p>
-
-<p><img class="img-responsive" src="/help/en/tutorials/assets/4cf326152797a8ecdf5630e921756f825ee00f8ee464d3ef9fed971d2852.png" alt="image"></p>
-
-<h3 id="Make_a_connection">Make a connection</h3>
-
-<p>Making connections between channels to share things is what social communications are all about.
-Making a connection is simple. If you do not already know how to reach a channel's home
-page, you might try a directory search by opening the <strong>Directory</strong> link from the menu on the right
-side of the top navbar.</p>
-
-<p><img class="img-responsive" src="/help/en/tutorials/assets/ef78bc6aa3fafebd46f353514c907b3fdfe019918fc5553bb3f31388a36f.png" alt="image"></p>
-
-<p>You can connect directly from the directory entry using the <strong>Connect</strong> button there,
-or you can open the channel page first and press the <strong>Connect</strong> button below the
-profile photo.</p>
-
-<p><img class="img-responsive" src="/help/en/tutorials/assets/75d2927b7ad0d2043d4d3b6ba1364fac8ead173edd39340adaf78be11c9d.png" alt="image"></p>
-
-<p>After you connect you are immediately taken to the connection editor page, where
-you make some important decisions about what you plan to share with this channel.</p>
-
-<p><img class="img-responsive" src="/help/en/tutorials/assets/b334915c03a665493915598c69c17a87c910a39db2cd3b5292e4623ea4c4.png" alt="image"></p>
-
-<p>The two important settings are</p>
-
-<ul><li>The individual permissions for the newly connected channel</li>
-<li>The privacy group(s) the connection is a member of</li>
-</ul><p>The individual permissions are mostly straightforward, but they can be slightly
-unclear at first. For example, <strong>Can view my file storage and photos</strong> does <em>not</em>
-mean that the connected channel will be able to view <em>all</em> of your photos and files!
-It means that you will have the <em>option</em> to share photos and files with that
-channel. It is perfectly possible for you to allow someone to read your posts but
-disallow them from seeing photos in that post. This kind of unusual situation is,
-as they say, not a bug; it is a feature.</p>
-
-<p>Privacy groups allow you to conveniently share items with groups of people. You can
-create whatever groups fit your needs by opening the <strong>Add privacy group</strong> link.</p>
-
-<p><img class="img-responsive" src="/help/en/tutorials/assets/facb0bdfdecb4c779de9048cd14b417c0d76de17af476be5f296b78d70e9.png" alt="image"></p>
-
-<p>In this editor, you can switch between the existing privacy groups and see at a
-glance what channels are and are not members of the group. Selecting the icon of
-a channel in either box will move it to the in or out of the group.</p>
-
-<p><img class="img-responsive" src="/help/en/tutorials/assets/25eaad2435200f72a1dd3a00ba17a76ca6db4c246b3c4fa286b390cae7c8.png" alt="image"></p>
-
-<p>When editing an individual channel's settings, you can set their privacy group
-membership using the widget on the left:</p>
-
-<p><img class="img-responsive" src="/help/en/tutorials/assets/bdbcf0ffd9004657237f6b7b7863da5a8e39a5bc17d2c67fa160efef2056.png" alt="image"></p>
-
-<p>Connections are a mutual engagement. The channel you connect can <em>choose</em> to approve your
-connection. They will receive a notification that you connected</p>
-
-<p><img class="img-responsive" src="/help/en/tutorials/assets/324247680b605fd214fd61aecd8f216fa8f5dfa0f16a04c8e968fdbc43d0.png" alt="image"></p>
-
-<p>which takes them to their <a href="https://grid.reticu.li/connections"><strong>Connections</strong></a> editor page where
-they can choose to approve the connection or not.</p>
-
-<p><img class="img-responsive" src="/help/en/tutorials/assets/e05248fdc5688d6d24bde52432fdc7b39692a094559aa504de99352940b1.png" alt="image"></p>
-
-<p>After you approve a connection, it is a good idea to open the individual connection
-editor by pressing the edit button beside the <strong>Delete</strong> button.</p>
-
-<p><img class="img-responsive" src="/help/en/tutorials/assets/c4cad3e4c356dd2a227df79bd4dc6d47edf1b66ea243f005b6b452ec366b.png" alt="image"></p>
-
-
diff --git a/doc/en/tutorials/pic/fedidb.png b/doc/en/tutorials/pic/fedidb.png
new file mode 100644
index 000000000..988658aff
--- /dev/null
+++ b/doc/en/tutorials/pic/fedidb.png
Binary files differ
diff --git a/doc/en/tutorials/pic/fedieverse-observer.png b/doc/en/tutorials/pic/fedieverse-observer.png
new file mode 100644
index 000000000..a80cbded9
--- /dev/null
+++ b/doc/en/tutorials/pic/fedieverse-observer.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt01.png b/doc/en/tutorials/pic/hopt01.png
new file mode 100644
index 000000000..6543906df
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt01.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt02.png b/doc/en/tutorials/pic/hopt02.png
new file mode 100644
index 000000000..8daa6bcce
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt02.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt03.png b/doc/en/tutorials/pic/hopt03.png
new file mode 100644
index 000000000..258d47001
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt03.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt04.png b/doc/en/tutorials/pic/hopt04.png
new file mode 100644
index 000000000..0cf80fc55
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt04.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt05.png b/doc/en/tutorials/pic/hopt05.png
new file mode 100644
index 000000000..acd442a5f
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt05.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt06.png b/doc/en/tutorials/pic/hopt06.png
new file mode 100644
index 000000000..1bde1ffed
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt06.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt07.png b/doc/en/tutorials/pic/hopt07.png
new file mode 100644
index 000000000..0d93acbd5
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt07.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt08.png b/doc/en/tutorials/pic/hopt08.png
new file mode 100644
index 000000000..e3e08c1f1
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt08.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt09.png b/doc/en/tutorials/pic/hopt09.png
new file mode 100644
index 000000000..a0332a804
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt09.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt10.png b/doc/en/tutorials/pic/hopt10.png
new file mode 100644
index 000000000..56eade1e1
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt10.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt11.png b/doc/en/tutorials/pic/hopt11.png
new file mode 100644
index 000000000..c8bfc949b
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt11.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt12.png b/doc/en/tutorials/pic/hopt12.png
new file mode 100644
index 000000000..785e485d6
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt12.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt13.png b/doc/en/tutorials/pic/hopt13.png
new file mode 100644
index 000000000..ca95acb14
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt13.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt14.png b/doc/en/tutorials/pic/hopt14.png
new file mode 100644
index 000000000..f48723302
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt14.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt15.png b/doc/en/tutorials/pic/hopt15.png
new file mode 100644
index 000000000..16458f64f
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt15.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt16.png b/doc/en/tutorials/pic/hopt16.png
new file mode 100644
index 000000000..a6204ffb8
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt16.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt17.png b/doc/en/tutorials/pic/hopt17.png
new file mode 100644
index 000000000..f1e17df7d
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt17.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt18.png b/doc/en/tutorials/pic/hopt18.png
new file mode 100644
index 000000000..1b65ecf53
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt18.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt19.png b/doc/en/tutorials/pic/hopt19.png
new file mode 100644
index 000000000..669ecfb99
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt19.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt20.png b/doc/en/tutorials/pic/hopt20.png
new file mode 100644
index 000000000..0d5102d6c
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt20.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt21.png b/doc/en/tutorials/pic/hopt21.png
new file mode 100644
index 000000000..29493ad27
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt21.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt22.png b/doc/en/tutorials/pic/hopt22.png
new file mode 100644
index 000000000..cfe9e4967
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt22.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt23.png b/doc/en/tutorials/pic/hopt23.png
new file mode 100644
index 000000000..ebe8934fd
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt23.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt24.png b/doc/en/tutorials/pic/hopt24.png
new file mode 100644
index 000000000..fcdd3c819
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt24.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt25.png b/doc/en/tutorials/pic/hopt25.png
new file mode 100644
index 000000000..b5660aab3
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt25.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt26.png b/doc/en/tutorials/pic/hopt26.png
new file mode 100644
index 000000000..baf283f26
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt26.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt27.png b/doc/en/tutorials/pic/hopt27.png
new file mode 100644
index 000000000..e13c1982e
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt27.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt28.png b/doc/en/tutorials/pic/hopt28.png
new file mode 100644
index 000000000..b6c4385ee
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt28.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt29.png b/doc/en/tutorials/pic/hopt29.png
new file mode 100644
index 000000000..2392d848e
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt29.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt30.png b/doc/en/tutorials/pic/hopt30.png
new file mode 100644
index 000000000..9fc4ee3bd
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt30.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt31.png b/doc/en/tutorials/pic/hopt31.png
new file mode 100644
index 000000000..9656ef6a5
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt31.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt32.png b/doc/en/tutorials/pic/hopt32.png
new file mode 100644
index 000000000..70afa3801
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt32.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt33.png b/doc/en/tutorials/pic/hopt33.png
new file mode 100644
index 000000000..0e4c57e66
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt33.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt34.png b/doc/en/tutorials/pic/hopt34.png
new file mode 100644
index 000000000..72fa8ed4f
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt34.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt35.png b/doc/en/tutorials/pic/hopt35.png
new file mode 100644
index 000000000..5aa06cc39
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt35.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt36.png b/doc/en/tutorials/pic/hopt36.png
new file mode 100644
index 000000000..dcc955b86
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt36.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt37.png b/doc/en/tutorials/pic/hopt37.png
new file mode 100644
index 000000000..466d9fbd0
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt37.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt38.png b/doc/en/tutorials/pic/hopt38.png
new file mode 100644
index 000000000..dcdeabd75
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt38.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt39.png b/doc/en/tutorials/pic/hopt39.png
new file mode 100644
index 000000000..852e1a5e1
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt39.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt40.png b/doc/en/tutorials/pic/hopt40.png
new file mode 100644
index 000000000..d7825a76e
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt40.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt41.png b/doc/en/tutorials/pic/hopt41.png
new file mode 100644
index 000000000..6e9d920fa
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt41.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt42.png b/doc/en/tutorials/pic/hopt42.png
new file mode 100644
index 000000000..3b594dfbe
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt42.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt43.png b/doc/en/tutorials/pic/hopt43.png
new file mode 100644
index 000000000..4910684f1
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt43.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt44.png b/doc/en/tutorials/pic/hopt44.png
new file mode 100644
index 000000000..77c769df1
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt44.png
Binary files differ
diff --git a/doc/en/tutorials/pic/hopt45.png b/doc/en/tutorials/pic/hopt45.png
new file mode 100644
index 000000000..6afa1b488
--- /dev/null
+++ b/doc/en/tutorials/pic/hopt45.png
Binary files differ
diff --git a/doc/en/tutorials/pic/nomadapp.png b/doc/en/tutorials/pic/nomadapp.png
new file mode 100644
index 000000000..f62e3d96d
--- /dev/null
+++ b/doc/en/tutorials/pic/nomadapp.png
Binary files differ
diff --git a/doc/en/tutorials/pic/pubsites.png b/doc/en/tutorials/pic/pubsites.png
new file mode 100644
index 000000000..6fe2e1e71
--- /dev/null
+++ b/doc/en/tutorials/pic/pubsites.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep01.png b/doc/en/tutorials/pic/stepbystep01.png
new file mode 100644
index 000000000..98fc350e5
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep01.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep02.png b/doc/en/tutorials/pic/stepbystep02.png
new file mode 100644
index 000000000..55d2069f7
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep02.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep03.png b/doc/en/tutorials/pic/stepbystep03.png
new file mode 100644
index 000000000..6def65c6d
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep03.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep04.png b/doc/en/tutorials/pic/stepbystep04.png
new file mode 100644
index 000000000..7356dadd5
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep04.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep05.png b/doc/en/tutorials/pic/stepbystep05.png
new file mode 100644
index 000000000..87fab8d76
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep05.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep06.png b/doc/en/tutorials/pic/stepbystep06.png
new file mode 100644
index 000000000..44d3ff8fd
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep06.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep07.png b/doc/en/tutorials/pic/stepbystep07.png
new file mode 100644
index 000000000..6d0b386e4
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep07.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep08.png b/doc/en/tutorials/pic/stepbystep08.png
new file mode 100644
index 000000000..ec86d2319
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep08.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep09.png b/doc/en/tutorials/pic/stepbystep09.png
new file mode 100644
index 000000000..962a24d30
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep09.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep10.png b/doc/en/tutorials/pic/stepbystep10.png
new file mode 100644
index 000000000..bfa768f66
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep10.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep11.png b/doc/en/tutorials/pic/stepbystep11.png
new file mode 100644
index 000000000..7d7a428ae
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep11.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep12.png b/doc/en/tutorials/pic/stepbystep12.png
new file mode 100644
index 000000000..03f8edcaf
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep12.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep13.png b/doc/en/tutorials/pic/stepbystep13.png
new file mode 100644
index 000000000..f34cf2840
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep13.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep14.png b/doc/en/tutorials/pic/stepbystep14.png
new file mode 100644
index 000000000..891e9fb78
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep14.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep15.png b/doc/en/tutorials/pic/stepbystep15.png
new file mode 100644
index 000000000..1ec9c43cf
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep15.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep16.png b/doc/en/tutorials/pic/stepbystep16.png
new file mode 100644
index 000000000..061b5a99d
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep16.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep17.png b/doc/en/tutorials/pic/stepbystep17.png
new file mode 100644
index 000000000..90e0d4a56
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep17.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep18.png b/doc/en/tutorials/pic/stepbystep18.png
new file mode 100644
index 000000000..4a1b6153e
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep18.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep19.png b/doc/en/tutorials/pic/stepbystep19.png
new file mode 100644
index 000000000..f867ac869
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep19.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep20.png b/doc/en/tutorials/pic/stepbystep20.png
new file mode 100644
index 000000000..ed0ad7a5f
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep20.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep21.png b/doc/en/tutorials/pic/stepbystep21.png
new file mode 100644
index 000000000..6a00aa7d3
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep21.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep22.png b/doc/en/tutorials/pic/stepbystep22.png
new file mode 100644
index 000000000..5e411cf2c
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep22.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep23.png b/doc/en/tutorials/pic/stepbystep23.png
new file mode 100644
index 000000000..41ee51552
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep23.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep24.png b/doc/en/tutorials/pic/stepbystep24.png
new file mode 100644
index 000000000..b4e711e3e
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep24.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep25.png b/doc/en/tutorials/pic/stepbystep25.png
new file mode 100644
index 000000000..0b8087e07
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep25.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep26.png b/doc/en/tutorials/pic/stepbystep26.png
new file mode 100644
index 000000000..cbdcb2cd4
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep26.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep27.png b/doc/en/tutorials/pic/stepbystep27.png
new file mode 100644
index 000000000..c88d02f07
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep27.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep28.png b/doc/en/tutorials/pic/stepbystep28.png
new file mode 100644
index 000000000..58e9c5a90
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep28.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep29.png b/doc/en/tutorials/pic/stepbystep29.png
new file mode 100644
index 000000000..4b92c7766
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep29.png
Binary files differ
diff --git a/doc/en/tutorials/pic/stepbystep30.png b/doc/en/tutorials/pic/stepbystep30.png
new file mode 100644
index 000000000..425bfab1f
--- /dev/null
+++ b/doc/en/tutorials/pic/stepbystep30.png
Binary files differ
diff --git a/doc/en/tutorials/step_with_hubzilla.md b/doc/en/tutorials/step_with_hubzilla.md
new file mode 100644
index 000000000..9da530597
--- /dev/null
+++ b/doc/en/tutorials/step_with_hubzilla.md
@@ -0,0 +1,157 @@
+## Step by step into the Fediverse with Hubzilla
+
+### Getting started
+
+As with any other Fediverse service, the first step is to choose a server (hub). This is the case in Fediverse and an essential part of the freedom it offers.
+
+You can find hubs in the usual ways: by using appropriate databases or lists.
+
+e.g.
+
+[FediDB](https://fedidb.org/software/hubzilla)
+
+![fedidb](/help/en/tutorials/pic/fedidb.png)
+
+[Fediverse Observer](https://hubzilla.fediverse.observer/list)
+
+![observer](/help/en/tutorials/pic/fedieverse-observer.png)
+
+[List of public hubs on a Hubzilla server](https://zotsite.net/pubsites)
+
+![pubsites](/help/en/tutorials/pic/pubsites.png)
+
+Once you have selected a hub, call up the URL. This will take you to a standard page. This may vary slightly from hub to hub, but you will usually find two menu items at the top of the screen: ‘Login’ and ‘Register’.
+
+![sbs01](/help/en/tutorials/pic/stepbystep01.png)
+
+![sbs02](/help/en/tutorials/pic/stepbystep02.png)
+
+Clicking on the link leads to a registration form. There are several possible scenarios here. Some hubs are set up so that you create a channel when you register (another special feature of Hubzilla is that you can operate several channels with one account). With other hubs, you initially only create one account using the form. Once you have created this account and log in for the first time, you will be directed to the channel creation form.
+
+![sbs04](/help/en/tutorials/pic/stepbystep04.png)
+
+To create an account, you need a ( valid ) e-mail address and you have to come up with a password. You also have to confirm your age and then you can send the information you have entered.
+You will then be taken to an input mask where you have to enter a verification code (usually, there are also hubs that skip this step... I think it's risky). You will receive this code by e-mail after submitting the registration form.
+
+![sbs05](/help/en/tutorials/pic/stepbystep05.png)
+
+If you did not have to create a channel when you registered, you will be redirected to the ‘Create a channel’ page at this point. Here you must now come up with a name for your own identity. And also a short name (‘nickname’) for the channel (a suggestion is automatically generated from the channel name). This short name will be the main component of the Fediverse handle (i.e. your own ‘Fediverse address’).
+
+![sbs06](/help/en/tutorials/pic/stepbystep06.png)
+
+| **Note regarding the handle at Hubzilla:** |
+| ------------------------------------------------------------ |
+| Compared to other Fediverse services, Hubzilla makes an exception with the handle. While an ‘@’ is placed in front of the handle almost everywhere, this is not the case with Hubzilla. However, this is quickly internalised. For example, if you use Hubzilla to search for a user who has a Mastodon account via their handle, you simply omit the leading ‘@’. However, if you want to follow or search for a Hubzilla user from another Fediverse service, simply add an ‘@’ in front of the Hubzilla handle. |
+
+Once you have created the channel, you are finally ‘in’. By default, Hubzilla takes you to the ‘’Headquarter‘’ (‘HQ’), an overview page of your own channel.
+
+![sbs08](/help/en/tutorials/pic/stepbystep08.png)
+
+And, as always when you enter Fediverse, it's pretty empty. In the left sidebar you will find tabs with various information:
+
+- Public (or Restricted) Messages: This is a compact view of your personal timeline (called ‘Stream’ on Hubzilla)
+- Direct Messages: Postings that are only exchanged among selected participants.
+- Favourite posts: You can add a ‘star’ to a post to mark it. Posts marked in this way end up here.
+- Notifications
+
+The personal stream (timeline) is displayed in the centre. Important information (notification of new contacts, new posts, etc.) is displayed in the right-hand sidebar. For a newly created channel, a list for the first steps with Hubzilla and links that lead to the corresponding functions also appear here.
+
+If you no longer need this help for beginners, you can switch it off under Settings → Display settings → Content settings.
+
+![sbs10](/help/en/tutorials/pic/stepbystep10.png)
+
+![sbs11](/help/en/tutorials/pic/stepbystep11.png)
+
+![sbs12](/help/en/tutorials/pic/stepbystep12.png)
+
+Firstly, you should fill your profile with useful information... as with any Fediverse service. No black magic!
+The navigation bar is located at the top of the screen. On the left is the menu for channel selection, your profile and settings. On the right are icons for some functions and the so-called ‘app menu’ (⋮), which takes you to the installed apps. The most important applications are already available there by default:
+
+- Files: Access to your own cloud
+- Photos: Access to your own photo album
+- Help: Help
+- Calendars
+- Channel: The page of your own channel with the channel information. Only your own posts are displayed here in the stream.
+- Stream: Switches to the federated stream view.
+- Connections: The existing connections are listed here (‘Followers’ and ‘Followed’). You can also add new connections in the connections directory.
+- Directory: The user directory is displayed. Note: You can view the global directory or just a directory with the users of your own instance. You can also create new connections here.
+
+**I strongly recommend installing and activating a few more apps:**
+
+**ActivityPub, Superblock and Privacy Groups.**
+
+**The ActivityPub app is essential if you want to participate in the Fediverse.** This app is a **MUST** and only requires installation and activation. No further settings are required.
+
+Superblock is also very important, as it allows you to exclude posts from (even unfollowed) users from the stream.
+
+Many (unfortunately, but understandably, not all) hubs also offer the ‘Public post stream’ app. This provides the public timeline of all Fediverse instances that are federated with your own hub. A good place to orientate yourself and find new contacts.
+
+Installing and activating apps is no problem. Select the last entry ‘+ Apps’ in the app menu to access the app management.
+
+![sbs13](/help/en/tutorials/pic/stepbystep13.png)
+
+Here you can view the installed apps (i.e. the preset apps) and the generally available apps (all apps, including those that are not installed).
+
+![sbs14](/help/en/tutorials/pic/stepbystep14.png)
+
+ActivityPub, Superblock and Privacy groups are still only available under ‘Available apps’. Click on ‘Install’ to install them and they will then also be available under ‘Installed apps’.
+
+![sbs15](/help/en/tutorials/pic/stepbystep15.png)
+
+![sbs16](/help/en/tutorials/pic/stepbystep16.png)
+
+![sbs17](/help/en/tutorials/pic/stepbystep17.png)
+
+The newly installed apps still need to be ‘activated’, i.e. made usable via the menu. You will find a ‘star symbol’ in the app box. If you click on it, the star turns yellow and the app is active and now also appears in the app menu.
+
+![sbs18](/help/en/tutorials/pic/stepbystep18.png)
+
+![sbs19](/help/en/tutorials/pic/stepbystep19.png)
+
+![sbs20](/help/en/tutorials/pic/stepbystep20.png)
+
+There is also a pin symbol. If you click on this, the app will also appear permanently in the navigation bar at the top right.
+
+![sbs21](/help/en/tutorials/pic/stepbystep21.png)
+
+It is also recommended that you take this opportunity to pin the ‘Channel’ and ‘Stream’ apps to the navigation bar, as these are often needed.
+
+### It is also important to know how to add contacts...
+
+For example, if you have found an interesting user in the public stream (if activated by the admin and the app is installed), you can simply click on the user's profile picture and select ‘Connect’ from the drop-down menu. You can also click on the user's handle, which will take you to a page with a ‘Connect’ button.
+
+If you know the handle of a Fediverse user, you can also simply click on the ‘Connections’ app. The connection directory opens. There is a ‘+ Add’ button at the top. If you click on this, an input field opens in which you can enter the handle (remember: without the leading ‘@’). Click on the ‘+’ next to it and the contact will be added.
+
+![sbs22](/help/en/tutorials/pic/stepbystep22.png)
+
+![sbs23](/help/en/tutorials/pic/stepbystep23.png)
+
+![sbs24](/help/en/tutorials/pic/stepbystep24.png)
+
+![sbs25](/help/en/tutorials/pic/stepbystep25.png)
+
+![sbs26](/help/en/tutorials/pic/stepbystep26.png)
+
+Finally, you can also call up the directory (it is best to deactivate ‘This website only’ in the left-hand sidebar in order to use the global directory). Here you can simply scroll through or search specifically by name or interests, or by tags (quick access also via a keyword cloud in the left sidebar). To connect, click on the ‘Connect’ button.
+
+![sbs27](/help/en/tutorials/pic/stepbystep27.png)
+
+![sbs28](/help/en/tutorials/pic/stepbystep28.png)
+
+![sbs29](/help/en/tutorials/pic/stepbystep29.png)
+
+![sbs30](/help/en/tutorials/pic/stepbystep30.png)
+
+Once you have contacts, installed the apps and completed your profile, you can now use Hubzilla just like any other Fediverse service.
+
+### Is there an app?
+
+Yes and no...
+
+Basically, you don't need one. You can simply call up the hub in your web browser on your mobile device. The responsive design makes it easy to use.
+
+However, there is an older app for Android that still works very well today. You can find it at [F-Droid](https://f-droid.org/de/), for example, under the name [Nomad](https://f-droid.org/de/packages/com.dfa.hubzilla_android/). I still use it when I want to work with Hubzilla on my smartphone (which is rare).
+
+![nomadapp](/help/en/tutorials/pic/nomadapp.png)
+
+Alternatively, if you are on the move, it is recommended that you install Hubzilla as a PWA on your device: [A Hubzilla app](https://info.hubzilla.hu/en/Hubzilla-App.html)
diff --git a/doc/en/ui.md b/doc/en/ui.md
new file mode 100644
index 000000000..6ae1c1505
--- /dev/null
+++ b/doc/en/ui.md
@@ -0,0 +1,5 @@
+# User interface / naming
+
+![ui01](/help/en/pic/ui01.png)
+
+![ui02](/help/en/pic/ui02.png)
diff --git a/include/account.php b/include/account.php
index 615c802f4..0c07bd85f 100644
--- a/include/account.php
+++ b/include/account.php
@@ -17,10 +17,38 @@ require_once('include/crypto.php');
require_once('include/channel.php');
-function get_account_by_id($account_id) {
- $r = q("select * from account where account_id = %d",
- intval($account_id)
- );
+/**
+ * Returns the id of a locally logged in account or false.
+ *
+ * Returns the numeric account id of the current session if authenticated, or
+ * false otherwise.
+ *
+ * @note It is possible to be authenticated, and not connected to a channel.
+ *
+ * @return int|false Numeric account id or false.
+ */
+function get_account_id(): int|false {
+ if (isset($_SESSION['account_id'])) {
+ return intval($_SESSION['account_id']);
+ }
+
+ if (App::$account) {
+ return intval(App::$account['account_id']);
+ }
+
+ return false;
+}
+
+/**
+ * Get the account with the given id from the database.
+ *
+ * @param int $account_id The numeric id of the account to fetch.
+ *
+ * @return array|false An array containing the attributes of the requested
+ * account, or false if it could not be retreived.
+ */
+function get_account_by_id(int $account_id): array|false {
+ $r = q("select * from account where account_id = %d", $account_id);
return (($r) ? $r[0] : false);
}
@@ -117,11 +145,16 @@ function check_account_invite($invite_code) {
}
function check_account_admin($arr) {
- if(is_site_admin())
+ if (is_site_admin()) {
return true;
+ }
+
$admin_email = trim(Config::Get('system','admin_email'));
- if(strlen($admin_email) && $admin_email === trim($arr['email']))
+
+ if (strlen($admin_email) && $admin_email === trim($arr['reg_email'])) {
return true;
+ }
+
return false;
}
@@ -132,167 +165,6 @@ function account_total() {
return false;
}
-// legacy
-function account_store_lowlevel_IS_OBSOLETE($arr) {
-
- $store = [
- 'account_parent' => ((array_key_exists('account_parent',$arr)) ? $arr['account_parent'] : '0'),
- 'account_default_channel' => ((array_key_exists('account_default_channel',$arr)) ? $arr['account_default_channel'] : '0'),
- 'account_salt' => ((array_key_exists('account_salt',$arr)) ? $arr['account_salt'] : ''),
- 'account_password' => ((array_key_exists('account_password',$arr)) ? $arr['account_password'] : ''),
- 'account_email' => ((array_key_exists('account_email',$arr)) ? $arr['account_email'] : ''),
- 'account_external' => ((array_key_exists('account_external',$arr)) ? $arr['account_external'] : ''),
- 'account_language' => ((array_key_exists('account_language',$arr)) ? $arr['account_language'] : 'en'),
- 'account_created' => ((array_key_exists('account_created',$arr)) ? $arr['account_created'] : '0001-01-01 00:00:00'),
- 'account_lastlog' => ((array_key_exists('account_lastlog',$arr)) ? $arr['account_lastlog'] : '0001-01-01 00:00:00'),
- 'account_flags' => ((array_key_exists('account_flags',$arr)) ? $arr['account_flags'] : '0'),
- 'account_roles' => ((array_key_exists('account_roles',$arr)) ? $arr['account_roles'] : '0'),
- 'account_reset' => ((array_key_exists('account_reset',$arr)) ? $arr['account_reset'] : ''),
- 'account_expires' => ((array_key_exists('account_expires',$arr)) ? $arr['account_expires'] : '0001-01-01 00:00:00'),
- 'account_expire_notified' => ((array_key_exists('account_expire_notified',$arr)) ? $arr['account_expire_notified'] : '0001-01-01 00:00:00'),
- 'account_service_class' => ((array_key_exists('account_service_class',$arr)) ? $arr['account_service_class'] : ''),
- 'account_level' => '5',
- 'account_password_changed' => ((array_key_exists('account_password_changed',$arr)) ? $arr['account_password_changed'] : '0001-01-01 00:00:00')
- ];
-
- // never ever is this a create table but a pdo insert into account
- // strange function placement in text.php (obscure by design :-)
- return create_table_from_array('account',$store);
- // the TODO may be to adjust others using create_table_from_array():
- // channel.php
- // connections.php
- // event.php
- // hubloc.php
- // import.php
-}
-
-
-
-// legacy
-function create_account_IS_OBSOLETE($arr) {
-
- // Required: { email, password }
-
- $result = array('success' => false, 'email' => '', 'password' => '', 'message' => '');
-
- $invite_code = ((x($arr,'invite_code')) ? notags(trim($arr['invite_code'])) : '');
- $email = ((x($arr,'email')) ? notags(punify(trim($arr['email']))) : '');
- $password = ((x($arr,'password')) ? trim($arr['password']) : '');
- $parent = ((x($arr,'parent')) ? intval($arr['parent']) : 0 );
- $flags = ((x($arr,'account_flags')) ? intval($arr['account_flags']) : ACCOUNT_OK);
- $roles = ((x($arr,'account_roles')) ? intval($arr['account_roles']) : 0 );
- $expires = ((x($arr,'expires')) ? intval($arr['expires']) : NULL_DATE);
-
- $default_service_class = Config::Get('system','default_service_class');
-
- if($default_service_class === false)
- $default_service_class = '';
-
- if((! x($email)) || (! x($password))) {
- $result['message'] = t('Please enter the required information.');
- return $result;
- }
-
- // prevent form hackery
-
- if($roles & ACCOUNT_ROLE_ADMIN) {
- $admin_result = check_account_admin($arr);
- if(! $admin_result) {
- $roles = 0;
- }
- }
-
- // allow the admin_email account to be admin, but only if it's the first account.
-
- $c = account_total();
- if (($c === 0) && (check_account_admin($arr)))
- $roles |= ACCOUNT_ROLE_ADMIN;
-
- // Ensure that there is a host keypair.
-
- if ((! Config::Get('system', 'pubkey')) && (! Config::Get('system', 'prvkey'))) {
- $hostkey = Crypto::new_keypair(4096);
- Config::Set('system', 'pubkey', $hostkey['pubkey']);
- Config::Set('system', 'prvkey', $hostkey['prvkey']);
- }
-
- $invite_result = check_account_invite($invite_code);
- if($invite_result['error']) {
- $result['message'] = $invite_result['message'];
- return $result;
- }
-
- $email_result = check_account_email($email);
-
- if($email_result['error']) {
- $result['message'] = $email_result['message'];
- return $result;
- }
-
- $password_result = check_account_password($password);
-
- if($password_result['error']) {
- $result['message'] = $password_result['message'];
- return $result;
- }
-
- $salt = random_string(32);
- $password_encoded = hash('whirlpool', $salt . $password);
-
- $r = account_store_lowlevel(
- [
- 'account_parent' => intval($parent),
- 'account_salt' => $salt,
- 'account_password' => $password_encoded,
- 'account_email' => $email,
- 'account_language' => get_best_language(),
- 'account_created' => datetime_convert(),
- 'account_flags' => intval($flags),
- 'account_roles' => intval($roles),
- 'account_level' => 5,
- 'account_expires' => $expires,
- 'account_service_class' => $default_service_class
- ]
- );
- if(! $r) {
- logger('create_account: DB INSERT failed.');
- $result['message'] = t('Failed to store account information.');
- return($result);
- }
-
- $r = q("select * from account where account_email = '%s' and account_password = '%s' limit 1",
- dbesc($email),
- dbesc($password_encoded)
- );
- if($r && count($r)) {
- $result['account'] = $r[0];
- }
- else {
- logger('create_account: could not retrieve newly created account');
- }
-
- // Set the parent record to the current record_id if no parent was provided
-
- if(! $parent) {
- $r = q("update account set account_parent = %d where account_id = %d",
- intval($result['account']['account_id']),
- intval($result['account']['account_id'])
- );
- if(! $r) {
- logger('create_account: failed to set parent');
- }
- $result['account']['parent'] = $result['account']['account_id'];
- }
-
- $result['success'] = true;
- $result['email'] = $email;
- $result['password'] = $password;
-
- call_hooks('register_account',$result);
-
- return $result;
-}
-
/**
* create_account_from_register
* @author hilmar runge
@@ -324,18 +196,18 @@ function create_account_from_register($arr) {
if($default_service_class === false)
$default_service_class = '';
- $roles = 0;
- // prevent form hackery
- if($roles & ACCOUNT_ROLE_ADMIN) {
- $admin_result = check_account_admin($arr);
- if(! $admin_result) {
- $roles = 0;
- }
+ // any accounts available ?
+ $total = q("SELECT COUNT(*) AS total FROM account");
+
+ if ($total && intval($total[0]['total']) === 0 && !check_account_admin($register[0])) {
+ logger('create_account: first account is not admin');
+ $result['message'] = t('First account is not admin.');
+ return $result;
}
- // any accounts available ?
- $isa = q("SELECT COUNT(*) AS isa FROM account");
- if ($isa && $isa[0]['isa'] == 0) {
+ $roles = 0;
+
+ if (check_account_admin($register[0])) {
$roles = ACCOUNT_ROLE_ADMIN;
}
@@ -446,76 +318,6 @@ function verify_email_address($arr) {
return $res;
}
-function verify_email_addressNOP($arr) {
-
- if(array_key_exists('resend',$arr)) {
- $a = q("select * from account where account_email = '%s' limit 1",
- dbesc($arr['email'])
- );
- if(! ($a && ($a[0]['account_flags'] & ACCOUNT_UNVERIFIED))) {
- return false;
- }
- $account = $a[0];
- // [hilmar ->
- $v = q("SELECT * FROM register WHERE reg_uid = %d AND reg_vital = 1 "
- . " AND reg_pass = 'verify' LIMIT 1",
- intval($account['account_id'])
- );
- // <- hilmar]
- if($v) {
- $hash = $v[0]['reg_hash'];
- }
- else {
- return false;
- }
- }
- else {
- $hash = random_string(24);
-
- // [hilmar ->
- q("INSERT INTO register ( reg_hash, reg_created, reg_uid, reg_pass, reg_lang, reg_stuff ) "
- ." VALUES ( '%s', '%s', %d, '%s', '%s', '' ) ",
- dbesc($hash),
- dbesc(datetime_convert()),
- intval($arr['account']['account_id']),
- dbesc('verify'),
- dbesc($arr['account']['account_language'])
- );
- // <- hilmar]
- $account = $arr['account'];
- }
-
- push_lang(($account['account_language']) ? $account['account_language'] : 'en');
-
- $email_msg = replace_macros(get_intltext_template('register_verify_member.tpl'),
- [
- '$sitename' => Config::Get('system','sitename'),
- '$siteurl' => z_root(),
- '$email' => $arr['email'],
- '$uid' => $account['account_id'],
- '$hash' => $hash,
- '$details' => ''
- ]
- );
-
- $res = z_mail(
- [
- 'toEmail' => $arr['email'],
- 'messageSubject' => sprintf( t('Registration confirmation for %s'), Config::Get('system','sitename')),
- 'textVersion' => $email_msg,
- ]
- );
-
- pop_lang();
-
- if(! $res)
- logger('send_reg_approval_email: failed to account_id: ' . $arr['account']['account_id']);
-
- return $res;
-}
-
-
-
function send_reg_approval_email($arr) {
diff --git a/include/acl_selectors.php b/include/acl_selectors.php
index f0e0140dc..139a913b2 100644
--- a/include/acl_selectors.php
+++ b/include/acl_selectors.php
@@ -181,7 +181,7 @@ function get_post_aclDialogDescription() {
$description = t('Post permissions %s cannot be changed %s after a post is shared.</br />These permissions set who is allowed to view the post.');
// Lets keep the emphasis styling seperate from the translation. It may change.
- $emphasisOpen = '<b><a href="' . z_root() . '/help/acl_dialog_post" target="hubzilla-help">';
+ $emphasisOpen = '<b><a href="' . z_root() . '/help/member/permissions" target="hubzilla-help">';
$emphasisClose = '</a></b>';
return sprintf($description, $emphasisOpen, $emphasisClose);
diff --git a/include/api_zot.php b/include/api_zot.php
index 56cec005d..a2753269d 100644
--- a/include/api_zot.php
+++ b/include/api_zot.php
@@ -546,15 +546,13 @@
return false;
}
-
- logger('api_red_item_store: REQUEST ' . print_r($_REQUEST,true));
+ logger('api_red_item_store: REQUEST ' . print_r($_POST,true));
logger('api_red_item_store: FILES ' . print_r($_FILES,true));
-
// set this so that the item_post() function is quiet and doesn't redirect or emit json
- $_REQUEST['api_source'] = true;
- $_REQUEST['profile_uid'] = api_user();
+ $_POST['api_source'] = true;
+ $_POST['profile_uid'] = api_user();
if(x($_FILES,'media')) {
$_FILES['userfile'] = $_FILES['media'];
@@ -562,11 +560,12 @@
$mod = new Zotlabs\Module\Wall_attach();
$media = $mod->post();
if($media)
- $_REQUEST['body'] = $media . "\n" . $_REQUEST['body'];
+ $_POST['body'] = $media . "\n" . $_POST['body'];
}
$mod = new Zotlabs\Module\Item();
$x = $mod->post();
+
json_return_and_die($x);
}
diff --git a/include/attach.php b/include/attach.php
index bda4905f1..0569b97fb 100644
--- a/include/attach.php
+++ b/include/attach.php
@@ -63,6 +63,7 @@ function z_mime_content_type($filename) {
'jpg' => 'image/jpeg',
'gif' => 'image/gif',
'webp' => 'image/webp',
+ 'avif' => 'image/avif',
'bmp' => 'image/bmp',
'ico' => 'image/vnd.microsoft.icon',
'tiff' => 'image/tiff',
@@ -673,7 +674,7 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) {
logger('getimagesize: ' . print_r($gis,true), LOGGER_DATA);
}
- if(($gis) && ($gis[2] === IMAGETYPE_GIF || $gis[2] === IMAGETYPE_JPEG || $gis[2] === IMAGETYPE_PNG || $gis[2] === IMAGETYPE_WEBP)) {
+ if(($gis) && ($gis[2] === IMAGETYPE_GIF || $gis[2] === IMAGETYPE_JPEG || $gis[2] === IMAGETYPE_PNG || $gis[2] === IMAGETYPE_WEBP || $gis[2] === IMAGETYPE_AVIF)) {
$is_photo = 1;
if($gis[2] === IMAGETYPE_GIF)
$def_extension = '.gif';
@@ -683,6 +684,8 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) {
$def_extension = '.png';
if($gis[2] === IMAGETYPE_WEBP)
$def_extension = '.webp';
+ if($gis[2] === IMAGETYPE_AVIF)
+ $def_extension = '.avif';
}
// If we know it's a photo, over-ride the type in case the source system could not determine what it was
@@ -796,6 +799,12 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) {
}
}
+ if (mb_strlen($filename, 'UTF-8') > MAX_FILENAME_LENGTH) {
+ logger('filename too long');
+ $ret['message'] = t('Filename too long');
+ return $ret;
+ }
+
if(! $hash)
$hash = new_uuid();
@@ -1180,11 +1189,17 @@ function attach_mkdir($channel, $observer_hash, $arr = null) {
return $ret;
}
- if(isset($arr['filename']) && !strlen($arr['filename'])) {
+ if(empty($arr['filename'])) {
$ret['message'] = t('Empty pathname');
return $ret;
}
+ if(mb_strlen($arr['filename'], 'UTF-8') > MAX_FOLDER_LENGTH) {
+ logger('pathname too long');
+ $ret['message'] = t('Pathname too long');
+ return $ret;
+ }
+
$arr['hash'] = $arr['hash'] ?? new_uuid();
// Check for duplicate name.
@@ -2589,6 +2604,11 @@ function attach_move($channel_id, $resource_id, $new_folder_hash, $newname = '',
}
}
+ if (mb_strlen($filename, 'UTF-8') > MAX_FILENAME_LENGTH) {
+ logger('filename too long');
+ $ret['message'] = t('Filename too long');
+ return $ret;
+ }
q("update attach set content = '%s', folder = '%s', filename = '%s', edited = '%s' where id = %d",
dbescbin($newstorepath),
diff --git a/include/auth.php b/include/auth.php
index 1fc2cc556..36a9043ce 100644
--- a/include/auth.php
+++ b/include/auth.php
@@ -216,12 +216,11 @@ function requires_mfa_check(int $account_id, string $module, string $arg): bool
* also handles logout
*/
-if((isset($_SESSION)) && (x($_SESSION, 'authenticated')) &&
- ((! (x($_POST, 'auth-params'))) || ($_POST['auth-params'] !== 'login'))) {
+if(!empty($_SESSION['authenticated']) && (empty($_POST['auth-params']) || $_POST['auth-params'] !== 'login')) {
// process a logout request
- if(((x($_POST, 'auth-params')) && ($_POST['auth-params'] === 'logout')) || (App::$module === 'logout')) {
+ if((!empty($_POST['auth-params']) && $_POST['auth-params'] === 'logout') || App::$module === 'logout') {
// process logout request
$args = array('channel_id' => local_channel());
call_hooks('logging_out', $args);
@@ -241,7 +240,7 @@ if((isset($_SESSION)) && (x($_SESSION, 'authenticated')) &&
// re-validate a visitor, optionally invoke "su" if permitted to do so
- if(x($_SESSION, 'visitor_id') && (! x($_SESSION, 'uid'))) {
+ if(!empty($_SESSION['visitor_id']) && empty($_SESSION['uid'])) {
// if our authenticated guest is allowed to take control of the admin channel, make it so.
$admins = Config::Get('system', 'remote_admin');
if($admins && is_array($admins) && in_array($_SESSION['visitor_id'], $admins)) {
@@ -281,7 +280,7 @@ if((isset($_SESSION)) && (x($_SESSION, 'authenticated')) &&
// already logged in user returning
- if(x($_SESSION, 'uid') || x($_SESSION, 'account_id')) {
+ if(!empty($_SESSION['uid']) || !empty($_SESSION['account_id'])) {
App::$session->return_check();
@@ -292,7 +291,7 @@ if((isset($_SESSION)) && (x($_SESSION, 'authenticated')) &&
if(($r) && (($r[0]['account_flags'] == ACCOUNT_OK) || ($r[0]['account_flags'] == ACCOUNT_UNVERIFIED))) {
App::$account = $r[0];
$login_refresh = false;
- if(! x($_SESSION,'last_login_date')) {
+ if(empty($_SESSION['last_login_date'])) {
$_SESSION['last_login_date'] = datetime_convert('UTC','UTC');
}
if(strcmp(datetime_convert('UTC','UTC','now - 12 hours'), $_SESSION['last_login_date']) > 0 ) {
@@ -331,7 +330,7 @@ else {
if($password)
$encrypted = hash('whirlpool', trim($password));
- if((x($_POST, 'auth-params')) && $_POST['auth-params'] === 'login') {
+ if(!empty($_POST['auth-params']) && $_POST['auth-params'] === 'login') {
$atoken = null;
$account = null;
@@ -354,9 +353,6 @@ else {
elseif($atoken) {
atoken_login($atoken);
}
- else {
- notice( t('Failed authentication') . EOL);
- }
if(! ($account || $atoken)) {
$error = 'authenticate: failed login attempt: ' . notags(trim($username)) . ' from IP ' . $_SERVER['REMOTE_ADDR'];
@@ -365,8 +361,8 @@ else {
$authlog = Config::Get('system', 'authlog');
if ($authlog)
@file_put_contents($authlog, datetime_convert() . ':' . session_id() . ' ' . $error . "\n", FILE_APPEND);
- notice( t('Login failed.') . EOL );
- goaway(z_root() . '/login');
+
+ goaway(z_root() . '/login?retry=1');
}
// If the user specified to remember the authentication, then change the cookie
diff --git a/include/bbcode.php b/include/bbcode.php
index c152d45cb..d5e1bafd9 100644
--- a/include/bbcode.php
+++ b/include/bbcode.php
@@ -420,12 +420,12 @@ function getAttachmentData($body) {
$type = "";
preg_match("/type='(.*?)'/ism", $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$type = strtolower($matches[1]);
}
preg_match('/type=\&quot\;(.*?)\&quot\;/ism', $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$type = strtolower($matches[1]);
}
@@ -442,12 +442,12 @@ function getAttachmentData($body) {
}
$url = "";
preg_match("/url='(.*?)'/ism", $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$url = $matches[1];
}
preg_match('/url=\&quot\;(.*?)\&quot\;/ism', $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$url = $matches[1];
}
@@ -457,12 +457,12 @@ function getAttachmentData($body) {
$title = "";
preg_match("/title='(.*?)'/ism", $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$title = $matches[1];
}
preg_match('/title=\&quot\;(.*?)\&quot\;/ism', $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$title = $matches[1];
}
if ($title != "") {
@@ -473,12 +473,12 @@ function getAttachmentData($body) {
$image = "";
preg_match("/image='(.*?)'/ism", $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$image = $matches[1];
}
preg_match('/image=\&quot\;(.*?)\&quot\;/ism', $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$image = $matches[1];
}
@@ -488,12 +488,12 @@ function getAttachmentData($body) {
$preview = "";
preg_match("/preview='(.*?)'/ism", $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$preview = $matches[1];
}
preg_match('/preview=\&quot\;(.*?)\&quot\;/ism', $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$preview = $matches[1];
}
if ($preview != "") {
@@ -1190,7 +1190,7 @@ function bbcode($text, $options = []) {
$cache = ((array_key_exists('cache',$options)) ? $options['cache'] : false);
$newwin = ((array_key_exists('newwin',$options)) ? $options['newwin'] : true);
- $target = (($newwin) ? ' target="_blank" ' : '');
+ $target = (($newwin) ? 'target="_blank"' : '');
/**
* @hooks bbcode_filter
@@ -1332,21 +1332,31 @@ function bbcode($text, $options = []) {
$text = str_replace('[observer.photo]','', $text);
}
-
-
// Perform URL Search
-
$urlchars = '[a-zA-Z0-9\pL\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,\@\(\)]';
if (strpos($text,'http') !== false) {
if($tryoembed) {
$text = preg_replace_callback("/([^\]\='".'"'."\;\/]|^|\#\^)(https?\:\/\/$urlchars+)/ismu", 'tryoembed', $text);
}
- // Is this still desired?
- // We already turn naked URLs into links during creation time cleanup_bbcode()
+
$text = preg_replace("/([^\]\='".'"'."\;\/]|^|\#\^)(https?\:\/\/$urlchars+)/ismu", '$1<a href="$2" ' . $target . ' rel="nofollow noopener">$2</a>', $text);
}
+ // Turn naked geo URIs into clickable links
+ if (str_contains($text, 'geo:')) {
+ $text = preg_replace_callback(
+ '/([^\]\=\'";\/]|^|\#\^)(geo:([A-Za-z0-9:.,;_+\-?&=%]+)(?:\(([^)]+)\))?)/ismu',
+ function ($matches) {
+ $before = $matches[1];
+ $geo_uri = $matches[2];
+ $label = ((!empty($matches[4])) ? '📍' . urldecode($matches[4]) : $geo_uri);
+ return $before . '<a href="' . htmlspecialchars($geo_uri) . '" target="_blank" rel="nofollow noopener">' . htmlspecialchars($label) . '</a>';
+ },
+ $text
+ );
+ }
+
$count = 0;
while (strpos($text,'[/share]') !== false && $count < 10) {
$text = preg_replace_callback("/\[share(.*?)\](.*?)\[\/share\]/ism", 'bb_ShareAttributes', $text);
@@ -1360,23 +1370,23 @@ function bbcode($text, $options = []) {
}
if (strpos($text,'[/url]') !== false) {
- $text = preg_replace("/\#\^\[url\]([$URLSearchString]*)\[\/url\]/ism", '<span class="bookmark-identifier">#^</span><a class="bookmark" href="$1" ' . $target . ' rel="nofollow noopener" >$1</a>', $text);
- $text = preg_replace("/\#\^\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '<span class="bookmark-identifier">#^</span><a class="bookmark" href="$1" ' . $target . ' rel="nofollow noopener" >$2</a>', $text);
- $text = preg_replace("/\[url\]([$URLSearchString]*)\[\/url\]/ism", '<a href="$1" ' . $target . ' rel="nofollow noopener" >$1</a>', $text);
- $text = preg_replace("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '<a href="$1" ' . $target . ' rel="nofollow noopener" >$2</a>', $text);
+ $text = preg_replace("/\#\^\[url\]([$URLSearchString]*)\[\/url\]/ism", '<span class="bookmark-identifier">#^</span><a class="bookmark" href="$1" ' . $target . ' rel="nofollow noopener">$1</a>', $text);
+ $text = preg_replace("/\#\^\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '<span class="bookmark-identifier">#^</span><a class="bookmark" href="$1" ' . $target . ' rel="nofollow noopener">$2</a>', $text);
+ $text = preg_replace("/\[url\]([$URLSearchString]*)\[\/url\]/ism", '<a href="$1" ' . $target . ' rel="nofollow noopener">$1</a>', $text);
+ $text = preg_replace("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '<a href="$1" ' . $target . ' rel="nofollow noopener">$2</a>', $text);
}
if (strpos($text,'[/zrl]') !== false) {
- $text = preg_replace("/\#\^\[zrl\]([$URLSearchString]*)\[\/zrl\]/ism", '<span class="bookmark-identifier">#^</span><a class="zrl bookmark" href="$1" ' . $target . ' rel="nofollow noopener" >$1</a>', $text);
- $text = preg_replace("/\#\^\[zrl\=([$URLSearchString]*)\](.*?)\[\/zrl\]/ism", '<span class="bookmark-identifier">#^</span><a class="zrl bookmark" href="$1" ' . $target . ' rel="nofollow noopener" >$2</a>', $text);
- $text = preg_replace("/\[zrl\]([$URLSearchString]*)\[\/zrl\]/ism", '<a class="zrl" href="$1" ' . $target . ' rel="nofollow noopener" >$1</a>', $text);
- $text = preg_replace("/\[zrl\=([$URLSearchString]*)\](.*?)\[\/zrl\]/ism", '<a class="zrl" href="$1" ' . $target . ' rel="nofollow noopener" >$2</a>', $text);
+ $text = preg_replace("/\#\^\[zrl\]([$URLSearchString]*)\[\/zrl\]/ism", '<span class="bookmark-identifier">#^</span><a class="zrl bookmark" href="$1" ' . $target . ' rel="nofollow noopener">$1</a>', $text);
+ $text = preg_replace("/\#\^\[zrl\=([$URLSearchString]*)\](.*?)\[\/zrl\]/ism", '<span class="bookmark-identifier">#^</span><a class="zrl bookmark" href="$1" ' . $target . ' rel="nofollow noopener">$2</a>', $text);
+ $text = preg_replace("/\[zrl\]([$URLSearchString]*)\[\/zrl\]/ism", '<a class="zrl" href="$1" ' . $target . ' rel="nofollow noopener">$1</a>', $text);
+ $text = preg_replace("/\[zrl\=([$URLSearchString]*)\](.*?)\[\/zrl\]/ism", '<a class="zrl" href="$1" ' . $target . ' rel="nofollow noopener">$2</a>', $text);
}
// Perform MAIL Search
if (strpos($text,'[/mail]') !== false) {
- $text = preg_replace("/\[mail\]([$MAILSearchString]*)\[\/mail\]/", '<a href="mailto:$1" ' . $target . ' rel="nofollow noopener" >$1</a>', $text);
- $text = preg_replace("/\[mail\=([$MAILSearchString]*)\](.*?)\[\/mail\]/", '<a href="mailto:$1" ' . $target . ' rel="nofollow noopener" >$2</a>', $text);
+ $text = preg_replace("/\[mail\]([$MAILSearchString]*)\[\/mail\]/", '<a href="mailto:$1" ' . $target . ' rel="nofollow noopener">$1</a>', $text);
+ $text = preg_replace("/\[mail\=([$MAILSearchString]*)\](.*?)\[\/mail\]/", '<a href="mailto:$1" ' . $target . ' rel="nofollow noopener">$2</a>', $text);
}
@@ -1737,17 +1747,17 @@ function bbcode($text, $options = []) {
// if video couldn't be embedded, link to it instead.
if (strpos($text,'[/video]') !== false) {
- $text = preg_replace("/\[video\](.*?)\[\/video\]/", '<a href="$1" ' . $target . ' rel="nofollow noopener" >$1</a>', $text);
+ $text = preg_replace("/\[video\](.*?)\[\/video\]/", '<a href="$1" ' . $target . ' rel="nofollow noopener">$1</a>', $text);
}
if (strpos($text,'[/audio]') !== false) {
- $text = preg_replace("/\[audio\](.*?)\[\/audio\]/", '<a href="$1" ' . $target . ' rel="nofollow noopener" >$1</a>', $text);
+ $text = preg_replace("/\[audio\](.*?)\[\/audio\]/", '<a href="$1" ' . $target . ' rel="nofollow noopener">$1</a>', $text);
}
if (strpos($text,'[/zvideo]') !== false) {
- $text = preg_replace("/\[zvideo\](.*?)\[\/zvideo\]/", '<a class="zid" href="$1" ' . $target . ' rel="nofollow noopener" >$1</a>', $text);
+ $text = preg_replace("/\[zvideo\](.*?)\[\/zvideo\]/", '<a class="zid" href="$1" ' . $target . ' rel="nofollow noopener">$1</a>', $text);
}
if (strpos($text,'[/zaudio]') !== false) {
- $text = preg_replace("/\[zaudio\](.*?)\[\/zaudio\]/", '<a class="zid" href="$1" ' . $target . ' rel="nofollow noopener" >$1</a>', $text);
+ $text = preg_replace("/\[zaudio\](.*?)\[\/zaudio\]/", '<a class="zid" href="$1" ' . $target . ' rel="nofollow noopener">$1</a>', $text);
}
// oembed tag
@@ -1761,7 +1771,7 @@ function bbcode($text, $options = []) {
// Summary (e.g. title) is required, earlier revisions only required description (in addition to
// start which is always required). Allow desc with a missing summary for compatibility.
- if ((x($ev,'desc') || x($ev,'summary')) && x($ev,'dtstart')) {
+ if ((!empty($ev['desc']) || !empty($ev['summary'])) && !empty($ev['dtstart'])) {
$sub = format_event_html($ev);
@@ -1813,9 +1823,13 @@ function bbcode($text, $options = []) {
$text = preg_replace("/\<(.*?)(src|href)=(.*?)\&amp\;(.*?)\>/ism", '<$1$2=$3&$4>', $text);
// This is subtle - it's an XSS filter. It only accepts links with a protocol scheme and where
- // the scheme begins with z (zhttp), h (http(s)), f (ftp(s)), m (mailto), t (tel) and named anchors.
+ // the scheme begins with http:, https:, mailto:, tel:, geo: and named anchors.
- $text = preg_replace("/\<(.*?)(src|href)=\"[^zhfmt#](.*?)\>/ism", '<$1$2="">', $text);
+ $text = preg_replace(
+ '/(<[^>]*?\b(?:src|href)\s*=\s*([\'"])\s*)(?!https?:|geo:|mailto:|tel:|#)[^\'"]*?\2/iu',
+ '$1$2$2',
+ $text
+ );
$text = bb_replace_images($text, $saved_images);
diff --git a/include/channel.php b/include/channel.php
index a3ba1a765..fe67f5304 100644
--- a/include/channel.php
+++ b/include/channel.php
@@ -94,7 +94,7 @@ function validate_channelname($name) {
*/
call_hooks('validate_channelname', $arr);
- if (x($arr, 'message'))
+ if (!empty($arr['message']))
return $arr['message'];
return null;
@@ -219,8 +219,8 @@ function create_identity($arr) {
}
$name = escape_tags($arr['name']);
- $pageflags = ((x($arr,'pageflags')) ? intval($arr['pageflags']) : PAGE_NORMAL);
- $system = ((x($arr,'system')) ? intval($arr['system']) : 0);
+ $pageflags = ((!empty($arr['pageflags'])) ? intval($arr['pageflags']) : PAGE_NORMAL);
+ $system = ((!empty($arr['system'])) ? intval($arr['system']) : 0);
$name_error = validate_channelname($arr['name']);
if($name_error) {
$ret['message'] = $name_error;
@@ -1634,19 +1634,19 @@ function profile_sidebar($profile, $block = 0, $show_connect = true, $details =
$connect_url = z_root() . '/connect/' . $profile['channel_address'];
}
- if((x($profile,'address') == 1)
- || (x($profile,'locality') == 1)
- || (x($profile,'region') == 1)
- || (x($profile,'postal_code') == 1)
- || (x($profile,'country_name') == 1))
+ if(!empty($profile['address'])
+ || !empty($profile['locality'])
+ || !empty($profile['region'])
+ || !empty($profile['postal_code'])
+ || !empty($profile['country_name']))
$location = t('Location:');
$profile['homepage'] = linkify($profile['homepage'],true);
- $gender = ((x($profile,'gender') == 1) ? t('Gender:') : False);
- $marital = ((x($profile,'marital') == 1) ? t('Status:') : False);
- $homepage = ((x($profile,'homepage') == 1) ? t('Homepage:') : False);
- $hometown = ((x($profile,'hometown') == 1) ? t('Hometown:') : False);
+ $gender = ((!empty($profile['gender'])) ? t('Gender:') : False);
+ $marital = ((!empty($profile['marital'])) ? t('Status:') : False);
+ $homepage = ((!empty($profile['homepage'])) ? t('Homepage:') : False);
+ $hometown = ((!empty($profile['hometown'])) ? t('Hometown:') : False);
$profile['online'] = (($profile['online_status'] === 'online') ? t('Online Now') : False);
// logger('online: ' . $profile['online']);
diff --git a/include/conversation.php b/include/conversation.php
index 942811449..07e4df088 100644
--- a/include/conversation.php
+++ b/include/conversation.php
@@ -177,9 +177,7 @@ function localize_item(&$item){
case ACTIVITY_OBJ_NOTE:
case 'Note':
default:
- $post_type = t('post');
- if(((isset($obj['parent']) && isset($obj['id']) && $obj['id'] != $obj['parent'])) || isset($obj['inReplyTo']))
- $post_type = t('comment');
+ $post_type = t('message');
break;
}
@@ -347,20 +345,20 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
. "<script> var profile_uid = " . $_SESSION['uid']
. "; var netargs = '" . substr(App::$cmd,8)
. '?f='
- . ((x($_GET,'cid')) ? '&cid=' . $_GET['cid'] : '')
- . ((x($_GET,'search')) ? '&search=' . $_GET['search'] : '')
- . ((x($_GET,'star')) ? '&star=' . $_GET['star'] : '')
- . ((x($_GET,'order')) ? '&order=' . $_GET['order'] : '')
- . ((x($_GET,'bmark')) ? '&bmark=' . $_GET['bmark'] : '')
- . ((x($_GET,'liked')) ? '&liked=' . $_GET['liked'] : '')
- . ((x($_GET,'conv')) ? '&conv=' . $_GET['conv'] : '')
- . ((x($_GET,'spam')) ? '&spam=' . $_GET['spam'] : '')
- . ((x($_GET,'nets')) ? '&nets=' . $_GET['nets'] : '')
- . ((x($_GET,'cmin')) ? '&cmin=' . $_GET['cmin'] : '')
- . ((x($_GET,'cmax')) ? '&cmax=' . $_GET['cmax'] : '')
- . ((x($_GET,'file')) ? '&file=' . $_GET['file'] : '')
- . ((x($_GET,'uri')) ? '&uri=' . $_GET['uri'] : '')
- . ((x($_GET,'pf')) ? '&pf=' . $_GET['pf'] : '')
+ . (!empty($_GET['cid']) ? '&cid=' . $_GET['cid'] : '')
+ . (!empty($_GET['search']) ? '&search=' . $_GET['search'] : '')
+ . (!empty($_GET['star']) ? '&star=' . $_GET['star'] : '')
+ . (!empty($_GET['order']) ? '&order=' . $_GET['order'] : '')
+ . (!empty($_GET['bmark']) ? '&bmark=' . $_GET['bmark'] : '')
+ . (!empty($_GET['liked']) ? '&liked=' . $_GET['liked'] : '')
+ . (!empty($_GET['conv']) ? '&conv=' . $_GET['conv'] : '')
+ . (!empty($_GET['spam']) ? '&spam=' . $_GET['spam'] : '')
+ . (!empty($_GET['nets']) ? '&nets=' . $_GET['nets'] : '')
+ . (!empty($_GET['cmin']) ? '&cmin=' . $_GET['cmin'] : '')
+ . (!empty($_GET['cmax']) ? '&cmax=' . $_GET['cmax'] : '')
+ . (!empty($_GET['file']) ? '&file=' . $_GET['file'] : '')
+ . (!empty($_GET['uri']) ? '&uri=' . $_GET['uri'] : '')
+ . (!empty($_GET['pf']) ? '&pf=' . $_GET['pf'] : '')
. "'; var profile_page = " . App::$pager['page'] . "; </script>\r\n";
}
}
@@ -467,17 +465,6 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
$items = $cb['items'];
- $conv_responses = [
- 'like' => ['title' => t('Likes','title')],
- 'dislike' => ['title' => t('Dislikes','title')],
- 'attendyes' => ['title' => t('Attending','title')],
- 'attendno' => ['title' => t('Not attending','title')],
- 'attendmaybe' => ['title' => t('Might attend','title')],
- 'answer' => [],
- 'announce' => ['title' => t('Repeats','title')],
- ];
-
-
// array with html for each thread (parent+comments)
$threads = array();
$threadsid = -1;
@@ -566,7 +553,7 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
$shareable = false;
$verified = (intval($item['item_verified']) ? t('Message signature validated') : '');
- $forged = ((($item['sig']) && (! intval($item['item_verified']))) ? t('Message signature incorrect') : '');
+ $forged = ((!empty($item['sig']) && !intval($item['item_verified'])) ? t('Message signature incorrect') : '');
$unverified = '';
@@ -704,14 +691,10 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
$item = $x['item'];
- builtin_activity_puller($item, $conv_responses);
-
- if(! visible_activity($item)) {
+ if (!visible_activity($item)) {
continue;
}
- $mid_uuid_map[$item['mid']] = $item['uuid'];
-
$item['pagedrop'] = $page_dropping;
if($item['id'] == $item['parent'] || $r_preview) {
@@ -720,7 +703,6 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
$conv->add_thread($item_object);
if(($page_mode === 'list') || ($page_mode === 'pager_list')) {
- $item_object->set_template('conv_list.tpl');
$item_object->set_display_mode('list');
}
if($mode === 'cards' || $mode === 'articles') {
@@ -730,7 +712,7 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
}
}
- $threads = $conv->get_template_data($conv_responses, $mid_uuid_map);
+ $threads = $conv->get_template_data();
if(!$threads) {
logger('[ERROR] conversation : Failed to get template data.', LOGGER_DEBUG);
$threads = array();
@@ -781,7 +763,7 @@ function best_link_url($item) {
$clean_url = isset($item['author-link']) ? normalise_link($item['author-link']) : '';
if($clean_url && local_channel() && (local_channel() == $item['uid'])) {
- if(isset(App::$contacts) && x(App::$contacts, $clean_url)) {
+ if(isset(App::$contacts) && !empty(App::$contacts[$clean_url])) {
if(App::$contacts[$clean_url]['network'] === NETWORK_DFRN) {
$best_url = z_root() . '/redir/' . App::$contacts[$clean_url]['id'];
$sparkle = true;
@@ -952,101 +934,6 @@ function thread_author_menu($item, $mode = '') {
}
-
-
-
-/**
- * @brief Checks item to see if it is one of the builtin activities (like/dislike, event attendance, consensus items, etc.)
- *
- * Increments the count of each matching activity and adds a link to the author as needed.
- *
- * @param array $item
- * @param array &$conv_responses (already created with builtin activity structure)
- */
-function builtin_activity_puller($item, &$conv_responses) {
-
- // if this item is a post or comment there's nothing for us to do here, just return.
-
- if(activity_match($item['verb'], ['Create', ACTIVITY_POST]) && $item['obj_type'] !== 'Answer')
- return;
-
- foreach($conv_responses as $mode => $v) {
-
- $url = '';
-
- switch($mode) {
- case 'like':
- $verb = ['Like', ACTIVITY_LIKE];
- break;
- case 'dislike':
- $verb = ['Dislike', ACTIVITY_DISLIKE];
- break;
- case 'attendyes':
- $verb = ['Accept', ACTIVITY_ATTEND];
- break;
- case 'attendno':
- $verb = ['Reject', ACTIVITY_ATTENDNO];
- break;
- case 'attendmaybe':
- $verb = ['TentativeAccept', ACTIVITY_ATTENDMAYBE];
- break;
- case 'answer':
- $verb = ['Create', ACTIVITY_POST];
- break;
- case 'announce':
- $verb = 'Announce';
- break;
- default:
- return;
- break;
- }
-
- if((activity_match($item['verb'], $verb)) && ($item['id'] != $item['parent'])) {
-
- $name = (($item['author']['xchan_name']) ? $item['author']['xchan_name'] : t('Unknown'));
-
- $moderate = ((intval($item['item_blocked']) === ITEM_MODERATED) ? '<a href="moderate/' . $item['id'] . '/approve" onclick="moderate_approve(' . $item['id'] . '); return false;" class="text-success pe-2" title="' . t('Approve this item') . '"><i class="bi bi-check-lg" ></i></a><a href="moderate/' . $item['id'] . '/drop" onclick="moderate_drop(' . $item['id'] . '); return false;" class="text-danger pe-2" title="' . t('Delete this item') . '"><i class="bi bi-trash" ></i></a>' : '');
-
- $url = (($item['author_xchan'] && $item['author']['xchan_photo_s'])
- ? '<div class="dropdown-item">' . $moderate . '<a href="' . chanlink_hash($item['author_xchan']) . '" class="text-reset">' . '<img class="menu-img-1" src="' . $item['author']['xchan_photo_s'] . '" alt="' . urlencode($name) . '" loading="lazy" /> ' . $name . '</a></div>'
- : '<a class="dropdown-item" href="#" class="disabled">' . $name . '</a>'
- );
-
-
-
- if(! $item['thr_parent'])
- $item['thr_parent'] = $item['parent_mid'];
-
- $conv_responses[$mode]['mids'][$item['thr_parent']][] = $item['uuid'];
-
- if($item['obj_type'] === 'Answer')
- continue;
-
- if(! ((isset($conv_responses[$mode][$item['thr_parent'] . '-l']))
- && (is_array($conv_responses[$mode][$item['thr_parent'] . '-l']))))
- $conv_responses[$mode][$item['thr_parent'] . '-l'] = array();
-
- // only list each unique author once
- if(in_array($url,$conv_responses[$mode][$item['thr_parent'] . '-l']))
- continue;
-
- if(! isset($conv_responses[$mode][$item['thr_parent']]))
- $conv_responses[$mode][$item['thr_parent']] = 1;
- else
- $conv_responses[$mode][$item['thr_parent']] ++;
-
- $conv_responses[$mode][$item['thr_parent'] . '-l'][] = $url;
- if(get_observer_hash() && get_observer_hash() === $item['author_xchan']) {
- $conv_responses[$mode][$item['thr_parent'] . '-m'] = true;
- }
-
- // there can only be one activity verb per item so if we found anything, we can stop looking
- return;
- }
- }
-}
-
-
/**
* @brief Format the like/dislike text for a profile item.
*
@@ -1108,48 +995,48 @@ function hz_status_editor($x, $popup = false) {
if($c && $c['channel_moved'])
return;
- $webpage = ((x($x,'webpage')) ? $x['webpage'] : '');
+ $webpage = ((!empty($x['webpage'])) ? $x['webpage'] : '');
$plaintext = true;
$feature_nocomment = feature_enabled($x['profile_uid'], 'disable_comments');
- if(x($x, 'disable_comments'))
+ if(!empty($x['disable_comments']))
$feature_nocomment = false;
$feature_expire = ((feature_enabled($x['profile_uid'], 'content_expire') && (! $webpage)) ? true : false);
- if(x($x, 'hide_expire'))
+ if(!empty($x['hide_expire']))
$feature_expire = false;
$feature_future = ((feature_enabled($x['profile_uid'], 'delayed_posting') && (! $webpage)) ? true : false);
- if(x($x, 'hide_future'))
+ if(!empty($x['hide_future']))
$feature_future = false;
$geotag = ((isset($x['allow_location']) && $x['allow_location']) ? replace_macros(get_markup_template('jot_geotag.tpl'), array()) : '');
$setloc = t('Set your location');
$clearloc = ((get_pconfig($x['profile_uid'], 'system', 'use_browser_location')) ? t('Clear browser location') : '');
- if(x($x, 'hide_location'))
+ if(!empty($x['hide_location']))
$geotag = $setloc = $clearloc = '';
- $mimetype = ((x($x,'mimetype')) ? $x['mimetype'] : 'text/bbcode');
+ $mimetype = ((!empty($x['mimetype'])) ? $x['mimetype'] : 'text/bbcode');
- $mimeselect = ((x($x,'mimeselect')) ? $x['mimeselect'] : false);
+ $mimeselect = ((!empty($x['mimeselect'])) ? $x['mimeselect'] : false);
if($mimeselect)
$mimeselect = mimetype_select($x['profile_uid'], $mimetype);
else
$mimeselect = '<input type="hidden" name="mimetype" value="' . $mimetype . '" />';
$weblink = (($mimetype === 'text/bbcode') ? t('Insert web link') : false);
- if(x($x, 'hide_weblink'))
+ if(!empty($x['hide_weblink']))
$weblink = false;
$embedPhotos = t('Embed (existing) photo from your photo albums');
$writefiles = (($mimetype === 'text/bbcode') ? perm_is_allowed($x['profile_uid'], get_observer_hash(), 'write_storage') : false);
- if(x($x, 'hide_attach'))
+ if(!empty($x['hide_attach']))
$writefiles = false;
- $layout = ((x($x,'layout')) ? $x['layout'] : '');
+ $layout = ((!empty($x['layout'])) ? $x['layout'] : '');
- $layoutselect = ((x($x,'layoutselect')) ? $x['layoutselect'] : false);
+ $layoutselect = ((!empty($x['layoutselect'])) ? $x['layoutselect'] : false);
if($layoutselect)
$layoutselect = layout_select($x['profile_uid'], $layout);
else
@@ -1162,7 +1049,7 @@ function hz_status_editor($x, $popup = false) {
else
$id_select = '';
- $reset = ((x($x,'reset')) ? $x['reset'] : '');
+ $reset = ((!empty($x['reset'])) ? $x['reset'] : '');
$feature_auto_save_draft = ((feature_enabled($x['profile_uid'], 'auto_save_draft')) ? "true" : "false");
@@ -1171,14 +1058,14 @@ function hz_status_editor($x, $popup = false) {
$tplmacros = [
'$baseurl' => z_root(),
'$editselect' => (($plaintext) ? 'none' : '/(profile-jot-text|prvmail-text)/'),
- '$pretext' => ((x($x,'pretext')) ? $x['pretext'] : ''),
+ '$pretext' => ((!empty($x['pretext'])) ? $x['pretext'] : ''),
'$geotag' => $geotag,
'$nickname' => $x['nickname'],
'$linkurl' => t('Please enter a link URL:'),
'$term' => t('Tag term:'),
'$whereareu' => t('Where are you right now?'),
- '$editor_autocomplete'=> ((x($x,'editor_autocomplete')) ? $x['editor_autocomplete'] : ''),
- '$bbco_autocomplete'=> ((x($x,'bbco_autocomplete')) ? $x['bbco_autocomplete'] : ''),
+ '$editor_autocomplete'=> ((!empty($x['editor_autocomplete'])) ? $x['editor_autocomplete'] : ''),
+ '$bbco_autocomplete'=> ((!empty($x['bbco_autocomplete'])) ? $x['bbco_autocomplete'] : ''),
'$modalchooseimages' => t('Choose images to embed'),
'$modalchoosealbum' => t('Choose an album'),
'$modaldiffalbum' => t('Choose a different album...'),
@@ -1205,7 +1092,7 @@ function hz_status_editor($x, $popup = false) {
$tpl = get_markup_template('jot.tpl');
$preview = t('Preview');
- if(x($x, 'hide_preview'))
+ if(!empty($x['hide_preview']))
$preview = '';
$defexpire = ((($z = get_pconfig($x['profile_uid'], 'system', 'default_post_expire')) && (! $webpage)) ? $z : '');
@@ -1233,21 +1120,21 @@ function hz_status_editor($x, $popup = false) {
call_hooks('jot_tool', $jotplugins);
$jotnets = '';
- if(x($x,'jotnets')) {
+ if(!empty($x['jotnets'])) {
call_hooks('jot_networks', $jotnets);
}
- $sharebutton = (x($x,'button') ? $x['button'] : t('Share'));
- $placeholdtext = (x($x,'content_label') ? $x['content_label'] : $sharebutton);
+ $sharebutton = (!empty($x['button']) ? $x['button'] : t('Submit'));
+ $placeholdtext = (!empty($x['content_label']) ? $x['content_label'] : t('Start a conversation'));
$tplmacros = [
- '$return_path' => ((x($x, 'return_path')) ? $x['return_path'] : App::$query_string),
+ '$return_path' => ((!empty($x['return_path'])) ? $x['return_path'] : App::$query_string),
'$action' => z_root() . '/item',
'$share' => $sharebutton,
'$placeholdtext' => $placeholdtext,
'$webpage' => $webpage,
- '$placeholdpagetitle' => ((x($x,'ptlabel')) ? $x['ptlabel'] : t('Page link name')),
- '$pagetitle' => (x($x,'pagetitle') ? $x['pagetitle'] : ''),
+ '$placeholdpagetitle' => ((!empty($x['ptlabel'])) ? $x['ptlabel'] : t('Page link name')),
+ '$pagetitle' => (!empty($x['pagetitle']) ? $x['pagetitle'] : ''),
'$id_select' => $id_select,
'$id_seltext' => t('Post as'),
'$writefiles' => $writefiles,
@@ -1276,18 +1163,18 @@ function hz_status_editor($x, $popup = false) {
'$feature_nocomment' => $feature_nocomment,
'$nocomment' => ((array_key_exists('item',$x)) ? $x['item']['item_nocomment'] : 0),
'$clearloc' => $clearloc,
- '$title' => ((x($x, 'title')) ? htmlspecialchars($x['title'], ENT_COMPAT,'UTF-8') : ''),
- '$summary' => ((x($x, 'summary')) ? htmlspecialchars($x['summary'], ENT_COMPAT,'UTF-8') : ''),
- '$placeholdertitle' => ((x($x, 'placeholdertitle')) ? $x['placeholdertitle'] : t('Title (optional)')),
- '$placeholdersummary' => ((x($x, 'placeholdersummary')) ? $x['placeholdersummary'] : t('Summary (optional)')),
+ '$title' => ((!empty($x['title'])) ? htmlspecialchars($x['title'], ENT_COMPAT,'UTF-8') : ''),
+ '$summary' => ((!empty($x['summary'])) ? htmlspecialchars($x['summary'], ENT_COMPAT,'UTF-8') : ''),
+ '$placeholdertitle' => ((!empty($x['placeholdertitle'])) ? $x['placeholdertitle'] : t('Title (optional)')),
+ '$placeholdersummary' => ((!empty($x['placeholdersummary'])) ? $x['placeholdersummary'] : t('Summary (optional)')),
'$catsenabled' => $catsenabled,
- '$category' => ((x($x, 'category')) ? $x['category'] : ''),
+ '$category' => ((!empty($x['category'])) ? $x['category'] : ''),
'$placeholdercategory' => t('Categories (optional, comma-separated list)'),
'$permset' => t('Permission settings'),
- '$ptyp' => ((x($x, 'ptyp')) ? $x['ptyp'] : ''),
- '$content' => ((x($x,'body')) ? htmlspecialchars($x['body'], ENT_COMPAT,'UTF-8') : ''),
- '$attachment' => ((x($x, 'attachment')) ? $x['attachment'] : ''),
- '$post_id' => ((x($x, 'post_id')) ? $x['post_id'] : ''),
+ '$ptyp' => ((!empty($x['ptyp'])) ? $x['ptyp'] : ''),
+ '$content' => ((!empty($x['body'])) ? htmlspecialchars($x['body'], ENT_COMPAT,'UTF-8') : ''),
+ '$attachment' => ((!empty($x['attachment'])) ? $x['attachment'] : ''),
+ '$post_id' => ((!empty($x['post_id'])) ? $x['post_id'] : ''),
'$defloc' => $x['default_location'] ?? '',
'$visitor' => $x['visitor'] ?? '',
'$lockstate' => $x['lockstate'] ?? '',
@@ -1302,7 +1189,7 @@ function hz_status_editor($x, $popup = false) {
'$bang' => $x['bang'] ?? '',
'$profile_uid' => $x['profile_uid'],
'$preview' => $preview,
- '$source' => ((x($x, 'source')) ? $x['source'] : ''),
+ '$source' => ((!empty($x['source'])) ? $x['source'] : ''),
'$jotplugins' => $jotplugins,
'$jotnets' => $jotnets,
'$jotnets_label' => t('Other networks and post services'),
@@ -1317,8 +1204,8 @@ function hz_status_editor($x, $popup = false) {
'$cipher' => $cipher,
'$expiryModalOK' => t('OK'),
'$expiryModalCANCEL' => t('Cancel'),
- '$expanded' => ((x($x, 'expanded')) ? $x['expanded'] : false),
- '$bbcode' => ((x($x, 'bbcode')) ? $x['bbcode'] : false),
+ '$expanded' => ((!empty($x['expanded'])) ? $x['expanded'] : false),
+ '$bbcode' => ((!empty($x['bbcode'])) ? $x['bbcode'] : false),
'$parent' => ((array_key_exists('parent',$x) && $x['parent']) ? $x['parent'] : 0),
'$reset' => $reset,
'$is_owner' => ((local_channel() && (local_channel() == $x['profile_uid'])) ? true : false),
@@ -1336,10 +1223,12 @@ function hz_status_editor($x, $popup = false) {
function get_item_children($arr, $parent) {
- $children = array();
+ $children = [];
+ $thread_allow = ((local_channel()) ? PConfig::Get(local_channel(), 'system', 'thread_allow', true) : Config::Get('system', 'thread_allow', true));
+
foreach($arr as $item) {
if($item['id'] != $item['parent']) {
- if(Config::Get('system','thread_allow')) {
+ if ($thread_allow) {
// Fallback to parent_mid if thr_parent is not set
$thr_parent = $item['thr_parent'];
if($thr_parent == '')
@@ -1526,7 +1415,7 @@ function prepare_page($item) {
));
}
-function get_responses($conv_responses,$response_verbs,$ob,$item) {
+function get_responses($response_verbs, $item) {
$ret = array();
foreach($response_verbs as $v) {
@@ -1536,12 +1425,8 @@ function get_responses($conv_responses,$response_verbs,$ob,$item) {
continue;
}
- $ret[$v] = [];
- $ret[$v]['count'] = $conv_responses[$v][$item['mid']] ?? 0;
- $ret[$v]['list'] = ((isset($conv_responses[$v][$item['mid']])) ? $conv_responses[$v][$item['mid'] . '-l'] : '');
- $ret[$v]['button'] = get_response_button_text($v, $ret[$v]['count']);
- $ret[$v]['title'] = $conv_responses[$v]['title'] ?? '';
- $ret[$v]['modal'] = (($ret[$v]['count'] > MAX_LIKERS) ? true : false);
+ $ret[$v]['count'] = $item[$v . '_count'] ?? 0;
+ $ret[$v]['button'] = get_response_button_text($v, $ret[$v]['count'], $item['item_thread_top']);
}
//logger('ret: ' . print_r($ret,true));
@@ -1549,25 +1434,28 @@ function get_responses($conv_responses,$response_verbs,$ob,$item) {
return $ret;
}
-function get_response_button_text($v,$count) {
+function get_response_button_text($v, $count = 0, $top_level = 0) {
switch($v) {
case 'like':
- return ['label' => tt('Like','Likes',$count,'noun'), 'icon' => 'hand-thumbs-up', 'class' => 'like', 'onclick' => 'dolike'];
+ return ['label' => tt('Like','Likes',$count,'noun'), 'icon' => 'hand-thumbs-up', 'class' => 'like', 'action' => 'dolike'];
break;
case 'announce':
- return ['label' => tt('Repeat','Repeats',$count,'noun'), 'icon' => 'repeat', 'class' => 'announce', 'onclick' => 'jotShare'];
+ return ['label' => tt('Repeat','Repeats',$count,'noun'), 'icon' => 'repeat', 'class' => 'announce', 'action' => 'jotShare'];
break;
case 'dislike':
- return ['label' => tt('Dislike','Dislikes',$count,'noun'), 'icon' => 'hand-thumbs-down', 'class' => 'dislike', 'onclick' => 'dolike'];
+ return ['label' => tt('Dislike','Dislikes',$count,'noun'), 'icon' => 'hand-thumbs-down', 'class' => 'dislike', 'action' => 'dolike'];
+ break;
+ case 'comment':
+ return ['label' => (($top_level) ? tt('Comment', 'Comments' ,$count, 'noun') : tt('Reply', 'Replies', $count, 'noun')), 'icon' => 'chat', 'class' => 'comment', 'action' => ''];
break;
- case 'attendyes':
- return ['label' => tt('Attending','Attending',$count,'noun'), 'icon' => 'calendar-check', 'class' => 'attendyes', 'onclick' => 'dolike'];
+ case 'accept':
+ return ['label' => tt('Attending','Attending',$count,'noun'), 'icon' => 'calendar-check', 'class' => 'accept', 'action' => 'dolike'];
break;
- case 'attendno':
- return ['label' => tt('Not Attending','Not Attending',$count,'noun'), 'icon' => 'calendar-x', 'class' => 'attendno', 'onclick' => 'dolike'];
+ case 'reject':
+ return ['label' => tt('Not attending','Not attending',$count,'noun'), 'icon' => 'calendar-x', 'class' => 'reject', 'action' => 'dolike'];
break;
- case 'attendmaybe':
- return ['label' => tt('Undecided','Undecided',$count,'noun'), 'icon' => 'calendar', 'class' => 'attendmaybe', 'onclick' => 'dolike'];
+ case 'tentativeaccept':
+ return ['label' => tt('Undecided','Undecided',$count,'noun'), 'icon' => 'calendar', 'class' => 'tentativeaccept', 'action' => 'dolike'];
break;
default:
return [];
diff --git a/include/crypto.php b/include/crypto.php
index 4d50310fb..4292bdb4d 100644
--- a/include/crypto.php
+++ b/include/crypto.php
@@ -2,9 +2,6 @@
use Zotlabs\Lib\Config;
-require_once('library/ASNValue.class.php');
-require_once('library/asn1.php');
-
function rsa_sign($data,$key,$alg = 'sha256') {
if(! $key)
return 'no key';
@@ -309,158 +306,6 @@ function new_keypair($bits) {
}
-function DerToPem($Der, $Private=false)
-{
- //Encode:
- $Der = base64_encode($Der);
- //Split lines:
- $lines = str_split($Der, 65);
- $body = implode("\n", $lines);
- //Get title:
- $title = $Private? 'RSA PRIVATE KEY' : 'PUBLIC KEY';
- //Add wrapping:
- $result = "-----BEGIN {$title}-----\n";
- $result .= $body . "\n";
- $result .= "-----END {$title}-----\n";
-
- return $result;
-}
-
-function DerToRsa($Der)
-{
- //Encode:
- $Der = base64_encode($Der);
- //Split lines:
- $lines = str_split($Der, 64);
- $body = implode("\n", $lines);
- //Get title:
- $title = 'RSA PUBLIC KEY';
- //Add wrapping:
- $result = "-----BEGIN {$title}-----\n";
- $result .= $body . "\n";
- $result .= "-----END {$title}-----\n";
-
- return $result;
-}
-
-
-function pkcs8_encode($Modulus,$PublicExponent) {
- //Encode key sequence
- $modulus = new ASNValue(ASNValue::TAG_INTEGER);
- $modulus->SetIntBuffer($Modulus);
- $publicExponent = new ASNValue(ASNValue::TAG_INTEGER);
- $publicExponent->SetIntBuffer($PublicExponent);
- $keySequenceItems = array($modulus, $publicExponent);
- $keySequence = new ASNValue(ASNValue::TAG_SEQUENCE);
- $keySequence->SetSequence($keySequenceItems);
- //Encode bit string
- $bitStringValue = $keySequence->Encode();
- $bitStringValue = chr(0x00) . $bitStringValue; //Add unused bits byte
- $bitString = new ASNValue(ASNValue::TAG_BITSTRING);
- $bitString->Value = $bitStringValue;
- //Encode body
- $bodyValue = "\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00" . $bitString->Encode();
- $body = new ASNValue(ASNValue::TAG_SEQUENCE);
- $body->Value = $bodyValue;
- //Get DER encoded public key:
- $PublicDER = $body->Encode();
- return $PublicDER;
-}
-
-
-function pkcs1_encode($Modulus,$PublicExponent) {
- //Encode key sequence
- $modulus = new ASNValue(ASNValue::TAG_INTEGER);
- $modulus->SetIntBuffer($Modulus);
- $publicExponent = new ASNValue(ASNValue::TAG_INTEGER);
- $publicExponent->SetIntBuffer($PublicExponent);
- $keySequenceItems = array($modulus, $publicExponent);
- $keySequence = new ASNValue(ASNValue::TAG_SEQUENCE);
- $keySequence->SetSequence($keySequenceItems);
- //Encode bit string
- $bitStringValue = $keySequence->Encode();
- return $bitStringValue;
-}
-
-
-// http://stackoverflow.com/questions/27568570/how-to-convert-raw-modulus-exponent-to-rsa-public-key-pem-format
-function metopem($m,$e) {
- $der = pkcs8_encode($m,$e);
- $key = DerToPem($der,false);
- return $key;
-}
-
-
-function pubrsatome($key,&$m,&$e) {
- require_once('library/asn1.php');
-
- $lines = explode("\n",$key);
- unset($lines[0]);
- unset($lines[count($lines)]);
- $x = base64_decode(implode('',$lines));
-
- $r = ASN_BASE::parseASNString($x);
-
- $m = base64url_decode($r[0]->asnData[0]->asnData);
- $e = base64url_decode($r[0]->asnData[1]->asnData);
-}
-
-
-function rsatopem($key) {
- pubrsatome($key,$m,$e);
- return(metopem($m,$e));
-}
-
-function pemtorsa($key) {
- pemtome($key,$m,$e);
- return(metorsa($m,$e));
-}
-
-function pemtome($key,&$m,&$e) {
- $lines = explode("\n",$key);
- unset($lines[0]);
- unset($lines[count($lines)]);
- $x = base64_decode(implode('',$lines));
-
- $r = ASN_BASE::parseASNString($x);
-
- $m = base64url_decode($r[0]->asnData[1]->asnData[0]->asnData[0]->asnData);
- $e = base64url_decode($r[0]->asnData[1]->asnData[0]->asnData[1]->asnData);
-}
-
-function metorsa($m,$e) {
- $der = pkcs1_encode($m,$e);
- $key = DerToRsa($der);
- return $key;
-}
-
-
-
-function salmon_key($pubkey) {
- pemtome($pubkey,$m,$e);
- return 'RSA' . '.' . base64url_encode($m,true) . '.' . base64url_encode($e,true) ;
-}
-
-
-function convert_salmon_key($key) {
-
- if(strstr($key,','))
- $rawkey = substr($key,strpos($key,',')+1);
- else
- $rawkey = substr($key,5);
-
- $key_info = explode('.',$rawkey);
-
- $m = base64url_decode($key_info[1]);
- $e = base64url_decode($key_info[2]);
-
- logger('key details: ' . print_r($key_info,true), LOGGER_DATA);
- $salmon_key = metopem($m,$e);
- return $salmon_key;
-
-}
-
-
function z_obscure($s) {
return json_encode(crypto_encapsulate($s,Config::Get('system','pubkey')));
}
diff --git a/include/dba/dba_pdo.php b/include/dba/dba_pdo.php
index a12629e19..3b0edefcd 100644
--- a/include/dba/dba_pdo.php
+++ b/include/dba/dba_pdo.php
@@ -78,7 +78,7 @@ class dba_pdo extends dba_driver {
$result = false;
$this->error = '';
- $select = stripos($sql, 'select') === 0 || stripos($sql, 'returning ') > 0;
+ $select = stripos($sql, 'select') === 0 || stripos($sql, 'with') === 0 || stripos($sql, 'returning ') > 0;
try {
$result = $this->db->query($sql, PDO::FETCH_ASSOC);
diff --git a/include/event.php b/include/event.php
index b83a733b8..39d0c49c2 100644
--- a/include/event.php
+++ b/include/event.php
@@ -106,7 +106,7 @@ function format_event_obj($jobject) {
$title = $object['name'] ?? '';
$content = html2bbcode($object['content']);
- if (strpos($object['source']['content'], '[/event-description]') !== false) {
+ if (isset($object['source']['content']) && strpos($object['source']['content'], '[/event-description]') !== false) {
$bbdescription = [];
preg_match("/\[event\-description\](.*?)\[\/event\-description\]/ism", $object['source']['content'], $bbdescription);
$content = $bbdescription[1];
diff --git a/include/html2plain.php b/include/html2plain.php
index 69fb5193a..6d580becf 100644
--- a/include/html2plain.php
+++ b/include/html2plain.php
@@ -120,6 +120,10 @@ function collecturls($message) {
function html2plain($html, $wraplength = 75, $compact = false)
{
+ if (!$html) {
+ return '';
+ }
+
$message = str_replace("\r", "", $html);
// mb_convert_encoding() is deprecated
diff --git a/include/items.php b/include/items.php
index 1f3671c83..339a61753 100644
--- a/include/items.php
+++ b/include/items.php
@@ -240,19 +240,22 @@ function comments_are_now_closed($item) {
return false;
}
-function item_normal() {
- $profile_uid = App::$profile['profile_uid'] ?? App::$profile_uid ?? null;
+function item_normal($profile_uid = null, $prefix = 'item') {
+ if ($profile_uid === null) {
+ $profile_uid = App::$profile['profile_uid'] ?? App::$profile_uid ?? null;
+ }
+
$uid = local_channel();
$is_owner = ($uid && intval($profile_uid) === $uid);
- $sql = " and item.item_hidden = 0 and item.item_type = 0 and item.item_deleted = 0
- and item.item_unpublished = 0 and item.item_pending_remove = 0";
+ $sql = " and $prefix.item_hidden = 0 and $prefix.item_type = 0 and $prefix.item_deleted = 0
+ and $prefix.item_unpublished = 0 and $prefix.item_pending_remove = 0";
if ($is_owner) {
- $sql .= " and item.item_blocked IN (0, " . intval(ITEM_MODERATED) . ") and item.item_delayed IN (0, 1) ";
+ $sql .= " and $prefix.item_blocked IN (0, " . intval(ITEM_MODERATED) . ") and $prefix.item_delayed IN (0, 1) ";
}
else {
- $sql .= " and item.item_blocked = 0 and item.item_delayed = 0 ";
+ $sql .= " and $prefix.item_blocked = 0 and $prefix.item_delayed = 0 ";
}
return $sql;
@@ -456,7 +459,7 @@ function post_activity_item($arr, $allow_code = false, $deliver = true, $channel
$ret = array('success' => false);
$is_comment = false;
- if((($arr['parent']) && $arr['parent'] != $arr['id']) || (($arr['parent_mid']) && $arr['parent_mid'] != $arr['mid']))
+ if((isset($arr['parent'], $arr['id']) && intval($arr['parent']) !== intval($arr['id'])) || (isset($arr['parent_mid'], $arr['mid']) && $arr['parent_mid'] !== $arr['mid']))
$is_comment = true;
if(! array_key_exists('item_origin',$arr))
@@ -474,8 +477,8 @@ function post_activity_item($arr, $allow_code = false, $deliver = true, $channel
$observer = App::get_observer();
}
- $arr['aid'] = ((x($arr,'aid')) ? $arr['aid'] : $channel['channel_account_id']);
- $arr['uid'] = ((x($arr,'uid')) ? $arr['uid'] : $channel['channel_id']);
+ $arr['aid'] = ((!empty($arr['aid'])) ? $arr['aid'] : $channel['channel_account_id']);
+ $arr['uid'] = ((!empty($arr['uid'])) ? $arr['uid'] : $channel['channel_id']);
if(! perm_is_allowed($arr['uid'],$observer['xchan_hash'],(($is_comment) ? 'post_comments' : 'post_wall'))) {
$ret['message'] = t('Permission denied');
@@ -491,18 +494,18 @@ function post_activity_item($arr, $allow_code = false, $deliver = true, $channel
$arr['mimetype'] = 'text/bbcode';
- if(! $arr['mid']) {
- $arr['uuid'] = ((x($arr,'uuid')) ? $arr['uuid'] : new_uuid());
+ if(empty($arr['mid'])) {
+ $arr['uuid'] = ((!empty($arr['uuid'])) ? $arr['uuid'] : new_uuid());
}
- $arr['mid'] = ((x($arr,'mid')) ? $arr['mid'] : z_root() . '/item/' . $arr['uuid']);
- $arr['parent_mid'] = ((x($arr,'parent_mid')) ? $arr['parent_mid'] : $arr['mid']);
- $arr['thr_parent'] = ((x($arr,'thr_parent')) ? $arr['thr_parent'] : $arr['mid']);
+ $arr['mid'] = ((!empty($arr['mid'])) ? $arr['mid'] : z_root() . '/item/' . $arr['uuid']);
+ $arr['parent_mid'] = ((!empty($arr['parent_mid'])) ? $arr['parent_mid'] : $arr['mid']);
+ $arr['thr_parent'] = ((!empty($arr['thr_parent'])) ? $arr['thr_parent'] : $arr['mid']);
- $arr['owner_xchan'] = ((x($arr,'owner_xchan')) ? $arr['owner_xchan'] : $channel['channel_hash']);
- $arr['author_xchan'] = ((x($arr,'author_xchan')) ? $arr['author_xchan'] : $observer['xchan_hash']);
+ $arr['owner_xchan'] = ((!empty($arr['owner_xchan'])) ? $arr['owner_xchan'] : $channel['channel_hash']);
+ $arr['author_xchan'] = ((!empty($arr['author_xchan'])) ? $arr['author_xchan'] : $observer['xchan_hash']);
- $arr['verb'] = ((x($arr,'verb')) ? $arr['verb'] : 'Create');
- $arr['obj_type'] = ((x($arr,'obj_type')) ? $arr['obj_type'] : 'Note');
+ $arr['verb'] = ((!empty($arr['verb'])) ? $arr['verb'] : 'Create');
+ $arr['obj_type'] = ((!empty($arr['obj_type'])) ? $arr['obj_type'] : 'Note');
if(! ( array_key_exists('allow_cid',$arr) || array_key_exists('allow_gid',$arr)
|| array_key_exists('deny_cid',$arr) || array_key_exists('deny_gid',$arr))) {
@@ -514,7 +517,7 @@ function post_activity_item($arr, $allow_code = false, $deliver = true, $channel
$arr['comment_policy'] = map_scope(PermissionLimits::Get($channel['channel_id'],'post_comments'));
- if ((! $arr['plink']) && (intval($arr['item_thread_top']))) {
+ if (empty($arr['plink']) && (intval($arr['item_thread_top']))) {
$arr['plink'] = $arr['mid'];
}
@@ -537,7 +540,7 @@ function post_activity_item($arr, $allow_code = false, $deliver = true, $channel
*/
call_hooks('post_local', $arr);
- if(x($arr, 'cancel')) {
+ if (!empty($arr['cancel'])) {
logger('Post cancelled by plugin.');
return $ret;
}
@@ -727,14 +730,14 @@ function get_item_elements($x,$allow_code = false) {
if($arr['edited'] > datetime_convert())
$arr['edited'] = datetime_convert();
- $arr['expires'] = ((x($x,'expires') && $x['expires'])
+ $arr['expires'] = ((!empty($x['expires']) && $x['expires'])
? datetime_convert('UTC','UTC',$x['expires'])
: NULL_DATE);
- $arr['commented'] = ((x($x,'commented') && $x['commented'])
+ $arr['commented'] = ((!empty($x['commented']) && $x['commented'])
? datetime_convert('UTC','UTC',$x['commented'])
: $arr['created']);
- $arr['comments_closed'] = ((x($x,'comments_closed') && $x['comments_closed'])
+ $arr['comments_closed'] = ((!empty($x['comments_closed']) && $x['comments_closed'])
? datetime_convert('UTC','UTC',$x['comments_closed'])
: NULL_DATE);
@@ -1679,7 +1682,7 @@ function item_store($arr, $allow_exec = false, $deliver = true, $addAndSync = tr
if(array_key_exists('parent',$arr))
unset($arr['parent']);
- $arr['mimetype'] = ((x($arr,'mimetype')) ? notags(trim($arr['mimetype'])) : 'text/bbcode');
+ $arr['mimetype'] = ((!empty($arr['mimetype'])) ? notags(trim($arr['mimetype'])) : 'text/bbcode');
if(($arr['mimetype'] == 'application/x-php') && (! $allow_exec)) {
logger('item_store: php mimetype but allow_exec is denied.');
@@ -1691,19 +1694,19 @@ function item_store($arr, $allow_exec = false, $deliver = true, $addAndSync = tr
$arr['summary'] = ((array_key_exists('summary',$arr) && $arr['summary']) ? trim($arr['summary']) : '');
$arr['body'] = ((array_key_exists('body',$arr) && $arr['body']) ? trim($arr['body']) : '');
- $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
- $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
- $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
- $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
- $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
- $arr['route'] = ((x($arr,'route')) ? trim($arr['route']) : '');
- $arr['uuid'] = ((x($arr,'uuid')) ? trim($arr['uuid']) : '');
- $arr['item_private'] = ((x($arr,'item_private')) ? intval($arr['item_private']) : 0 );
- $arr['item_wall'] = ((x($arr,'item_wall')) ? intval($arr['item_wall']) : 0 );
- $arr['item_type'] = ((x($arr,'item_type')) ? intval($arr['item_type']) : 0 );
+ $arr['allow_cid'] = ((!empty($arr['allow_cid'])) ? trim($arr['allow_cid']) : '');
+ $arr['allow_gid'] = ((!empty($arr['allow_gid'])) ? trim($arr['allow_gid']) : '');
+ $arr['deny_cid'] = ((!empty($arr['deny_cid'])) ? trim($arr['deny_cid']) : '');
+ $arr['deny_gid'] = ((!empty($arr['deny_gid'])) ? trim($arr['deny_gid']) : '');
+ $arr['postopts'] = ((!empty($arr['postopts'])) ? trim($arr['postopts']) : '');
+ $arr['route'] = ((!empty($arr['route'])) ? trim($arr['route']) : '');
+ $arr['uuid'] = ((!empty($arr['uuid'])) ? trim($arr['uuid']) : '');
+ $arr['item_private'] = ((!empty($arr['item_private'])) ? intval($arr['item_private']) : 0 );
+ $arr['item_wall'] = ((!empty($arr['item_wall'])) ? intval($arr['item_wall']) : 0 );
+ $arr['item_type'] = ((!empty($arr['item_type'])) ? intval($arr['item_type']) : 0 );
// obsolete, but needed so as not to throw not-null constraints on some database driveres
- $arr['item_flags'] = ((x($arr,'item_flags')) ? intval($arr['item_flags']) : 0 );
+ $arr['item_flags'] = ((!empty($arr['item_flags'])) ? intval($arr['item_flags']) : 0 );
$arr['lang'] = detect_language($arr['body']);
@@ -1744,32 +1747,32 @@ function item_store($arr, $allow_exec = false, $deliver = true, $addAndSync = tr
$arr = $translate['item'];
}
- if((x($arr,'obj')) && is_array($arr['obj'])) {
+ if((!empty($arr['obj'])) && is_array($arr['obj'])) {
activity_sanitise($arr['obj']);
$arr['obj'] = json_encode($arr['obj']);
}
- if((x($arr,'target')) && is_array($arr['target'])) {
+ if((!empty($arr['target'])) && is_array($arr['target'])) {
activity_sanitise($arr['target']);
$arr['target'] = json_encode($arr['target']);
}
- if((x($arr,'attach')) && is_array($arr['attach'])) {
+ if((!empty($arr['attach'])) && is_array($arr['attach'])) {
activity_sanitise($arr['attach']);
$arr['attach'] = json_encode($arr['attach']);
}
- $arr['aid'] = ((x($arr,'aid')) ? intval($arr['aid']) : 0);
- $arr['mid'] = ((x($arr,'mid')) ? notags(trim($arr['mid'])) : random_string());
- $arr['revision'] = ((x($arr,'revision') && intval($arr['revision']) > 0) ? intval($arr['revision']) : 0);
+ $arr['aid'] = ((!empty($arr['aid'])) ? intval($arr['aid']) : 0);
+ $arr['mid'] = ((!empty($arr['mid'])) ? notags(trim($arr['mid'])) : random_string());
+ $arr['revision'] = ((!empty($arr['revision']) && intval($arr['revision']) > 0) ? intval($arr['revision']) : 0);
- $arr['author_xchan'] = ((x($arr,'author_xchan')) ? notags(trim($arr['author_xchan'])) : '');
- $arr['owner_xchan'] = ((x($arr,'owner_xchan')) ? notags(trim($arr['owner_xchan'])) : '');
- $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
- $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
- $arr['expires'] = ((x($arr,'expires') !== false) ? datetime_convert('UTC','UTC',$arr['expires']) : NULL_DATE);
- $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
- $arr['comments_closed'] = ((x($arr,'comments_closed') !== false) ? datetime_convert('UTC','UTC',$arr['comments_closed']) : NULL_DATE);
+ $arr['author_xchan'] = ((!empty($arr['author_xchan'])) ? notags(trim($arr['author_xchan'])) : '');
+ $arr['owner_xchan'] = ((!empty($arr['owner_xchan'])) ? notags(trim($arr['owner_xchan'])) : '');
+ $arr['created'] = ((!empty($arr['created']) !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
+ $arr['edited'] = ((!empty($arr['edited']) !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
+ $arr['expires'] = ((!empty($arr['expires']) !== false) ? datetime_convert('UTC','UTC',$arr['expires']) : NULL_DATE);
+ $arr['commented'] = ((!empty($arr['commented']) !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
+ $arr['comments_closed'] = ((!empty($arr['comments_closed']) !== false) ? datetime_convert('UTC','UTC',$arr['comments_closed']) : NULL_DATE);
$arr['html'] = ((array_key_exists('html',$arr)) ? $arr['html'] : '');
if($deliver) {
@@ -1783,26 +1786,26 @@ function item_store($arr, $allow_exec = false, $deliver = true, $addAndSync = tr
// will still take place through backdoor methods. Since these fields are rarely used
// otherwise, just preserve the original timestamp.
- $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
- $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
+ $arr['received'] = ((!empty($arr['received']) !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
+ $arr['changed'] = ((!empty($arr['changed']) !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
}
- $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
- $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
- $arr['parent_mid'] = ((x($arr,'parent_mid')) ? notags(trim($arr['parent_mid'])) : '');
- $arr['thr_parent'] = ((x($arr,'thr_parent')) ? notags(trim($arr['thr_parent'])) : $arr['parent_mid']);
- $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : 'Create');
- $arr['obj_type'] = ((x($arr,'obj_type')) ? notags(trim($arr['obj_type'])) : 'Note');
- $arr['obj'] = ((x($arr,'obj')) ? trim($arr['obj']) : '');
- $arr['tgt_type'] = ((x($arr,'tgt_type')) ? notags(trim($arr['tgt_type'])) : '');
- $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
- $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
- $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
- $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
+ $arr['location'] = ((!empty($arr['location'])) ? notags(trim($arr['location'])) : '');
+ $arr['coord'] = ((!empty($arr['coord'])) ? notags(trim($arr['coord'])) : '');
+ $arr['parent_mid'] = ((!empty($arr['parent_mid'])) ? notags(trim($arr['parent_mid'])) : '');
+ $arr['thr_parent'] = ((!empty($arr['thr_parent'])) ? notags(trim($arr['thr_parent'])) : $arr['parent_mid']);
+ $arr['verb'] = ((!empty($arr['verb'])) ? notags(trim($arr['verb'])) : 'Create');
+ $arr['obj_type'] = ((!empty($arr['obj_type'])) ? notags(trim($arr['obj_type'])) : 'Note');
+ $arr['obj'] = ((!empty($arr['obj'])) ? trim($arr['obj']) : '');
+ $arr['tgt_type'] = ((!empty($arr['tgt_type'])) ? notags(trim($arr['tgt_type'])) : '');
+ $arr['target'] = ((!empty($arr['target'])) ? trim($arr['target']) : '');
+ $arr['plink'] = ((!empty($arr['plink'])) ? notags(trim($arr['plink'])) : '');
+ $arr['attach'] = ((!empty($arr['attach'])) ? notags(trim($arr['attach'])) : '');
+ $arr['app'] = ((!empty($arr['app'])) ? notags(trim($arr['app'])) : '');
- $arr['public_policy'] = ((x($arr,'public_policy')) ? notags(trim($arr['public_policy'])) : '' );
+ $arr['public_policy'] = ((!empty($arr['public_policy'])) ? notags(trim($arr['public_policy'])) : '' );
- $arr['comment_policy'] = ((x($arr,'comment_policy')) ? notags(trim($arr['comment_policy'])) : 'contacts' );
+ $arr['comment_policy'] = ((!empty($arr['comment_policy'])) ? notags(trim($arr['comment_policy'])) : 'contacts' );
if(! array_key_exists('item_unseen',$arr))
$arr['item_unseen'] = 1;
@@ -1946,7 +1949,7 @@ function item_store($arr, $allow_exec = false, $deliver = true, $addAndSync = tr
*/
call_hooks('post_remote', $arr);
- if(x($arr, 'cancel')) {
+ if(!empty($arr['cancel'])) {
logger('Post cancelled by plugin.');
$ret['message'] = 'cancelled.';
return $ret;
@@ -1989,7 +1992,9 @@ function item_store($arr, $allow_exec = false, $deliver = true, $addAndSync = tr
// find the item we just created
- $r = q("SELECT * FROM item WHERE mid = '%s' AND uid = %d and revision = %d ORDER BY id ASC ",
+ $r = q("SELECT item.*, tp.uuid AS thr_parent_uuid FROM item
+ LEFT JOIN item tp ON item.thr_parent = tp.mid AND item.uid = tp.uid
+ WHERE item.mid = '%s' AND item.uid = %d and item.revision = %d ORDER BY item.id ASC ",
dbesc($arr['mid']),
intval($arr['uid']),
intval($arr['revision'])
@@ -2071,9 +2076,8 @@ function item_store($arr, $allow_exec = false, $deliver = true, $addAndSync = tr
item_update_parent_commented($arr);
-
- if(strpos($arr['body'],'[embed]') !== false) {
- Master::Summon([ 'Cache_embeds', $current_post ]);
+ if (str_contains($arr['body'], '[/embed]') || str_contains($arr['body'], '[/img]') || str_contains($arr['body'], '[/zmg]')) {
+ Master::Summon(['Cache_embeds', $arr['uuid']]);
}
$ret['success'] = true;
@@ -2163,7 +2167,7 @@ function item_store_update($arr, $allow_exec = false, $deliver = true, $addAndSy
if(array_key_exists('edit',$arr))
unset($arr['edit']);
- $arr['mimetype'] = ((x($arr,'mimetype')) ? notags(trim($arr['mimetype'])) : 'text/bbcode');
+ $arr['mimetype'] = ((!empty($arr['mimetype'])) ? notags(trim($arr['mimetype'])) : 'text/bbcode');
if(($arr['mimetype'] == 'application/x-php') && (! $allow_exec)) {
logger('item_store: php mimetype but allow_exec is denied.');
@@ -2234,10 +2238,10 @@ function item_store_update($arr, $allow_exec = false, $deliver = true, $addAndSy
unset($arr['thr_parent']);
unset($arr['llink']);
- $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
- $arr['expires'] = ((x($arr,'expires') !== false) ? datetime_convert('UTC','UTC',$arr['expires']) : $orig[0]['expires']);
+ $arr['edited'] = ((!empty($arr['edited'])) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
+ $arr['expires'] = ((!empty($arr['expires'])) ? datetime_convert('UTC','UTC',$arr['expires']) : $orig[0]['expires']);
- $arr['revision'] = ((x($arr,'revision') && $arr['revision'] > 0) ? intval($arr['revision']) : 0);
+ $arr['revision'] = ((!empty($arr['revision'])) ? intval($arr['revision']) : 0);
if(array_key_exists('comments_closed',$arr))
$arr['comments_closed'] = datetime_convert('UTC','UTC',$arr['comments_closed']);
@@ -2251,15 +2255,15 @@ function item_store_update($arr, $allow_exec = false, $deliver = true, $addAndSy
$arr['route'] = ((array_key_exists('route',$arr)) ? trim($arr['route']) : $orig[0]['route']);
- $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : $orig[0]['location']);
- $arr['uuid'] = ((x($arr,'uuid')) ? notags(trim($arr['uuid'])) : $orig[0]['uuid']);
- $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : $orig[0]['coord']);
- $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : $orig[0]['verb']);
- $arr['obj_type'] = ((x($arr,'obj_type')) ? notags(trim($arr['obj_type'])) : $orig[0]['obj_type']);
- $arr['obj'] = ((x($arr,'obj')) ? trim($arr['obj']) : $orig[0]['obj']);
- $arr['tgt_type'] = ((x($arr,'tgt_type')) ? notags(trim($arr['tgt_type'])) : $orig[0]['tgt_type']);
- $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : $orig[0]['target']);
- $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : $orig[0]['plink']);
+ $arr['location'] = ((!empty($arr['location'])) ? notags(trim($arr['location'])) : $orig[0]['location']);
+ $arr['uuid'] = ((!empty($arr['uuid'])) ? notags(trim($arr['uuid'])) : $orig[0]['uuid']);
+ $arr['coord'] = ((!empty($arr['coord'])) ? notags(trim($arr['coord'])) : $orig[0]['coord']);
+ $arr['verb'] = ((!empty($arr['verb'])) ? notags(trim($arr['verb'])) : $orig[0]['verb']);
+ $arr['obj_type'] = ((!empty($arr['obj_type'])) ? notags(trim($arr['obj_type'])) : $orig[0]['obj_type']);
+ $arr['obj'] = ((!empty($arr['obj'])) ? trim($arr['obj']) : $orig[0]['obj']);
+ $arr['tgt_type'] = ((!empty($arr['tgt_type'])) ? notags(trim($arr['tgt_type'])) : $orig[0]['tgt_type']);
+ $arr['target'] = ((!empty($arr['target'])) ? trim($arr['target']) : $orig[0]['target']);
+ $arr['plink'] = ((!empty($arr['plink'])) ? notags(trim($arr['plink'])) : $orig[0]['plink']);
$arr['allow_cid'] = ((array_key_exists('allow_cid',$arr)) ? trim($arr['allow_cid']) : $orig[0]['allow_cid']);
$arr['allow_gid'] = ((array_key_exists('allow_gid',$arr)) ? trim($arr['allow_gid']) : $orig[0]['allow_gid']);
@@ -2298,11 +2302,11 @@ function item_store_update($arr, $allow_exec = false, $deliver = true, $addAndSy
$arr['item_pending_remove'] = ((array_key_exists('item_pending_remove',$arr)) ? intval($arr['item_pending_remove']) : $orig[0]['item_pending_remove'] );
$arr['item_blocked'] = ((array_key_exists('item_blocked',$arr)) ? intval($arr['item_blocked']) : $orig[0]['item_blocked'] );
- $arr['sig'] = ((x($arr,'sig')) ? $arr['sig'] : '');
+ $arr['sig'] = ((!empty($arr['sig'])) ? $arr['sig'] : '');
$arr['layout_mid'] = ((array_key_exists('layout_mid',$arr)) ? dbesc($arr['layout_mid']) : $orig[0]['layout_mid'] );
- $arr['public_policy'] = ((x($arr,'public_policy')) ? notags(trim($arr['public_policy'])) : $orig[0]['public_policy'] );
- $arr['comment_policy'] = ((x($arr,'comment_policy')) ? notags(trim($arr['comment_policy'])) : $orig[0]['comment_policy'] );
+ $arr['public_policy'] = ((!empty($arr['public_policy'])) ? notags(trim($arr['public_policy'])) : $orig[0]['public_policy'] );
+ $arr['comment_policy'] = ((!empty($arr['comment_policy'])) ? notags(trim($arr['comment_policy'])) : $orig[0]['comment_policy'] );
/**
* @hooks post_remote_update
@@ -2310,7 +2314,7 @@ function item_store_update($arr, $allow_exec = false, $deliver = true, $addAndSy
*/
call_hooks('post_remote_update', $arr);
- if(x($arr, 'cancel')) {
+ if(!empty($arr['cancel'])) {
logger('Post cancelled by plugin.');
$ret['message'] = 'cancelled.';
return $ret;
@@ -2362,7 +2366,9 @@ function item_store_update($arr, $allow_exec = false, $deliver = true, $addAndSy
// fetch an unescaped complete copy of the stored item
- $r = q("select * from item where id = %d",
+ $r = q("SELECT item.*, tp.uuid AS thr_parent_uuid FROM item
+ LEFT JOIN item tp ON item.thr_parent = tp.mid AND item.uid = tp.uid
+ WHERE item.id = %d",
intval($orig_post_id)
);
if($r)
@@ -2376,14 +2382,15 @@ function item_store_update($arr, $allow_exec = false, $deliver = true, $addAndSy
if(is_array($terms)) {
foreach($terms as $t) {
- q("insert into term (uid,oid,otype,ttype,term,url)
- values(%d,%d,%d,%d,'%s','%s') ",
+ q("insert into term (uid, oid, otype, ttype, term, url, imgurl)
+ values (%d, %d, %d, %d, '%s', '%s', '%s')",
intval($uid),
intval($orig_post_id),
intval(TERM_OBJ_POST),
intval($t['ttype']),
dbesc($t['term']),
- dbesc($t['url'])
+ dbesc($t['url']),
+ dbesc($t['imgurl'] ?? ''),
);
}
$arr['term'] = $terms;
@@ -2414,8 +2421,8 @@ function item_store_update($arr, $allow_exec = false, $deliver = true, $addAndSy
*/
call_hooks('item_stored_update',$arr);
- if(strpos($arr['body'],'[embed]') !== false) {
- Master::Summon([ 'Cache_embeds', $orig_post_id ]);
+ if (str_contains($arr['body'], '[/embed]') || str_contains($arr['body'], '[/img]') || str_contains($arr['body'], '[/zmg]')) {
+ Master::Summon(['Cache_embeds', $arr['uuid']]);
}
$ret['success'] = true;
@@ -2478,21 +2485,23 @@ function send_status_notifications($post_id,$item) {
$parent = 0;
$is_reaction = false;
- $thr_parent_id = 0;
+ $thr_parent_id = null;
+ $thr_parent_uuid = null;
$type = ((intval($item['item_private']) === 2) ? NOTIFY_MAIL : NOTIFY_COMMENT);
- if(array_key_exists('verb',$item) && activity_match($item['verb'], ['Like', 'Dislike', ACTIVITY_LIKE, ACTIVITY_DISLIKE])) {
+ if(array_key_exists('verb',$item) && activity_match($item['verb'], ['Like', 'Dislike', ACTIVITY_LIKE, ACTIVITY_DISLIKE, 'Announce'])) {
$type = NOTIFY_LIKE;
- $r = q("select id from item where mid = '%s' and uid = %d limit 1",
+ $r = q("select id, uuid from item where mid = '%s' and uid = %d limit 1",
dbesc($item['thr_parent']),
intval($item['uid'])
);
if ($r) {
$thr_parent_id = $r[0]['id'];
+ $thr_parent_uuid = $r[0]['uuid'];
}
}
@@ -2517,6 +2526,7 @@ function send_status_notifications($post_id,$item) {
dbesc($item['parent_mid']),
intval($item['uid'])
);
+
if($x) {
foreach($x as $xx) {
if($xx['author_xchan'] === $r[0]['channel_hash']) {
@@ -2561,7 +2571,7 @@ function send_status_notifications($post_id,$item) {
'link' => $link,
'verb' => $item['verb'],
'otype' => 'item',
- 'parent' => $thr_parent_id ? $thr_parent_id : $parent,
+ 'parent' => $thr_parent_id ?? $parent,
'parent_mid' => $thr_parent_id ? $item['thr_parent'] : $item['parent_mid']
));
}
@@ -2838,8 +2848,8 @@ function tag_deliver($uid, $item_id) {
'from_xchan' => $item['author_xchan'],
'type' => NOTIFY_TAGSELF,
'item' => $item,
- 'link' => $i[0]['llink'],
- 'verb' => ACTIVITY_TAG,
+ 'link' => $item['llink'],
+ 'verb' => $item['verb'],
'otype' => 'item'
));
@@ -4792,19 +4802,19 @@ function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = C
return $items;
}
-function webpage_to_namespace($webpage) {
+function item_type_to_namespace($item_type) {
- if($webpage == ITEM_TYPE_WEBPAGE)
+ if($item_type == ITEM_TYPE_WEBPAGE)
$page_type = 'WEBPAGE';
- elseif($webpage == ITEM_TYPE_BLOCK)
+ elseif($item_type == ITEM_TYPE_BLOCK)
$page_type = 'BUILDBLOCK';
- elseif($webpage == ITEM_TYPE_PDL)
+ elseif($item_type == ITEM_TYPE_PDL)
$page_type = 'PDL';
- elseif($webpage == ITEM_TYPE_CARD)
+ elseif($item_type == ITEM_TYPE_CARD)
$page_type = 'CARD';
- elseif($webpage == ITEM_TYPE_ARTICLE)
+ elseif($item_type == ITEM_TYPE_ARTICLE)
$page_type = 'ARTICLE';
- elseif($webpage == ITEM_TYPE_DOC)
+ elseif($item_type == ITEM_TYPE_DOC)
$page_type = 'docfile';
else
$page_type = 'unknown';
@@ -4813,12 +4823,12 @@ function webpage_to_namespace($webpage) {
}
-function update_remote_id($channel,$post_id,$webpage,$pagetitle,$namespace,$remote_id,$mid) {
+function update_remote_id($channel,$post_id,$item_type,$pagetitle,$namespace,$remote_id,$mid) {
if(! intval($post_id))
return;
- $page_type = webpage_to_namespace($webpage);
+ $page_type = item_type_to_namespace($item_type);
if($page_type == 'unknown' && $namespace && $remote_id) {
$page_type = $namespace;
@@ -5350,3 +5360,416 @@ function set_activity_mid($string) {
return str_replace(z_root() . '/item/', z_root() . '/activity/', $string);
}
+/**
+ * @brief returns an item by id and parent belonging to local_channel()
+ * including activity counts.
+ * @param int $id
+ * @param int $parent
+ */
+
+function item_by_item_id(int $id, int $parent): array
+{
+ if (!$id && !$parent && !local_channel()) {
+ return [];
+ }
+
+ $item_normal_sql = item_normal();
+
+ $reaction = item_reaction_sql($parent);
+ $reaction_cte_sql = $reaction['cte'];
+ $reaction_select_sql = $reaction['select'];
+ $reaction_join_sql = $reaction['join'];
+
+ return q("WITH
+ $reaction_cte_sql
+ SELECT
+ *,
+ $reaction_select_sql
+ FROM item
+ $reaction_join_sql
+ WHERE
+ item.id = %d
+ AND item.uid = %d
+ AND item.verb IN ('Create', 'Update', 'EmojiReact')
+ $item_normal_sql",
+ intval($id),
+ intval(local_channel())
+ );
+}
+
+
+/**
+ * @brief returns an array of items by ids
+ * ATTENTION: no permissions for the parents are checked here!!!
+ * Permissions MUST be checked by the module which calls this function.
+ * @param array $parents
+ * @param null|array $thr_parents (optional) - thr_parent mids which will be included
+ * @param string $permission_sql (optional) - SQL as provided by item_permission_sql() from the calling module
+ * @param bool $blog_mode (optional) - if set to yes only the parent items will be returned
+ */
+
+function items_by_parent_ids(array $parents, null|array $thr_parents = null, string $permission_sql = '', bool $blog_mode = false): array
+{
+ if (!$parents) {
+ return [];
+ }
+
+ $ids = ids_to_querystr($parents, 'item_id');
+ $thread_allow = ((local_channel()) ? PConfig::Get(local_channel(), 'system', 'thread_allow', true) : Config::Get('system', 'thread_allow', true));
+ $item_normal_sql = item_normal();
+ $limit = $thread_allow ? 3 : 1000;
+
+ $thr_parent_sql = (($thread_allow) ? " AND item.thr_parent = item.parent_mid " : '');
+ if ($thr_parents && $thread_allow) {
+ $limit = 300;
+ $thr_parent_str = stringify_array($thr_parents, true);
+ $thr_parent_sql = " AND item.thr_parent IN (" . protect_sprintf($thr_parent_str) . ") ";
+ }
+
+ $reaction = item_reaction_sql($ids, $permission_sql, 'final_selection', $blog_mode);
+ $reaction_cte_sql = $reaction['cte'];
+ $reaction_select_sql = $reaction['select'];
+ $reaction_join_sql = $reaction['join'];
+
+ if ($blog_mode) {
+ $q = <<<SQL
+ WITH
+ final_selection AS (
+ SELECT
+ item.*
+ FROM
+ item
+ WHERE
+ item.id IN ($ids)
+ ),
+
+ $reaction_cte_sql
+
+ SELECT
+ final_selection.*,
+ $reaction_select_sql
+ FROM final_selection
+ $reaction_join_sql
+ SQL;
+
+ return dbq(trim($q));
+ }
+
+ $q = <<<SQL
+ WITH
+ parent_items AS (
+ SELECT
+ item.*,
+ 0 AS rn
+ FROM item
+ WHERE
+ item.id IN ($ids)
+ ),
+
+ $reaction_cte_sql,
+
+ all_comments AS (
+ SELECT
+ item.*,
+ ROW_NUMBER() OVER (PARTITION BY item.parent ORDER BY item.created DESC) AS rn
+ FROM item
+ WHERE item.parent IN ($ids)
+ AND item.verb IN ('Create', 'Update', 'EmojiReact')
+ AND item.item_thread_top = 0
+ $thr_parent_sql
+ $permission_sql
+ $item_normal_sql
+ ),
+
+ final_selection AS (
+ SELECT * FROM parent_items
+ UNION ALL
+ SELECT * FROM all_comments WHERE all_comments.rn <= $limit
+ )
+
+ SELECT
+ final_selection.*,
+ $reaction_select_sql
+ FROM final_selection
+ $reaction_join_sql
+ SQL;
+
+ return dbq(trim($q));
+}
+
+/**
+ * @brief prepare reaction sql for items_by_parent_ids()
+ * ATTENTION: no permissions for the pa are checked here!!!
+ * Permissions MUST be checked by the function which returns the ids.
+ * @param string $ids
+ * @param string $permission_sql (optional) - SQL provided by item_permission_sql()
+ * @param string $join_prefix (optional) - prefix for the join part defaults to 'item'
+ */
+
+function item_reaction_sql(string $ids, string $permission_sql = '', string $join_prefix = 'item', bool $blog_mode = false): array
+{
+ $item_normal_sql = item_normal();
+ $observer = get_observer_hash();
+
+ $verbs = [
+ 'like' => ['Like'],
+ 'dislike' => ['Dislike'],
+ 'announce' => ['Announce'],
+ 'accept' => ['Accept'],
+ 'reject' => ['Reject'],
+ 'tentativeaccept' => ['TentativeAccept']
+ ];
+
+ $thread_allow = ((local_channel()) ? PConfig::Get(local_channel(), 'system', 'thread_allow', true) : Config::Get('system', 'thread_allow', true));
+
+ if ($thread_allow || $blog_mode) {
+ $verbs['comment'] = ['Create', 'Update', 'EmojiReact'];
+ }
+
+ $cte = '';
+ $select = '';
+ $join = '';
+
+ foreach($verbs as $k => $v) {
+
+ $observer_sql = "0 AS observer_{$k}_count";
+ if ($observer) {
+ $observer_sql = "COUNT(CASE WHEN item.author_xchan = '$observer' THEN 1 END) AS observer_{$k}_count";
+ }
+
+ $verbs_str = stringify_array($v);
+
+ if ($cte) {
+ $cte .= ",\n";
+ }
+
+ $cte .= <<<SQL
+ reaction_{$k} AS (
+ SELECT
+ item.thr_parent,
+ -- COUNT(DISTINCT item.author_xchan) AS {$k}_count, (should we prevent multiple reactions by the same author?)
+ COUNT(*) AS {$k}_count,
+ $observer_sql
+ FROM item
+ WHERE item.verb IN ($verbs_str)
+ AND item.item_thread_top = 0
+ AND item.parent IN ($ids)
+ $item_normal_sql
+ $permission_sql
+ GROUP BY item.thr_parent
+ )
+ SQL;
+
+ if ($select) {
+ $select .= ",\n";
+ }
+
+ $select .= <<<SQL
+ COALESCE(reaction_{$k}.{$k}_count, 0) AS {$k}_count,
+ COALESCE(reaction_{$k}.observer_{$k}_count, 0) AS observer_{$k}_count
+ SQL;
+
+ $join .= <<<SQL
+ LEFT JOIN reaction_{$k} ON reaction_{$k}.thr_parent = $join_prefix.mid
+ SQL;
+
+ }
+
+ $ret['cte'] = $cte;
+ $ret['select'] = $select;
+ $ret['join'] = $join;
+
+ return $ret;
+}
+
+
+
+/**
+ * @brief returns an array of items by thr_parent mid of a parent
+
+ * @param string $mid
+ * @param int $parent
+ * @param int|null $offset
+ */
+
+function items_by_thr_parent(string $mid, int $parent, int|null $offset = null): array
+{
+ if (!$mid && !$parent) {
+ return [];
+ }
+
+ $parent_item = q("SELECT uid FROM item WHERE id = %d",
+ intval($parent)
+ );
+
+ $order_sql = "ORDER BY item.created";
+ if (isset($offset)) {
+ $order_sql = "ORDER BY item.created DESC, item.received DESC LIMIT 3 OFFSET $offset";
+ }
+
+ $owner_uid = intval($parent_item[0]['uid']);
+ $item_normal_sql = item_normal($owner_uid);
+
+ if (local_channel() === $owner_uid) {
+ $reaction = item_reaction_sql($parent);
+ $reaction_cte_sql = $reaction['cte'];
+ $reaction_select_sql = $reaction['select'];
+ $reaction_join_sql = $reaction['join'];
+
+ $ret = q("WITH
+ $reaction_cte_sql
+ SELECT
+ item.*,
+ $reaction_select_sql
+ FROM item
+ $reaction_join_sql
+ WHERE
+ item.thr_parent = '%s'
+ AND item.uid = %d
+ AND item.verb IN ('Create', 'Update', 'EmojiReact')
+ AND item.item_thread_top = 0
+ $item_normal_sql
+ $order_sql",
+ dbesc($mid),
+ intval($owner_uid)
+ );
+ }
+ else {
+ $observer_hash = get_observer_hash();
+ $permission_sql = item_permissions_sql($owner_uid, $observer_hash);
+
+ $reaction = item_reaction_sql($parent, $permission_sql);
+ $reaction_cte_sql = $reaction['cte'];
+ $reaction_select_sql = $reaction['select'];
+ $reaction_join_sql = $reaction['join'];
+
+ $ret = q("WITH
+ $reaction_cte_sql
+ SELECT
+ item.*,
+ $reaction_select_sql
+ FROM item
+ $reaction_join_sql
+ WHERE
+ item.thr_parent = '%s'
+ AND item.uid = %d
+ AND item.verb IN ('Create', 'Update', 'EmojiReact')
+ AND item.item_thread_top = 0
+ $permission_sql
+ $item_normal_sql
+ $order_sql",
+ dbesc($mid),
+ intval($owner_uid)
+ );
+ }
+
+ if (isset($offset)) {
+ $ret = array_reverse($ret);
+ }
+
+ return $ret;
+}
+
+
+/**
+ * @brief returns an array of xchan entries (partly) for activities of an item by mid of a parent.
+ * Also checks if observer is allowed to add activities to the item.
+ * @param string $mid
+ * @param int $parent
+ * @param string $verb
+ */
+
+function item_activity_xchans(string $mid, int $parent, string $verb): array
+{
+ if (!$mid && !$parent && !$verb) {
+ return [];
+ }
+
+ $observer_hash = get_observer_hash();
+ $parent_item = q("SELECT * FROM item WHERE id = %d",
+ intval($parent)
+ );
+
+ $owner_uid = intval($parent_item[0]['uid']);
+ $item_normal = item_normal($owner_uid);
+
+ if (local_channel() === $owner_uid) {
+ $ret = q("SELECT item.id, item.item_blocked, xchan.xchan_hash, xchan.xchan_name as name, xchan.xchan_url as url, xchan.xchan_photo_s as photo FROM item
+ LEFT JOIN xchan ON item.author_xchan = xchan.xchan_hash
+ WHERE item.uid = %d
+ AND item.parent = %d
+ AND item.thr_parent = '%s'
+ AND item.verb = '%s'
+ AND item.item_thread_top = 0
+ $item_normal
+ -- GROUP BY item.author_xchan (should we prevent multiple reactions by the same author?)
+ ORDER BY item.created",
+ intval(local_channel()),
+ intval($parent),
+ dbesc($mid),
+ dbesc($verb)
+ );
+ }
+ else {
+ $sql_extra = item_permissions_sql($owner_uid, $observer_hash);
+
+ $ret = q("SELECT item.id, item.item_blocked, xchan.xchan_hash, xchan.xchan_name as name, xchan.xchan_url as url, xchan.xchan_photo_s as photo FROM item
+ LEFT JOIN xchan ON item.author_xchan = xchan.xchan_hash
+ WHERE item.uid = %d
+ AND item.thr_parent = '%s'
+ AND item.verb = '%s'
+ AND item.item_thread_top = 0
+ $sql_extra
+ $item_normal
+ -- GROUP BY item.author_xchan (should we prevent multiple reactions by the same author?)
+ ORDER BY item.created",
+ intval($owner_uid),
+ dbesc($mid),
+ dbesc($verb)
+ );
+ }
+
+ $ret['is_commentable'] = can_comment_on_post($observer_hash, $parent_item[0]);
+
+ return $ret;
+}
+
+
+/**
+ * @brief find and return thr_parents we need to show when displaying a nested comment.
+ * TODO: can this be improved or maybe implemented differently in the UI?
+ * @param array $item
+ */
+
+function get_recursive_thr_parents(array $item): array|null
+{
+ if ($item['id'] === $item['parent']) {
+ // This is a toplevel post, return null.
+ return null;
+ }
+
+ $thr_parents[] = $item['thr_parent'];
+
+ $mid = $item['thr_parent'];
+ $parent_mid = $item['parent_mid'];
+ $uid = $item['uid'];
+ $i = 0;
+
+ while ($mid !== $item['parent_mid'] && $i < 100) {
+ $x = q("SELECT thr_parent, mid FROM item WHERE uid = %d AND mid = '%s'",
+ intval($uid),
+ dbesc($mid)
+ );
+
+ if (!$x) {
+ break;
+ }
+
+ $mid = $x[0]['thr_parent'];
+ $thr_parents[] = $x[0]['thr_parent'];
+
+ $i++;
+ }
+
+ return $thr_parents;
+}
diff --git a/include/js_strings.php b/include/js_strings.php
index b41c34508..6f2ffd351 100644
--- a/include/js_strings.php
+++ b/include/js_strings.php
@@ -5,8 +5,6 @@ function js_strings() {
'$delitem' => t('Delete this item?'),
'$itemdel' => t('Item deleted'),
'$comment' => t('Comment'),
- '$showmore' => t('show all'),
- '$showfewer' => t('show less'),
'$divgrowmore' => t('expand'),
'$divgrowless' => t('collapse'),
'$pwshort' => t("Password too short"),
@@ -38,6 +36,7 @@ function js_strings() {
'$pinned' => t('Pinned'),
'$pin_item' => t('Pin to the top'),
'$unpin_item' => t('Unpin from the top'),
+ '$dblclick_to_exit_zoom' => t('Double click to exit zoom'),
// translatable prefix and suffix strings for jquery.timeago -
// using the defaults set below if left untranslated, empty strings if
diff --git a/include/language.php b/include/language.php
index 538f67d90..9b68717f8 100644
--- a/include/language.php
+++ b/include/language.php
@@ -29,7 +29,7 @@ function get_browser_language() {
$langs = [];
$lang_parse = [];
- if (x($_SERVER, 'HTTP_ACCEPT_LANGUAGE')) {
+ if (!empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
// break up string into pieces (languages and q factors)
preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i',
$_SERVER['HTTP_ACCEPT_LANGUAGE'], $lang_parse);
@@ -204,7 +204,7 @@ function load_translation_table($lang, $install = false) {
function t($s, $ctx = ''): string {
$cs = $ctx ? '__ctx:' . $ctx . '__ ' . $s : $s;
- if (x(App::$strings, $cs)) {
+ if (!empty(App::$strings[$cs])) {
$t = App::$strings[$cs];
return ((is_array($t)) ? translate_projectname($t[0]) : translate_projectname($t));
@@ -219,7 +219,7 @@ function t($s, $ctx = ''): string {
*/
function translate_projectname($s) {
- if(strpos($s,'rojectname') !== false) {
+ if(str_contains($s,'rojectname')) {
return str_replace(array('$projectname','$Projectname'),array(Zotlabs\Lib\System::get_platform_name(),ucfirst(Zotlabs\Lib\System::get_platform_name())),$s);
}
return $s;
@@ -239,7 +239,7 @@ function translate_projectname($s) {
function tt($singular, $plural, $count, $ctx = ''){
$cs = $ctx ? "__ctx:" . $ctx . "__ " . $singular : $singular;
- if (x(App::$strings,$cs)) {
+ if (!empty(App::$strings[$cs])) {
$t = App::$strings[$cs];
$f = 'string_plural_select_' . str_replace('-', '_', App::$language);
if (! function_exists($f))
@@ -282,9 +282,8 @@ function ta($k){
*/
function tf() {
-
- $s = "plural_function_code";
- return (x(App::$strings, $s) ? App::$strings[$s] : "0");
+ $s = "plural_function_code";
+ return (!empty(App::$strings[$s]) ? App::$strings[$s] : "0");
}
/**
@@ -391,7 +390,6 @@ function language_list() {
$langs = glob('view/*/hstrings.php');
$lang_options = array();
- $selected = "";
if(is_array($langs) && count($langs)) {
if(! in_array('view/en/hstrings.php',$langs))
@@ -406,41 +404,6 @@ function language_list() {
return $lang_options;
}
-function lang_selector() {
-
- $langs = glob('view/*/hstrings.php');
-
- $lang_options = array();
- $selected = "";
-
- if(is_array($langs) && count($langs)) {
- $langs[] = '';
- if(! in_array('view/en/hstrings.php',$langs))
- $langs[] = 'view/en/';
- asort($langs);
- foreach($langs as $l) {
- if($l == '') {
- $lang_options[""] = t('default');
- continue;
- }
- $ll = substr($l,5);
- $ll = substr($ll,0,strrpos($ll,'/'));
- $selected = (($ll === App::$language && (x($_SESSION, 'language'))) ? $ll : $selected);
- $lang_options[$ll] = get_language_name($ll, $ll) . " ($ll)";
- }
- }
-
- $tpl = get_markup_template('lang_selector.tpl');
-
- $o = replace_macros($tpl, array(
- '$title' => t('Select an alternate language'),
- '$langs' => array($lang_options, $selected),
-
- ));
-
- return $o;
-}
-
function rtl_languages() {
return [
'ar',
diff --git a/include/markdown.php b/include/markdown.php
index 90d671fe4..d2379e7ed 100644
--- a/include/markdown.php
+++ b/include/markdown.php
@@ -64,6 +64,12 @@ function markdown_to_bb($s, $use_zrl = false, $options = []) {
// Escaping the hash tags
$s = preg_replace('/\#([^\s\#])/','&#35;$1',$s);
+ // Protect mentions from being mangled by the markdown parser
+ $s = preg_replace_callback(
+ '|@\{([^}]+)\}|',
+ fn ($matches) => '@{' . base64_encode($matches[1]) . '}',
+ $s);
+
$s = MarkdownExtra::defaultTransform($s);
@@ -76,6 +82,12 @@ function markdown_to_bb($s, $use_zrl = false, $options = []) {
$s = str_replace("\r","",$s);
}
+ // Restore mentions after markdown conversion
+ $s = preg_replace_callback(
+ '|@\{([^}]+)\}|',
+ fn ($matches) => '@{' . base64_decode($matches[1]) . '}',
+ $s);
+
$s = str_replace('&#35;','#',$s);
$s = html2bbcode($s);
diff --git a/include/nav.php b/include/nav.php
index 1bee5a2db..3426db5f2 100644
--- a/include/nav.php
+++ b/include/nav.php
@@ -366,7 +366,7 @@ function nav($template = 'default') {
'$form_security_token' => get_form_security_token('pconfig')
]);
- if (x($_SESSION, 'reload_avatar') && $observer) {
+ if (!empty($_SESSION['reload_avatar']) && $observer) {
// The avatar has been changed on the server but the browser doesn't know that,
// force the browser to reload the image from the server instead of its cache.
$tpl = get_markup_template('force_image_reload.tpl');
diff --git a/include/network.php b/include/network.php
index 55eecac84..83bb281a4 100644
--- a/include/network.php
+++ b/include/network.php
@@ -73,21 +73,21 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) {
if($ciphers)
@curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, $ciphers);
- if(x($opts,'filep')) {
+ if(!empty($opts['filep'])) {
@curl_setopt($ch, CURLOPT_FILE, $opts['filep']);
@curl_setopt($ch, CURLOPT_HEADER, false);
}
- if(x($opts,'upload'))
+ if(!empty($opts['upload']))
@curl_setopt($ch, CURLOPT_UPLOAD, $opts['upload']);
- if(x($opts,'infile'))
+ if(!empty($opts['infile']))
@curl_setopt($ch, CURLOPT_INFILE, $opts['infile']);
- if(x($opts,'infilesize'))
+ if(!empty($opts['infilesize']))
@curl_setopt($ch, CURLOPT_INFILESIZE, $opts['infilesize']);
- if(x($opts,'readfunc'))
+ if(!empty($opts['readfunc']))
@curl_setopt($ch, CURLOPT_READFUNCTION, $opts['readfunc']);
// When using the session option and fetching from our own site,
@@ -97,7 +97,7 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) {
$instance_headers = ((array_key_exists('headers',$opts) && is_array($opts['headers'])) ? $opts['headers'] : []);
- if(x($opts,'session')) {
+ if(!empty($opts['session'])) {
if(strpos($url,z_root()) === 0) {
$instance_headers[] = 'Cookie: PHPSESSID=' . session_id();
}
@@ -106,13 +106,13 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) {
@curl_setopt($ch, CURLOPT_HTTPHEADER, $instance_headers);
- if(x($opts,'nobody'))
+ if(!empty($opts['nobody']))
@curl_setopt($ch, CURLOPT_NOBODY, $opts['nobody']);
- if(x($opts,'custom'))
+ if(!empty($opts['custom']))
@curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $opts['custom']);
- if(x($opts,'timeout') && intval($opts['timeout'])) {
+ if(!empty($opts['timeout'])) {
@curl_setopt($ch, CURLOPT_TIMEOUT, intval($opts['timeout']));
}
else {
@@ -120,7 +120,7 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) {
@curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== 0) ? $curl_time : 60));
}
- if(x($opts,'connecttimeout') && intval($opts['connecttimeout'])) {
+ if(!empty($opts['connecttimeout'])) {
@curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, intval($opts['connecttimeout']));
}
else {
@@ -128,7 +128,7 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) {
@curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, (($curl_contime !== 0) ? $curl_contime : 30));
}
- if(x($opts,'http_auth')) {
+ if(!empty($opts['http_auth'])) {
// "username" . ':' . "password"
@curl_setopt($ch, CURLOPT_USERPWD, $opts['http_auth']);
}
@@ -136,16 +136,16 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) {
if(array_key_exists('http_version',$opts))
@curl_setopt($ch,CURLOPT_HTTP_VERSION,$opts['http_version']);
- if(x($opts,'cookiejar'))
+ if(!empty($opts['cookiejar']))
@curl_setopt($ch, CURLOPT_COOKIEJAR, $opts['cookiejar']);
- if(x($opts,'cookiefile'))
+ if(!empty($opts['cookiefile']))
@curl_setopt($ch, CURLOPT_COOKIEFILE, $opts['cookiefile']);
- if(x($opts,'cookie'))
+ if(!empty($opts['cookie']))
@curl_setopt($ch, CURLOPT_COOKIE, $opts['cookie']);
@curl_setopt($ch, CURLOPT_SSL_VERIFYPEER,
- ((x($opts,'novalidate') && intval($opts['novalidate'])) ? false : true));
+ ((!empty($opts['novalidate'])) ? false : true));
$prx = @Config::Get('system','proxy');
if(strlen($prx)) {
@@ -205,7 +205,7 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) {
$ret['header'] = $header;
$ret['request_target'] = $opts['request_target'];
- if(x($opts,'debug')) {
+ if(!empty($opts['debug'])) {
$ret['debug'] = $curl_info;
}
@@ -433,7 +433,6 @@ function as_return_and_die($obj, $channel = []) {
$headers['Content-Type'] = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' ;
$headers['Date'] = datetime_convert('UTC','UTC', 'now', 'D, d M Y H:i:s \\G\\M\\T');
$headers['Digest'] = HTTPSig::generate_digest_header($ret);
- $headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI'];
if ($channel) {
$h = HTTPSig::create_sig($headers, $channel['channel_prvkey'], channel_url($channel));
diff --git a/include/observer.php b/include/observer.php
new file mode 100644
index 000000000..4483e1d8b
--- /dev/null
+++ b/include/observer.php
@@ -0,0 +1,68 @@
+<?php
+/**
+ * Helper functions for getting info about the observer.
+ *
+ * SPDX-FileCopyrightText: 2025 The Hubzilla Community
+ * SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * The _observer_ in Hubzilla is the channel visiting the site in the current
+ * session. This could be a local channel, or a remote channel logged in via
+ * OpenWebAuth.
+ *
+ * If the observer is not set, or empty, this indicates an unauthenticated
+ * visitor, which may mean a visitor from another site that don't support, or
+ * has not enabled OpenWebAuth.
+ */
+
+/**
+ * Get the unique hash identifying the current observer.
+ *
+ * Observer can be a local or remote channel.
+ *
+ * @return string Unique hash of observer, otherwise empty string if no
+ * observer
+ */
+function get_observer_hash() {
+ $observer = App::get_observer();
+ if (is_array($observer)) {
+ return $observer['xchan_hash'];
+ }
+
+ return '';
+}
+
+/**
+ * Get the guid of the current observer.
+ *
+ * Observer can be a local or remote channel.
+ *
+ * @return string The GUID of the observer, otherwise empty string if no
+ * observer
+ */
+function get_observer_guid() {
+ $observer = App::get_observer();
+ if (is_array($observer)) {
+ return $observer['xchan_guid'];
+ }
+
+ return '';
+}
+
+/**
+ * Get the name of the current observer.
+ *
+ * Observer can be a local or remote channel.
+ *
+ * @return string The name of the observer, otherwise empty string if no
+ * observer
+ */
+function get_observer_name() {
+ $observer = App::get_observer();
+ if (is_array($observer)) {
+ return $observer['xchan_name'];
+ }
+
+ return '';
+}
diff --git a/include/photo/photo_driver.php b/include/photo/photo_driver.php
index 66a5d19f9..88b9d1d62 100644
--- a/include/photo/photo_driver.php
+++ b/include/photo/photo_driver.php
@@ -65,114 +65,95 @@ function photo_factory($data, $type = null) {
*
* @param string $filename
* Image filename
- * @param string $data (optional)
+ * @param array $data (optional)
* Data array fetched from cURL with z_fetch_url
* @return null|string Guessed mimetype
*/
-function guess_image_type($filename, $data = '') {
-
- if($data)
- $headers = (is_array($data) ? $data['header'] : $data);
-
- // logger('Photo: guess_image_type: '.$filename . ($headers?' from curl headers':''), LOGGER_DEBUG);
-
- $type = null;
- $m = null;
- $headers = '';
-
+function guess_image_type($filename, $data = []) {
$ph = photo_factory('');
$types = $ph->supportedTypes();
- if($headers) {
- $hdrs = [];
- $h = explode("\n", $headers);
- foreach ($h as $l) {
- if (strpos($l, ':') === false) {
- continue;
- }
+ logger('filename: ' . print_r($filename, true), LOGGER_DEBUG);
- list($k, $v) = array_map('trim', explode(':', trim($l), 2));
- $hdrs[strtolower($k)] = $v;
+ // Try Fileinfo from raw data
+ if (class_exists('finfo') && !empty($data['body'])) {
+ $finfo = new finfo(FILEINFO_MIME_TYPE);
+ $mime = $finfo->buffer($data['body']);
+ if ($mime && array_key_exists($mime, $types)) {
+ logger('finfo mime type: ' . print_r($mime, true), LOGGER_DEBUG);
+ return $mime;
}
- logger('Curl headers: ' .var_export($hdrs, true), LOGGER_DEBUG);
- if(array_key_exists('content-type', $hdrs) && array_key_exists($hdrs['content-type'], $types))
- $type = $hdrs['content-type'];
}
- if(is_null($type)){
- $ignore_imagick = Config::Get('system', 'ignore_imagick');
- // Guessing from extension? Isn't that... dangerous?
- if(class_exists('Imagick') && ! $ignore_imagick) {
- $v = Imagick::getVersion();
- preg_match('/ImageMagick ([0-9]+\.[0-9]+\.[0-9]+)/', $v['versionString'], $m);
- if(version_compare($m[1], '6.6.7') >= 0) {
- /**
- * Well, this not much better,
- * but at least it comes from the data inside the image,
- * we won't be tricked by a manipulated extension
- */
- $body = false;
- if (strpos($filename, 'http') === false && file_exists($filename) && is_readable($filename))
- $body == file_get_contents($filename);
- elseif (is_array($data) && array_key_exists('body', $data))
- $body = $data['body'];
- if ($body) {
- $image = new Imagick();
-
- try{
- $image->readImageBlob($body);
- } catch (\Exception $e) {
- logger('Imagick readImageBlob() exception:' . print_r($e, true));
- return $type;
- }
-
- $r = $image->identifyImage();
- if ($r && is_array($r) && array_key_exists($r['mimetype'], $types))
- $type = $r['mimetype'];
- }
- }
- else {
- // earlier imagick versions have issues with scaling png's
- // don't log this because it will just fill the logfile.
- // leave this note here so those who are looking for why
- // we aren't using imagick can find it
+ // Try exif_imagetype + image_type_to_mime_type if file exists locally
+ if (function_exists('exif_imagetype') && is_file($filename) && is_readable($filename)) {
+ $image_type = @exif_imagetype($filename);
+ if ($image_type !== false) {
+ $mime = image_type_to_mime_type($image_type);
+ if ($mime && array_key_exists($mime, $types)) {
+ logger('exif_imagetype mime type: ' . print_r($mime, true), LOGGER_DEBUG);
+ return $mime;
}
}
+ }
- if(is_null($type)) {
- $ext = pathinfo($filename, PATHINFO_EXTENSION);
- foreach($types as $m => $e) {
- if($ext === $e) {
- $type = $m;
+ // Try getimagesize for URLs
+ if (filter_var($filename, FILTER_VALIDATE_URL)) {
+ $size = @getimagesize($filename);
+ if (isset($size['mime']) && array_key_exists($size['mime'], $types)) {
+ logger('getimagesize mime type: ' . print_r($size['mime'], true), LOGGER_DEBUG);
+ return $size['mime'];
+ }
+ }
+
+ // Try Imagick if available and not disabled
+ $ignore_imagick = Config::Get('system', 'ignore_imagick');
+ if (class_exists('Imagick') && !$ignore_imagick) {
+ $v = Imagick::getVersion();
+ if (preg_match('/ImageMagick ([0-9]+\.[0-9]+\.[0-9]+)/', $v['versionString'], $m) && version_compare($m[1], '6.6.7') >= 0) {
+ $body = false;
+ if (is_file($filename) && is_readable($filename)) {
+ $body = file_get_contents($filename);
+ } elseif (!empty($data['body'])) {
+ $body = $data['body'];
+ }
+ if ($body) {
+ $image = new Imagick();
+ try {
+ $image->readImageBlob($body);
+ $r = $image->identifyImage();
+ if (isset($r['mimetype']) && array_key_exists($r['mimetype'], $types)) {
+ logger('imagick mime type: ' . print_r($r['mimetype'], true), LOGGER_DEBUG);
+ return $r['mimetype'];
+ }
+ } catch (\Exception $e) {
+ logger('Imagick readImageBlob() exception:' . print_r($e, true));
}
}
}
+ }
- if(is_null($type) && strpos($filename, 'http') === 0) {
- $size = getimagesize($filename);
- if ($size && array_key_exists($size['mime'], $types))
- $type = $size['mime'];
+ // Try Content-Type header
+ if (!empty($data['header'])) {
+ $hdrs = [];
+ foreach (explode("\n", $data['header']) as $l) {
+ if (strpos($l, ':') !== false) {
+ list($k, $v) = array_map('trim', explode(':', trim($l), 2));
+ $hdrs[strtolower($k)] = $v;
+ }
}
-
- if(is_null($type)) {
- if(strpos(strtolower($filename),'jpg') !== false)
- $type = 'image/jpeg';
- elseif(strpos(strtolower($filename),'jpeg') !== false)
- $type = 'image/jpeg';
- elseif(strpos(strtolower($filename),'gif') !== false)
- $type = 'image/gif';
- elseif(strpos(strtolower($filename),'png') !== false)
- $type = 'image/png';
- elseif(strpos(strtolower($filename),'webp') !== false)
- $type = 'image/webp';
+ if (isset($hdrs['content-type']) && array_key_exists($hdrs['content-type'], $types)) {
+ logger('headers mime type: ' . print_r($hdrs['content-type'], true), LOGGER_DEBUG);
+ return $hdrs['content-type'];
}
-
}
- logger('Photo: guess_image_type: filename = ' . $filename . ' type = ' . $type, LOGGER_DEBUG);
- return $type;
+ logger('failed to guess image type', LOGGER_DEBUG);
+
+ return null;
}
+
/**
* @brief Delete thing photo from database.
*
diff --git a/include/security.php b/include/security.php
index 4b072cf92..32ca4f268 100644
--- a/include/security.php
+++ b/include/security.php
@@ -22,7 +22,7 @@ function authenticate_success($user_record, $channel = null, $login_initial = fa
$lastlog_updated = false;
$uid_to_load = null;
- if (x($user_record, 'account_id')) {
+ if (!empty($user_record['account_id'])) {
App::$account = $user_record;
$_SESSION['account_id'] = $user_record['account_id'];
$_SESSION['authenticated'] = 1;
@@ -31,7 +31,7 @@ function authenticate_success($user_record, $channel = null, $login_initial = fa
$uid_to_load = $channel['channel_id'];
if (!$uid_to_load) {
- $uid_to_load = (((x($_SESSION, 'uid')) && (intval($_SESSION['uid'])))
+ $uid_to_load = ((!empty($_SESSION['uid']))
? intval($_SESSION['uid'])
: intval(App::$account['account_default_channel'])
);
@@ -60,12 +60,12 @@ function authenticate_success($user_record, $channel = null, $login_initial = fa
// might want to log success here
}
- if ($return || x($_SESSION, 'workflow')) {
+ if ($return || isset($_SESSION['workflow'])) {
unset($_SESSION['workflow']);
return;
}
- if ((App::$module !== 'home') && x($_SESSION, 'login_return_url') && strlen($_SESSION['login_return_url'])) {
+ if (App::$module !== 'home' && !empty($_SESSION['login_return_url'])) {
$return_url = $_SESSION['login_return_url'];
// don't let members get redirected to a raw ajax page update - this can happen
@@ -432,7 +432,7 @@ function item_permissions_sql($owner_id, $remote_observer = null) {
* default permissions - anonymous user
*/
- $sql = " AND item_private = 0 ";
+ $sql = " AND item.item_private = 0 ";
/**
* Profile owner - everything is visible
@@ -492,10 +492,10 @@ function item_permissions_sql($owner_id, $remote_observer = null) {
$regexop = db_getfunc('REGEXP');
$sql = sprintf(
- " AND ( author_xchan = '%s' OR owner_xchan = '%s' OR
- (( NOT (deny_cid $regexop '%s' OR deny_gid $regexop '%s')
- AND ( allow_cid $regexop '%s' OR allow_gid $regexop '%s' OR ( allow_cid = '' AND allow_gid = '' AND item_private = 0 ))
- )) OR ( item_private = 1 $scope ))
+ " AND ( item.author_xchan = '%s' OR item.owner_xchan = '%s' OR
+ (( NOT (item.deny_cid $regexop '%s' OR item.deny_gid $regexop '%s')
+ AND ( item.allow_cid $regexop '%s' OR item.allow_gid $regexop '%s' OR ( item.allow_cid = '' AND item.allow_gid = '' AND item.item_private = 0 ))
+ )) OR ( item.item_private = 1 $scope ))
",
dbesc($observer),
dbesc($observer),
@@ -518,11 +518,11 @@ function item_permissions_sql($owner_id, $remote_observer = null) {
function scopes_sql($uid, $observer) {
- $str = " and ( public_policy = 'authenticated' ";
+ $str = " and ( item.public_policy = 'authenticated' ";
if (!is_foreigner($observer))
- $str .= " or public_policy = 'network: red' ";
+ $str .= " or item.public_policy = 'network: red' ";
if (local_channel())
- $str .= " or public_policy = 'site: " . App::get_hostname() . "' ";
+ $str .= " or item.public_policy = 'site: " . App::get_hostname() . "' ";
$ab = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1",
dbesc($observer),
@@ -531,8 +531,8 @@ function scopes_sql($uid, $observer) {
if (!$ab)
return $str . " ) ";
if ($ab[0]['abook_pending'])
- $str .= " or public_policy = 'any connections' ";
- $str .= " or public_policy = 'contacts' ) ";
+ $str .= " or item.public_policy = 'any connections' ";
+ $str .= " or item.public_policy = 'contacts' ) ";
return $str;
}
@@ -607,14 +607,14 @@ function public_permissions_sql($observer_hash) {
function get_form_security_token($typename = '') {
$timestamp = time();
- $guid = App::$observer['xchan_guid'] ?? '';
+ $guid = get_observer_guid();
$sec_hash = hash('whirlpool', $guid . ((local_channel()) ? App::$channel['channel_prvkey'] : '') . session_id() . $timestamp . $typename);
return $timestamp . '.' . $sec_hash;
}
function check_form_security_token($typename = '', $formname = 'form_security_token') {
- if (!x($_REQUEST, $formname)) return false;
+ if (empty($_REQUEST[$formname])) return false;
$hash = $_REQUEST[$formname];
$max_livetime = 10800; // 3 hours
@@ -623,7 +623,7 @@ function check_form_security_token($typename = '', $formname = 'form_security_to
if (time() > (IntVal($x[0]) + $max_livetime))
return false;
- $sec_hash = hash('whirlpool', App::$observer['xchan_guid'] . ((local_channel()) ? App::$channel['channel_prvkey'] : '') . session_id() . $x[0] . $typename);
+ $sec_hash = hash('whirlpool', get_observer_guid() . ((local_channel()) ? App::$channel['channel_prvkey'] : '') . session_id() . $x[0] . $typename);
return ($sec_hash == $x[1]);
}
@@ -635,7 +635,7 @@ function check_form_security_std_err_msg() {
function check_form_security_token_redirectOnErr($err_redirect, $typename = '', $formname = 'form_security_token') {
if (!check_form_security_token($typename, $formname)) {
- logger('check_form_security_token failed: user ' . App::$observer['xchan_name'] . ' - form element ' . $typename);
+ logger('check_form_security_token failed: user ' . get_observer_name() . ' - form element ' . $typename);
logger('check_form_security_token failed: _REQUEST data: ' . print_r($_REQUEST, true), LOGGER_DATA);
notice(check_form_security_std_err_msg());
goaway(z_root() . $err_redirect);
@@ -644,7 +644,7 @@ function check_form_security_token_redirectOnErr($err_redirect, $typename = '',
function check_form_security_token_ForbiddenOnErr($typename = '', $formname = 'form_security_token') {
if (!check_form_security_token($typename, $formname)) {
- logger('check_form_security_token failed: user ' . App::$observer['xchan_name'] . ' - form element ' . $typename);
+ logger('check_form_security_token failed: user ' . get_observer_name() . ' - form element ' . $typename);
logger('check_form_security_token failed: _REQUEST data: ' . print_r($_REQUEST, true), LOGGER_DATA);
header('HTTP/1.1 403 Forbidden');
killme();
diff --git a/include/socgraph.php b/include/socgraph.php
index 336c1c0c3..2da0040b0 100644
--- a/include/socgraph.php
+++ b/include/socgraph.php
@@ -364,8 +364,8 @@ function poco() {
elseif(argv(3) === '@self')
$justme = true;
}
- if(argc() > 4 && intval(argv(4)) && $justme == false)
- $cid = intval(argv(4));
+
+ $cid = ((argc() > 4 && intval(argv(4)) && $justme == false) ? intval(argv(4)) : null);
if(! $system_mode) {
@@ -413,11 +413,8 @@ function poco() {
else
$totalResults = 0;
- $startIndex = intval($_GET['startIndex']);
- if(! $startIndex)
- $startIndex = 0;
-
- $itemsPerPage = ((x($_GET,'count') && intval($_GET['count'])) ? intval($_GET['count']) : $totalResults);
+ $startIndex = $_GET['startIndex'] ?? 0;
+ $itemsPerPage = $_GET['count'] ?? $totalResults;
if($system_mode) {
$r = q("SELECT abook.*, xchan.* from abook left join xchan on abook_xchan = xchan_hash where abook_self = 1
diff --git a/include/text.php b/include/text.php
index 52a6440cb..9ac6efdc2 100644
--- a/include/text.php
+++ b/include/text.php
@@ -1425,11 +1425,10 @@ function smilies($s, $sample = false, $terms = []) {
|| (local_channel() && intval(get_pconfig(local_channel(), 'system', 'no_smilies'))))
return $s;
- $s = preg_replace_callback('{<(pre|code)>.*?</\1>}ism', 'smile_shield', $s);
+ $s = preg_replace_callback('/<(pre|code)\b[^>]*>.*?<\/(pre|code)>/ism', 'smile_shield', $s);
$s = preg_replace_callback('/<[a-z]+ .*?>/ism', 'smile_shield', $s);
if (preg_match_all('/(\:(\w|\+|\-)+\:)(?=|[\!\.\?]|$)/', $s, $match)) {
-
// emoji shortcodes
$emojis = get_emojis();
foreach ($match[0] as $mtch) {
@@ -1447,7 +1446,7 @@ function smilies($s, $sample = false, $terms = []) {
}
}
- if (!$emoji) {
+ if (!$emoji || empty($emoji['filepath'])) {
continue;
}
@@ -1611,7 +1610,7 @@ function theme_attachments(&$item) {
$url = z_root() . '/magic?owa=1&bdest=' . bin2hex($r['href']);
}
- if (isset($label) && isset($url) && isset($icon) && isset($title)) {
+ if (isset($label, $url, $icon, $title)) {
array_unshift($attaches, ['label' => $label, 'url' => $url, 'icon' => $icon, 'title' => $title]);
}
}
@@ -2188,7 +2187,7 @@ function get_plink($item,$conversation_mode = true) {
if(array_key_exists('author',$item) && $item['author']['xchan_network'] !== 'zot6')
$zidify = false;
- if(x($item,$key)) {
+ if(!empty($item[$key])) {
return array(
'href' => (($zidify) ? zid($item[$key]) : $item[$key]),
'title' => t('Link to Source'),
@@ -2381,17 +2380,19 @@ function item_post_type($item) {
$post_type = t('event');
break;
default:
- $post_type = t('post');
+ $post_type = t('conversation');
if($item['mid'] != $item['parent_mid'])
- $post_type = t('comment');
+ $post_type = t('message');
break;
}
- if(strlen($item['verb']) && (! activity_match($item['verb'],ACTIVITY_POST)))
+ if(strlen($item['verb']) && (!activity_match($item['verb'], ['Create', ACTIVITY_POST]))) {
$post_type = t('activity');
+ }
- if($item['obj_type'] === 'Question')
+ if($item['obj_type'] === 'Question') {
$post_type = t('poll');
+ }
return $post_type;
}
@@ -2652,7 +2653,7 @@ function xchan_query(&$items, $abook = true, $effective_uid = 0) {
$arr[] = "'" . dbesc($item['owner_xchan']) . "'";
if($item['author_xchan'] && (! in_array("'" . dbesc($item['author_xchan']) . "'",$arr)))
$arr[] = "'" . dbesc($item['author_xchan']) . "'";
- if($item['source_xchan'] && (! in_array("'" . dbesc($item['source_xchan']) . "'",$arr)))
+ if(!empty($item['source_xchan']) && (! in_array("'" . dbesc($item['source_xchan']) . "'",$arr)))
$arr[] = "'" . dbesc($item['source_xchan']) . "'";
}
}
@@ -2677,7 +2678,9 @@ function xchan_query(&$items, $abook = true, $effective_uid = 0) {
for($x = 0; $x < count($items); $x ++) {
$items[$x]['owner'] = find_xchan_in_array($items[$x]['owner_xchan'],$chans);
$items[$x]['author'] = find_xchan_in_array($items[$x]['author_xchan'],$chans);
- $items[$x]['source'] = find_xchan_in_array($items[$x]['source_xchan'],$chans);
+ if (!empty($items[$x]['source_xchan'])) {
+ $items[$x]['source'] = find_xchan_in_array($items[$x]['source_xchan'],$chans);
+ }
}
}
}
@@ -3232,6 +3235,7 @@ function getIconFromType($type) {
'text/markdown' => 'bi-filetype-md',
'text/bbcode' => 'bi-file-earmark-text',
'text/html' => 'bi-filetype-html',
+ 'text/uri-list' => 'bi-box-arrow-up-right',
'application/msword' => 'bi-file-earmark-word',
'application/pdf' => 'bi-file-earmark-pdf',
'application/vnd.oasis.opendocument.text' => 'bifile--earmark-text',
diff --git a/install/INSTALL.txt b/install/INSTALL.txt
index 0d1f7d0ee..464f54a51 100644
--- a/install/INSTALL.txt
+++ b/install/INSTALL.txt
@@ -192,11 +192,11 @@ website.
6. *If* the automated installation fails for any reason, check the following:
- ".htconfig.php" exists
- If not, edit htconfig.php and change system settings. Rename
- to .htconfig.php
- - Database is populated.
+ If not, edit htconfig.php and change system settings. Rename to .htconfig.php
+ - Database is populated.
If not, import the contents of "install/schema_xxxxx.sql" with phpmyadmin
or mysql command line (replace 'xxxxx' with your DB type).
+ - Manualy create the sys channel by running `util/init_sys_channel`
7. At this point visit your website again, and register your personal account.
Registration errors should all be recoverable automatically.
diff --git a/install/schema_mysql.sql b/install/schema_mysql.sql
index 3cf8039e4..3d8db255f 100644
--- a/install/schema_mysql.sql
+++ b/install/schema_mysql.sql
@@ -1078,7 +1078,7 @@ CREATE TABLE IF NOT EXISTS `register` (
CREATE TABLE IF NOT EXISTS `session` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`sid` char(191) NOT NULL DEFAULT '',
- `sess_data` text NOT NULL,
+ `sess_data` mediumtext NOT NULL,
`expire` bigint(20) unsigned NOT NULL DEFAULT 0 ,
PRIMARY KEY (`id`),
KEY `sid` (`sid`),
diff --git a/library/ASNValue.class.php b/library/ASNValue.class.php
deleted file mode 100644
index 7c17d10b4..000000000
--- a/library/ASNValue.class.php
+++ /dev/null
@@ -1,169 +0,0 @@
-<?php
-//-----------------------------------------------------------------------------
-// ASNValue class by A.Oliinyk
-// contact@pumka.net
-//-----------------------------------------------------------------------------
-class ASNValue
-{
- const TAG_INTEGER = 0x02;
- const TAG_BITSTRING = 0x03;
- const TAG_SEQUENCE = 0x30;
-
- public $Tag;
- public $Value;
-
- function __construct($Tag=0x00, $Value='')
- {
- $this->Tag = $Tag;
- $this->Value = $Value;
- }
-
- function Encode()
- {
- //Write type
- $result = chr($this->Tag);
-
- //Write size
- $size = strlen($this->Value);
- if ($size < 127) {
- //Write size as is
- $result .= chr($size);
- }
- else {
- //Prepare length sequence
- $sizeBuf = self::IntToBin($size);
-
- //Write length sequence
- $firstByte = 0x80 + strlen($sizeBuf);
- $result .= chr($firstByte) . $sizeBuf;
- }
-
- //Write value
- $result .= $this->Value;
-
- return $result;
- }
-
- function Decode(&$Buffer)
- {
- //Read type
- $this->Tag = self::ReadByte($Buffer);
-
- //Read first byte
- $firstByte = self::ReadByte($Buffer);
-
- if ($firstByte < 127) {
- $size = $firstByte;
- }
- else if ($firstByte > 127) {
- $sizeLen = $firstByte - 0x80;
- //Read length sequence
- $size = self::BinToInt(self::ReadBytes($Buffer, $sizeLen));
- }
- else {
- throw new Exception("Invalid ASN length value");
- }
-
- $this->Value = self::ReadBytes($Buffer, $size);
- }
-
- protected static function ReadBytes(&$Buffer, $Length)
- {
- $result = substr($Buffer, 0, $Length);
- $Buffer = substr($Buffer, $Length);
-
- return $result;
- }
-
- protected static function ReadByte(&$Buffer)
- {
- return ord(self::ReadBytes($Buffer, 1));
- }
-
- protected static function BinToInt($Bin)
- {
- $len = strlen($Bin);
- $result = 0;
- for ($i=0; $i<$len; $i++) {
- $curByte = self::ReadByte($Bin);
- $result += $curByte << (($len-$i-1)*8);
- }
-
- return $result;
- }
-
- protected static function IntToBin($Int)
- {
- $result = '';
- do {
- $curByte = $Int % 256;
- $result .= chr($curByte);
-
- $Int = ($Int - $curByte) / 256;
- } while ($Int > 0);
-
- $result = strrev($result);
-
- return $result;
- }
-
- function SetIntBuffer($Value)
- {
- if (strlen($Value) > 1) {
- $firstByte = ord($Value[0]);
- if ($firstByte & 0x80) { //first bit set
- $Value = chr(0x00) . $Value;
- }
- }
-
- $this->Value = $Value;
- }
-
- function GetIntBuffer()
- {
- $result = $this->Value;
- if (ord($result[0]) == 0x00) {
- $result = substr($result, 1);
- }
-
- return $result;
- }
-
- function SetInt($Value)
- {
- $Value = self::IntToBin($Value);
-
- $this->SetIntBuffer($Value);
- }
-
- function GetInt()
- {
- $result = $this->GetIntBuffer();
- $result = self::BinToInt($result);
-
- return $result;
- }
-
- function SetSequence($Values)
- {
- $result = '';
- foreach ($Values as $item) {
- $result .= $item->Encode();
- }
-
- $this->Value = $result;
- }
-
- function GetSequence()
- {
- $result = array();
- $seq = $this->Value;
- while (strlen($seq)) {
- $val = new ASNValue();
- $val->Decode($seq);
- $result[] = $val;
- }
-
- return $result;
- }
-}
diff --git a/library/asn1.php b/library/asn1.php
deleted file mode 100644
index 6ab8c6210..000000000
--- a/library/asn1.php
+++ /dev/null
@@ -1,291 +0,0 @@
-<?php
-
-// ASN.1 parsing library
-// Attribution: http://www.krisbailey.com
-// license: unknown
-// modified: Mike Macgrivin mike@macgirvin.com 6-oct-2010 to support Salmon auto-discovery
-// from openssl public keys
-
-
-class ASN_BASE {
- public $asnData = null;
- private $cursor = 0;
- private $parent = null;
-
- public static $ASN_MARKERS = array(
- 'ASN_UNIVERSAL' => 0x00,
- 'ASN_APPLICATION' => 0x40,
- 'ASN_CONTEXT' => 0x80,
- 'ASN_PRIVATE' => 0xC0,
-
- 'ASN_PRIMITIVE' => 0x00,
- 'ASN_CONSTRUCTOR' => 0x20,
-
- 'ASN_LONG_LEN' => 0x80,
- 'ASN_EXTENSION_ID' => 0x1F,
- 'ASN_BIT' => 0x80,
- );
-
- public static $ASN_TYPES = array(
- 1 => 'ASN_BOOLEAN',
- 2 => 'ASN_INTEGER',
- 3 => 'ASN_BIT_STR',
- 4 => 'ASN_OCTET_STR',
- 5 => 'ASN_NULL',
- 6 => 'ASN_OBJECT_ID',
- 9 => 'ASN_REAL',
- 10 => 'ASN_ENUMERATED',
- 13 => 'ASN_RELATIVE_OID',
- 48 => 'ASN_SEQUENCE',
- 49 => 'ASN_SET',
- 19 => 'ASN_PRINT_STR',
- 22 => 'ASN_IA5_STR',
- 23 => 'ASN_UTC_TIME',
- 24 => 'ASN_GENERAL_TIME',
- );
-
- function __construct($v = false)
- {
- if (false !== $v) {
- $this->asnData = $v;
- if (is_array($this->asnData)) {
- foreach ($this->asnData as $key => $value) {
- if (is_object($value)) {
- $this->asnData[$key]->setParent($this);
- }
- }
- } else {
- if (is_object($this->asnData)) {
- $this->asnData->setParent($this);
- }
- }
- }
- }
-
- public function setParent($parent)
- {
- if (false !== $parent) {
- $this->parent = $parent;
- }
- }
-
- /**
- * This function will take the markers and types arrays and
- * dynamically generate classes that extend this class for each one,
- * and also define constants for them.
- */
- public static function generateSubclasses()
- {
- define('ASN_BASE', 0);
- foreach (self::$ASN_MARKERS as $name => $bit)
- self::makeSubclass($name, $bit);
- foreach (self::$ASN_TYPES as $bit => $name)
- self::makeSubclass($name, $bit);
- }
-
- /**
- * Helper function for generateSubclasses()
- */
- public static function makeSubclass($name, $bit)
- {
- define($name, $bit);
- eval("class ".$name." extends ASN_BASE {}");
- }
-
- /**
- * This function reset's the internal cursor used for value iteration.
- */
- public function reset()
- {
- $this->cursor = 0;
- }
-
- /**
- * This function catches calls to get the value for the type, typeName, value, values, and data
- * from the object. For type calls we just return the class name or the value of the constant that
- * is named the same as the class.
- */
- public function __get($name)
- {
- if ('type' == $name) {
- // int flag of the data type
- return constant(get_class($this));
- } elseif ('typeName' == $name) {
- // name of the data type
- return get_class($this);
- } elseif ('value' == $name) {
- // will always return one value and can be iterated over with:
- // while ($v = $obj->value) { ...
- // because $this->asnData["invalid key"] will return false
- return is_array($this->asnData) ? $this->asnData[$this->cursor++] : $this->asnData;
- } elseif ('values' == $name) {
- // will always return an array
- return is_array($this->asnData) ? $this->asnData : array($this->asnData);
- } elseif ('data' == $name) {
- // will always return the raw data
- return $this->asnData;
- }
- }
-
- /**
- * Parse an ASN.1 binary string.
- *
- * This function takes a binary ASN.1 string and parses it into it's respective
- * pieces and returns it. It can optionally stop at any depth.
- *
- * @param string $string The binary ASN.1 String
- * @param int $level The current parsing depth level
- * @param int $maxLevel The max parsing depth level
- * @return ASN_BASE The array representation of the ASN.1 data contained in $string
- */
- public static function parseASNString($string=false, $level=1, $maxLevels=false){
- if (!class_exists('ASN_UNIVERSAL'))
- self::generateSubclasses();
- if ($level>$maxLevels && $maxLevels)
- return array(new ASN_BASE($string));
- $parsed = array();
- $endLength = strlen($string);
- $bigLength = $length = $type = $dtype = $p = 0;
- while ($p<$endLength){
- $type = ord($string[$p++]);
- $dtype = ($type & 192) >> 6;
- if ($type==0){ // if we are type 0, just continue
- } else {
- $length = ord($string[$p++]);
- if (($length & ASN_LONG_LEN)==ASN_LONG_LEN){
- $tempLength = 0;
- for ($x=0; $x<($length & (ASN_LONG_LEN-1)); $x++){
- $tempLength = ord($string[$p++]) + ($tempLength * 256);
- }
- $length = $tempLength;
- }
- $data = substr($string, $p, intval($length));
- $parsed[] = self::parseASNData($type, $data, $level, $maxLevels);
- $p = $p + $length;
- }
- }
- return $parsed;
- }
-
- /**
- * Parse an ASN.1 field value.
- *
- * This function takes a binary ASN.1 value and parses it according to it's specified type
- *
- * @param int $type The type of data being provided
- * @param string $data The raw binary data string
- * @param int $level The current parsing depth
- * @param int $maxLevels The max parsing depth
- * @return mixed The data that was parsed from the raw binary data string
- */
- public static function parseASNData($type, $data, $level, $maxLevels){
- $type = $type%50; // strip out context
- switch ($type){
- default:
- return new ASN_BASE($data);
- case ASN_BOOLEAN:
- return new ASN_BOOLEAN((bool)$data);
- case ASN_INTEGER:
- return new ASN_INTEGER(strtr(base64_encode($data),'+/','-_'));
- case ASN_BIT_STR:
- return new ASN_BIT_STR(self::parseASNString($data, $level+1, $maxLevels));
- case ASN_OCTET_STR:
- return new ASN_OCTET_STR($data);
- case ASN_NULL:
- return new ASN_NULL(null);
- case ASN_REAL:
- return new ASN_REAL($data);
- case ASN_ENUMERATED:
- return new ASN_ENUMERATED(self::parseASNString($data, $level+1, $maxLevels));
- case ASN_RELATIVE_OID: // I don't really know how this works and don't have an example :-)
- // so, lets just return it ...
- return new ASN_RELATIVE_OID($data);
- case ASN_SEQUENCE:
- return new ASN_SEQUENCE(self::parseASNString($data, $level+1, $maxLevels));
- case ASN_SET:
- return new ASN_SET(self::parseASNString($data, $level+1, $maxLevels));
- case ASN_PRINT_STR:
- return new ASN_PRINT_STR($data);
- case ASN_IA5_STR:
- return new ASN_IA5_STR($data);
- case ASN_UTC_TIME:
- return new ASN_UTC_TIME($data);
- case ASN_GENERAL_TIME:
- return new ASN_GENERAL_TIME($data);
- case ASN_OBJECT_ID:
- return new ASN_OBJECT_ID(self::parseOID($data));
- }
- }
-
- /**
- * Parse an ASN.1 OID value.
- *
- * This takes the raw binary string that represents an OID value and parses it into its
- * dot notation form. example - 1.2.840.113549.1.1.5
- * look up OID's here: http://www.oid-info.com/
- * (the multi-byte OID section can be done in a more efficient way, I will fix it later)
- *
- * @param string $data The raw binary data string
- * @return string The OID contained in $data
- */
- public static function parseOID($string){
- $ret = floor(ord($string[0])/40).".";
- $ret .= (ord($string[0]) % 40);
- $build = array();
- $cs = 0;
-
- for ($i=1; $i<strlen($string); $i++){
- $v = ord($string[$i]);
- if ($v>127){
- $build[] = ord($string[$i])-ASN_BIT;
- } elseif ($build){
- // do the build here for multibyte values
- $build[] = ord($string[$i])-ASN_BIT;
- // you know, it seems there should be a better way to do this...
- $build = array_reverse($build);
- $num = 0;
- for ($x=0; $x<count($build); $x++){
- $mult = $x==0?1:pow(256, $x);
- if ($x+1==count($build)){
- $value = ((($build[$x] & (ASN_BIT-1)) >> $x)) * $mult;
- } else {
- $value = ((($build[$x] & (ASN_BIT-1)) >> $x) ^ ($build[$x+1] << (7 - $x) & 255)) * $mult;
- }
- $num += $value;
- }
- $ret .= ".".$num;
- $build = array(); // start over
- } else {
- $ret .= ".".$v;
- $build = array();
- }
- }
- return $ret;
- }
-
- public static function printASN($x, $indent=''){
- if (is_object($x)) {
- echo $indent.$x->typeName."\n";
- if (ASN_NULL == $x->type) return;
- if (is_array($x->data)) {
- while ($d = $x->value) {
- echo self::printASN($d, $indent.'. ');
- }
- $x->reset();
- } else {
- echo self::printASN($x->data, $indent.'. ');
- }
- } elseif (is_array($x)) {
- foreach ($x as $d) {
- echo self::printASN($d, $indent);
- }
- } else {
- if (preg_match('/[^[:print:]]/', $x)) // if we have non-printable characters that would
- $x = base64_encode($x); // mess up the console, then print the base64 of them...
- echo $indent.$x."\n";
- }
- }
-
-
-}
-
diff --git a/phpstan.neon.dist b/phpstan.neon.dist
index 4a083851d..58f3b803d 100644
--- a/phpstan.neon.dist
+++ b/phpstan.neon.dist
@@ -8,4 +8,4 @@ parameters:
scanDirectories:
- library
ignoreErrors:
- - '#Method [\w:()_\\]+ should return (object|string) but return statement is missing.#'
+ - '#Method [\w:\(\)_\\]+ should return (object|string) but return statement is missing.#'
diff --git a/tests/unit/Lib/ActivityTest.php b/tests/unit/Lib/ActivityTest.php
index 543bf60bc..456a7535e 100644
--- a/tests/unit/Lib/ActivityTest.php
+++ b/tests/unit/Lib/ActivityTest.php
@@ -46,7 +46,7 @@ class ActivityTest extends UnitTestCase {
*
* @dataProvider get_mid_and_uuid_provider
*/
- public function test_get_mid_and_uuid(string $payload, $mid, $uuid): void {
+ public function test_get_mid_and_uuid(string $payload, string $mid, string $uuid): void {
//
@@ -274,4 +274,50 @@ class ActivityTest extends UnitTestCase {
];
}
+ public function testBuildPacketWithEmptyChannel(): void {
+ $data = [ 'aKey' => 'aValue' ];
+ $packet = json_decode(Activity::build_packet($data, []), true);
+
+ $this->assertArrayHasKey('aKey', $packet);
+ $this->assertEquals('aValue', $packet['aKey']);
+ }
+
+ /**
+ * Test get protocols from an activitystreams actor object
+ *
+ * @dataProvider get_actor_protocols_provider
+ */
+ public function test_get_actor_protocols(array $actor, array $expected): void {
+ $this->assertEquals($expected, Activity::get_actor_protocols($actor));
+ }
+
+ /**
+ * Dataprovider for test_get_actor_protocols.
+ */
+ public static function get_actor_protocols_provider(): array {
+ return [
+ 'none' => [
+ ['tag' => [['type' => 'Note', 'name' => 'Website', 'content' => 'https://example.com']]],
+ []
+ ],
+ 'legacy' => [
+ ['tag' => [
+ ['type' => 'PropertyValue', 'name' => 'Protocol', 'value' => 'zot6'],
+ ['type' => 'PropertyValue', 'name' => 'Protocol', 'value' => 'activitypub'],
+ ['type' => 'PropertyValue', 'name' => 'Protocol', 'value' => 'diaspora']
+ ]],
+ ['zot6', 'activitypub', 'diaspora']
+ ],
+ 'fep-fb2a' => [
+ ['tag' => [['type' => 'Note', 'name' => 'Protocols', 'content' => 'zot6,activitypub,diaspora']]],
+ ['zot6', 'activitypub', 'diaspora']
+ ],
+ 'fep-fb2a with spaces' => [
+ ['tag' => [['type' => 'Note', 'name' => 'Protocols', 'content' => 'zot6, activitypub, diaspora']]],
+ ['zot6', 'activitypub', 'diaspora']
+ ],
+ ];
+
+
+ }
}
diff --git a/tests/unit/Lib/JcsEddsa2022Test.php b/tests/unit/Lib/JcsEddsa2022Test.php
index d18ad01ce..7cdc655f8 100644
--- a/tests/unit/Lib/JcsEddsa2022Test.php
+++ b/tests/unit/Lib/JcsEddsa2022Test.php
@@ -3,6 +3,7 @@
namespace Zotlabs\Tests\Unit\Lib;
use Zotlabs\Lib\JcsEddsa2022;
+use Zotlabs\Lib\JcsEddsa2022SignException;
use Zotlabs\Tests\Unit\UnitTestCase;
class JcsEddsa2022Test extends UnitTestCase {
@@ -171,4 +172,11 @@ class JcsEddsa2022Test extends UnitTestCase {
$this->assertTrue($verified, 'Verify encode and decode eddsa-jcs-2022');
}
+
+ public function testSignWithInvalidChannelShouldBeRejected(): void {
+ $this->expectException(JcsEddsa2022SignException::class);
+
+ $alg = new JcsEddsa2022();
+ $res = $alg->sign([], []);
+ }
}
diff --git a/tests/unit/Lib/KeyutilsTest.php b/tests/unit/Lib/KeyutilsTest.php
index d1b0b5ab8..1f3fa2295 100644
--- a/tests/unit/Lib/KeyutilsTest.php
+++ b/tests/unit/Lib/KeyutilsTest.php
@@ -23,8 +23,9 @@
namespace Zotlabs\Tests\Unit\Lib;
-use phpseclib\Crypt\RSA;
-use phpseclib\Math\BigInteger;
+use phpseclib3\Crypt\PublicKeyLoader;
+use phpseclib3\Math\BigInteger;
+
use Zotlabs\Tests\Unit\UnitTestCase;
use Zotlabs\Lib\Keyutils;
@@ -38,56 +39,54 @@ class KeyutilsTest extends UnitTestCase {
protected function getPubPKCS1() {
$key = '-----BEGIN RSA PUBLIC KEY-----
-MIIBCgKCAQEArXcEXQSkk25bwDxq5Ym85/OwernfOz0hgve46Jm1KXCF0+yeje8J
-BDbQTsMgkF+G8eP1er3oz3E0qlIFpYrza5o6kaaLETSroTyZR5QW5S21r/QJHE+4
-F08bw1zp9hrlvoOCE/g/W0mr3asO/x7LrQRKOETlZ/U6HGexTdYLyKlXJtB+VKjI
-XKAHxfVLRW2AvnFj+deowS1OhTN8ECpz88xG9wnh5agoq7Uol0WZNNm0p4oR6+cd
-zTPx/mBwcOoSqHLlO7ZACbx/VyD5G7mQKWfGP4b96D8FcUO74531my+aKIpLF4Io
-1JN4R4a4P8tZ8BkCnMvpuq9TF1s6vEthYQIDAQAB
+MIIBCgKCAQEAsSBBV5khOWvMAOIV2AhxQukBgWc1pfYqUM/9OIFfL+KaRDOFAT0y
+jMbtTCbHEjxZbasS3CNnHtAgJ+4BWjtFnZl7zY4pXb2RDe4IWNnK8BjqsWDVuPks
+sc+yRBTepR/50FG/xB4HfI4GRc/8EDmynyAdLDD/r6oPxmz1gMICqSGDX8yXYG4o
+DliNT3bWDH+uf1+6pWsN6IFQYmUoRLly1xsfc8AV4H1GmSFrbAvQpHA95GOlxnPY
+IwVth1m8O+D9SH2+0e8ourdXH3/9ccJnh6FVvFvkyeC1e2vY+J+uC0uwu+6IvHFV
+pSnS8lTmwG/BBh2dA5eqV3s+REdLdS/tsQIDAQAB
-----END RSA PUBLIC KEY-----';
return str_replace(["\r", "\n"], "\r\n", $key);
}
protected function getPubPKCS8() {
$key = '-----BEGIN PUBLIC KEY-----
-MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDUKfOIkFX/Zcv6bmaTIYO6OO2g
-XQOne+iPfXo6YDdrtvvQNZwW5P/fptrgBzmUBkpuc/sEEKpMV2bGhBLsWSlPBYHe
-2ewwLwyzbnuHvGhc1PzwMNQ7R60ubVDQT6sBVigYGZIDBgUPjAXeqmg5qgWWh04H
-8Zf/YxyoGEovWDMxGQIDAQAB
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsSBBV5khOWvMAOIV2Ahx
+QukBgWc1pfYqUM/9OIFfL+KaRDOFAT0yjMbtTCbHEjxZbasS3CNnHtAgJ+4BWjtF
+nZl7zY4pXb2RDe4IWNnK8BjqsWDVuPkssc+yRBTepR/50FG/xB4HfI4GRc/8EDmy
+nyAdLDD/r6oPxmz1gMICqSGDX8yXYG4oDliNT3bWDH+uf1+6pWsN6IFQYmUoRLly
+1xsfc8AV4H1GmSFrbAvQpHA95GOlxnPYIwVth1m8O+D9SH2+0e8ourdXH3/9ccJn
+h6FVvFvkyeC1e2vY+J+uC0uwu+6IvHFVpSnS8lTmwG/BBh2dA5eqV3s+REdLdS/t
+sQIDAQAB
-----END PUBLIC KEY-----';
return str_replace(["\r", "\n"], "\r\n", $key);
}
public function testMeToPem() {
- Keyutils::pemToMe($this->getPubPKCS8(), $m, $e);
+ [$m, $e] = Keyutils::pemToMe($this->getPubPKCS8());
$gen_key = Keyutils::meToPem($m, $e);
self::assertEquals($this->getPubPKCS8(), $gen_key);
}
public function testRsaToPem() {
- $rsa = new RSA();
- $rsa->setPublicKey($this->getPubPKCS8());
- $key = $rsa->getPublicKey(RSA::PUBLIC_FORMAT_PKCS1);
- $gen_key = Keyutils::rsaToPem($key);
+ $gen_key = Keyutils::rsaToPem($this->getPubPKCS1());
self::assertEquals($gen_key, $this->getPubPKCS8());
}
public function testPemToRsa() {
- $rsa = new RSA();
- $rsa->setPublicKey($this->getPubPKCS1());
- $key = $rsa->getPublicKey(RSA::PUBLIC_FORMAT_PKCS8);
- $gen_key = Keyutils::pemToRsa($key);
+ $gen_key = Keyutils::pemToRsa($this->getPubPKCS8());
self::assertEquals($gen_key, $this->getPubPKCS1());
}
public function testPemToMe() {
- Keyutils::pemToMe($this->getPubPKCS8(), $m, $e);
- $gen_key = new RSA();
- $gen_key->loadKey([
+ [$m, $e] = Keyutils::pemToMe($this->getPubPKCS8());
+
+ $parsedKey = PublicKeyLoader::load([
'e' => new BigInteger($e, 256),
'n' => new BigInteger($m, 256)
]);
- self::assertEquals($gen_key->getPublicKey(), $this->getPubPKCS8());
+
+ self::assertEquals($parsedKey->toString('PKCS8'), $this->getPubPKCS8());
}
}
diff --git a/tests/unit/Lib/MessageFilterTest.php b/tests/unit/Lib/MessageFilterTest.php
new file mode 100644
index 000000000..0a2aea0c6
--- /dev/null
+++ b/tests/unit/Lib/MessageFilterTest.php
@@ -0,0 +1,207 @@
+<?php
+namespace Zotlabs\Tests\Unit\Lib;
+
+use Zotlabs\Tests\Unit\UnitTestCase;
+use Zotlabs\Lib\MessageFilter;
+
+class MessageFilterTest extends UnitTestCase {
+
+ /**
+ * Test the `evaluate` function.
+ *
+ * @dataProvider evaluate_provider
+ */
+ public function test_evaluate(string $incl, string $excl, bool $result) : void {
+ // This is for simpler handling of the timestamps
+ date_default_timezone_set('UTC');
+
+ $item = [
+ 'title' => '',
+ 'body' => "A grasshopper spent the summer hopping about in the sun and singing to his heart's content. One day, an ant went hurrying by, looking very hot and weary.\r\n#story #grasshopper #ant",
+ 'term' => [
+ ['ttype' => TERM_HASHTAG, 'term' => 'story'],
+ ['ttype' => TERM_HASHTAG, 'term' => 'grasshopper'],
+ ['ttype' => TERM_HASHTAG, 'term' => 'ant']
+ ],
+ 'verb' => 'Create',
+ 'obj_type' => 'Note',
+ 'obj' => [
+ 'type' => 'Note',
+ 'attributedTo' => 'https://example.com/users/test',
+ 'summary' => null,
+ 'content' => "A grasshopper spent the summer hopping about in the sun and singing to his heart's content. One day, an ant went hurrying by, looking very hot and weary.\r\n#story #grasshopper #ant",
+ 'sensitive' => false
+ ],
+ 'item_private' => 0,
+ 'item_thread_top' => 1,
+ 'created' => '2025-04-18 20:50:00'
+ ];
+
+ $this->assertEquals($result, MessageFilter::evaluate($item, $incl, $excl));
+ }
+
+ public static function evaluate_provider() : array {
+ return [
+ 'body contains incl' => [
+ 'summer',
+ '',
+ true
+ ],
+ 'body contains excl' => [
+ '',
+ 'summer',
+ false
+ ],
+ 'body contains word hopper (starting with a space) in excl using regex' => [
+ '',
+ '/ hopper/',
+ true
+ ],
+ 'lang=en in incl' => [
+ 'lang=en',
+ '',
+ true
+ ],
+ 'lang=en in excl' => [
+ '',
+ 'lang=en',
+ false
+ ],
+ 'lang=de in incl' => [
+ 'lang=de',
+ '',
+ false
+ ],
+ 'lang=de in excl' => [
+ '',
+ 'lang=de',
+ true
+ ],
+ 'until=2025-04-18 20:49:00 in excl' => [
+ '',
+ 'until=2025-04-18 20:49:00',
+ true
+ ],
+ 'until=2025-04-18 20:51:00 in excl' => [
+ '',
+ 'until=2025-04-18 20:51:00',
+ false
+ ],
+ 'until=2025-04-18 20:49:00 in incl' => [
+ 'until=2025-04-18 20:49:00',
+ '',
+ false
+ ],
+ 'until=2025-04-18 20:51:00 in incl' => [
+ 'until=2025-04-18 20:51:00',
+ '',
+ true
+ ],
+ 'hashtag in incl' => [
+ '#grasshopper',
+ '',
+ true
+ ],
+ 'hashtag in excl' => [
+ '',
+ '#grasshopper',
+ false
+ ],
+ 'any hashtag in excl' => [
+ '',
+ '#*',
+ false
+ ],
+ 'item.body contains substring hopper in excl' => [
+ '',
+ '?body ~= hopper',
+ false
+ ],
+ 'item.verb == Announce in excl' => [
+ '',
+ '?verb == Announce',
+ true
+ ],
+ 'item.verb != Announce in incl' => [
+ '?verb != Announce',
+ '',
+ true
+ ],
+ 'combined body contains word and item.verb == Announce in excl' => [
+ '',
+ "summer\r\n?verb == Announce",
+ false
+ ],
+ 'item.item_thread_top == 1 in excl' => [
+ '',
+ "?item_thread_top == 1",
+ false
+ ],
+ 'combined item_private == 0 and item.item_thread_top == 1 in excl' => [
+ '',
+ "?item_private == 0\r\n?item_thread_top == 1",
+ false
+ ],
+ 'item.item_private < 1 in excl' => [
+ '',
+ "?item_private < 1",
+ false
+ ],
+ 'item.item_thread_top = 1 and item.item_private > 0 in excl' => [
+ '',
+ "?item_thread_top == 1 && ?item_private > 0 ",
+ true
+ ],
+ 'item.item_thread_top = 1 and item.item_private < 1 in excl' => [
+ '',
+ "?item_thread_top == 1 && ?item_private < 1 ",
+ false
+ ],
+ 'item.item_thread_top = 1 or item.item_private = 0 in excl' => [
+ '',
+ "?item_thread_top == 1 && ?item_private == 0",
+ false
+ ],
+ 'item.item_private < 1 and item.item_thread_top = 1 in excl' => [
+ '',
+ "?item_private < 1 && ?item_thread_top == 1",
+ false
+ ],
+ 'item.item_private < 1 and item.item_thread_top = 0 in excl' => [
+ '',
+ "?item_private < 1 && ?item_thread_top == 0",
+ true
+ ],
+ 'combined item.verb = Create, item.item_private < 1 and item.item_thread_top = 0 in excl' => [
+ '',
+ "?verb == Create\r\n?item_private < 1 && ?item_thread_top == 1",
+ false
+ ],
+ 'item.obj contains value Note in incl' => [
+ '?obj {} Note',
+ '',
+ true
+ ],
+ 'item.obj contains key type in incl' => [
+ '?obj {*} type',
+ '',
+ true
+ ],
+ 'obj.type = Note in incl' => [
+ '?+type == Note',
+ '',
+ true
+ ],
+ 'obj.sensitive = true in incl' => [
+ '?+sensitive',
+ '',
+ false
+ ],
+ 'obj.sensitive != false in incl' => [
+ '?+!sensitive',
+ '',
+ true
+ ],
+ ];
+ }
+}
diff --git a/tests/unit/Module/HelpTest.php b/tests/unit/Module/HelpTest.php
index 1feef26a8..e1aea1a06 100644
--- a/tests/unit/Module/HelpTest.php
+++ b/tests/unit/Module/HelpTest.php
@@ -76,13 +76,13 @@ class HelpTest extends \Zotlabs\Tests\Unit\Module\TestCase {
public function test_get_request_without_args_redirects_to_about_page(): void {
$this->stub_goaway();
$this->expectException(\Zotlabs\Tests\Unit\Module\RedirectException::class);
- $this->expectExceptionMessage('about/about');
+ $this->expectExceptionMessage('about');
$this->get('help');
}
public function test_getting_locale_with_no_topic_should_redirect_to_about_page(): void {
- $this->expectRedirectTo('help/about/about');
+ $this->expectRedirectTo('help/about');
$this->get('help/de');
}
diff --git a/tests/unit/Module/MagicTest.php b/tests/unit/Module/MagicTest.php
index 4a03d9d57..2c426bf76 100644
--- a/tests/unit/Module/MagicTest.php
+++ b/tests/unit/Module/MagicTest.php
@@ -46,9 +46,9 @@ class MagicTest extends TestCase {
App::set_baseurl($baseurl);
- App::$observer = [
+ App::set_observer([
'xchan_hash' => 'the hash',
- ];
+ ]);
// We pass a local URL, and have a valid observer, but as the
// delegate param is not passed, nothing will be done except
@@ -72,9 +72,9 @@ class MagicTest extends TestCase {
App::$timezone = 'UTC';
// Simulate a foreign (to this hub) observer,
- App::$observer = [
+ App::set_observer([
'xchan_hash' => 'foreign hash',
- ];
+ ]);
// Create the channel the foreign observer wants to access
$result = create_identity([
diff --git a/tests/unit/Module/TestCase.php b/tests/unit/Module/TestCase.php
index 1a4cf52fc..dd88a5a3b 100644
--- a/tests/unit/Module/TestCase.php
+++ b/tests/unit/Module/TestCase.php
@@ -43,6 +43,8 @@ class TestCase extends UnitTestCase {
$_SERVER['REQUEST_METHOD'] = $method;
$_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1';
$_SERVER['QUERY_STRING'] = "q={$uri}";
+ $_SERVER['REQUEST_URI'] = $uri;
+
// phpcs:disable Generic.PHP.DisallowRequestSuperglobal.Found
$_REQUEST = array_merge($_GET, $_POST);
// phpcs::enable
diff --git a/tests/unit/Widget/MessagesWidgetTest.php b/tests/unit/Widget/MessagesWidgetTest.php
new file mode 100644
index 000000000..ca025ff43
--- /dev/null
+++ b/tests/unit/Widget/MessagesWidgetTest.php
@@ -0,0 +1,83 @@
+<?php
+/*
+ * SPDX-FileCopyrightText: 2025 The Hubzilla Community
+ * SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+namespace Zotlabs\Tests\Unit\Widget;
+
+use App;
+use Zotlabs\Widget\Messages;
+use Zotlabs\Tests\Unit\Module\TestCase;
+
+class MessagesWidgetTest extends TestCase
+{
+ use \phpmock\phpunit\PHPMock;
+
+ /**
+ * List of file tags should be empty if there are no file tags.
+ */
+ public function testNoFileTags(): void
+ {
+ $local_channe_stub = $this->getFunctionMock('Zotlabs\Widget', 'local_channel')
+ ->expects($this->any())
+ ->willReturn(42);
+
+ $feature_enabled_stub = $this->getFunctionMock('Zotlabs\Widget', 'feature_enabled')
+ ->expects($this->any())
+ ->willReturn(true);
+
+ $this->renderWidget();
+ $this->assertOutputMatches('|<datalist\s+id="data_filetags">\s+</datalist>|');
+ }
+
+ /**
+ * The widget lists file tags that are defined for the channel.
+ */
+ public function testFileTagsAreListed(): void
+ {
+ $local_channe_stub = $this->getFunctionMock('Zotlabs\Widget', 'local_channel')
+ ->expects($this->any())
+ ->willReturn(42);
+
+ $feature_enabled_stub = $this->getFunctionMock('Zotlabs\Widget', 'feature_enabled')
+ ->expects($this->any())
+ ->willReturn(true);
+
+ /*
+ * Add a few tags.
+ */
+ store_item_tag(42, 1, TERM_OBJ_POST, TERM_FILE, 'test_file_tag', '');
+ store_item_tag(42, 1, TERM_OBJ_POST, TERM_FILE, 'test_file_tag2', '');
+
+ $this->renderWidget();
+ $this->assertOutputMatches('|<option\s+value="test_file_tag">|');
+ $this->assertOutputMatches('|<option\s+value="test_file_tag2">|');
+ }
+
+ /**
+ * Initializes the app and calls the widget code.
+ */
+ private function renderWidget(): void {
+ $_GET['q'] = 'hq';
+ $_SERVER['REQUEST_METHOD'] = 'GET';
+
+ App::init();
+
+ $widget = new Messages();
+ $this->output = $widget->widget([]);
+ }
+
+ /**
+ * Asserts that the output matches a given regex pattern.
+ *
+ * If the pattern does not match, the test will be marked as failed.
+ *
+ * @param string $pattern The regex that should be matched.
+ */
+ private function assertOutputMatches(string $pattern): void {
+ $this->assertMatchesRegularExpression($pattern, $this->output);
+ }
+}
diff --git a/tests/unit/includes/AccountTest.php b/tests/unit/includes/AccountTest.php
index 3978f9d04..66c761ef5 100644
--- a/tests/unit/includes/AccountTest.php
+++ b/tests/unit/includes/AccountTest.php
@@ -1,9 +1,28 @@
<?php
+
+use Zotlabs\Tests\Unit\UnitTestCase;
+
/**
* Tests for account handling helper functions.
*/
+class AccountTest extends UnitTestCase {
+
+ /**
+ * Test the `get_account_id()` function.
+ */
+ public function test_get_account_id() {
+ App::set_account(null);
+ unset($_SESSION['account_id']);
+
+ $this->assertEquals(false, get_account_id(), 'get_account_id() should return false if not authenticated');
+
+ App::set_account(['account_id' => 36]);
+ $this->assertEquals(36, get_account_id(), 'get_account_id() should return account from global App object');
+
+ $_SESSION['account_id'] = 42;
+ $this->assertEquals(42, get_account_id(), 'get_account_id() should return the account from the session');
+ }
-class AccountTest extends Zotlabs\Tests\Unit\UnitTestCase {
public function test_get_account_by_id_returns_existing_account() {
$account = get_account_by_id(42);
$this->assertNotFalse($account);
diff --git a/tests/unit/includes/BBCodeTest.php b/tests/unit/includes/BBCodeTest.php
index 136fc6e0e..982ef4eb9 100644
--- a/tests/unit/includes/BBCodeTest.php
+++ b/tests/unit/includes/BBCodeTest.php
@@ -23,6 +23,7 @@
namespace Zotlabs\Tests\Unit\includes;
+use App;
use Zotlabs\Tests\Unit\UnitTestCase;
class BBCodeTest extends UnitTestCase {
@@ -42,7 +43,7 @@ class BBCodeTest extends UnitTestCase {
*/
public function test_bbcode_observer(string $src, bool $logged_in, string $lang, string $expected): void {
if ($logged_in) {
- \App::$observer = [
+ App::set_observer([
'xchan_addr' => '',
'xchan_name' => '',
'xchan_connurl' => '',
@@ -50,9 +51,9 @@ class BBCodeTest extends UnitTestCase {
// port required in xchan url due to bug in get_rpost_path
'xchan_url' => 'https://example.com:666',
- ];
+ ]);
} else {
- \App::$observer = null;
+ App::set_observer(null);
}
\App::$language = $lang;
@@ -141,20 +142,36 @@ class BBCodeTest extends UnitTestCase {
],
'naked url is converted to link' => [
'example url: https://example.com',
- 'example url: <a href="https://example.com" target="_blank" rel="nofollow noopener">https://example.com</a>'
+ 'example url: <a href="https://example.com" target="_blank" rel="nofollow noopener">https://example.com</a>'
],
'naked url followed by newline' => [
"https://www.example.com\nhave a great day.",
- '<a href="https://www.example.com" target="_blank" rel="nofollow noopener">https://www.example.com</a><br />have a great day.',
+ '<a href="https://www.example.com" target="_blank" rel="nofollow noopener">https://www.example.com</a><br />have a great day.',
],
'inline naked url' => [
"This is a link https://example.com/some/path more info.",
- 'This is a link <a href="https://example.com/some/path" target="_blank" rel="nofollow noopener">https://example.com/some/path</a> more info.',
+ 'This is a link <a href="https://example.com/some/path" target="_blank" rel="nofollow noopener">https://example.com/some/path</a> more info.',
],
'naked url within code block is not converted to link' => [
"[code]\nhttp://example.com\n[/code]",
"<pre><code>http://example.com</code></pre>"
],
+ 'geo uri is converted to link' => [
+ 'example url: [url]geo:37.786971,-122.399677;u=35[/url]',
+ 'example url: <a href="geo:37.786971,-122.399677;u=35" target="_blank" rel="nofollow noopener">geo:37.786971,-122.399677;u=35</a>'
+ ],
+ 'geo uri with label is converted to link' => [
+ 'example url: [url=geo:37.786971,-122.399677;u=35(Wikimedia+Foundation)]Wikimedia Foundation[/url]',
+ 'example url: <a href="geo:37.786971,-122.399677;u=35(Wikimedia+Foundation)" target="_blank" rel="nofollow noopener">Wikimedia Foundation</a>'
+ ],
+ 'naked geo uri is converted to link' => [
+ 'example url: geo:37.786971,-122.399677;u=35',
+ 'example url: <a href="geo:37.786971,-122.399677;u=35" target="_blank" rel="nofollow noopener">geo:37.786971,-122.399677;u=35</a>'
+ ],
+ 'naked geo uri with label is converted to link' => [
+ 'example url: geo:37.78918,-122.40335(Wikimedia+Foundation)',
+ 'example url: <a href="geo:37.78918,-122.40335(Wikimedia+Foundation)" target="_blank" rel="nofollow noopener">📍Wikimedia Foundation</a>'
+ ],
];
}
@@ -205,7 +222,7 @@ class BBCodeTest extends UnitTestCase {
'[rpost=a title]This is the body[/rpost]',
true,
'en',
- '<a href="https://example.com:666/rpost?f=&title=a+title&body=This+is+the+body" target="_blank" rel="nofollow noopener">https://example.com:666/rpost?f=&title=a+title&body=This+is+the+body</a>',
+ '<a href="https://example.com:666/rpost?f=&title=a+title&body=This+is+the+body" target="_blank" rel="nofollow noopener">https://example.com:666/rpost?f=&title=a+title&body=This+is+the+body</a>',
],
'unauthenticated observer rpost' => [
'[rpost=a title]This is the body[/rpost]',
diff --git a/tests/unit/includes/MarkdownTest.php b/tests/unit/includes/MarkdownTest.php
index 55dbb4445..eec809174 100644
--- a/tests/unit/includes/MarkdownTest.php
+++ b/tests/unit/includes/MarkdownTest.php
@@ -114,6 +114,10 @@ class MarkdownTest extends UnitTestCase {
'This is a link https://example.com/some/path more info.',
'This is a link https://example.com/some/path more info.',
],
+ 'mention with underscores is untouched' => [
+ '@{_test_@somesite.example} @{test_2_@othersite.example}',
+ '@{_test_@somesite.example} @{test_2_@othersite.example}',
+ ],
];
}
diff --git a/tests/unit/includes/PhotodriverTest.php b/tests/unit/includes/PhotodriverTest.php
index 34dc058b7..db9883589 100644
--- a/tests/unit/includes/PhotodriverTest.php
+++ b/tests/unit/includes/PhotodriverTest.php
@@ -20,4 +20,62 @@ class PhotodriverTest extends UnitTestCase {
$photo = \photo_factory(file_get_contents('images/hz-16.png'), 'image/png');
$this->assertInstanceOf('Zotlabs\Photo\PhotoGd', $photo);
}
+
+ // Helper to create a temporary image file
+ private function createTempImage($type = 'jpeg'): string
+ {
+ $tmp = tempnam(sys_get_temp_dir(), 'img');
+ switch ($type) {
+ case 'png':
+ $im = imagecreatetruecolor(10, 10);
+ imagepng($im, $tmp);
+ imagedestroy($im);
+ break;
+ case 'jpeg':
+ default:
+ $im = imagecreatetruecolor(10, 10);
+ imagejpeg($im, $tmp);
+ imagedestroy($im);
+ break;
+ }
+ return $tmp;
+ }
+
+ public function testGuessImageTypeFromRawData()
+ {
+ $filename = 'irrelevant';
+ $data = [
+ 'body' => file_get_contents($this->createTempImage('jpeg'))
+ ];
+ $result = guess_image_type($filename, $data);
+ $this->assertEquals('image/jpeg', $result);
+ }
+
+ public function testGuessImageTypeFromLocalFile()
+ {
+ $file = $this->createTempImage('png');
+ $result = guess_image_type($file);
+ $this->assertEquals('image/png', $result);
+ unlink($file);
+ }
+
+ public function testGuessImageTypeFromHeaders()
+ {
+ $filename = 'irrelevant';
+ $data = [
+ 'header' => "Content-Type: image/jpeg\nOther: value"
+ ];
+ $result = guess_image_type($filename, $data);
+ $this->assertEquals('image/jpeg', $result);
+ }
+
+ public function testGuessImageTypeUnknownTypeReturnsNull()
+ {
+ $filename = 'not_an_image.txt';
+ $data = [
+ 'body' => 'not an image'
+ ];
+ $result = guess_image_type($filename, $data);
+ $this->assertNull($result);
+ }
}
diff --git a/util/Doxyfile b/util/Doxyfile
index 10c43e46d..faf7e13a8 100755
--- a/util/Doxyfile
+++ b/util/Doxyfile
@@ -4,12 +4,13 @@ PROJECT_NAME = "Hubzilla"
PROJECT_BRIEF = "A powerful, privacy oriented fediverse CMS."
PROJECT_LOGO = images/hz-64.png
IMAGE_PATH = images/
-EXCLUDE = .htconfig.php library/ doc/ store/ vendor/ .git/ util/zotsh/easywebdav/ util/generate-hooks-index/
+EXCLUDE = .htconfig.php library/ doc/ store/ vendor/ .git/ util/zotsh/easywebdav/ util/generate-hooks-index/ Zotlabs/Update/
EXCLUDE_PATTERNS = *smarty3* *strings.php *.out *test*
OUTPUT_DIRECTORY = doc
GENERATE_HTML = YES
HTML_OUTPUT = html/
HTML_FILE_EXTENSION = .html
+HTML_COLORSTYLE = TOGGLE
GENERATE_LATEX = NO
EXTRACT_ALL = YES
EXTRACT_PRIVATE = YES
@@ -17,7 +18,6 @@ GENERATE_TODOLIST = YES
USE_MDFILE_AS_MAINPAGE = README.md
REFERENCED_BY_RELATION = YES
GENERATE_TREEVIEW = YES
-HTML_FOOTER = util/Doxygen.footer
ALIASES += "license=@par License:\n"
ALIASES += "fixme=\xrefitem fixme \"Fixme\" \"Fixme List\""
ALIASES += "FIXME=\fixme"
diff --git a/util/Doxygen.footer b/util/Doxygen.footer
deleted file mode 100755
index fd40910d9..000000000
--- a/util/Doxygen.footer
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/util/hmessages.po b/util/hmessages.po
index 8ef51a278..df91a5b63 100644
--- a/util/hmessages.po
+++ b/util/hmessages.po
@@ -6,9 +6,9 @@
#, fuzzy
msgid ""
msgstr ""
-"Project-Id-Version: 10.2RC\n"
+"Project-Id-Version: 10.4RC1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-03-05 10:11+0000\n"
+"POT-Creation-Date: 2025-06-24 07:52+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -21,7 +21,7 @@ msgstr ""
msgid "Source channel not found."
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:18 ../../include/text.php:3518
+#: ../../view/theme/redbasic/php/config.php:19 ../../include/text.php:3544
#: ../../addon/cart/submodules/orderoptions.php:335
#: ../../addon/cart/submodules/orderoptions.php:359
#: ../../addon/cart/submodules/orderoptions.php:435
@@ -30,13 +30,14 @@ msgstr ""
msgid "Default"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:19
-#: ../../view/theme/redbasic/php/config.php:22
+#: ../../view/theme/redbasic/php/config.php:20
+#: ../../view/theme/redbasic/php/config.php:23
msgid "Focus (Hubzilla default)"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:188 ../../include/js_strings.php:23
-#: ../../addon/irc/irc.php:45 ../../addon/socialauth/Mod_SocialAuth.php:341
+#: ../../view/theme/redbasic/php/config.php:193 ../../include/js_strings.php:21
+#: ../../include/conversation.php:1127 ../../addon/irc/irc.php:45
+#: ../../addon/socialauth/Mod_SocialAuth.php:341
#: ../../addon/hubwall/hubwall.php:96
#: ../../addon/openclipatar/openclipatar.php:54
#: ../../addon/hzfiles/hzfiles.php:86 ../../addon/piwik/piwik.php:95
@@ -45,7 +46,6 @@ msgstr ""
#: ../../addon/ijpost/Mod_Ijpost.php:72 ../../addon/redred/Mod_Redred.php:88
#: ../../addon/startpage/Mod_Startpage.php:75
#: ../../addon/libertree/Mod_Libertree.php:68 ../../addon/logrot/logrot.php:35
-#: ../../addon/pubcrawl/Mod_Pubcrawl.php:61
#: ../../addon/dwpost/Mod_Dwpost.php:78 ../../addon/diaspora/diaspora.php:90
#: ../../addon/diaspora/Mod_Diaspora.php:101
#: ../../addon/fuzzloc/Mod_Fuzzloc.php:54 ../../addon/mailtest/mailtest.php:100
@@ -56,7 +56,6 @@ msgstr ""
#: ../../addon/statusnet/Mod_Statusnet.php:191
#: ../../addon/statusnet/Mod_Statusnet.php:249
#: ../../addon/statusnet/Mod_Statusnet.php:304
-#: ../../addon/flashcards/Mod_Flashcards.php:269
#: ../../addon/ljpost/Mod_Ljpost.php:80 ../../addon/faces/Mod_Faces.php:141
#: ../../addon/cart/submodules/hzservices.php:645
#: ../../addon/cart/submodules/subscriptions.php:410
@@ -78,7 +77,7 @@ msgstr ""
#: ../../addon/rtof/Mod_Rtof.php:70 ../../addon/nofed/Mod_Nofed.php:51
#: ../../addon/photocache/Mod_Photocache.php:63
#: ../../addon/likebanner/likebanner.php:57
-#: ../../Zotlabs/Lib/ThreadItem.php:799 ../../Zotlabs/Storage/Browser.php:386
+#: ../../Zotlabs/Lib/ThreadItem.php:809 ../../Zotlabs/Storage/Browser.php:390
#: ../../Zotlabs/Module/Oauth.php:110 ../../Zotlabs/Module/Import_items.php:125
#: ../../Zotlabs/Module/Thing.php:364 ../../Zotlabs/Module/Thing.php:414
#: ../../Zotlabs/Module/Tokens.php:294 ../../Zotlabs/Module/Pdledit.php:137
@@ -101,15 +100,15 @@ msgstr ""
#: ../../Zotlabs/Module/Admin/Security.php:130
#: ../../Zotlabs/Module/Affinity.php:84 ../../Zotlabs/Module/Permcats.php:257
#: ../../Zotlabs/Module/Xchan.php:15 ../../Zotlabs/Module/Group.php:151
-#: ../../Zotlabs/Module/Group.php:160 ../../Zotlabs/Module/Invite.php:564
-#: ../../Zotlabs/Module/Mitem.php:257 ../../Zotlabs/Module/Photos.php:1056
-#: ../../Zotlabs/Module/Photos.php:1095 ../../Zotlabs/Module/Photos.php:1208
+#: ../../Zotlabs/Module/Group.php:160 ../../Zotlabs/Module/Invite.php:547
+#: ../../Zotlabs/Module/Mitem.php:257 ../../Zotlabs/Module/Photos.php:1057
+#: ../../Zotlabs/Module/Photos.php:1096 ../../Zotlabs/Module/Photos.php:1197
#: ../../Zotlabs/Module/Sources.php:123 ../../Zotlabs/Module/Sources.php:160
#: ../../Zotlabs/Module/Profiles.php:738 ../../Zotlabs/Module/Chat.php:208
#: ../../Zotlabs/Module/Chat.php:247 ../../Zotlabs/Module/Regate.php:408
#: ../../Zotlabs/Module/Setup.php:322 ../../Zotlabs/Module/Setup.php:362
#: ../../Zotlabs/Module/Editpost.php:88 ../../Zotlabs/Module/Oauth2.php:115
-#: ../../Zotlabs/Module/Settings/Display.php:188
+#: ../../Zotlabs/Module/Settings/Display.php:187
#: ../../Zotlabs/Module/Settings/Network.php:62
#: ../../Zotlabs/Module/Settings/Channel_home.php:91
#: ../../Zotlabs/Module/Settings/Account.php:109
@@ -131,63 +130,62 @@ msgstr ""
msgid "Submit"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:192
+#: ../../view/theme/redbasic/php/config.php:197
msgid "Theme settings"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:193
+#: ../../view/theme/redbasic/php/config.php:198
msgid "Dark style"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:194
+#: ../../view/theme/redbasic/php/config.php:199
msgid "Light style"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:195
+#: ../../view/theme/redbasic/php/config.php:200
msgid "Common settings"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:196
+#: ../../view/theme/redbasic/php/config.php:201
msgid "Primary theme color"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:196
-#: ../../view/theme/redbasic/php/config.php:197
-#: ../../view/theme/redbasic/php/config.php:198
-#: ../../view/theme/redbasic/php/config.php:199
-#: ../../view/theme/redbasic/php/config.php:200
+#: ../../view/theme/redbasic/php/config.php:201
+#: ../../view/theme/redbasic/php/config.php:202
+#: ../../view/theme/redbasic/php/config.php:203
+#: ../../view/theme/redbasic/php/config.php:204
+#: ../../view/theme/redbasic/php/config.php:205
msgid "Current color, leave empty for default"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:197
+#: ../../view/theme/redbasic/php/config.php:202
msgid "Success theme color"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:198
+#: ../../view/theme/redbasic/php/config.php:203
msgid "Info theme color"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:199
+#: ../../view/theme/redbasic/php/config.php:204
msgid "Warning theme color"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:200
+#: ../../view/theme/redbasic/php/config.php:205
msgid "Danger theme color"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:201
+#: ../../view/theme/redbasic/php/config.php:206
msgid "Default to dark mode"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:201
-#: ../../view/theme/redbasic/php/config.php:202
-#: ../../view/theme/redbasic/php/config.php:203
-#: ../../view/theme/redbasic/php/config.php:215
-#: ../../include/conversation.php:1274
+#: ../../view/theme/redbasic/php/config.php:206
+#: ../../view/theme/redbasic/php/config.php:207
+#: ../../view/theme/redbasic/php/config.php:208
+#: ../../view/theme/redbasic/php/config.php:220
+#: ../../include/conversation.php:1159
#: ../../addon/socialauth/Mod_SocialAuth.php:218
#: ../../addon/ijpost/Mod_Ijpost.php:61 ../../addon/redred/Mod_Redred.php:61
#: ../../addon/libertree/Mod_Libertree.php:57
-#: ../../addon/pubcrawl/Mod_Pubcrawl.php:45
#: ../../addon/dwpost/Mod_Dwpost.php:59 ../../addon/dwpost/Mod_Dwpost.php:63
#: ../../addon/diaspora/Mod_Diaspora.php:70
#: ../../addon/pumpio/Mod_Pumpio.php:92 ../../addon/pumpio/Mod_Pumpio.php:96
@@ -223,11 +221,11 @@ msgstr ""
#: ../../addon/content_import/Mod_content_import.php:135
#: ../../addon/content_import/Mod_content_import.php:136
#: ../../addon/rtof/Mod_Rtof.php:47 ../../addon/nofed/Mod_Nofed.php:40
-#: ../../boot.php:1759 ../../Zotlabs/Lib/Libzotdir.php:166
+#: ../../boot.php:1766 ../../Zotlabs/Lib/Libzotdir.php:166
#: ../../Zotlabs/Lib/Libzotdir.php:167 ../../Zotlabs/Lib/Libzotdir.php:169
-#: ../../Zotlabs/Storage/Browser.php:310 ../../Zotlabs/Storage/Browser.php:311
-#: ../../Zotlabs/Storage/Browser.php:312 ../../Zotlabs/Storage/Browser.php:393
-#: ../../Zotlabs/Storage/Browser.php:395 ../../Zotlabs/Storage/Browser.php:558
+#: ../../Zotlabs/Storage/Browser.php:314 ../../Zotlabs/Storage/Browser.php:315
+#: ../../Zotlabs/Storage/Browser.php:316 ../../Zotlabs/Storage/Browser.php:397
+#: ../../Zotlabs/Storage/Browser.php:399 ../../Zotlabs/Storage/Browser.php:562
#: ../../Zotlabs/Module/Filestorage.php:203
#: ../../Zotlabs/Module/Filestorage.php:211
#: ../../Zotlabs/Module/Contactedit.php:270
@@ -241,7 +239,7 @@ msgstr ""
#: ../../Zotlabs/Module/Group.php:250 ../../Zotlabs/Module/Group.php:302
#: ../../Zotlabs/Module/Group.php:303 ../../Zotlabs/Module/Mitem.php:176
#: ../../Zotlabs/Module/Mitem.php:177 ../../Zotlabs/Module/Mitem.php:254
-#: ../../Zotlabs/Module/Mitem.php:255 ../../Zotlabs/Module/Photos.php:666
+#: ../../Zotlabs/Module/Mitem.php:255 ../../Zotlabs/Module/Photos.php:668
#: ../../Zotlabs/Module/Sources.php:122 ../../Zotlabs/Module/Sources.php:157
#: ../../Zotlabs/Module/Profiles.php:674 ../../Zotlabs/Module/Profiles.php:684
#: ../../Zotlabs/Module/Profiles.php:692 ../../Zotlabs/Module/Profiles.php:696
@@ -259,15 +257,14 @@ msgstr ""
msgid "No"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:201
-#: ../../view/theme/redbasic/php/config.php:202
-#: ../../view/theme/redbasic/php/config.php:203
-#: ../../view/theme/redbasic/php/config.php:215
-#: ../../include/conversation.php:1274
+#: ../../view/theme/redbasic/php/config.php:206
+#: ../../view/theme/redbasic/php/config.php:207
+#: ../../view/theme/redbasic/php/config.php:208
+#: ../../view/theme/redbasic/php/config.php:220
+#: ../../include/conversation.php:1159
#: ../../addon/socialauth/Mod_SocialAuth.php:218
#: ../../addon/ijpost/Mod_Ijpost.php:61 ../../addon/redred/Mod_Redred.php:61
#: ../../addon/libertree/Mod_Libertree.php:57
-#: ../../addon/pubcrawl/Mod_Pubcrawl.php:45
#: ../../addon/dwpost/Mod_Dwpost.php:59 ../../addon/dwpost/Mod_Dwpost.php:63
#: ../../addon/diaspora/Mod_Diaspora.php:70
#: ../../addon/pumpio/Mod_Pumpio.php:92 ../../addon/pumpio/Mod_Pumpio.php:96
@@ -303,11 +300,11 @@ msgstr ""
#: ../../addon/content_import/Mod_content_import.php:135
#: ../../addon/content_import/Mod_content_import.php:136
#: ../../addon/rtof/Mod_Rtof.php:47 ../../addon/nofed/Mod_Nofed.php:40
-#: ../../boot.php:1759 ../../Zotlabs/Lib/Libzotdir.php:166
+#: ../../boot.php:1766 ../../Zotlabs/Lib/Libzotdir.php:166
#: ../../Zotlabs/Lib/Libzotdir.php:167 ../../Zotlabs/Lib/Libzotdir.php:169
-#: ../../Zotlabs/Storage/Browser.php:310 ../../Zotlabs/Storage/Browser.php:311
-#: ../../Zotlabs/Storage/Browser.php:312 ../../Zotlabs/Storage/Browser.php:393
-#: ../../Zotlabs/Storage/Browser.php:395 ../../Zotlabs/Storage/Browser.php:558
+#: ../../Zotlabs/Storage/Browser.php:314 ../../Zotlabs/Storage/Browser.php:315
+#: ../../Zotlabs/Storage/Browser.php:316 ../../Zotlabs/Storage/Browser.php:397
+#: ../../Zotlabs/Storage/Browser.php:399 ../../Zotlabs/Storage/Browser.php:562
#: ../../Zotlabs/Module/Filestorage.php:203
#: ../../Zotlabs/Module/Filestorage.php:211
#: ../../Zotlabs/Module/Contactedit.php:270
@@ -319,7 +316,7 @@ msgstr ""
#: ../../Zotlabs/Module/Group.php:250 ../../Zotlabs/Module/Group.php:302
#: ../../Zotlabs/Module/Group.php:303 ../../Zotlabs/Module/Mitem.php:176
#: ../../Zotlabs/Module/Mitem.php:177 ../../Zotlabs/Module/Mitem.php:254
-#: ../../Zotlabs/Module/Mitem.php:255 ../../Zotlabs/Module/Photos.php:666
+#: ../../Zotlabs/Module/Mitem.php:255 ../../Zotlabs/Module/Photos.php:668
#: ../../Zotlabs/Module/Sources.php:122 ../../Zotlabs/Module/Sources.php:157
#: ../../Zotlabs/Module/Profiles.php:674 ../../Zotlabs/Module/Profiles.php:684
#: ../../Zotlabs/Module/Profiles.php:692 ../../Zotlabs/Module/Profiles.php:696
@@ -337,80 +334,80 @@ msgstr ""
msgid "Yes"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:202
+#: ../../view/theme/redbasic/php/config.php:207
msgid "Always use light icons for navbar"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:202
+#: ../../view/theme/redbasic/php/config.php:207
msgid "Enable this option if you use a dark navbar color in light mode"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:203
+#: ../../view/theme/redbasic/php/config.php:208
msgid "Narrow navbar"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:204
+#: ../../view/theme/redbasic/php/config.php:209
msgid "Navigation bar background color"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:205
+#: ../../view/theme/redbasic/php/config.php:210
msgid "Dark navigation bar background color"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:206
+#: ../../view/theme/redbasic/php/config.php:211
msgid "Set the background color"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:207
+#: ../../view/theme/redbasic/php/config.php:212
msgid "Set the dark background color"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:208
+#: ../../view/theme/redbasic/php/config.php:213
msgid "Set the background image"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:209
+#: ../../view/theme/redbasic/php/config.php:214
msgid "Set the dark background image"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:210
+#: ../../view/theme/redbasic/php/config.php:215
msgid "Set font-size for the entire application"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:210
+#: ../../view/theme/redbasic/php/config.php:215
msgid "Examples: 1rem, 100%, 16px"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:211
+#: ../../view/theme/redbasic/php/config.php:216
msgid "Set radius of corners in rem"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:211
+#: ../../view/theme/redbasic/php/config.php:216
msgid "Leave empty for default radius"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:212
+#: ../../view/theme/redbasic/php/config.php:217
msgid "Set maximum width of content region in rem"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:212
+#: ../../view/theme/redbasic/php/config.php:217
msgid "Leave empty for default width"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:213
+#: ../../view/theme/redbasic/php/config.php:218
msgid "Set size of conversation author photo"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:213
-#: ../../view/theme/redbasic/php/config.php:214
+#: ../../view/theme/redbasic/php/config.php:218
+#: ../../view/theme/redbasic/php/config.php:219
msgid "Leave empty for default size"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:214
+#: ../../view/theme/redbasic/php/config.php:219
msgid "Set size of followup author photos"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:215
+#: ../../view/theme/redbasic/php/config.php:220
msgid "Show advanced settings"
msgstr ""
@@ -424,23 +421,23 @@ msgstr ""
msgid "This is the home page of %s."
msgstr ""
-#: ../../include/auth.php:232
+#: ../../include/auth.php:231
msgid "Delegation session ended."
msgstr ""
-#: ../../include/auth.php:236
+#: ../../include/auth.php:235
msgid "Logged out."
msgstr ""
-#: ../../include/auth.php:342
+#: ../../include/auth.php:341
msgid "Email validation is incomplete. Please check your email."
msgstr ""
-#: ../../include/auth.php:358
+#: ../../include/auth.php:357
msgid "Failed authentication"
msgstr ""
-#: ../../include/auth.php:368 ../../addon/openid/Mod_Openid.php:189
+#: ../../include/auth.php:367 ../../addon/openid/Mod_Openid.php:189
msgid "Login failed."
msgstr ""
@@ -502,8 +499,8 @@ msgid "This event has been added to your calendar."
msgstr ""
#: ../../include/event.php:1366 ../../include/conversation.php:153
-#: ../../include/text.php:2359 ../../Zotlabs/Module/Tagger.php:77
-#: ../../Zotlabs/Module/Like.php:453
+#: ../../include/text.php:2380 ../../Zotlabs/Module/Tagger.php:77
+#: ../../Zotlabs/Module/Like.php:441
#: ../../Zotlabs/Module/Channel_calendar.php:209
msgid "event"
msgstr ""
@@ -534,7 +531,6 @@ msgid "Mobile"
msgstr ""
#: ../../include/event.php:1536 ../../include/connections.php:791
-#: ../../Zotlabs/Widget/Notifications.php:43
#: ../../Zotlabs/Module/Connedit.php:742 ../../Zotlabs/Module/Cdav.php:1378
msgid "Home"
msgstr ""
@@ -569,80 +565,79 @@ msgstr ""
msgid "Other"
msgstr ""
-#: ../../include/feedutils.php:863 ../../include/text.php:1580
+#: ../../include/feedutils.php:863 ../../include/text.php:1602
msgid "unknown"
msgstr ""
-#: ../../include/items.php:481 ../../addon/hzfiles/hzfiles.php:75
+#: ../../include/items.php:484 ../../addon/hzfiles/hzfiles.php:75
#: ../../addon/redphotos/redphotos.php:119
#: ../../addon/redfiles/redfiles.php:109
#: ../../Zotlabs/Module/Import_items.php:116
#: ../../Zotlabs/Module/Dreport.php:10 ../../Zotlabs/Module/Dreport.php:60
#: ../../Zotlabs/Module/Subthread.php:89 ../../Zotlabs/Module/Group.php:109
-#: ../../Zotlabs/Module/Like.php:344 ../../Zotlabs/Module/Profperm.php:29
-#: ../../Zotlabs/Web/WebServer.php:120
+#: ../../Zotlabs/Module/Like.php:332 ../../Zotlabs/Module/Profperm.php:29
msgid "Permission denied"
msgstr ""
-#: ../../include/items.php:1275
+#: ../../include/items.php:1278
msgid "Visible to anybody on the internet."
msgstr ""
-#: ../../include/items.php:1277
+#: ../../include/items.php:1280
msgid "Visible to you only."
msgstr ""
-#: ../../include/items.php:1279
+#: ../../include/items.php:1282
msgid "Visible to anybody in this network."
msgstr ""
-#: ../../include/items.php:1281
+#: ../../include/items.php:1284
msgid "Visible to anybody authenticated."
msgstr ""
-#: ../../include/items.php:1283
+#: ../../include/items.php:1286
#, php-format
msgid "Visible to anybody on %s."
msgstr ""
-#: ../../include/items.php:1285
+#: ../../include/items.php:1288
msgid "Visible to all connections."
msgstr ""
-#: ../../include/items.php:1287
+#: ../../include/items.php:1290
msgid "Visible to approved connections."
msgstr ""
-#: ../../include/items.php:1289
+#: ../../include/items.php:1292
msgid "Visible to specific connections."
msgstr ""
-#: ../../include/items.php:3491 ../../Zotlabs/Lib/Activity.php:2282
+#: ../../include/items.php:3502 ../../Zotlabs/Lib/Activity.php:2225
#: ../../Zotlabs/Module/Share.php:124
#, php-format
msgid "&#x1f501; Repeated %1$s's %2$s"
msgstr ""
-#: ../../include/items.php:4568 ../../Zotlabs/Module/Group.php:63
+#: ../../include/items.php:4579 ../../Zotlabs/Module/Group.php:63
#: ../../Zotlabs/Module/Group.php:207
msgid "Privacy group not found."
msgstr ""
-#: ../../include/items.php:4584
+#: ../../include/items.php:4595
msgid "Privacy group is empty."
msgstr ""
-#: ../../include/items.php:4591
+#: ../../include/items.php:4602
#, php-format
msgid "Privacy group: %s"
msgstr ""
-#: ../../include/items.php:4601
+#: ../../include/items.php:4612
#, php-format
msgid "Connection: %s"
msgstr ""
-#: ../../include/items.php:4603
+#: ../../include/items.php:4614
msgid "Connection not found."
msgstr ""
@@ -863,7 +858,8 @@ msgstr ""
msgid "Change channels directly from within the navigation dropdown menu"
msgstr ""
-#: ../../include/features.php:296 ../../Zotlabs/Widget/Notifications.php:23
+#: ../../include/features.php:296 ../../Zotlabs/Lib/Apps.php:344
+#: ../../Zotlabs/Widget/Notifications.php:23
#: ../../Zotlabs/Module/Connections.php:347
msgid "Network"
msgstr ""
@@ -990,12 +986,12 @@ msgstr ""
msgid "Ability to create multiple profiles"
msgstr ""
-#: ../../include/attach.php:156 ../../include/attach.php:205
-#: ../../include/attach.php:278 ../../include/attach.php:329
-#: ../../include/attach.php:431 ../../include/attach.php:445
-#: ../../include/attach.php:452 ../../include/attach.php:534
-#: ../../include/attach.php:1106 ../../include/attach.php:1179
-#: ../../include/attach.php:1344 ../../include/photos.php:32
+#: ../../include/attach.php:157 ../../include/attach.php:206
+#: ../../include/attach.php:279 ../../include/attach.php:330
+#: ../../include/attach.php:432 ../../include/attach.php:446
+#: ../../include/attach.php:453 ../../include/attach.php:535
+#: ../../include/attach.php:1115 ../../include/attach.php:1188
+#: ../../include/attach.php:1359 ../../include/photos.php:32
#: ../../addon/openid/Mod_Id.php:53 ../../addon/keepout/keepout.php:36
#: ../../addon/cards/Mod_Cards.php:89 ../../addon/cards/Mod_Card_edit.php:51
#: ../../addon/pumpio/pumpio.php:44
@@ -1003,7 +999,7 @@ msgstr ""
#: ../../addon/articles/Mod_Articles.php:94 ../../addon/wiki/Mod_Wiki.php:63
#: ../../addon/wiki/Mod_Wiki.php:288 ../../addon/wiki/Mod_Wiki.php:425
#: ../../Zotlabs/Lib/Chatroom.php:135 ../../Zotlabs/Module/Page.php:34
-#: ../../Zotlabs/Module/Page.php:133 ../../Zotlabs/Module/Display.php:392
+#: ../../Zotlabs/Module/Page.php:133 ../../Zotlabs/Module/Display.php:387
#: ../../Zotlabs/Module/Cover_photo.php:299
#: ../../Zotlabs/Module/Cover_photo.php:312
#: ../../Zotlabs/Module/Editwebpage.php:68
@@ -1020,9 +1016,9 @@ msgstr ""
#: ../../Zotlabs/Module/Filestorage.php:119
#: ../../Zotlabs/Module/Filestorage.php:165
#: ../../Zotlabs/Module/Register.php:201 ../../Zotlabs/Module/Appman.php:163
-#: ../../Zotlabs/Module/Viewsrc.php:19 ../../Zotlabs/Module/Item.php:288
-#: ../../Zotlabs/Module/Item.php:307 ../../Zotlabs/Module/Item.php:317
-#: ../../Zotlabs/Module/Item.php:1276 ../../Zotlabs/Module/Achievements.php:34
+#: ../../Zotlabs/Module/Viewsrc.php:19 ../../Zotlabs/Module/Item.php:289
+#: ../../Zotlabs/Module/Item.php:308 ../../Zotlabs/Module/Item.php:318
+#: ../../Zotlabs/Module/Item.php:1272 ../../Zotlabs/Module/Achievements.php:34
#: ../../Zotlabs/Module/Connedit.php:299 ../../Zotlabs/Module/Defperms.php:181
#: ../../Zotlabs/Module/Suggest.php:32 ../../Zotlabs/Module/Regmod.php:20
#: ../../Zotlabs/Module/Profile_photo.php:390
@@ -1039,12 +1035,12 @@ msgstr ""
#: ../../Zotlabs/Module/Attach_edit.php:99
#: ../../Zotlabs/Module/Attach_edit.php:106 ../../Zotlabs/Module/Group.php:15
#: ../../Zotlabs/Module/Group.php:31 ../../Zotlabs/Module/Invite.php:65
-#: ../../Zotlabs/Module/Invite.php:316 ../../Zotlabs/Module/Like.php:242
+#: ../../Zotlabs/Module/Invite.php:316 ../../Zotlabs/Module/Like.php:230
#: ../../Zotlabs/Module/Blocks.php:73 ../../Zotlabs/Module/Blocks.php:80
#: ../../Zotlabs/Module/Mitem.php:129 ../../Zotlabs/Module/New_channel.php:106
#: ../../Zotlabs/Module/New_channel.php:131 ../../Zotlabs/Module/Photos.php:71
-#: ../../Zotlabs/Module/Channel.php:234 ../../Zotlabs/Module/Channel.php:391
-#: ../../Zotlabs/Module/Channel.php:429 ../../Zotlabs/Module/Profile.php:99
+#: ../../Zotlabs/Module/Channel.php:234 ../../Zotlabs/Module/Channel.php:395
+#: ../../Zotlabs/Module/Channel.php:434 ../../Zotlabs/Module/Profile.php:99
#: ../../Zotlabs/Module/Profile.php:114 ../../Zotlabs/Module/Moderate.php:15
#: ../../Zotlabs/Module/Sources.php:80 ../../Zotlabs/Module/Profiles.php:168
#: ../../Zotlabs/Module/Profiles.php:611
@@ -1057,92 +1053,99 @@ msgstr ""
#: ../../Zotlabs/Module/Setup.php:220 ../../Zotlabs/Module/Editpost.php:17
#: ../../Zotlabs/Module/Connections.php:32
#: ../../Zotlabs/Module/Editblock.php:67 ../../Zotlabs/Module/Vote.php:19
-#: ../../Zotlabs/Web/WebServer.php:121
msgid "Permission denied."
msgstr ""
-#: ../../include/attach.php:273 ../../include/attach.php:324
-#: ../../include/attach.php:426
+#: ../../include/attach.php:274 ../../include/attach.php:325
+#: ../../include/attach.php:427
msgid "Item was not found."
msgstr ""
-#: ../../include/attach.php:290
+#: ../../include/attach.php:291
msgid "Unknown error."
msgstr ""
-#: ../../include/attach.php:621
+#: ../../include/attach.php:622
msgid "No source file."
msgstr ""
-#: ../../include/attach.php:643
+#: ../../include/attach.php:644
msgid "Cannot locate file to replace"
msgstr ""
-#: ../../include/attach.php:662
+#: ../../include/attach.php:663
msgid "Cannot locate file to revise/update"
msgstr ""
-#: ../../include/attach.php:808
+#: ../../include/attach.php:804 ../../include/attach.php:2609
+msgid "Filename too long"
+msgstr ""
+
+#: ../../include/attach.php:817
#, php-format
msgid "File exceeds size limit of %d"
msgstr ""
-#: ../../include/attach.php:829
+#: ../../include/attach.php:838
#, php-format
msgid "You have reached your limit of %1$.0f Mbytes attachment storage."
msgstr ""
-#: ../../include/attach.php:1019
+#: ../../include/attach.php:1028
msgid "File upload failed. Possible system limit or action terminated."
msgstr ""
-#: ../../include/attach.php:1048
+#: ../../include/attach.php:1057
msgid "Stored file could not be verified. Upload failed."
msgstr ""
-#: ../../include/attach.php:1120 ../../include/attach.php:1136
+#: ../../include/attach.php:1129 ../../include/attach.php:1145
msgid "Path not available."
msgstr ""
-#: ../../include/attach.php:1184 ../../include/attach.php:1349
+#: ../../include/attach.php:1193 ../../include/attach.php:1364
msgid "Empty pathname"
msgstr ""
-#: ../../include/attach.php:1210
+#: ../../include/attach.php:1199
+msgid "Pathname too long"
+msgstr ""
+
+#: ../../include/attach.php:1225
msgid "duplicate filename or path"
msgstr ""
-#: ../../include/attach.php:1238
+#: ../../include/attach.php:1253
msgid "Path not found."
msgstr ""
-#: ../../include/attach.php:1305
+#: ../../include/attach.php:1320
msgid "mkdir failed."
msgstr ""
-#: ../../include/attach.php:1309
+#: ../../include/attach.php:1324
msgid "database storage failed."
msgstr ""
-#: ../../include/attach.php:1355
+#: ../../include/attach.php:1370
msgid "Empty path"
msgstr ""
-#: ../../include/attach.php:1992
+#: ../../include/attach.php:2007
#, php-format
msgid "%s shared an %s with you"
msgstr ""
-#: ../../include/attach.php:1992
+#: ../../include/attach.php:2007
#, php-format
msgid "%s shared a %s with you"
msgstr ""
-#: ../../include/attach.php:1992 ../../Zotlabs/Module/Like.php:450
+#: ../../include/attach.php:2007 ../../Zotlabs/Module/Like.php:438
msgid "image"
msgstr ""
-#: ../../include/attach.php:1992 ../../addon/redfiles/redfilehelper.php:64
+#: ../../include/attach.php:2007 ../../addon/redfiles/redfilehelper.php:64
msgid "file"
msgstr ""
@@ -1505,8 +1508,8 @@ msgstr ""
#: ../../include/taxonomy.php:527 ../../include/taxonomy.php:548
#: ../../addon/cards/Widget/Cards_categories.php:80
#: ../../addon/articles/Widget/Articles_categories.php:80
-#: ../../Zotlabs/Storage/Browser.php:293 ../../Zotlabs/Storage/Browser.php:392
-#: ../../Zotlabs/Storage/Browser.php:406 ../../Zotlabs/Module/Cdav.php:1062
+#: ../../Zotlabs/Storage/Browser.php:297 ../../Zotlabs/Storage/Browser.php:396
+#: ../../Zotlabs/Storage/Browser.php:410 ../../Zotlabs/Module/Cdav.php:1062
msgid "Categories"
msgstr ""
@@ -1528,12 +1531,11 @@ msgid "Summary: "
msgstr ""
#: ../../include/cdav.php:158 ../../include/cdav.php:159
-#: ../../include/cdav.php:167 ../../include/conversation.php:1006
-#: ../../Zotlabs/Lib/Apps.php:1168 ../../Zotlabs/Lib/Apps.php:1252
-#: ../../Zotlabs/Lib/Activity.php:1726 ../../Zotlabs/Widget/Album.php:90
-#: ../../Zotlabs/Widget/Pinned.php:256 ../../Zotlabs/Widget/Portfolio.php:99
-#: ../../Zotlabs/Module/Embedphotos.php:177 ../../Zotlabs/Module/Photos.php:788
-#: ../../Zotlabs/Module/Photos.php:1246
+#: ../../include/cdav.php:167 ../../Zotlabs/Lib/Apps.php:1168
+#: ../../Zotlabs/Lib/Apps.php:1252 ../../Zotlabs/Lib/Activity.php:1696
+#: ../../Zotlabs/Widget/Album.php:93 ../../Zotlabs/Widget/Portfolio.php:103
+#: ../../Zotlabs/Module/Embedphotos.php:179 ../../Zotlabs/Module/Photos.php:790
+#: ../../Zotlabs/Module/Photos.php:1240
msgid "Unknown"
msgstr ""
@@ -1619,7 +1621,7 @@ msgstr ""
msgid "wants"
msgstr ""
-#: ../../include/taxonomy.php:589 ../../Zotlabs/Lib/ThreadItem.php:296
+#: ../../include/taxonomy.php:589
msgid "like"
msgstr ""
@@ -1627,7 +1629,7 @@ msgstr ""
msgid "likes"
msgstr ""
-#: ../../include/taxonomy.php:590 ../../Zotlabs/Lib/ThreadItem.php:297
+#: ../../include/taxonomy.php:590
msgid "dislike"
msgstr ""
@@ -1635,15 +1637,15 @@ msgstr ""
msgid "dislikes"
msgstr ""
-#: ../../include/taxonomy.php:677 ../../include/conversation.php:1561
-#: ../../Zotlabs/Module/Photos.php:1129
+#: ../../include/taxonomy.php:677 ../../include/conversation.php:1440
+#: ../../Zotlabs/Module/Photos.php:1118
msgctxt "noun"
msgid "Like"
msgid_plural "Likes"
msgstr[0] ""
msgstr[1] ""
-#: ../../include/photo/photo_driver.php:458
+#: ../../include/photo/photo_driver.php:439
#: ../../Zotlabs/Module/Profile_photo.php:168
#: ../../Zotlabs/Module/Profile_photo.php:337
msgid "Profile Photos"
@@ -1675,73 +1677,68 @@ msgstr ""
msgid "Invitation could not be verified."
msgstr ""
-#: ../../include/account.php:192
-msgid "Please enter the required information."
-msgstr ""
-
-#: ../../include/account.php:259 ../../include/account.php:367
+#: ../../include/account.php:206
msgid "Failed to store account information."
msgstr ""
-#: ../../include/account.php:436 ../../include/account.php:504
-#: ../../Zotlabs/Module/Register.php:329
+#: ../../include/account.php:275 ../../Zotlabs/Module/Register.php:329
#, php-format
msgid "Registration confirmation for %s"
msgstr ""
-#: ../../include/account.php:579
+#: ../../include/account.php:348
#, php-format
msgid "Registration request at %s"
msgstr ""
-#: ../../include/account.php:601
+#: ../../include/account.php:370
msgid "your registration password"
msgstr ""
-#: ../../include/account.php:607 ../../include/account.php:680
+#: ../../include/account.php:376 ../../include/account.php:449
#, php-format
msgid "Registration details for %s"
msgstr ""
-#: ../../include/account.php:695
+#: ../../include/account.php:464
msgid "Account approved."
msgstr ""
-#: ../../include/account.php:747
+#: ../../include/account.php:516
#, php-format
msgid "Registration revoked for %s"
msgstr ""
-#: ../../include/account.php:754
+#: ../../include/account.php:523
#, php-format
msgid "Could not revoke registration for %s"
msgstr ""
-#: ../../include/account.php:1171 ../../include/account.php:1173
+#: ../../include/account.php:940 ../../include/account.php:942
msgid "Click here to upgrade."
msgstr ""
-#: ../../include/account.php:1179
+#: ../../include/account.php:948
msgid "This action exceeds the limits set by your subscription plan."
msgstr ""
-#: ../../include/account.php:1184
+#: ../../include/account.php:953
msgid "This action is not available under your subscription plan."
msgstr ""
-#: ../../include/account.php:1244
+#: ../../include/account.php:1013
msgid "open"
msgstr ""
-#: ../../include/account.php:1244
+#: ../../include/account.php:1013
msgid "closed"
msgstr ""
-#: ../../include/account.php:1251
+#: ../../include/account.php:1020
msgid "Registration is currently"
msgstr ""
-#: ../../include/account.php:1260
+#: ../../include/account.php:1029
msgid "please come back"
msgstr ""
@@ -1753,367 +1750,363 @@ msgstr ""
msgid "Item deleted"
msgstr ""
-#: ../../include/js_strings.php:7 ../../Zotlabs/Lib/ThreadItem.php:798
-#: ../../Zotlabs/Module/Photos.php:1094 ../../Zotlabs/Module/Photos.php:1207
+#: ../../include/js_strings.php:7 ../../Zotlabs/Lib/ThreadItem.php:808
+#: ../../Zotlabs/Module/Photos.php:1095 ../../Zotlabs/Module/Photos.php:1196
msgid "Comment"
msgstr ""
-#: ../../include/js_strings.php:8 ../../Zotlabs/Lib/ThreadItem.php:507
-msgid "show all"
-msgstr ""
-
-#: ../../include/js_strings.php:9
-msgid "show less"
-msgstr ""
-
-#: ../../include/js_strings.php:10
+#: ../../include/js_strings.php:8
msgid "expand"
msgstr ""
-#: ../../include/js_strings.php:11
+#: ../../include/js_strings.php:9
msgid "collapse"
msgstr ""
-#: ../../include/js_strings.php:12
+#: ../../include/js_strings.php:10
msgid "Password too short"
msgstr ""
-#: ../../include/js_strings.php:13 ../../Zotlabs/Module/Register.php:162
+#: ../../include/js_strings.php:11 ../../Zotlabs/Module/Register.php:162
msgid "Passwords do not match"
msgstr ""
-#: ../../include/js_strings.php:14
+#: ../../include/js_strings.php:12
msgid "everybody"
msgstr ""
-#: ../../include/js_strings.php:15
+#: ../../include/js_strings.php:13
msgid "Secret Passphrase"
msgstr ""
-#: ../../include/js_strings.php:16
+#: ../../include/js_strings.php:14
msgid "Passphrase hint"
msgstr ""
-#: ../../include/js_strings.php:17
+#: ../../include/js_strings.php:15
msgid "Notice: Permissions have changed but have not yet been submitted."
msgstr ""
-#: ../../include/js_strings.php:18
+#: ../../include/js_strings.php:16
msgid "close all"
msgstr ""
-#: ../../include/js_strings.php:19
+#: ../../include/js_strings.php:17
msgid "Nothing new here"
msgstr ""
-#: ../../include/js_strings.php:20
+#: ../../include/js_strings.php:18
msgid "Rate This Channel (this is public)"
msgstr ""
-#: ../../include/js_strings.php:21
+#: ../../include/js_strings.php:19
msgid "Rating"
msgstr ""
-#: ../../include/js_strings.php:22
+#: ../../include/js_strings.php:20
msgid "Describe (optional)"
msgstr ""
-#: ../../include/js_strings.php:24
+#: ../../include/js_strings.php:22
msgid "Please enter a link URL"
msgstr ""
-#: ../../include/js_strings.php:25
+#: ../../include/js_strings.php:23
msgid "Unsaved changes. Are you sure you wish to leave this page?"
msgstr ""
-#: ../../include/js_strings.php:26 ../../Zotlabs/Module/Locs.php:121
+#: ../../include/js_strings.php:24 ../../Zotlabs/Module/Locs.php:121
#: ../../Zotlabs/Module/Pubsites.php:55 ../../Zotlabs/Module/Profiles.php:476
#: ../../Zotlabs/Module/Profiles.php:749 ../../Zotlabs/Module/Cdav.php:1006
msgid "Location"
msgstr ""
-#: ../../include/js_strings.php:27
+#: ../../include/js_strings.php:25
msgid "lovely"
msgstr ""
-#: ../../include/js_strings.php:28
+#: ../../include/js_strings.php:26
msgid "wonderful"
msgstr ""
-#: ../../include/js_strings.php:29
+#: ../../include/js_strings.php:27
msgid "fantastic"
msgstr ""
-#: ../../include/js_strings.php:30
+#: ../../include/js_strings.php:28
msgid "great"
msgstr ""
-#: ../../include/js_strings.php:31
+#: ../../include/js_strings.php:29
msgid ""
"Your chosen nickname was either already taken or not valid. Please use our "
"suggestion ("
msgstr ""
-#: ../../include/js_strings.php:32
+#: ../../include/js_strings.php:30
msgid ") or enter a new one."
msgstr ""
-#: ../../include/js_strings.php:33
+#: ../../include/js_strings.php:31
msgid "Thank you, this nickname is valid."
msgstr ""
-#: ../../include/js_strings.php:34
+#: ../../include/js_strings.php:32
msgid "A channel name is required."
msgstr ""
-#: ../../include/js_strings.php:35
+#: ../../include/js_strings.php:33
msgid "This is a "
msgstr ""
-#: ../../include/js_strings.php:36
+#: ../../include/js_strings.php:34
msgid " channel name"
msgstr ""
-#: ../../include/js_strings.php:37
+#: ../../include/js_strings.php:35
msgid "Back to reply"
msgstr ""
-#: ../../include/js_strings.php:38
+#: ../../include/js_strings.php:36
msgid "Pinned"
msgstr ""
-#: ../../include/js_strings.php:39 ../../Zotlabs/Lib/ThreadItem.php:457
+#: ../../include/js_strings.php:37 ../../Zotlabs/Lib/ThreadItem.php:454
msgid "Pin to the top"
msgstr ""
-#: ../../include/js_strings.php:40 ../../Zotlabs/Lib/ThreadItem.php:457
-#: ../../Zotlabs/Widget/Pinned.php:150
+#: ../../include/js_strings.php:38 ../../Zotlabs/Lib/ThreadItem.php:454
+#: ../../Zotlabs/Widget/Pinned.php:153
msgid "Unpin from the top"
msgstr ""
-#: ../../include/js_strings.php:46
+#: ../../include/js_strings.php:39
+msgid "Double click to exit zoom"
+msgstr ""
+
+#: ../../include/js_strings.php:45
#, php-format
msgid "%d minutes"
msgid_plural "%d minutes"
msgstr[0] ""
msgstr[1] ""
-#: ../../include/js_strings.php:47
+#: ../../include/js_strings.php:46
#, php-format
msgid "about %d hours"
msgid_plural "about %d hours"
msgstr[0] ""
msgstr[1] ""
-#: ../../include/js_strings.php:48
+#: ../../include/js_strings.php:47
#, php-format
msgid "%d days"
msgid_plural "%d days"
msgstr[0] ""
msgstr[1] ""
-#: ../../include/js_strings.php:49
+#: ../../include/js_strings.php:48
#, php-format
msgid "%d months"
msgid_plural "%d months"
msgstr[0] ""
msgstr[1] ""
-#: ../../include/js_strings.php:50
+#: ../../include/js_strings.php:49
#, php-format
msgid "%d years"
msgid_plural "%d years"
msgstr[0] ""
msgstr[1] ""
-#: ../../include/js_strings.php:52 ../../include/text.php:1503
+#: ../../include/js_strings.php:51 ../../include/text.php:1525
msgid "January"
msgstr ""
-#: ../../include/js_strings.php:53 ../../include/text.php:1503
+#: ../../include/js_strings.php:52 ../../include/text.php:1525
msgid "February"
msgstr ""
-#: ../../include/js_strings.php:54 ../../include/text.php:1503
+#: ../../include/js_strings.php:53 ../../include/text.php:1525
msgid "March"
msgstr ""
-#: ../../include/js_strings.php:55 ../../include/text.php:1503
+#: ../../include/js_strings.php:54 ../../include/text.php:1525
msgid "April"
msgstr ""
-#: ../../include/js_strings.php:56
+#: ../../include/js_strings.php:55
msgctxt "long"
msgid "May"
msgstr ""
-#: ../../include/js_strings.php:57 ../../include/text.php:1503
+#: ../../include/js_strings.php:56 ../../include/text.php:1525
msgid "June"
msgstr ""
-#: ../../include/js_strings.php:58 ../../include/text.php:1503
+#: ../../include/js_strings.php:57 ../../include/text.php:1525
msgid "July"
msgstr ""
-#: ../../include/js_strings.php:59 ../../include/text.php:1503
+#: ../../include/js_strings.php:58 ../../include/text.php:1525
msgid "August"
msgstr ""
-#: ../../include/js_strings.php:60 ../../include/text.php:1503
+#: ../../include/js_strings.php:59 ../../include/text.php:1525
msgid "September"
msgstr ""
-#: ../../include/js_strings.php:61 ../../include/text.php:1503
+#: ../../include/js_strings.php:60 ../../include/text.php:1525
msgid "October"
msgstr ""
-#: ../../include/js_strings.php:62 ../../include/text.php:1503
+#: ../../include/js_strings.php:61 ../../include/text.php:1525
msgid "November"
msgstr ""
-#: ../../include/js_strings.php:63 ../../include/text.php:1503
+#: ../../include/js_strings.php:62 ../../include/text.php:1525
msgid "December"
msgstr ""
-#: ../../include/js_strings.php:64
+#: ../../include/js_strings.php:63
msgid "Jan"
msgstr ""
-#: ../../include/js_strings.php:65
+#: ../../include/js_strings.php:64
msgid "Feb"
msgstr ""
-#: ../../include/js_strings.php:66
+#: ../../include/js_strings.php:65
msgid "Mar"
msgstr ""
-#: ../../include/js_strings.php:67
+#: ../../include/js_strings.php:66
msgid "Apr"
msgstr ""
-#: ../../include/js_strings.php:68
+#: ../../include/js_strings.php:67
msgctxt "short"
msgid "May"
msgstr ""
-#: ../../include/js_strings.php:69
+#: ../../include/js_strings.php:68
msgid "Jun"
msgstr ""
-#: ../../include/js_strings.php:70
+#: ../../include/js_strings.php:69
msgid "Jul"
msgstr ""
-#: ../../include/js_strings.php:71
+#: ../../include/js_strings.php:70
msgid "Aug"
msgstr ""
-#: ../../include/js_strings.php:72
+#: ../../include/js_strings.php:71
msgid "Sep"
msgstr ""
-#: ../../include/js_strings.php:73
+#: ../../include/js_strings.php:72
msgid "Oct"
msgstr ""
-#: ../../include/js_strings.php:74
+#: ../../include/js_strings.php:73
msgid "Nov"
msgstr ""
-#: ../../include/js_strings.php:75
+#: ../../include/js_strings.php:74
msgid "Dec"
msgstr ""
-#: ../../include/js_strings.php:76 ../../include/text.php:1499
+#: ../../include/js_strings.php:75 ../../include/text.php:1521
msgid "Sunday"
msgstr ""
-#: ../../include/js_strings.php:77 ../../include/text.php:1499
+#: ../../include/js_strings.php:76 ../../include/text.php:1521
msgid "Monday"
msgstr ""
-#: ../../include/js_strings.php:78 ../../include/text.php:1499
+#: ../../include/js_strings.php:77 ../../include/text.php:1521
msgid "Tuesday"
msgstr ""
-#: ../../include/js_strings.php:79 ../../include/text.php:1499
+#: ../../include/js_strings.php:78 ../../include/text.php:1521
msgid "Wednesday"
msgstr ""
-#: ../../include/js_strings.php:80 ../../include/text.php:1499
+#: ../../include/js_strings.php:79 ../../include/text.php:1521
msgid "Thursday"
msgstr ""
-#: ../../include/js_strings.php:81 ../../include/text.php:1499
+#: ../../include/js_strings.php:80 ../../include/text.php:1521
msgid "Friday"
msgstr ""
-#: ../../include/js_strings.php:82 ../../include/text.php:1499
+#: ../../include/js_strings.php:81 ../../include/text.php:1521
msgid "Saturday"
msgstr ""
-#: ../../include/js_strings.php:83
+#: ../../include/js_strings.php:82
msgid "Sun"
msgstr ""
-#: ../../include/js_strings.php:84
+#: ../../include/js_strings.php:83
msgid "Mon"
msgstr ""
-#: ../../include/js_strings.php:85
+#: ../../include/js_strings.php:84
msgid "Tue"
msgstr ""
-#: ../../include/js_strings.php:86
+#: ../../include/js_strings.php:85
msgid "Wed"
msgstr ""
-#: ../../include/js_strings.php:87
+#: ../../include/js_strings.php:86
msgid "Thu"
msgstr ""
-#: ../../include/js_strings.php:88
+#: ../../include/js_strings.php:87
msgid "Fri"
msgstr ""
-#: ../../include/js_strings.php:89
+#: ../../include/js_strings.php:88
msgid "Sat"
msgstr ""
-#: ../../include/js_strings.php:90
+#: ../../include/js_strings.php:89
msgctxt "calendar"
msgid "today"
msgstr ""
-#: ../../include/js_strings.php:91
+#: ../../include/js_strings.php:90
msgctxt "calendar"
msgid "month"
msgstr ""
-#: ../../include/js_strings.php:92
+#: ../../include/js_strings.php:91
msgctxt "calendar"
msgid "week"
msgstr ""
-#: ../../include/js_strings.php:93
+#: ../../include/js_strings.php:92
msgctxt "calendar"
msgid "day"
msgstr ""
-#: ../../include/js_strings.php:94
+#: ../../include/js_strings.php:93
msgctxt "calendar"
msgid "All day"
msgstr ""
-#: ../../include/js_strings.php:97
+#: ../../include/js_strings.php:96
msgid "Please stand by while your download is being prepared."
msgstr ""
-#: ../../include/js_strings.php:100
+#: ../../include/js_strings.php:99
msgid "Email address not valid"
msgstr ""
-#: ../../include/js_strings.php:101 ../../include/datetime.php:211
+#: ../../include/js_strings.php:100 ../../include/datetime.php:211
#: ../../addon/cart/submodules/orderoptions.php:334
#: ../../addon/cart/submodules/orderoptions.php:358
#: ../../addon/cart/submodules/orderoptions.php:434
@@ -2130,105 +2123,65 @@ msgstr ""
msgid "OpenWebAuth: %1$s welcomes %2$s"
msgstr ""
-#: ../../include/conversation.php:149 ../../include/text.php:2356
+#: ../../include/conversation.php:149 ../../include/text.php:2377
#: ../../addon/redphotos/redphotohelper.php:71
-#: ../../addon/diaspora/Receiver.php:1704
+#: ../../addon/diaspora/Receiver.php:1705
#: ../../Zotlabs/Module/Subthread.php:112 ../../Zotlabs/Module/Tagger.php:73
msgid "photo"
msgstr ""
-#: ../../include/conversation.php:157 ../../Zotlabs/Module/Like.php:183
+#: ../../include/conversation.php:157 ../../Zotlabs/Module/Like.php:171
msgid "channel"
msgstr ""
-#: ../../include/conversation.php:180 ../../include/markdown.php:192
-#: ../../include/text.php:2362 ../../include/bbcode.php:572
-#: ../../Zotlabs/Module/Tagger.php:81
-msgid "post"
-msgstr ""
-
-#: ../../include/conversation.php:182 ../../include/text.php:2364
-#: ../../Zotlabs/Module/Tagger.php:83
-msgid "comment"
+#: ../../include/conversation.php:180 ../../include/text.php:2385
+msgid "message"
msgstr ""
-#: ../../include/conversation.php:196 ../../addon/diaspora/Receiver.php:1639
-#: ../../Zotlabs/Module/Like.php:486
+#: ../../include/conversation.php:194 ../../addon/diaspora/Receiver.php:1640
+#: ../../Zotlabs/Module/Like.php:474
#, php-format
msgid "%1$s likes %2$s's %3$s"
msgstr ""
-#: ../../include/conversation.php:198
+#: ../../include/conversation.php:196
#, php-format
msgid "likes %1$s's %2$s"
msgstr ""
-#: ../../include/conversation.php:201 ../../Zotlabs/Module/Like.php:488
+#: ../../include/conversation.php:199 ../../Zotlabs/Module/Like.php:476
#, php-format
msgid "%1$s doesn't like %2$s's %3$s"
msgstr ""
-#: ../../include/conversation.php:202
+#: ../../include/conversation.php:200
#, php-format
msgid "doesn't like %1$s's %2$s"
msgstr ""
-#: ../../include/conversation.php:205
+#: ../../include/conversation.php:203
#, php-format
msgid "%1$s repeated %2$s's %3$s"
msgstr ""
-#: ../../include/conversation.php:206
+#: ../../include/conversation.php:204
#, php-format
msgid "repeated %1$s's %2$s"
msgstr ""
-#: ../../include/conversation.php:334 ../../Zotlabs/Lib/ThreadItem.php:474
+#: ../../include/conversation.php:332 ../../Zotlabs/Lib/ThreadItem.php:472
msgid "This is an unsaved preview"
msgstr ""
-#: ../../include/conversation.php:471 ../../Zotlabs/Module/Photos.php:1110
-msgctxt "title"
-msgid "Likes"
-msgstr ""
-
-#: ../../include/conversation.php:472 ../../Zotlabs/Module/Photos.php:1110
-msgctxt "title"
-msgid "Dislikes"
-msgstr ""
-
-#: ../../include/conversation.php:473 ../../Zotlabs/Widget/Pinned.php:73
-#: ../../Zotlabs/Module/Photos.php:1111
-msgctxt "title"
-msgid "Attending"
-msgstr ""
-
-#: ../../include/conversation.php:474 ../../Zotlabs/Widget/Pinned.php:74
-#: ../../Zotlabs/Module/Photos.php:1111
-msgctxt "title"
-msgid "Not attending"
-msgstr ""
-
-#: ../../include/conversation.php:475 ../../Zotlabs/Widget/Pinned.php:75
-#: ../../Zotlabs/Module/Photos.php:1111
-msgctxt "title"
-msgid "Might attend"
-msgstr ""
-
-#: ../../include/conversation.php:477
-msgctxt "title"
-msgid "Repeats"
-msgstr ""
-
-#: ../../include/conversation.php:546
+#: ../../include/conversation.php:533
msgid "Select"
msgstr ""
-#: ../../include/conversation.php:547 ../../include/conversation.php:608
+#: ../../include/conversation.php:534 ../../include/conversation.php:595
#: ../../addon/cards/Mod_Card_edit.php:124
#: ../../addon/articles/Mod_Article_edit.php:124 ../../Zotlabs/Lib/Apps.php:618
-#: ../../Zotlabs/Lib/ThreadItem.php:183 ../../Zotlabs/Lib/ThreadItem.php:482
-#: ../../Zotlabs/Storage/Browser.php:388
+#: ../../Zotlabs/Lib/ThreadItem.php:180 ../../Zotlabs/Lib/ThreadItem.php:480
+#: ../../Zotlabs/Storage/Browser.php:392
#: ../../Zotlabs/Module/Editwebpage.php:167
#: ../../Zotlabs/Module/Webpages.php:251 ../../Zotlabs/Module/Oauth.php:172
#: ../../Zotlabs/Module/Thing.php:302 ../../Zotlabs/Module/Tokens.php:295
@@ -2238,116 +2191,116 @@ msgstr ""
#: ../../Zotlabs/Module/Admin/Profs.php:177
#: ../../Zotlabs/Module/Admin/Channels.php:171
#: ../../Zotlabs/Module/Permcats.php:261 ../../Zotlabs/Module/Group.php:252
-#: ../../Zotlabs/Module/Blocks.php:160 ../../Zotlabs/Module/Photos.php:1173
+#: ../../Zotlabs/Module/Blocks.php:160 ../../Zotlabs/Module/Photos.php:1162
#: ../../Zotlabs/Module/Editlayout.php:138 ../../Zotlabs/Module/Cdav.php:1047
#: ../../Zotlabs/Module/Cdav.php:1385 ../../Zotlabs/Module/Oauth2.php:193
#: ../../Zotlabs/Module/Editblock.php:139
msgid "Delete"
msgstr ""
-#: ../../include/conversation.php:553 ../../Zotlabs/Lib/ThreadItem.php:248
+#: ../../include/conversation.php:540 ../../Zotlabs/Lib/ThreadItem.php:242
msgid "Toggle Star Status"
msgstr ""
-#: ../../include/conversation.php:559
+#: ../../include/conversation.php:546
msgid "Private Message"
msgstr ""
-#: ../../include/conversation.php:568 ../../Zotlabs/Lib/ThreadItem.php:258
+#: ../../include/conversation.php:555 ../../Zotlabs/Lib/ThreadItem.php:251
#: ../../Zotlabs/Widget/Pinned.php:84
msgid "Message signature validated"
msgstr ""
-#: ../../include/conversation.php:569 ../../Zotlabs/Lib/ThreadItem.php:259
+#: ../../include/conversation.php:556 ../../Zotlabs/Lib/ThreadItem.php:252
#: ../../Zotlabs/Widget/Pinned.php:85
msgid "Message signature incorrect"
msgstr ""
-#: ../../include/conversation.php:607 ../../Zotlabs/Lib/ThreadItem.php:481
+#: ../../include/conversation.php:594 ../../Zotlabs/Lib/ThreadItem.php:479
#: ../../Zotlabs/Module/Admin/Accounts.php:218
#: ../../Zotlabs/Module/Connections.php:358
#: ../../Zotlabs/Module/Connections.php:409
msgid "Approve"
msgstr ""
-#: ../../include/conversation.php:613
+#: ../../include/conversation.php:600
#, php-format
msgid "View %s's profile @ %s"
msgstr ""
-#: ../../include/conversation.php:635
+#: ../../include/conversation.php:622
msgid "Categories:"
msgstr ""
-#: ../../include/conversation.php:636
+#: ../../include/conversation.php:623
msgid "Filed under:"
msgstr ""
-#: ../../include/conversation.php:642 ../../Zotlabs/Lib/ThreadItem.php:414
-#: ../../Zotlabs/Widget/Pinned.php:127
+#: ../../include/conversation.php:629 ../../Zotlabs/Lib/ThreadItem.php:411
+#: ../../Zotlabs/Widget/Pinned.php:130
#, php-format
msgid "from %s"
msgstr ""
-#: ../../include/conversation.php:645 ../../Zotlabs/Widget/Pinned.php:130
+#: ../../include/conversation.php:632 ../../Zotlabs/Widget/Pinned.php:133
#, php-format
msgid "last edited: %s"
msgstr ""
-#: ../../include/conversation.php:646 ../../Zotlabs/Widget/Pinned.php:131
+#: ../../include/conversation.php:633 ../../Zotlabs/Widget/Pinned.php:134
#, php-format
msgid "Expires: %s"
msgstr ""
-#: ../../include/conversation.php:661 ../../addon/cards/cards.php:82
+#: ../../include/conversation.php:648 ../../addon/cards/cards.php:82
#: ../../addon/articles/articles.php:83
msgid "View in context"
msgstr ""
-#: ../../include/conversation.php:663 ../../Zotlabs/Lib/ThreadItem.php:475
-#: ../../Zotlabs/Module/Photos.php:1077
+#: ../../include/conversation.php:650 ../../Zotlabs/Lib/ThreadItem.php:473
+#: ../../Zotlabs/Module/Photos.php:1078
msgid "Please wait"
msgstr ""
-#: ../../include/conversation.php:764
+#: ../../include/conversation.php:746
msgid "remove"
msgstr ""
-#: ../../include/conversation.php:768
+#: ../../include/conversation.php:750
msgid "Loading..."
msgstr ""
-#: ../../include/conversation.php:769 ../../Zotlabs/Lib/ThreadItem.php:275
+#: ../../include/conversation.php:751 ../../Zotlabs/Lib/ThreadItem.php:268
msgid "Conversation Features"
msgstr ""
-#: ../../include/conversation.php:770
+#: ../../include/conversation.php:752
msgid "Delete Selected Items"
msgstr ""
-#: ../../include/conversation.php:806
+#: ../../include/conversation.php:788
msgid "View Source"
msgstr ""
-#: ../../include/conversation.php:816
+#: ../../include/conversation.php:798
msgid "Follow Thread"
msgstr ""
-#: ../../include/conversation.php:825
+#: ../../include/conversation.php:807
msgid "Unfollow Thread"
msgstr ""
-#: ../../include/conversation.php:902 ../../include/nav.php:127
+#: ../../include/conversation.php:884 ../../include/nav.php:127
#: ../../addon/openclipatar/openclipatar.php:58 ../../Zotlabs/Lib/Apps.php:350
#: ../../Zotlabs/Module/Connedit.php:480
msgid "View Profile"
msgstr ""
-#: ../../include/conversation.php:914 ../../Zotlabs/Module/Connedit.php:501
+#: ../../include/conversation.php:896 ../../Zotlabs/Module/Connedit.php:501
msgid "Recent Activity"
msgstr ""
-#: ../../include/conversation.php:926 ../../include/connections.php:150
+#: ../../include/conversation.php:908 ../../include/connections.php:150
#: ../../include/channel.php:1627 ../../Zotlabs/Widget/Suggestions.php:51
#: ../../Zotlabs/Widget/Follow.php:37 ../../Zotlabs/Module/Suggest.php:69
#: ../../Zotlabs/Module/Directory.php:371
@@ -2355,209 +2308,198 @@ msgstr ""
msgid "Connect"
msgstr ""
-#: ../../include/conversation.php:938
+#: ../../include/conversation.php:920
msgid "Edit Connection"
msgstr ""
-#: ../../include/conversation.php:1008
-msgid "Approve this item"
-msgstr ""
-
-#: ../../include/conversation.php:1008
-msgid "Delete this item"
-msgstr ""
-
-#: ../../include/conversation.php:1062
+#: ../../include/conversation.php:949
#, php-format
msgid "%s likes this."
msgstr ""
-#: ../../include/conversation.php:1062
+#: ../../include/conversation.php:949
#, php-format
msgid "%s doesn't like this."
msgstr ""
-#: ../../include/conversation.php:1066
+#: ../../include/conversation.php:953
#, php-format
msgid "<span %1$s>%2$d people</span> like this."
msgid_plural "<span %1$s>%2$d people</span> like this."
msgstr[0] ""
msgstr[1] ""
-#: ../../include/conversation.php:1068
+#: ../../include/conversation.php:955
#, php-format
msgid "<span %1$s>%2$d people</span> don't like this."
msgid_plural "<span %1$s>%2$d people</span> don't like this."
msgstr[0] ""
msgstr[1] ""
-#: ../../include/conversation.php:1074
+#: ../../include/conversation.php:961
msgid "and"
msgstr ""
-#: ../../include/conversation.php:1077
+#: ../../include/conversation.php:964
#, php-format
msgid ", and %d other people"
msgid_plural ", and %d other people"
msgstr[0] ""
msgstr[1] ""
-#: ../../include/conversation.php:1078
+#: ../../include/conversation.php:965
#, php-format
msgid "%s like this."
msgstr ""
-#: ../../include/conversation.php:1078
+#: ../../include/conversation.php:965
#, php-format
msgid "%s don't like this."
msgstr ""
-#: ../../include/conversation.php:1129 ../../addon/hsse/hsse.php:82
+#: ../../include/conversation.php:1014 ../../addon/hsse/hsse.php:82
msgid "Set your location"
msgstr ""
-#: ../../include/conversation.php:1130 ../../addon/hsse/hsse.php:83
+#: ../../include/conversation.php:1015 ../../addon/hsse/hsse.php:83
msgid "Clear browser location"
msgstr ""
-#: ../../include/conversation.php:1142 ../../addon/cards/Mod_Card_edit.php:94
+#: ../../include/conversation.php:1027 ../../addon/cards/Mod_Card_edit.php:94
#: ../../addon/articles/Mod_Article_edit.php:94 ../../addon/hsse/hsse.php:95
#: ../../Zotlabs/Module/Editwebpage.php:143 ../../Zotlabs/Module/Chat.php:219
#: ../../Zotlabs/Module/Editblock.php:116
msgid "Insert web link"
msgstr ""
-#: ../../include/conversation.php:1146 ../../addon/hsse/hsse.php:99
+#: ../../include/conversation.php:1031 ../../addon/hsse/hsse.php:99
+#: ../../Zotlabs/Lib/ThreadItem.php:816
msgid "Embed (existing) photo from your photo albums"
msgstr ""
-#: ../../include/conversation.php:1179 ../../addon/hsse/hsse.php:134
+#: ../../include/conversation.php:1064 ../../addon/hsse/hsse.php:134
#: ../../Zotlabs/Module/Chat.php:217
msgid "Please enter a link URL:"
msgstr ""
-#: ../../include/conversation.php:1180 ../../addon/hsse/hsse.php:135
+#: ../../include/conversation.php:1065 ../../addon/hsse/hsse.php:135
msgid "Tag term:"
msgstr ""
-#: ../../include/conversation.php:1181 ../../addon/hsse/hsse.php:136
+#: ../../include/conversation.php:1066 ../../addon/hsse/hsse.php:136
msgid "Where are you right now?"
msgstr ""
-#: ../../include/conversation.php:1184 ../../addon/wiki/Mod_Wiki.php:400
+#: ../../include/conversation.php:1069 ../../addon/wiki/Mod_Wiki.php:400
#: ../../addon/hsse/hsse.php:139 ../../Zotlabs/Module/Cover_photo.php:388
#: ../../Zotlabs/Module/Profile_photo.php:555
msgid "Choose images to embed"
msgstr ""
-#: ../../include/conversation.php:1185 ../../addon/wiki/Mod_Wiki.php:401
+#: ../../include/conversation.php:1070 ../../addon/wiki/Mod_Wiki.php:401
#: ../../addon/hsse/hsse.php:140 ../../Zotlabs/Module/Cover_photo.php:389
#: ../../Zotlabs/Module/Profile_photo.php:556
msgid "Choose an album"
msgstr ""
-#: ../../include/conversation.php:1186 ../../addon/hsse/hsse.php:141
+#: ../../include/conversation.php:1071 ../../addon/hsse/hsse.php:141
msgid "Choose a different album..."
msgstr ""
-#: ../../include/conversation.php:1187 ../../addon/wiki/Mod_Wiki.php:403
+#: ../../include/conversation.php:1072 ../../addon/wiki/Mod_Wiki.php:403
#: ../../addon/hsse/hsse.php:142 ../../Zotlabs/Module/Cover_photo.php:391
#: ../../Zotlabs/Module/Profile_photo.php:558
msgid "Error getting album list"
msgstr ""
-#: ../../include/conversation.php:1188 ../../addon/wiki/Mod_Wiki.php:404
+#: ../../include/conversation.php:1073 ../../addon/wiki/Mod_Wiki.php:404
#: ../../addon/hsse/hsse.php:143 ../../Zotlabs/Module/Cover_photo.php:392
#: ../../Zotlabs/Module/Profile_photo.php:559
msgid "Error getting photo link"
msgstr ""
-#: ../../include/conversation.php:1189 ../../addon/wiki/Mod_Wiki.php:405
+#: ../../include/conversation.php:1074 ../../addon/wiki/Mod_Wiki.php:405
#: ../../addon/hsse/hsse.php:144 ../../Zotlabs/Module/Cover_photo.php:393
#: ../../Zotlabs/Module/Profile_photo.php:560
msgid "Error getting album"
msgstr ""
-#: ../../include/conversation.php:1190 ../../addon/hsse/hsse.php:145
+#: ../../include/conversation.php:1075 ../../addon/hsse/hsse.php:145
msgid "Comments enabled"
msgstr ""
-#: ../../include/conversation.php:1191 ../../addon/hsse/hsse.php:146
-#: ../../Zotlabs/Lib/ThreadItem.php:472
+#: ../../include/conversation.php:1076 ../../addon/hsse/hsse.php:146
+#: ../../Zotlabs/Lib/ThreadItem.php:470
msgid "Comments disabled"
msgstr ""
-#: ../../include/conversation.php:1192
+#: ../../include/conversation.php:1077
msgid "Confirm delete"
msgstr ""
-#: ../../include/conversation.php:1209 ../../addon/hsse/hsse.php:153
-#: ../../Zotlabs/Lib/ThreadItem.php:810 ../../Zotlabs/Module/Webpages.php:256
-#: ../../Zotlabs/Module/Photos.php:1096
+#: ../../include/conversation.php:1094 ../../addon/hsse/hsse.php:153
+#: ../../Zotlabs/Lib/ThreadItem.php:820 ../../Zotlabs/Module/Webpages.php:256
+#: ../../Zotlabs/Module/Photos.php:1097
msgid "Preview"
msgstr ""
-#: ../../include/conversation.php:1242 ../../addon/wiki/Mod_Wiki.php:304
-#: ../../addon/hsse/hsse.php:186 ../../Zotlabs/Lib/ThreadItem.php:305
-#: ../../Zotlabs/Widget/Cdav.php:142 ../../Zotlabs/Module/Webpages.php:250
-#: ../../Zotlabs/Module/Layouts.php:192 ../../Zotlabs/Module/Blocks.php:159
-#: ../../Zotlabs/Module/Photos.php:1076
-msgid "Share"
+#: ../../include/conversation.php:1128
+msgid "Start a conversation"
msgstr ""
-#: ../../include/conversation.php:1251 ../../addon/hsse/hsse.php:195
+#: ../../include/conversation.php:1136 ../../addon/hsse/hsse.php:195
msgid "Page link name"
msgstr ""
-#: ../../include/conversation.php:1254 ../../addon/hsse/hsse.php:198
+#: ../../include/conversation.php:1139 ../../addon/hsse/hsse.php:198
msgid "Post as"
msgstr ""
-#: ../../include/conversation.php:1256 ../../addon/hsse/hsse.php:200
-#: ../../Zotlabs/Lib/ThreadItem.php:800
+#: ../../include/conversation.php:1141 ../../addon/hsse/hsse.php:200
+#: ../../Zotlabs/Lib/ThreadItem.php:810
msgid "Bold"
msgstr ""
-#: ../../include/conversation.php:1257 ../../addon/hsse/hsse.php:201
-#: ../../Zotlabs/Lib/ThreadItem.php:801
+#: ../../include/conversation.php:1142 ../../addon/hsse/hsse.php:201
+#: ../../Zotlabs/Lib/ThreadItem.php:811
msgid "Italic"
msgstr ""
-#: ../../include/conversation.php:1258 ../../Zotlabs/Lib/ThreadItem.php:802
+#: ../../include/conversation.php:1143 ../../Zotlabs/Lib/ThreadItem.php:812
msgid "Highlight selected text"
msgstr ""
-#: ../../include/conversation.php:1259 ../../addon/hsse/hsse.php:202
-#: ../../Zotlabs/Lib/ThreadItem.php:803
+#: ../../include/conversation.php:1144 ../../addon/hsse/hsse.php:202
+#: ../../Zotlabs/Lib/ThreadItem.php:813
msgid "Underline"
msgstr ""
-#: ../../include/conversation.php:1260 ../../addon/hsse/hsse.php:203
-#: ../../Zotlabs/Lib/ThreadItem.php:804
+#: ../../include/conversation.php:1145 ../../addon/hsse/hsse.php:203
+#: ../../Zotlabs/Lib/ThreadItem.php:814
msgid "Quote"
msgstr ""
-#: ../../include/conversation.php:1261 ../../addon/hsse/hsse.php:204
-#: ../../Zotlabs/Lib/ThreadItem.php:805
+#: ../../include/conversation.php:1146 ../../addon/hsse/hsse.php:204
+#: ../../Zotlabs/Lib/ThreadItem.php:815
msgid "Code"
msgstr ""
-#: ../../include/conversation.php:1262 ../../addon/hsse/hsse.php:205
-#: ../../Zotlabs/Lib/ThreadItem.php:807
+#: ../../include/conversation.php:1147 ../../addon/hsse/hsse.php:205
+#: ../../Zotlabs/Lib/ThreadItem.php:817
msgid "Attach/Upload file"
msgstr ""
-#: ../../include/conversation.php:1265 ../../addon/wiki/Mod_Wiki.php:397
+#: ../../include/conversation.php:1150 ../../addon/wiki/Mod_Wiki.php:397
#: ../../addon/hsse/hsse.php:208
msgid "Embed an image from your albums"
msgstr ""
-#: ../../include/conversation.php:1266 ../../include/conversation.php:1321
+#: ../../include/conversation.php:1151 ../../include/conversation.php:1206
#: ../../addon/cards/Mod_Card_edit.php:126
#: ../../addon/articles/Mod_Article_edit.php:126
#: ../../addon/wiki/Mod_Wiki.php:365 ../../addon/wiki/Mod_Wiki.php:398
#: ../../addon/hsse/hsse.php:209 ../../addon/hsse/hsse.php:258
-#: ../../Zotlabs/Storage/Browser.php:387
+#: ../../Zotlabs/Storage/Browser.php:391
#: ../../Zotlabs/Module/Cover_photo.php:386
#: ../../Zotlabs/Module/Editwebpage.php:169 ../../Zotlabs/Module/Oauth.php:111
#: ../../Zotlabs/Module/Oauth.php:136 ../../Zotlabs/Module/Connedit.php:750
@@ -2571,118 +2513,132 @@ msgstr ""
msgid "Cancel"
msgstr ""
-#: ../../include/conversation.php:1267 ../../include/conversation.php:1320
+#: ../../include/conversation.php:1152 ../../include/conversation.php:1205
#: ../../addon/wiki/Mod_Wiki.php:399 ../../addon/hsse/hsse.php:210
#: ../../addon/hsse/hsse.php:257 ../../Zotlabs/Module/Cover_photo.php:387
#: ../../Zotlabs/Module/Profile_photo.php:554
msgid "OK"
msgstr ""
-#: ../../include/conversation.php:1269 ../../addon/hsse/hsse.php:212
+#: ../../include/conversation.php:1154 ../../addon/hsse/hsse.php:212
msgid "Toggle voting"
msgstr ""
-#: ../../include/conversation.php:1270
+#: ../../include/conversation.php:1155
msgid "Toggle poll"
msgstr ""
-#: ../../include/conversation.php:1271
+#: ../../include/conversation.php:1156
msgid "Option"
msgstr ""
-#: ../../include/conversation.php:1272
+#: ../../include/conversation.php:1157
msgid "Add option"
msgstr ""
-#: ../../include/conversation.php:1273
+#: ../../include/conversation.php:1158
msgid "Minutes"
msgstr ""
-#: ../../include/conversation.php:1273
+#: ../../include/conversation.php:1158
msgid "Hours"
msgstr ""
-#: ../../include/conversation.php:1273
+#: ../../include/conversation.php:1158
msgid "Days"
msgstr ""
-#: ../../include/conversation.php:1274
+#: ../../include/conversation.php:1159
msgid "Allow multiple answers"
msgstr ""
-#: ../../include/conversation.php:1276 ../../addon/hsse/hsse.php:215
+#: ../../include/conversation.php:1161 ../../addon/hsse/hsse.php:215
msgid "Disable comments"
msgstr ""
-#: ../../include/conversation.php:1277 ../../addon/hsse/hsse.php:216
+#: ../../include/conversation.php:1162 ../../addon/hsse/hsse.php:216
msgid "Toggle comments"
msgstr ""
-#: ../../include/conversation.php:1283 ../../addon/cards/Mod_Card_edit.php:111
+#: ../../include/conversation.php:1168 ../../addon/cards/Mod_Card_edit.php:111
#: ../../addon/articles/Mod_Article_edit.php:111 ../../addon/hsse/hsse.php:221
-#: ../../Zotlabs/Module/Photos.php:667 ../../Zotlabs/Module/Photos.php:1042
+#: ../../Zotlabs/Module/Photos.php:669 ../../Zotlabs/Module/Photos.php:1043
#: ../../Zotlabs/Module/Editblock.php:129
msgid "Title (optional)"
msgstr ""
-#: ../../include/conversation.php:1284
+#: ../../include/conversation.php:1169
msgid "Summary (optional)"
msgstr ""
-#: ../../include/conversation.php:1287 ../../addon/hsse/hsse.php:224
+#: ../../include/conversation.php:1172 ../../addon/hsse/hsse.php:224
msgid "Categories (optional, comma-separated list)"
msgstr ""
-#: ../../include/conversation.php:1288 ../../addon/hsse/hsse.php:225
+#: ../../include/conversation.php:1173 ../../addon/hsse/hsse.php:225
msgid "Permission settings"
msgstr ""
-#: ../../include/conversation.php:1310 ../../addon/hsse/hsse.php:247
+#: ../../include/conversation.php:1195 ../../addon/hsse/hsse.php:247
msgid "Other networks and post services"
msgstr ""
-#: ../../include/conversation.php:1313 ../../addon/hsse/hsse.php:250
+#: ../../include/conversation.php:1198 ../../addon/hsse/hsse.php:250
msgid "Set expiration date"
msgstr ""
-#: ../../include/conversation.php:1316 ../../addon/hsse/hsse.php:253
+#: ../../include/conversation.php:1201 ../../addon/hsse/hsse.php:253
msgid "Set publish date"
msgstr ""
-#: ../../include/conversation.php:1318 ../../addon/hsse/hsse.php:255
-#: ../../Zotlabs/Lib/ThreadItem.php:813 ../../Zotlabs/Module/Chat.php:218
+#: ../../include/conversation.php:1203 ../../addon/hsse/hsse.php:255
+#: ../../Zotlabs/Lib/ThreadItem.php:823 ../../Zotlabs/Module/Chat.php:218
msgid "Encrypt text"
msgstr ""
-#: ../../include/conversation.php:1564
+#: ../../include/conversation.php:1443
msgctxt "noun"
msgid "Repeat"
msgid_plural "Repeats"
msgstr[0] ""
msgstr[1] ""
-#: ../../include/conversation.php:1567 ../../Zotlabs/Module/Photos.php:1134
+#: ../../include/conversation.php:1446 ../../Zotlabs/Module/Photos.php:1123
msgctxt "noun"
msgid "Dislike"
msgid_plural "Dislikes"
msgstr[0] ""
msgstr[1] ""
-#: ../../include/conversation.php:1570
+#: ../../include/conversation.php:1449
+msgctxt "noun"
+msgid "Comment"
+msgid_plural "Comments"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../../include/conversation.php:1449
+msgctxt "noun"
+msgid "Reply"
+msgid_plural "Replies"
+msgstr[0] ""
+msgstr[1] ""
+
+#: ../../include/conversation.php:1452
msgctxt "noun"
msgid "Attending"
msgid_plural "Attending"
msgstr[0] ""
msgstr[1] ""
-#: ../../include/conversation.php:1573
+#: ../../include/conversation.php:1455
msgctxt "noun"
-msgid "Not Attending"
-msgid_plural "Not Attending"
+msgid "Not attending"
+msgid_plural "Not attending"
msgstr[0] ""
msgstr[1] ""
-#: ../../include/conversation.php:1576
+#: ../../include/conversation.php:1458
msgctxt "noun"
msgid "Undecided"
msgid_plural "Undecided"
@@ -2721,7 +2677,7 @@ msgid "Account/Channel Settings"
msgstr ""
#: ../../include/nav.php:124 ../../include/nav.php:154
-#: ../../include/nav.php:175 ../../boot.php:1753
+#: ../../include/nav.php:175 ../../boot.php:1760
msgid "Logout"
msgstr ""
@@ -2751,8 +2707,8 @@ msgstr ""
msgid "Edit your profile"
msgstr ""
-#: ../../include/nav.php:139 ../../include/nav.php:143 ../../boot.php:1754
-#: ../../Zotlabs/Lib/Apps.php:342
+#: ../../include/nav.php:139 ../../include/nav.php:143 ../../boot.php:1761
+#: ../../Zotlabs/Lib/Apps.php:342 ../../Zotlabs/Module/Login.php:15
msgid "Login"
msgstr ""
@@ -2768,7 +2724,7 @@ msgstr ""
msgid "Log me out of this site"
msgstr ""
-#: ../../include/nav.php:180 ../../boot.php:1731
+#: ../../include/nav.php:180 ../../boot.php:1738
#: ../../Zotlabs/Module/Register.php:543
msgid "Register"
msgstr ""
@@ -2807,7 +2763,7 @@ msgid "Site Setup and Configuration"
msgstr ""
#: ../../include/nav.php:345 ../../Zotlabs/Widget/Notifications.php:175
-#: ../../Zotlabs/Widget/Messages.php:50 ../../Zotlabs/Module/Defperms.php:254
+#: ../../Zotlabs/Widget/Messages.php:52 ../../Zotlabs/Module/Defperms.php:254
#: ../../Zotlabs/Module/New_channel.php:158
#: ../../Zotlabs/Module/New_channel.php:165
msgid "Loading"
@@ -2842,6 +2798,7 @@ msgid "Featured Apps"
msgstr ""
#: ../../include/nav.php:447 ../../Zotlabs/Lib/Apps.php:349
+#: ../../Zotlabs/Widget/Notifications.php:43
#: ../../Zotlabs/Module/Admin/Channels.php:176
msgid "Channel"
msgstr ""
@@ -2865,7 +2822,7 @@ msgstr ""
#: ../../include/nav.php:478 ../../Zotlabs/Lib/Apps.php:346
#: ../../Zotlabs/Widget/Notifications.php:108
#: ../../Zotlabs/Widget/Channel_activities.php:125
-#: ../../Zotlabs/Storage/Browser.php:351 ../../Zotlabs/Module/Fbrowser.php:87
+#: ../../Zotlabs/Storage/Browser.php:355 ../../Zotlabs/Module/Fbrowser.php:87
msgid "Files"
msgstr ""
@@ -2920,7 +2877,7 @@ msgstr ""
msgid "YYYY-MM-DD or MM-DD"
msgstr ""
-#: ../../include/datetime.php:238 ../../boot.php:2768
+#: ../../include/datetime.php:238 ../../boot.php:2680
msgid "never"
msgstr ""
@@ -3033,8 +2990,8 @@ msgctxt "photo_upload"
msgid "%1$s posted %2$s to %3$s"
msgstr ""
-#: ../../include/photos.php:748 ../../Zotlabs/Module/Photos.php:1339
-#: ../../Zotlabs/Module/Photos.php:1352 ../../Zotlabs/Module/Photos.php:1353
+#: ../../include/photos.php:748 ../../Zotlabs/Module/Photos.php:1333
+#: ../../Zotlabs/Module/Photos.php:1346 ../../Zotlabs/Module/Photos.php:1347
msgid "Recent Photos"
msgstr ""
@@ -3066,27 +3023,27 @@ msgstr ""
msgid "content-type: "
msgstr ""
-#: ../../include/network.php:1775 ../../include/network.php:1776
+#: ../../include/network.php:1774 ../../include/network.php:1775
msgid "Friendica"
msgstr ""
-#: ../../include/network.php:1777
+#: ../../include/network.php:1776
msgid "OStatus"
msgstr ""
-#: ../../include/network.php:1778
+#: ../../include/network.php:1777
msgid "GNU-Social"
msgstr ""
-#: ../../include/network.php:1779
+#: ../../include/network.php:1778
msgid "RSS/Atom"
msgstr ""
-#: ../../include/network.php:1780
+#: ../../include/network.php:1779
msgid "ActivityPub"
msgstr ""
-#: ../../include/network.php:1781 ../../addon/openid/MysqlProvider.php:56
+#: ../../include/network.php:1780 ../../addon/openid/MysqlProvider.php:56
#: ../../addon/openid/MysqlProvider.php:57 ../../addon/redred/Mod_Redred.php:69
#: ../../addon/rtof/Mod_Rtof.php:55 ../../Zotlabs/Module/Connedit.php:736
#: ../../Zotlabs/Module/Admin/Accounts.php:216
@@ -3095,27 +3052,27 @@ msgstr ""
msgid "Email"
msgstr ""
-#: ../../include/network.php:1782
+#: ../../include/network.php:1781
msgid "Diaspora"
msgstr ""
-#: ../../include/network.php:1783
+#: ../../include/network.php:1782
msgid "Facebook"
msgstr ""
-#: ../../include/network.php:1784
+#: ../../include/network.php:1783
msgid "Zot"
msgstr ""
-#: ../../include/network.php:1785
+#: ../../include/network.php:1784
msgid "LinkedIn"
msgstr ""
-#: ../../include/network.php:1786
+#: ../../include/network.php:1785
msgid "XMPP/IM"
msgstr ""
-#: ../../include/network.php:1787
+#: ../../include/network.php:1786
msgid "MySpace"
msgstr ""
@@ -3124,23 +3081,20 @@ msgstr ""
msgid "%1$s wrote the following %2$s %3$s"
msgstr ""
-#: ../../include/markdown.php:262 ../../include/bbcode.php:661
-msgid "spoiler"
-msgstr ""
-
-#: ../../include/language.php:423 ../../include/text.php:2197
-msgid "default"
+#: ../../include/markdown.php:192 ../../include/bbcode.php:572
+#: ../../Zotlabs/Module/Tagger.php:81
+msgid "post"
msgstr ""
-#: ../../include/language.php:436
-msgid "Select an alternate language"
+#: ../../include/markdown.php:262 ../../include/bbcode.php:661
+msgid "spoiler"
msgstr ""
#: ../../include/menu.php:120 ../../include/channel.php:1541
#: ../../include/channel.php:1545 ../../addon/cards/cards.php:74
#: ../../addon/articles/articles.php:75 ../../addon/wiki/Mod_Wiki.php:214
#: ../../addon/wiki/Mod_Wiki.php:381 ../../Zotlabs/Lib/Apps.php:617
-#: ../../Zotlabs/Lib/ThreadItem.php:162 ../../Zotlabs/Widget/Cdav.php:144
+#: ../../Zotlabs/Lib/ThreadItem.php:159 ../../Zotlabs/Widget/Cdav.php:144
#: ../../Zotlabs/Widget/Cdav.php:181 ../../Zotlabs/Module/Editwebpage.php:142
#: ../../Zotlabs/Module/Webpages.php:249 ../../Zotlabs/Module/Oauth.php:171
#: ../../Zotlabs/Module/Thing.php:301 ../../Zotlabs/Module/Layouts.php:191
@@ -3169,7 +3123,6 @@ msgstr ""
#: ../../include/acl_selectors.php:125
#: ../../Zotlabs/Widget/Notifications.php:131
-#: ../../Zotlabs/Widget/Notifications.php:132
#: ../../Zotlabs/Widget/Activity_filter.php:130
#: ../../Zotlabs/Widget/Forums.php:77
msgid "Forums"
@@ -3203,17 +3156,16 @@ msgstr ""
msgid "Don't allow"
msgstr ""
-#: ../../include/acl_selectors.php:154
-#: ../../addon/flashcards/Mod_Flashcards.php:261
-#: ../../Zotlabs/Module/Thing.php:357 ../../Zotlabs/Module/Thing.php:407
-#: ../../Zotlabs/Module/Filestorage.php:195 ../../Zotlabs/Module/Photos.php:671
-#: ../../Zotlabs/Module/Photos.php:1045 ../../Zotlabs/Module/Chat.php:240
+#: ../../include/acl_selectors.php:154 ../../Zotlabs/Module/Thing.php:357
+#: ../../Zotlabs/Module/Thing.php:407 ../../Zotlabs/Module/Filestorage.php:195
+#: ../../Zotlabs/Module/Photos.php:673 ../../Zotlabs/Module/Photos.php:1046
+#: ../../Zotlabs/Module/Chat.php:240
msgid "Permissions"
msgstr ""
-#: ../../include/acl_selectors.php:156 ../../Zotlabs/Lib/ThreadItem.php:470
-#: ../../Zotlabs/Widget/Pinned.php:153 ../../Zotlabs/Storage/Browser.php:414
-#: ../../Zotlabs/Module/Photos.php:1266
+#: ../../include/acl_selectors.php:156 ../../Zotlabs/Lib/ThreadItem.php:467
+#: ../../Zotlabs/Widget/Pinned.php:156 ../../Zotlabs/Storage/Browser.php:418
+#: ../../Zotlabs/Module/Photos.php:1260
msgid "Close"
msgstr ""
@@ -3294,33 +3246,33 @@ msgstr ""
msgid "Save"
msgstr ""
-#: ../../include/text.php:1503
+#: ../../include/text.php:1525
msgid "May"
msgstr ""
-#: ../../include/text.php:1577
+#: ../../include/text.php:1599
msgid "Unknown attachment"
msgstr ""
-#: ../../include/text.php:1580 ../../Zotlabs/Storage/Browser.php:383
+#: ../../include/text.php:1602 ../../Zotlabs/Storage/Browser.php:387
#: ../../Zotlabs/Module/Sharedwithme.php:109
msgid "Size"
msgstr ""
-#: ../../include/text.php:1621
+#: ../../include/text.php:1643
msgid "remove category"
msgstr ""
-#: ../../include/text.php:1699
+#: ../../include/text.php:1721
msgid "remove from file"
msgstr ""
-#: ../../include/text.php:1886
+#: ../../include/text.php:1908
msgid "Download binary/encrypted content"
msgstr ""
-#: ../../include/text.php:1944 ../../include/text.php:1953
-#: ../../include/text.php:1980 ../../include/text.php:1989
+#: ../../include/text.php:1966 ../../include/text.php:1975
+#: ../../include/text.php:2002 ../../include/text.php:2011
#, php-format
msgctxt "noun"
msgid "%d Vote"
@@ -3328,7 +3280,7 @@ msgid_plural "%d Votes"
msgstr[0] ""
msgstr[1] ""
-#: ../../include/text.php:1996
+#: ../../include/text.php:2018
#, php-format
msgctxt "noun"
msgid "%d Vote in total"
@@ -3336,157 +3288,165 @@ msgid_plural "%d Votes in total"
msgstr[0] ""
msgstr[1] ""
-#: ../../include/text.php:2002
+#: ../../include/text.php:2024
msgid "Poll has ended"
msgstr ""
-#: ../../include/text.php:2005
+#: ../../include/text.php:2027
#, php-format
-msgid "Poll ends in %s"
+msgid "Poll ends %s"
msgstr ""
-#: ../../include/text.php:2012 ../../Zotlabs/Lib/ThreadItem.php:430
+#: ../../include/text.php:2034 ../../Zotlabs/Lib/ThreadItem.php:427
msgid "Vote"
msgstr ""
-#: ../../include/text.php:2172
+#: ../../include/text.php:2193
msgid "Link to Source"
msgstr ""
-#: ../../include/text.php:2205
+#: ../../include/text.php:2218 ../../Zotlabs/Module/Lang.php:75
+msgid "default"
+msgstr ""
+
+#: ../../include/text.php:2226
msgid "Page layout"
msgstr ""
-#: ../../include/text.php:2205
+#: ../../include/text.php:2226
msgid "You can create your own with the layouts tool"
msgstr ""
-#: ../../include/text.php:2215 ../../addon/wiki/Mod_Wiki.php:220
+#: ../../include/text.php:2236 ../../addon/wiki/Mod_Wiki.php:220
#: ../../addon/wiki/Mod_Wiki.php:368 ../../addon/wiki/Mod_Wiki.php:903
#: ../../addon/wiki/Widget/Wiki_pages.php:68
msgid "BBcode"
msgstr ""
-#: ../../include/text.php:2216
+#: ../../include/text.php:2237
msgid "HTML"
msgstr ""
-#: ../../include/text.php:2217 ../../addon/wiki/Mod_Wiki.php:220
+#: ../../include/text.php:2238 ../../addon/wiki/Mod_Wiki.php:220
#: ../../addon/wiki/Mod_Wiki.php:368 ../../addon/wiki/Mod_Wiki.php:903
#: ../../addon/wiki/Widget/Wiki_pages.php:68 ../../addon/mdpost/mdpost.php:41
msgid "Markdown"
msgstr ""
-#: ../../include/text.php:2218 ../../addon/wiki/Mod_Wiki.php:220
+#: ../../include/text.php:2239 ../../addon/wiki/Mod_Wiki.php:220
#: ../../addon/wiki/Mod_Wiki.php:903 ../../addon/wiki/Widget/Wiki_pages.php:68
msgid "Text"
msgstr ""
-#: ../../include/text.php:2219
+#: ../../include/text.php:2240
msgid "Comanche Layout"
msgstr ""
-#: ../../include/text.php:2224
+#: ../../include/text.php:2245
msgid "PHP"
msgstr ""
-#: ../../include/text.php:2236
+#: ../../include/text.php:2257
msgid "Page content type"
msgstr ""
-#: ../../include/text.php:2369
+#: ../../include/text.php:2383
+msgid "conversation"
+msgstr ""
+
+#: ../../include/text.php:2390
msgid "activity"
msgstr ""
-#: ../../include/text.php:2372
+#: ../../include/text.php:2394
msgid "poll"
msgstr ""
-#: ../../include/text.php:2485
+#: ../../include/text.php:2508
msgid "a-z, 0-9, -, and _ only"
msgstr ""
-#: ../../include/text.php:2793
+#: ../../include/text.php:2818
msgid "Design Tools"
msgstr ""
-#: ../../include/text.php:2796 ../../Zotlabs/Module/Blocks.php:152
+#: ../../include/text.php:2821 ../../Zotlabs/Module/Blocks.php:152
msgid "Blocks"
msgstr ""
-#: ../../include/text.php:2797 ../../Zotlabs/Module/Menu.php:171
+#: ../../include/text.php:2822 ../../Zotlabs/Module/Menu.php:171
msgid "Menus"
msgstr ""
-#: ../../include/text.php:2798 ../../Zotlabs/Module/Layouts.php:182
+#: ../../include/text.php:2823 ../../Zotlabs/Module/Layouts.php:182
msgid "Layouts"
msgstr ""
-#: ../../include/text.php:2799
+#: ../../include/text.php:2824
msgid "Pages"
msgstr ""
-#: ../../include/text.php:2811
+#: ../../include/text.php:2836
msgid "Import"
msgstr ""
-#: ../../include/text.php:2812
+#: ../../include/text.php:2837
msgid "Import website..."
msgstr ""
-#: ../../include/text.php:2813
+#: ../../include/text.php:2838
msgid "Select folder to import"
msgstr ""
-#: ../../include/text.php:2814
+#: ../../include/text.php:2839
msgid "Import from a zipped folder:"
msgstr ""
-#: ../../include/text.php:2815
+#: ../../include/text.php:2840
msgid "Import from cloud files:"
msgstr ""
-#: ../../include/text.php:2816
+#: ../../include/text.php:2841
msgid "/cloud/channel/path/to/folder"
msgstr ""
-#: ../../include/text.php:2817
+#: ../../include/text.php:2842
msgid "Enter path to website files"
msgstr ""
-#: ../../include/text.php:2818
+#: ../../include/text.php:2843
msgid "Select folder"
msgstr ""
-#: ../../include/text.php:2819
+#: ../../include/text.php:2844
msgid "Export website..."
msgstr ""
-#: ../../include/text.php:2820
+#: ../../include/text.php:2845
msgid "Export to a zip file"
msgstr ""
-#: ../../include/text.php:2821
+#: ../../include/text.php:2846
msgid "website.zip"
msgstr ""
-#: ../../include/text.php:2822
+#: ../../include/text.php:2847
msgid "Enter a name for the zip file."
msgstr ""
-#: ../../include/text.php:2823
+#: ../../include/text.php:2848
msgid "Export to cloud files"
msgstr ""
-#: ../../include/text.php:2824
+#: ../../include/text.php:2849
msgid "/path/to/export/folder"
msgstr ""
-#: ../../include/text.php:2825
+#: ../../include/text.php:2850
msgid "Enter a path to a cloud files destination."
msgstr ""
-#: ../../include/text.php:2826
+#: ../../include/text.php:2851
msgid "Specify folder"
msgstr ""
@@ -3539,6 +3499,7 @@ msgstr ""
#: ../../include/channel.php:1438 ../../addon/cards/Mod_Cards.php:42
#: ../../addon/articles/Mod_Articles.php:46
+#: ../../addon/flashcards/Mod_Flashcards.php:74
#: ../../addon/gallery/Mod_Gallery.php:49
#: ../../Zotlabs/Module/Editwebpage.php:32 ../../Zotlabs/Module/Webpages.php:39
#: ../../Zotlabs/Module/Connect.php:17 ../../Zotlabs/Module/Filestorage.php:59
@@ -3713,7 +3674,7 @@ msgstr ""
msgid "cover photo"
msgstr ""
-#: ../../include/channel.php:2628 ../../boot.php:1755
+#: ../../include/channel.php:2628 ../../boot.php:1762
#: ../../Zotlabs/Module/Rmagic.php:96
msgid "Remote Authentication"
msgstr ""
@@ -3732,7 +3693,7 @@ msgid "Account '%s' deleted"
msgstr ""
#: ../../include/bbcode.php:234 ../../include/bbcode.php:994
-#: ../../include/bbcode.php:1663 ../../include/bbcode.php:1671
+#: ../../include/bbcode.php:1676 ../../include/bbcode.php:1684
msgid "Image/photo"
msgstr ""
@@ -3793,7 +3754,7 @@ msgstr ""
msgid "Different viewers will see this text differently"
msgstr ""
-#: ../../include/bbcode.php:1639
+#: ../../include/bbcode.php:1652
msgid "$1 wrote:"
msgstr ""
@@ -4056,7 +4017,7 @@ msgid "Last Name"
msgstr ""
#: ../../addon/openid/MysqlProvider.php:54 ../../addon/redred/Mod_Redred.php:73
-#: ../../boot.php:1748
+#: ../../boot.php:1755
msgid "Nickname"
msgstr ""
@@ -4306,9 +4267,9 @@ msgid "$Projectname"
msgstr ""
#: ../../addon/opensearch/opensearch.php:42 ../../Zotlabs/Lib/Enotify.php:67
-#: ../../Zotlabs/Module/Home.php:88 ../../Zotlabs/Module/Home.php:96
-#: ../../Zotlabs/Module/Invite.php:239 ../../Zotlabs/Module/Invite.php:508
-#: ../../Zotlabs/Module/Invite.php:522
+#: ../../Zotlabs/Module/Home.php:92 ../../Zotlabs/Module/Home.php:100
+#: ../../Zotlabs/Module/Invite.php:239 ../../Zotlabs/Module/Invite.php:491
+#: ../../Zotlabs/Module/Invite.php:505
msgid "$Projectname"
msgstr ""
@@ -4545,7 +4506,7 @@ msgstr ""
msgid "Channel is required."
msgstr ""
-#: ../../addon/redred/Mod_Redred.php:29 ../../Zotlabs/Module/Network.php:333
+#: ../../addon/redred/Mod_Redred.php:29 ../../Zotlabs/Module/Network.php:317
msgid "Invalid channel."
msgstr ""
@@ -4581,7 +4542,7 @@ msgstr ""
msgid "Hubzilla Crosspost Connector"
msgstr ""
-#: ../../addon/cards/cards.php:48 ../../addon/cards/cards.php:160
+#: ../../addon/cards/cards.php:48 ../../addon/cards/cards.php:161
#: ../../addon/cards/Mod_Cards.php:209 ../../Zotlabs/Lib/Apps.php:332
msgid "Cards"
msgstr ""
@@ -4594,7 +4555,7 @@ msgstr ""
#: ../../addon/wiki/Lib/NativeWikiPage.php:549
#: ../../Zotlabs/Module/Page.php:136 ../../Zotlabs/Module/Display.php:155
#: ../../Zotlabs/Module/Block.php:77 ../../Zotlabs/Module/Help.php:173
-#: ../../Zotlabs/Web/Router.php:188
+#: ../../Zotlabs/Web/Router.php:194
msgid "Page not found."
msgstr ""
@@ -4688,41 +4649,29 @@ msgstr ""
msgid "Your test account is about to expire."
msgstr ""
-#: ../../addon/pubcrawl/Mod_Pubcrawl.php:28
-msgid "ActivityPub Protocol Settings updated."
-msgstr ""
-
-#: ../../addon/pubcrawl/Mod_Pubcrawl.php:44
+#: ../../addon/pubcrawl/Mod_Pubcrawl.php:24
msgid ""
"The activitypub protocol does not support location independence. Connections "
"you make within that network may be unreachable from alternate channel "
"locations."
msgstr ""
-#: ../../addon/pubcrawl/Mod_Pubcrawl.php:50
-msgid "Send activities of type note instead of article"
-msgstr ""
-
-#: ../../addon/pubcrawl/Mod_Pubcrawl.php:50
-msgid "Microblog services such as Mastodon do not properly support articles"
-msgstr ""
-
-#: ../../addon/pubcrawl/Mod_Pubcrawl.php:58
+#: ../../addon/pubcrawl/Mod_Pubcrawl.php:31
msgid "Activitypub Protocol"
msgstr ""
-#: ../../addon/pubcrawl/pubcrawl.php:1099 ../../addon/diaspora/diaspora.php:415
+#: ../../addon/pubcrawl/pubcrawl.php:1099 ../../addon/diaspora/diaspora.php:417
#: ../../Zotlabs/Module/Contactedit.php:494
msgid "Refresh failed"
msgstr ""
-#: ../../addon/pubcrawl/pubcrawl.php:1106 ../../addon/diaspora/diaspora.php:420
+#: ../../addon/pubcrawl/pubcrawl.php:1106 ../../addon/diaspora/diaspora.php:422
#: ../../Zotlabs/Module/Contactedit.php:491
msgid "Refresh succeeded"
msgstr ""
#: ../../addon/bookmarker/bookmarker.php:38
-#: ../../Zotlabs/Lib/ThreadItem.php:458
+#: ../../Zotlabs/Lib/ThreadItem.php:455
msgid "Save Bookmarks"
msgstr ""
@@ -4791,7 +4740,7 @@ msgstr ""
msgid "Diaspora relay could not be imported"
msgstr ""
-#: ../../addon/diaspora/diaspora.php:1110
+#: ../../addon/diaspora/diaspora.php:1112
msgid "No subject"
msgstr ""
@@ -4816,27 +4765,27 @@ msgstr ""
msgid "$projectname"
msgstr ""
-#: ../../addon/diaspora/Receiver.php:1643
+#: ../../addon/diaspora/Receiver.php:1644
#, php-format
msgid "%1$s dislikes %2$s's %3$s"
msgstr ""
-#: ../../addon/diaspora/Receiver.php:1704
-#: ../../Zotlabs/Module/Subthread.php:112 ../../Zotlabs/Module/Like.php:459
+#: ../../addon/diaspora/Receiver.php:1705
+#: ../../Zotlabs/Module/Subthread.php:112 ../../Zotlabs/Module/Like.php:447
msgid "status"
msgstr ""
-#: ../../addon/diaspora/Receiver.php:2265 ../../Zotlabs/Module/Like.php:490
+#: ../../addon/diaspora/Receiver.php:2266 ../../Zotlabs/Module/Like.php:478
#, php-format
msgid "%1$s is attending %2$s's %3$s"
msgstr ""
-#: ../../addon/diaspora/Receiver.php:2267 ../../Zotlabs/Module/Like.php:492
+#: ../../addon/diaspora/Receiver.php:2268 ../../Zotlabs/Module/Like.php:480
#, php-format
msgid "%1$s is not attending %2$s's %3$s"
msgstr ""
-#: ../../addon/diaspora/Receiver.php:2269 ../../Zotlabs/Module/Like.php:494
+#: ../../addon/diaspora/Receiver.php:2270 ../../Zotlabs/Module/Like.php:482
#, php-format
msgid "%1$s may attend %2$s's %3$s"
msgstr ""
@@ -4896,28 +4845,32 @@ msgstr ""
msgid "Enter some text"
msgstr ""
-#: ../../addon/superblock/Mod_Superblock.php:62
+#: ../../addon/superblock/Mod_Superblock.php:117
msgid "superblock settings updated"
msgstr ""
-#: ../../addon/superblock/Mod_Superblock.php:86
+#: ../../addon/superblock/Mod_Superblock.php:141
msgid "Currently blocked"
msgstr ""
-#: ../../addon/superblock/Mod_Superblock.php:88
+#: ../../addon/superblock/Mod_Superblock.php:143
msgid "No channels currently blocked"
msgstr ""
-#: ../../addon/superblock/Mod_Superblock.php:90
+#: ../../addon/superblock/Mod_Superblock.php:145
#: ../../Zotlabs/Module/Cover_photo.php:382 ../../Zotlabs/Module/Tagrm.php:137
-#: ../../Zotlabs/Module/Photos.php:994
+#: ../../Zotlabs/Module/Photos.php:995
msgid "Remove"
msgstr ""
-#: ../../addon/superblock/superblock.php:355
+#: ../../addon/superblock/superblock.php:378
msgid "Block Completely"
msgstr ""
+#: ../../addon/superblock/superblock.php:387
+msgid "Block from site"
+msgstr ""
+
#: ../../addon/randpost/randpost.php:101
msgid "You're welcome."
msgstr ""
@@ -5051,7 +5004,7 @@ msgstr ""
#: ../../addon/rendezvous/rendezvous.php:172 ../../addon/wiki/Mod_Wiki.php:221
#: ../../addon/wiki/Lib/NativeWikiPage.php:592
#: ../../addon/wiki/Widget/Wiki_page_history.php:28
-#: ../../Zotlabs/Storage/Browser.php:381 ../../Zotlabs/Module/Oauth.php:112
+#: ../../Zotlabs/Storage/Browser.php:385 ../../Zotlabs/Module/Oauth.php:112
#: ../../Zotlabs/Module/Oauth.php:137 ../../Zotlabs/Module/Connedit.php:732
#: ../../Zotlabs/Module/Admin/Channels.php:181
#: ../../Zotlabs/Module/Sharedwithme.php:107 ../../Zotlabs/Module/Chat.php:256
@@ -5251,7 +5204,7 @@ msgstr ""
msgid "Edit Article"
msgstr ""
-#: ../../addon/articles/articles.php:48 ../../addon/articles/articles.php:160
+#: ../../addon/articles/articles.php:48 ../../addon/articles/articles.php:161
#: ../../addon/articles/Mod_Articles.php:228 ../../Zotlabs/Lib/Apps.php:331
msgid "Articles"
msgstr ""
@@ -5265,7 +5218,7 @@ msgid "Add Article"
msgstr ""
#: ../../addon/wiki/Mod_Wiki.php:36
-#: ../../addon/flashcards/Mod_Flashcards.php:52
+#: ../../addon/flashcards/Mod_Flashcards.php:58
#: ../../addon/faces/Mod_Faces.php:64 ../../addon/cart/cart.php:1458
msgid "Profile Unavailable."
msgstr ""
@@ -5273,7 +5226,7 @@ msgstr ""
#: ../../addon/wiki/Mod_Wiki.php:81 ../../addon/cart/manual_payments.php:93
#: ../../addon/cart/submodules/paypalbuttonV2.php:486
#: ../../addon/cart/submodules/paypalbutton.php:456
-#: ../../addon/cart/myshop.php:37 ../../addon/cart/cart.php:1609
+#: ../../addon/cart/myshop.php:37 ../../addon/cart/cart.php:1608
msgid "Invalid channel"
msgstr ""
@@ -5290,11 +5243,11 @@ msgid "Error downloading wiki: "
msgstr ""
#: ../../addon/wiki/Mod_Wiki.php:209 ../../addon/wiki/Widget/Wiki_list.php:23
-#: ../../addon/wiki/wiki.php:45 ../../addon/wiki/wiki.php:98
+#: ../../addon/wiki/wiki.php:45 ../../addon/wiki/wiki.php:99
msgid "Wikis"
msgstr ""
-#: ../../addon/wiki/Mod_Wiki.php:215 ../../Zotlabs/Storage/Browser.php:407
+#: ../../addon/wiki/Mod_Wiki.php:215 ../../Zotlabs/Storage/Browser.php:411
msgid "Download"
msgstr ""
@@ -5317,7 +5270,7 @@ msgstr ""
msgid "Content type"
msgstr ""
-#: ../../addon/wiki/Mod_Wiki.php:222 ../../Zotlabs/Storage/Browser.php:382
+#: ../../addon/wiki/Mod_Wiki.php:222 ../../Zotlabs/Storage/Browser.php:386
msgid "Type"
msgstr ""
@@ -5345,6 +5298,13 @@ msgstr ""
msgid "Rename page"
msgstr ""
+#: ../../addon/wiki/Mod_Wiki.php:304 ../../addon/hsse/hsse.php:186
+#: ../../Zotlabs/Lib/ThreadItem.php:294 ../../Zotlabs/Widget/Cdav.php:142
+#: ../../Zotlabs/Module/Webpages.php:250 ../../Zotlabs/Module/Layouts.php:192
+#: ../../Zotlabs/Module/Blocks.php:159 ../../Zotlabs/Module/Photos.php:1077
+msgid "Share"
+msgstr ""
+
#: ../../addon/wiki/Mod_Wiki.php:318
msgid "Error retrieving page content"
msgstr ""
@@ -5803,23 +5763,10 @@ msgstr ""
msgid "GNU-Social Crosspost Connector"
msgstr ""
-#: ../../addon/flashcards/Mod_Flashcards.php:225
-msgid "Not allowed."
-msgstr ""
-
-#: ../../addon/flashcards/Mod_Flashcards.php:268
-#: ../../Zotlabs/Module/Filestorage.php:202
-msgid "Set/edit permissions"
-msgstr ""
-
-#: ../../addon/flashcards/Mod_Flashcards.php:291
-#: ../../addon/flashcards/Mod_Flashcards.php:292
-#: ../../Zotlabs/Module/Display.php:59 ../../Zotlabs/Module/Display.php:120
-#: ../../Zotlabs/Module/Display.php:396 ../../Zotlabs/Module/Thing.php:120
-#: ../../Zotlabs/Module/Filestorage.php:29 ../../Zotlabs/Module/Viewsrc.php:25
-#: ../../Zotlabs/Module/Admin/Themes.php:73
-#: ../../Zotlabs/Module/Admin/Addons.php:42 ../../Zotlabs/Module/Admin.php:63
-msgid "Item not found."
+#: ../../addon/flashcards/Mod_Flashcards.php:161
+#: ../../Zotlabs/Module/Contactedit.php:431
+#: ../../Zotlabs/Module/Connedit.php:572
+msgid "Affinity"
msgstr ""
#: ../../addon/ljpost/ljpost.php:49
@@ -5861,7 +5808,7 @@ msgstr ""
#: ../../addon/cart/manual_payments.php:68
#: ../../addon/cart/submodules/paypalbuttonV2.php:417
#: ../../addon/cart/submodules/paypalbutton.php:392
-#: ../../addon/cart/cart.php:1631
+#: ../../addon/cart/cart.php:1630
msgid "Order not found."
msgstr ""
@@ -6099,35 +6046,35 @@ msgstr ""
msgid "Shop"
msgstr ""
-#: ../../addon/cart/cart.php:1598
+#: ../../addon/cart/cart.php:1597
msgid "You must be logged into the Grid to shop."
msgstr ""
-#: ../../addon/cart/cart.php:1647
+#: ../../addon/cart/cart.php:1646
msgid "Access denied."
msgstr ""
-#: ../../addon/cart/cart.php:1699 ../../addon/cart/cart.php:1842
+#: ../../addon/cart/cart.php:1698 ../../addon/cart/cart.php:1841
msgid "No Order Found"
msgstr ""
-#: ../../addon/cart/cart.php:1708
+#: ../../addon/cart/cart.php:1707
msgid "An unknown error has occurred Please start again."
msgstr ""
-#: ../../addon/cart/cart.php:1851
+#: ../../addon/cart/cart.php:1850
msgid "Requirements not met."
msgstr ""
-#: ../../addon/cart/cart.php:1851
+#: ../../addon/cart/cart.php:1850
msgid "Review your order and complete any needed requirements."
msgstr ""
-#: ../../addon/cart/cart.php:1877
+#: ../../addon/cart/cart.php:1876
msgid "Invalid Payment Type. Please start again."
msgstr ""
-#: ../../addon/cart/cart.php:1884
+#: ../../addon/cart/cart.php:1883
msgid "Order not found"
msgstr ""
@@ -6629,45 +6576,45 @@ msgstr ""
msgid "I won!"
msgstr ""
-#: ../../boot.php:1730
+#: ../../boot.php:1737
msgid "Create an account to access services and applications"
msgstr ""
-#: ../../boot.php:1748
+#: ../../boot.php:1755
msgid "Email or nickname"
msgstr ""
-#: ../../boot.php:1758
+#: ../../boot.php:1765
msgid "Password"
msgstr ""
-#: ../../boot.php:1759
+#: ../../boot.php:1766
msgid "Remember me"
msgstr ""
-#: ../../boot.php:1762
+#: ../../boot.php:1769
msgid "Forgot your password?"
msgstr ""
-#: ../../boot.php:1763 ../../Zotlabs/Module/Lostpass.php:91
+#: ../../boot.php:1770 ../../Zotlabs/Module/Lostpass.php:91
msgid "Password Reset"
msgstr ""
-#: ../../boot.php:2641
+#: ../../boot.php:2551
#, php-format
msgid "[$Projectname] Website SSL error for %s"
msgstr ""
-#: ../../boot.php:2646
+#: ../../boot.php:2556
msgid "Website SSL certificate is not valid. Please correct."
msgstr ""
-#: ../../boot.php:2762
+#: ../../boot.php:2674
#, php-format
msgid "[$Projectname] Cron tasks not running on %s"
msgstr ""
-#: ../../boot.php:2767
+#: ../../boot.php:2679
msgid "Cron/Scheduled tasks not running."
msgstr ""
@@ -6713,7 +6660,7 @@ msgstr ""
#: ../../Zotlabs/Lib/Enotify.php:134
#, php-format
-msgid "%1$s sent you a new direct message at %2$s"
+msgid "%1$s sent you a new private message at %2$s"
msgstr ""
#: ../../Zotlabs/Lib/Enotify.php:135
@@ -6727,34 +6674,38 @@ msgstr ""
#: ../../Zotlabs/Lib/Enotify.php:136
#, php-format
-msgid "Please visit %s to view and/or reply to your direct messages."
+msgid "Please visit %s to view and/or reply to your private messages."
msgstr ""
#: ../../Zotlabs/Lib/Enotify.php:149
-msgid "requested to comment on"
+msgid "requested to post in"
msgstr ""
#: ../../Zotlabs/Lib/Enotify.php:149
-msgid "commented on"
+msgid "posted in"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:162 ../../Zotlabs/Lib/Enotify.php:318
+#: ../../Zotlabs/Lib/Enotify.php:162 ../../Zotlabs/Lib/Enotify.php:325
msgid "requested to like"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:162 ../../Zotlabs/Lib/Enotify.php:318
+#: ../../Zotlabs/Lib/Enotify.php:162 ../../Zotlabs/Lib/Enotify.php:325
msgid "liked"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:165 ../../Zotlabs/Lib/Enotify.php:321
+#: ../../Zotlabs/Lib/Enotify.php:165 ../../Zotlabs/Lib/Enotify.php:328
msgid "requested to dislike"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:165 ../../Zotlabs/Lib/Enotify.php:321
+#: ../../Zotlabs/Lib/Enotify.php:165 ../../Zotlabs/Lib/Enotify.php:328
msgid "disliked"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:168
+#: ../../Zotlabs/Lib/Enotify.php:168 ../../Zotlabs/Lib/Enotify.php:331
+msgid "requested to repeat"
+msgstr ""
+
+#: ../../Zotlabs/Lib/Enotify.php:168 ../../Zotlabs/Lib/Enotify.php:331
msgid "repeated"
msgstr ""
@@ -6762,226 +6713,221 @@ msgstr ""
msgid "voted on"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:216
+#: ../../Zotlabs/Lib/Enotify.php:217
#, php-format
msgid "%1$s %2$s [zrl=%3$s]a %4$s[/zrl]"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:224
+#: ../../Zotlabs/Lib/Enotify.php:227
#, php-format
msgid "%1$s %2$s [zrl=%3$s]%4$s's %5$s[/zrl]"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:233 ../../Zotlabs/Lib/Enotify.php:325
+#: ../../Zotlabs/Lib/Enotify.php:239 ../../Zotlabs/Lib/Enotify.php:335
#, php-format
msgid "%1$s %2$s [zrl=%3$s]your %4$s[/zrl]"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:245
+#: ../../Zotlabs/Lib/Enotify.php:253
#, php-format
msgid "[$Projectname:Notify] Moderated Comment to conversation #%1$d by %2$s"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:247
+#: ../../Zotlabs/Lib/Enotify.php:255
#, php-format
msgid "[$Projectname:Notify] Comment to conversation #%1$d by %2$s"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:248
+#: ../../Zotlabs/Lib/Enotify.php:256
#, php-format
msgid "%1$s commented on an item/conversation you have been following"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:251 ../../Zotlabs/Lib/Enotify.php:345
-#: ../../Zotlabs/Lib/Enotify.php:361 ../../Zotlabs/Lib/Enotify.php:385
-#: ../../Zotlabs/Lib/Enotify.php:402 ../../Zotlabs/Lib/Enotify.php:416
+#: ../../Zotlabs/Lib/Enotify.php:259 ../../Zotlabs/Lib/Enotify.php:356
+#: ../../Zotlabs/Lib/Enotify.php:372 ../../Zotlabs/Lib/Enotify.php:396
+#: ../../Zotlabs/Lib/Enotify.php:413 ../../Zotlabs/Lib/Enotify.php:427
#, php-format
msgid "Please visit %s to view and/or reply to the conversation."
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:255 ../../Zotlabs/Lib/Enotify.php:256
+#: ../../Zotlabs/Lib/Enotify.php:263 ../../Zotlabs/Lib/Enotify.php:264
#, php-format
msgid "Please visit %s to approve or reject this comment."
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:341
+#: ../../Zotlabs/Lib/Enotify.php:352
#, php-format
msgid "[$Projectname:Notify] Like received to conversation #%1$d by %2$s"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:342
+#: ../../Zotlabs/Lib/Enotify.php:353
#, php-format
msgid "%1$s liked an item/conversation you created"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:353
+#: ../../Zotlabs/Lib/Enotify.php:364
#, php-format
msgid "[$Projectname:Notify] %s posted to your profile wall"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:355
+#: ../../Zotlabs/Lib/Enotify.php:366
#, php-format
msgid "%1$s posted to your profile wall at %2$s"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:357
+#: ../../Zotlabs/Lib/Enotify.php:368
#, php-format
msgid "%1$s posted to [zrl=%2$s]your wall[/zrl]"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:379
+#: ../../Zotlabs/Lib/Enotify.php:390
#, php-format
msgid "[$Projectname:Notify] %s tagged you"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:380
+#: ../../Zotlabs/Lib/Enotify.php:391
#, php-format
msgid "%1$s tagged you at %2$s"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:381
+#: ../../Zotlabs/Lib/Enotify.php:392
#, php-format
msgid "%1$s [zrl=%2$s]tagged you[/zrl]."
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:392
+#: ../../Zotlabs/Lib/Enotify.php:403
#, php-format
msgid "[$Projectname:Notify] %1$s poked you"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:393
+#: ../../Zotlabs/Lib/Enotify.php:404
#, php-format
msgid "%1$s poked you at %2$s"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:394
+#: ../../Zotlabs/Lib/Enotify.php:405
#, php-format
msgid "%1$s [zrl=%2$s]poked you[/zrl]."
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:410
+#: ../../Zotlabs/Lib/Enotify.php:421
#, php-format
msgid "[$Projectname:Notify] %s tagged your post"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:411
+#: ../../Zotlabs/Lib/Enotify.php:422
#, php-format
msgid "%1$s tagged your post at %2$s"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:412
+#: ../../Zotlabs/Lib/Enotify.php:423
#, php-format
msgid "%1$s tagged [zrl=%2$s]your post[/zrl]"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:422
+#: ../../Zotlabs/Lib/Enotify.php:433
msgid "[$Projectname:Notify] Introduction received"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:423
+#: ../../Zotlabs/Lib/Enotify.php:434
#, php-format
msgid "You've received an new connection request from '%1$s' at %2$s"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:424
+#: ../../Zotlabs/Lib/Enotify.php:435
#, php-format
msgid "You've received [zrl=%1$s]a new connection request[/zrl] from %2$s."
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:427 ../../Zotlabs/Lib/Enotify.php:446
+#: ../../Zotlabs/Lib/Enotify.php:438 ../../Zotlabs/Lib/Enotify.php:457
#, php-format
msgid "You may visit their profile at %s"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:429
+#: ../../Zotlabs/Lib/Enotify.php:440
#, php-format
msgid "Please visit %s to approve or reject the connection request."
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:437
+#: ../../Zotlabs/Lib/Enotify.php:448
msgid "[$Projectname:Notify] Friend suggestion received"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:438
+#: ../../Zotlabs/Lib/Enotify.php:449
#, php-format
msgid "You've received a friend suggestion from '%1$s' at %2$s"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:439
+#: ../../Zotlabs/Lib/Enotify.php:450
#, php-format
msgid "You've received [zrl=%1$s]a friend suggestion[/zrl] for %2$s from %3$s."
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:444
+#: ../../Zotlabs/Lib/Enotify.php:455
msgid "Name:"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:445
+#: ../../Zotlabs/Lib/Enotify.php:456
msgid "Photo:"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:448
+#: ../../Zotlabs/Lib/Enotify.php:459
#, php-format
msgid "Please visit %s to approve or reject the suggestion."
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:677
+#: ../../Zotlabs/Lib/Enotify.php:694
msgid "[$Projectname:Notify]"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:843
-msgid "created a new poll"
+#: ../../Zotlabs/Lib/Enotify.php:860
+msgid "started a poll"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:843
-msgid "created a new post"
+#: ../../Zotlabs/Lib/Enotify.php:860
+msgid "started a conversation"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:844
+#: ../../Zotlabs/Lib/Enotify.php:861
#, php-format
msgid "voted on %s's poll"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:844
+#: ../../Zotlabs/Lib/Enotify.php:861
#, php-format
-msgid "commented on %s's post"
+msgid "posted in %s's conversation"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:848 ../../Zotlabs/Lib/Enotify.php:948
+#: ../../Zotlabs/Lib/Enotify.php:865 ../../Zotlabs/Lib/Enotify.php:957
msgid "shared a file with you"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:857
+#: ../../Zotlabs/Lib/Enotify.php:873
#, php-format
-msgid "edited a post dated %s"
+msgid "edited a message dated %s"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:860
-#, php-format
-msgid "edited a comment dated %s"
-msgstr ""
-
-#: ../../Zotlabs/Lib/Enotify.php:933
+#: ../../Zotlabs/Lib/Enotify.php:942
msgid "added your channel"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:963
+#: ../../Zotlabs/Lib/Enotify.php:972
msgid "sent you a direct message"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:970
+#: ../../Zotlabs/Lib/Enotify.php:979
msgid "g A l F d"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:973
+#: ../../Zotlabs/Lib/Enotify.php:982
msgid "[today]"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:983
+#: ../../Zotlabs/Lib/Enotify.php:992
msgid "created an event"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:998
+#: ../../Zotlabs/Lib/Enotify.php:1007
msgid "status verified"
msgstr ""
@@ -7009,10 +6955,6 @@ msgstr ""
msgid "Channel Manager"
msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:344
-msgid "Stream"
-msgstr ""
-
#: ../../Zotlabs/Lib/Apps.php:348
msgid "Wiki"
msgstr ""
@@ -7045,11 +6987,12 @@ msgstr ""
msgid "Features"
msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:364 ../../Zotlabs/Storage/Browser.php:410
+#: ../../Zotlabs/Lib/Apps.php:364 ../../Zotlabs/Storage/Browser.php:414
msgid "Post"
msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:369
+#: ../../Zotlabs/Lib/Apps.php:369 ../../Zotlabs/Widget/Notifications.php:116
+#: ../../Zotlabs/Widget/Messages.php:51
msgid "Notifications"
msgstr ""
@@ -7206,216 +7149,225 @@ msgstr ""
msgid "Update %s failed. See error logs."
msgstr ""
-#: ../../Zotlabs/Lib/Connect.php:45 ../../Zotlabs/Lib/Connect.php:146
+#: ../../Zotlabs/Lib/Connect.php:51 ../../Zotlabs/Lib/Connect.php:152
msgid "Channel is blocked on this site."
msgstr ""
-#: ../../Zotlabs/Lib/Connect.php:50
+#: ../../Zotlabs/Lib/Connect.php:56
msgid "Channel location missing."
msgstr ""
-#: ../../Zotlabs/Lib/Connect.php:104
+#: ../../Zotlabs/Lib/Connect.php:110
msgid "Remote channel or protocol unavailable."
msgstr ""
-#: ../../Zotlabs/Lib/Connect.php:140
+#: ../../Zotlabs/Lib/Connect.php:146
msgid "Channel discovery failed."
msgstr ""
-#: ../../Zotlabs/Lib/Connect.php:158
+#: ../../Zotlabs/Lib/Connect.php:164
msgid "Protocol disabled."
msgstr ""
-#: ../../Zotlabs/Lib/Connect.php:170
+#: ../../Zotlabs/Lib/Connect.php:176
msgid "Cannot connect to yourself."
msgstr ""
-#: ../../Zotlabs/Lib/Connect.php:275
+#: ../../Zotlabs/Lib/Connect.php:281
msgid "error saving data"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:110
+#: ../../Zotlabs/Lib/ThreadItem.php:107
msgid "Restricted message"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:117
-msgid "Direct message"
+#: ../../Zotlabs/Lib/ThreadItem.php:114
+msgid "Private message"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:122
+#: ../../Zotlabs/Lib/ThreadItem.php:119
msgid "Public Policy"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:156
+#: ../../Zotlabs/Lib/ThreadItem.php:153
msgid "Privacy conflict. Discretion advised."
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:187 ../../Zotlabs/Storage/Browser.php:373
+#: ../../Zotlabs/Lib/ThreadItem.php:184 ../../Zotlabs/Storage/Browser.php:377
msgid "Admin Delete"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:190 ../../Zotlabs/Module/Filer.php:66
+#: ../../Zotlabs/Lib/ThreadItem.php:187 ../../Zotlabs/Module/Filer.php:66
msgid "Save to Folder"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:217 ../../Zotlabs/Widget/Pinned.php:77
+#: ../../Zotlabs/Lib/ThreadItem.php:214
msgid "I will attend"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:217 ../../Zotlabs/Widget/Pinned.php:77
+#: ../../Zotlabs/Lib/ThreadItem.php:214
msgid "I will not attend"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:217 ../../Zotlabs/Widget/Pinned.php:77
+#: ../../Zotlabs/Lib/ThreadItem.php:214
msgid "I might attend"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:296 ../../Zotlabs/Module/Photos.php:1074
-msgid "I like this (toggle)"
-msgstr ""
-
-#: ../../Zotlabs/Lib/ThreadItem.php:297 ../../Zotlabs/Module/Photos.php:1075
-msgid "I don't like this (toggle)"
-msgstr ""
-
-#: ../../Zotlabs/Lib/ThreadItem.php:298
-msgid "Reply to this comment"
+#: ../../Zotlabs/Lib/ThreadItem.php:287
+msgid "Reply to this message"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:298
+#: ../../Zotlabs/Lib/ThreadItem.php:287
msgid "reply"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:298
+#: ../../Zotlabs/Lib/ThreadItem.php:287
msgid "Reply to"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:305 ../../Zotlabs/Widget/Pinned.php:95
+#: ../../Zotlabs/Lib/ThreadItem.php:294 ../../Zotlabs/Widget/Pinned.php:95
msgid "share"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:309
+#: ../../Zotlabs/Lib/ThreadItem.php:298
msgid "Repeat"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:309
+#: ../../Zotlabs/Lib/ThreadItem.php:298
msgid "repeat"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:320
+#: ../../Zotlabs/Lib/ThreadItem.php:309
msgid "Delivery Report"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:341
+#: ../../Zotlabs/Lib/ThreadItem.php:330
#, php-format
msgid "%d comment"
msgid_plural "%d comments"
msgstr[0] ""
msgstr[1] ""
-#: ../../Zotlabs/Lib/ThreadItem.php:342
+#: ../../Zotlabs/Lib/ThreadItem.php:332
#, php-format
msgid "%d unseen"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:391
+#: ../../Zotlabs/Lib/ThreadItem.php:363
+#, php-format
+msgid "Load the next few of total %d comments"
+msgstr ""
+
+#: ../../Zotlabs/Lib/ThreadItem.php:369
+msgid "Expand Replies"
+msgstr ""
+
+#: ../../Zotlabs/Lib/ThreadItem.php:388
msgid "Forum"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:398
+#: ../../Zotlabs/Lib/ThreadItem.php:395
msgid "to"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:399 ../../Zotlabs/Widget/Messages.php:175
-#: ../../Zotlabs/Widget/Messages.php:178 ../../Zotlabs/Widget/Pinned.php:122
+#: ../../Zotlabs/Lib/ThreadItem.php:396 ../../Zotlabs/Widget/Messages.php:179
+#: ../../Zotlabs/Widget/Messages.php:182 ../../Zotlabs/Widget/Pinned.php:125
msgid "via"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:400
+#: ../../Zotlabs/Lib/ThreadItem.php:397
msgid "Wall-to-Wall"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:401
+#: ../../Zotlabs/Lib/ThreadItem.php:398
msgid "via Wall-To-Wall:"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:417
+#: ../../Zotlabs/Lib/ThreadItem.php:414
#, php-format
msgid "Last edited %s"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:418
+#: ../../Zotlabs/Lib/ThreadItem.php:415
#, php-format
msgid "Expires %s"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:421
+#: ../../Zotlabs/Lib/ThreadItem.php:418
#, php-format
msgid "Published %s"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:428
+#: ../../Zotlabs/Lib/ThreadItem.php:425
msgid "Attend"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:429 ../../Zotlabs/Widget/Pinned.php:136
+#: ../../Zotlabs/Lib/ThreadItem.php:426 ../../Zotlabs/Widget/Pinned.php:139
msgid "Attendance Options"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:431 ../../Zotlabs/Widget/Pinned.php:137
+#: ../../Zotlabs/Lib/ThreadItem.php:428 ../../Zotlabs/Widget/Pinned.php:140
msgid "Voting Options"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:446
+#: ../../Zotlabs/Lib/ThreadItem.php:442
msgid "Go to previous comment"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:455 ../../Zotlabs/Widget/Pinned.php:149
+#: ../../Zotlabs/Lib/ThreadItem.php:452 ../../Zotlabs/Widget/Pinned.php:152
msgid "Pinned post"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:459
+#: ../../Zotlabs/Lib/ThreadItem.php:456
msgid "Add to Calendar"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:467
+#: ../../Zotlabs/Lib/ThreadItem.php:464
msgid "Mark all comments seen"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:485
+#: ../../Zotlabs/Lib/ThreadItem.php:483
msgid "Add yours"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:485
+#: ../../Zotlabs/Lib/ThreadItem.php:483
msgid "Remove yours"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:796 ../../Zotlabs/Module/Photos.php:1092
-#: ../../Zotlabs/Module/Photos.php:1205
-msgid "This is you"
+#: ../../Zotlabs/Lib/ThreadItem.php:496
+msgid "show less"
+msgstr ""
+
+#: ../../Zotlabs/Lib/ThreadItem.php:497
+msgid "show more"
+msgstr ""
+
+#: ../../Zotlabs/Lib/ThreadItem.php:497
+msgid "show all"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:806
-msgid "Image"
+#: ../../Zotlabs/Lib/ThreadItem.php:806 ../../Zotlabs/Module/Photos.php:1093
+#: ../../Zotlabs/Module/Photos.php:1194
+msgid "This is you"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:808
+#: ../../Zotlabs/Lib/ThreadItem.php:818
msgid "Insert Link"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:809
+#: ../../Zotlabs/Lib/ThreadItem.php:819
msgid "Video"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:818
+#: ../../Zotlabs/Lib/ThreadItem.php:828
msgid "Your full name (required)"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:819
+#: ../../Zotlabs/Lib/ThreadItem.php:829
msgid "Your email address (required)"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:820
+#: ../../Zotlabs/Lib/ThreadItem.php:830
msgid "Your website URL (optional)"
msgstr ""
@@ -7423,32 +7375,32 @@ msgstr ""
msgid "Unable to verify channel signature"
msgstr ""
-#: ../../Zotlabs/Lib/Activity.php:2259
+#: ../../Zotlabs/Lib/Activity.php:2202
#, php-format
msgid "Likes %1$s's %2$s"
msgstr ""
-#: ../../Zotlabs/Lib/Activity.php:2262
+#: ../../Zotlabs/Lib/Activity.php:2205
#, php-format
msgid "Doesn't like %1$s's %2$s"
msgstr ""
-#: ../../Zotlabs/Lib/Activity.php:2268
+#: ../../Zotlabs/Lib/Activity.php:2211
#, php-format
msgid "Will attend %s's event"
msgstr ""
-#: ../../Zotlabs/Lib/Activity.php:2271
+#: ../../Zotlabs/Lib/Activity.php:2214
#, php-format
msgid "Will not attend %s's event"
msgstr ""
-#: ../../Zotlabs/Lib/Activity.php:2274
+#: ../../Zotlabs/Lib/Activity.php:2217
#, php-format
msgid "May attend %s's event"
msgstr ""
-#: ../../Zotlabs/Lib/Activity.php:2277
+#: ../../Zotlabs/Lib/Activity.php:2220
#, php-format
msgid "May not attend %s's event"
msgstr ""
@@ -7707,7 +7659,7 @@ msgid "View Ratings"
msgstr ""
#: ../../Zotlabs/Widget/Notifications.php:24
-msgid "New network activity notifications"
+msgid "Unseen network activity"
msgstr ""
#: ../../Zotlabs/Widget/Notifications.php:27
@@ -7716,14 +7668,14 @@ msgstr ""
#: ../../Zotlabs/Widget/Notifications.php:30
#: ../../Zotlabs/Widget/Notifications.php:69
-msgid "Mark all notifications read"
+msgid "Mark all read"
msgstr ""
#: ../../Zotlabs/Widget/Notifications.php:33
#: ../../Zotlabs/Widget/Notifications.php:53
#: ../../Zotlabs/Widget/Notifications.php:72
#: ../../Zotlabs/Widget/Notifications.php:166
-msgid "Show new posts only"
+msgid "Conversation starters"
msgstr ""
#: ../../Zotlabs/Widget/Notifications.php:34
@@ -7731,33 +7683,36 @@ msgstr ""
#: ../../Zotlabs/Widget/Notifications.php:73
#: ../../Zotlabs/Widget/Notifications.php:134
#: ../../Zotlabs/Widget/Notifications.php:167
-#: ../../Zotlabs/Widget/Messages.php:53
+#: ../../Zotlabs/Widget/Messages.php:55
msgid "Filter by name or address"
msgstr ""
#: ../../Zotlabs/Widget/Notifications.php:44
-msgid "New home activity notifications"
+#: ../../Zotlabs/Module/Settings/Channel.php:259
+msgid "Unseen channel activity"
msgstr ""
#: ../../Zotlabs/Widget/Notifications.php:47
-msgid "Home stream"
+msgid "Channel stream"
msgstr ""
#: ../../Zotlabs/Widget/Notifications.php:50
-msgid "Mark all notifications seen"
+#: ../../Zotlabs/Widget/Notifications.php:88
+#: ../../Zotlabs/Widget/Notifications.php:123
+#: ../../Zotlabs/Module/Notifications.php:111
+msgid "Mark all seen"
msgstr ""
#: ../../Zotlabs/Widget/Notifications.php:62
-#: ../../Zotlabs/Widget/Activity_filter.php:44
-msgid "Direct Messages"
+msgid "Private"
msgstr ""
#: ../../Zotlabs/Widget/Notifications.php:63
-msgid "New direct messages notifications"
+msgid "Unseen private activity"
msgstr ""
#: ../../Zotlabs/Widget/Notifications.php:66
-msgid "Direct messages stream"
+msgid "Private stream"
msgstr ""
#: ../../Zotlabs/Widget/Notifications.php:81
@@ -7766,46 +7721,39 @@ msgid "Events"
msgstr ""
#: ../../Zotlabs/Widget/Notifications.php:82
-msgid "New events notifications"
+msgid "Unseen events activity"
msgstr ""
#: ../../Zotlabs/Widget/Notifications.php:85
msgid "View events"
msgstr ""
-#: ../../Zotlabs/Widget/Notifications.php:88
-msgid "Mark all events seen"
-msgstr ""
-
#: ../../Zotlabs/Widget/Notifications.php:96
#: ../../Zotlabs/Module/Connections.php:168
msgid "New Connections"
msgstr ""
#: ../../Zotlabs/Widget/Notifications.php:97
-msgid "New connections notifications"
+#: ../../Zotlabs/Module/Settings/Channel.php:267
+msgid "New connections"
msgstr ""
#: ../../Zotlabs/Widget/Notifications.php:100
-msgid "View all connections"
+#: ../../Zotlabs/Widget/Notifications.php:120
+#: ../../Zotlabs/Module/Photos.php:1114 ../../Zotlabs/Module/Photos.php:1126
+msgid "View all"
msgstr ""
#: ../../Zotlabs/Widget/Notifications.php:109
-msgid "New files notifications"
+msgid "Useen files activity"
msgstr ""
-#: ../../Zotlabs/Widget/Notifications.php:116
#: ../../Zotlabs/Widget/Notifications.php:117
-#: ../../Zotlabs/Widget/Messages.php:49
-msgid "Notices"
+msgid "Unseen notifications"
msgstr ""
-#: ../../Zotlabs/Widget/Notifications.php:120
-msgid "View all notices"
-msgstr ""
-
-#: ../../Zotlabs/Widget/Notifications.php:123
-msgid "Mark all notices seen"
+#: ../../Zotlabs/Widget/Notifications.php:132
+msgid "Unseen forums activity"
msgstr ""
#: ../../Zotlabs/Widget/Notifications.php:144
@@ -7813,11 +7761,12 @@ msgid "Registrations"
msgstr ""
#: ../../Zotlabs/Widget/Notifications.php:145
-msgid "New registrations notifications"
+msgid "Unseen registration activity"
msgstr ""
#: ../../Zotlabs/Widget/Notifications.php:155
-msgid "New public stream notifications"
+#: ../../Zotlabs/Module/Settings/Channel.php:270
+msgid "Unseen public stream activity"
msgstr ""
#: ../../Zotlabs/Widget/Notifications.php:158
@@ -7905,7 +7854,7 @@ msgid "View public stream"
msgstr ""
#: ../../Zotlabs/Widget/Newmember.php:82
-#: ../../Zotlabs/Module/Settings/Display.php:202
+#: ../../Zotlabs/Module/Settings/Display.php:201
msgid "New Member Links"
msgstr ""
@@ -7921,31 +7870,31 @@ msgstr ""
msgid "See more..."
msgstr ""
-#: ../../Zotlabs/Widget/Messages.php:45
-msgid "Public and restricted messages"
+#: ../../Zotlabs/Widget/Messages.php:47
+msgid "Public and restricted conversations"
msgstr ""
-#: ../../Zotlabs/Widget/Messages.php:46
-msgid "Direct messages"
+#: ../../Zotlabs/Widget/Messages.php:48
+msgid "Private conversations"
msgstr ""
-#: ../../Zotlabs/Widget/Messages.php:47
-msgid "Starred messages"
+#: ../../Zotlabs/Widget/Messages.php:49
+msgid "Starred conversations"
msgstr ""
-#: ../../Zotlabs/Widget/Messages.php:48
+#: ../../Zotlabs/Widget/Messages.php:50
msgid "Filed messages"
msgstr ""
-#: ../../Zotlabs/Widget/Messages.php:51
-msgid "No messages"
+#: ../../Zotlabs/Widget/Messages.php:53
+msgid "No conversations"
msgstr ""
-#: ../../Zotlabs/Widget/Messages.php:52
-msgid "Unseen"
+#: ../../Zotlabs/Widget/Messages.php:54
+msgid "Unseen reactions"
msgstr ""
-#: ../../Zotlabs/Widget/Messages.php:54
+#: ../../Zotlabs/Widget/Messages.php:56
msgid "Filter by file name"
msgstr ""
@@ -7961,24 +7910,24 @@ msgstr ""
msgid "Channel activities"
msgstr ""
-#: ../../Zotlabs/Widget/Album.php:84 ../../Zotlabs/Widget/Portfolio.php:91
-#: ../../Zotlabs/Module/Embedphotos.php:171 ../../Zotlabs/Module/Photos.php:782
-#: ../../Zotlabs/Module/Photos.php:1324
+#: ../../Zotlabs/Widget/Album.php:87 ../../Zotlabs/Widget/Portfolio.php:95
+#: ../../Zotlabs/Module/Embedphotos.php:173 ../../Zotlabs/Module/Photos.php:784
+#: ../../Zotlabs/Module/Photos.php:1318
msgid "View Photo"
msgstr ""
-#: ../../Zotlabs/Widget/Album.php:101 ../../Zotlabs/Widget/Portfolio.php:112
-#: ../../Zotlabs/Module/Embedphotos.php:187 ../../Zotlabs/Module/Photos.php:813
+#: ../../Zotlabs/Widget/Album.php:104 ../../Zotlabs/Widget/Portfolio.php:116
+#: ../../Zotlabs/Module/Embedphotos.php:189 ../../Zotlabs/Module/Photos.php:815
msgid "Edit Album"
msgstr ""
-#: ../../Zotlabs/Widget/Album.php:103 ../../Zotlabs/Widget/Portfolio.php:114
+#: ../../Zotlabs/Widget/Album.php:106 ../../Zotlabs/Widget/Portfolio.php:118
#: ../../Zotlabs/Widget/Cdav.php:152 ../../Zotlabs/Widget/Cdav.php:188
-#: ../../Zotlabs/Storage/Browser.php:546
+#: ../../Zotlabs/Storage/Browser.php:550
#: ../../Zotlabs/Module/Cover_photo.php:381
-#: ../../Zotlabs/Module/Embedphotos.php:189
+#: ../../Zotlabs/Module/Embedphotos.php:191
#: ../../Zotlabs/Module/Profile_photo.php:547
-#: ../../Zotlabs/Module/Photos.php:681
+#: ../../Zotlabs/Module/Photos.php:683
msgid "Upload"
msgstr ""
@@ -7986,12 +7935,12 @@ msgstr ""
msgid "Share This"
msgstr ""
-#: ../../Zotlabs/Widget/Pinned.php:117 ../../Zotlabs/Widget/Pinned.php:118
+#: ../../Zotlabs/Widget/Pinned.php:120 ../../Zotlabs/Widget/Pinned.php:121
#, php-format
msgid "View %s's profile - %s"
msgstr ""
-#: ../../Zotlabs/Widget/Pinned.php:151
+#: ../../Zotlabs/Widget/Pinned.php:154
msgid "Don't show"
msgstr ""
@@ -8032,20 +7981,20 @@ msgstr ""
msgid "Saved"
msgstr ""
-#: ../../Zotlabs/Widget/Activity_order.php:96
-msgid "Commented Date"
+#: ../../Zotlabs/Widget/Activity_order.php:94
+msgid "Posted Date"
msgstr ""
-#: ../../Zotlabs/Widget/Activity_order.php:100
-msgid "Order by last commented date"
+#: ../../Zotlabs/Widget/Activity_order.php:98
+msgid "Order by last posted date"
msgstr ""
-#: ../../Zotlabs/Widget/Activity_order.php:103
-msgid "Posted Date"
+#: ../../Zotlabs/Widget/Activity_order.php:102
+msgid "Commented Date"
msgstr ""
-#: ../../Zotlabs/Widget/Activity_order.php:107
-msgid "Order by last posted date"
+#: ../../Zotlabs/Widget/Activity_order.php:106
+msgid "Order by last commented date"
msgstr ""
#: ../../Zotlabs/Widget/Activity_order.php:110
@@ -8060,6 +8009,10 @@ msgstr ""
msgid "Stream Order"
msgstr ""
+#: ../../Zotlabs/Widget/Activity_filter.php:44
+msgid "Direct Messages"
+msgstr ""
+
#: ../../Zotlabs/Widget/Activity_filter.php:48
msgid "Show direct (private) messages"
msgstr ""
@@ -8343,7 +8296,7 @@ msgid "Create new CalDAV calendar"
msgstr ""
#: ../../Zotlabs/Widget/Cdav.php:146 ../../Zotlabs/Widget/Cdav.php:184
-#: ../../Zotlabs/Storage/Browser.php:368 ../../Zotlabs/Storage/Browser.php:544
+#: ../../Zotlabs/Storage/Browser.php:372 ../../Zotlabs/Storage/Browser.php:548
#: ../../Zotlabs/Module/Webpages.php:248 ../../Zotlabs/Module/Connedit.php:747
#: ../../Zotlabs/Module/Layouts.php:183 ../../Zotlabs/Module/Menu.php:182
#: ../../Zotlabs/Module/Blocks.php:157 ../../Zotlabs/Module/New_channel.php:190
@@ -8408,129 +8361,129 @@ msgstr ""
msgid "Privacy groups"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:292
+#: ../../Zotlabs/Storage/Browser.php:296
msgid "Change filename to"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:309 ../../Zotlabs/Storage/Browser.php:394
+#: ../../Zotlabs/Storage/Browser.php:313 ../../Zotlabs/Storage/Browser.php:398
msgid "Select a target location"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:310 ../../Zotlabs/Storage/Browser.php:395
+#: ../../Zotlabs/Storage/Browser.php:314 ../../Zotlabs/Storage/Browser.php:399
msgid "Copy to target location"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:311 ../../Zotlabs/Storage/Browser.php:393
+#: ../../Zotlabs/Storage/Browser.php:315 ../../Zotlabs/Storage/Browser.php:397
msgid "Set permissions for all files and sub folders"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:312
+#: ../../Zotlabs/Storage/Browser.php:316
msgid "Notify your contacts about this file"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:351
+#: ../../Zotlabs/Storage/Browser.php:355
msgid "File category"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:365
+#: ../../Zotlabs/Storage/Browser.php:369
msgid "Total"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:367
+#: ../../Zotlabs/Storage/Browser.php:371
msgid "Shared"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:369
+#: ../../Zotlabs/Storage/Browser.php:373
msgid "Add Files"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:384
+#: ../../Zotlabs/Storage/Browser.php:388
#: ../../Zotlabs/Module/Sharedwithme.php:110
msgid "Last Modified"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:385
+#: ../../Zotlabs/Storage/Browser.php:389
msgid "parent"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:390
+#: ../../Zotlabs/Storage/Browser.php:394
#: ../../Zotlabs/Module/Filestorage.php:206
msgid "Copy/paste this code to attach file to a post"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:391
+#: ../../Zotlabs/Storage/Browser.php:395
#: ../../Zotlabs/Module/Filestorage.php:207
msgid "Copy/paste this URL to link file from a web page"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:402
+#: ../../Zotlabs/Storage/Browser.php:406
msgid "Select All"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:403
+#: ../../Zotlabs/Storage/Browser.php:407
msgid "Bulk Actions"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:404
+#: ../../Zotlabs/Storage/Browser.php:408
msgid "Adjust Permissions"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:405
+#: ../../Zotlabs/Storage/Browser.php:409
msgid "Move or Copy"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:408
+#: ../../Zotlabs/Storage/Browser.php:412
msgid "Info"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:409
+#: ../../Zotlabs/Storage/Browser.php:413
msgid "Rename"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:411
+#: ../../Zotlabs/Storage/Browser.php:415
msgid "Attachment BBcode"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:412
+#: ../../Zotlabs/Storage/Browser.php:416
msgid "Embed BBcode"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:413
+#: ../../Zotlabs/Storage/Browser.php:417
msgid "Link BBcode"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:486
+#: ../../Zotlabs/Storage/Browser.php:490
#, php-format
msgid "You are using %1$s of your available file storage."
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:491
+#: ../../Zotlabs/Storage/Browser.php:495
#, php-format
msgid "You are using %1$s of %2$s available file storage. (%3$s&#37;)"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:502
+#: ../../Zotlabs/Storage/Browser.php:506
msgid "WARNING:"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:543
+#: ../../Zotlabs/Storage/Browser.php:547
msgid "Create new folder"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:545
+#: ../../Zotlabs/Storage/Browser.php:549
msgid "Upload file"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:557
+#: ../../Zotlabs/Storage/Browser.php:561
msgid "Drop files here to immediately upload"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:558
+#: ../../Zotlabs/Storage/Browser.php:562
#: ../../Zotlabs/Module/Filestorage.php:211
msgid "Show in your contacts shared folder"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:560
+#: ../../Zotlabs/Storage/Browser.php:564
msgid ""
"You can select files via the upload button or drop them right here or into "
"an existing folder."
@@ -8602,27 +8555,35 @@ msgstr ""
msgid "Public access denied."
msgstr ""
-#: ../../Zotlabs/Module/Display.php:53 ../../Zotlabs/Module/Oep.php:82
+#: ../../Zotlabs/Module/Display.php:53 ../../Zotlabs/Module/Oep.php:83
#: ../../Zotlabs/Module/Pubstream.php:55 ../../Zotlabs/Module/Channel.php:195
msgid "Malformed message id."
msgstr ""
-#: ../../Zotlabs/Module/Display.php:95 ../../Zotlabs/Module/Network.php:213
-#: ../../Zotlabs/Module/Hq.php:99 ../../Zotlabs/Module/Pubstream.php:98
+#: ../../Zotlabs/Module/Display.php:59 ../../Zotlabs/Module/Display.php:120
+#: ../../Zotlabs/Module/Display.php:391 ../../Zotlabs/Module/Thing.php:120
+#: ../../Zotlabs/Module/Filestorage.php:29 ../../Zotlabs/Module/Viewsrc.php:25
+#: ../../Zotlabs/Module/Admin/Themes.php:73
+#: ../../Zotlabs/Module/Admin/Addons.php:42 ../../Zotlabs/Module/Admin.php:63
+msgid "Item not found."
+msgstr ""
+
+#: ../../Zotlabs/Module/Display.php:95 ../../Zotlabs/Module/Network.php:215
+#: ../../Zotlabs/Module/Hq.php:101 ../../Zotlabs/Module/Pubstream.php:98
#: ../../Zotlabs/Module/Channel.php:279 ../../Zotlabs/Module/Rpost.php:111
msgid "Reset form"
msgstr ""
-#: ../../Zotlabs/Module/Display.php:320 ../../Zotlabs/Module/Channel.php:501
+#: ../../Zotlabs/Module/Display.php:315 ../../Zotlabs/Module/Channel.php:503
msgid ""
"You must enable javascript for your browser to be able to view this content."
msgstr ""
-#: ../../Zotlabs/Module/Display.php:340
+#: ../../Zotlabs/Module/Display.php:335
msgid "Article"
msgstr ""
-#: ../../Zotlabs/Module/Display.php:389
+#: ../../Zotlabs/Module/Display.php:384
msgid "Item has been removed."
msgstr ""
@@ -8707,11 +8668,11 @@ msgstr ""
msgid "Edit Webpage"
msgstr ""
-#: ../../Zotlabs/Module/Sse_bs.php:633
+#: ../../Zotlabs/Module/Sse_bs.php:657
msgid "Private forum"
msgstr ""
-#: ../../Zotlabs/Module/Sse_bs.php:633
+#: ../../Zotlabs/Module/Sse_bs.php:657
msgid "Public forum"
msgstr ""
@@ -8847,23 +8808,23 @@ msgstr ""
msgid "Remove authorization"
msgstr ""
-#: ../../Zotlabs/Module/Network.php:108
+#: ../../Zotlabs/Module/Network.php:110
msgid "No such group"
msgstr ""
-#: ../../Zotlabs/Module/Network.php:160
+#: ../../Zotlabs/Module/Network.php:162
msgid "No such channel"
msgstr ""
-#: ../../Zotlabs/Module/Network.php:172 ../../Zotlabs/Module/Channel.php:246
+#: ../../Zotlabs/Module/Network.php:174 ../../Zotlabs/Module/Channel.php:246
msgid "Search Results For:"
msgstr ""
-#: ../../Zotlabs/Module/Network.php:248
+#: ../../Zotlabs/Module/Network.php:250
msgid "Privacy group is empty"
msgstr ""
-#: ../../Zotlabs/Module/Network.php:258
+#: ../../Zotlabs/Module/Network.php:260
msgid "Privacy group: "
msgstr ""
@@ -9054,10 +9015,6 @@ msgstr ""
msgid "System Notifications"
msgstr ""
-#: ../../Zotlabs/Module/Notifications.php:111
-msgid "Mark all seen"
-msgstr ""
-
#: ../../Zotlabs/Module/Z6trans.php:19
msgid "Update to Hubzilla 5.0 step 2"
msgstr ""
@@ -9074,6 +9031,18 @@ msgstr ""
msgid "from the terminal."
msgstr ""
+#: ../../Zotlabs/Module/Request.php:80
+msgid "+ Repeat again"
+msgstr ""
+
+#: ../../Zotlabs/Module/Request.php:80
+msgid "- Remove yours"
+msgstr ""
+
+#: ../../Zotlabs/Module/Request.php:80
+msgid "+ Add yours"
+msgstr ""
+
#: ../../Zotlabs/Module/Thing.php:146
msgid "Thing updated"
msgstr ""
@@ -9285,6 +9254,10 @@ msgstr ""
msgid "Edit file permissions"
msgstr ""
+#: ../../Zotlabs/Module/Filestorage.php:202
+msgid "Set/edit permissions"
+msgstr ""
+
#: ../../Zotlabs/Module/Filestorage.php:203
msgid "Include all files and sub folders"
msgstr ""
@@ -9445,11 +9418,6 @@ msgstr ""
msgid "Permission"
msgstr ""
-#: ../../Zotlabs/Module/Contactedit.php:431
-#: ../../Zotlabs/Module/Connedit.php:572
-msgid "Affinity"
-msgstr ""
-
#: ../../Zotlabs/Module/Contactedit.php:432
msgid "Content filter"
msgstr ""
@@ -9815,36 +9783,36 @@ msgstr ""
msgid "item"
msgstr ""
-#: ../../Zotlabs/Module/Item.php:253 ../../Zotlabs/Module/Pin.php:36
+#: ../../Zotlabs/Module/Item.php:254 ../../Zotlabs/Module/Pin.php:37
msgid "Unable to locate original post."
msgstr ""
-#: ../../Zotlabs/Module/Item.php:541
+#: ../../Zotlabs/Module/Item.php:542
msgid "Empty post discarded."
msgstr ""
-#: ../../Zotlabs/Module/Item.php:1010
+#: ../../Zotlabs/Module/Item.php:1005
msgid "Duplicate post suppressed."
msgstr ""
-#: ../../Zotlabs/Module/Item.php:1160
+#: ../../Zotlabs/Module/Item.php:1155
msgid "System error. Post not saved."
msgstr ""
-#: ../../Zotlabs/Module/Item.php:1200
+#: ../../Zotlabs/Module/Item.php:1195
msgid "Your comment is awaiting approval."
msgstr ""
-#: ../../Zotlabs/Module/Item.php:1337
+#: ../../Zotlabs/Module/Item.php:1333
msgid "Unable to obtain post information from database."
msgstr ""
-#: ../../Zotlabs/Module/Item.php:1344
+#: ../../Zotlabs/Module/Item.php:1340
#, php-format
msgid "You have reached your limit of %1$.0f top level posts."
msgstr ""
-#: ../../Zotlabs/Module/Item.php:1351
+#: ../../Zotlabs/Module/Item.php:1347
#, php-format
msgid "You have reached your limit of %1$.0f webpages."
msgstr ""
@@ -10313,7 +10281,7 @@ msgstr ""
msgid "The main page content can not be edited!"
msgstr ""
-#: ../../Zotlabs/Module/Home.php:105
+#: ../../Zotlabs/Module/Home.php:112
#, php-format
msgid "Welcome to %s"
msgstr ""
@@ -11189,15 +11157,15 @@ msgid ""
"This role will be used for the first channel created after registration."
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:344 ../../Zotlabs/Module/Invite.php:411
+#: ../../Zotlabs/Module/Admin/Site.php:344 ../../Zotlabs/Module/Invite.php:394
msgid "Minute(s)"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:345 ../../Zotlabs/Module/Invite.php:412
+#: ../../Zotlabs/Module/Admin/Site.php:345 ../../Zotlabs/Module/Invite.php:395
msgid "Hour(s)"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:346 ../../Zotlabs/Module/Invite.php:413
+#: ../../Zotlabs/Module/Admin/Site.php:346 ../../Zotlabs/Module/Invite.php:396
msgid "Day(s)"
msgstr ""
@@ -11222,7 +11190,7 @@ msgid "Time to wait before a registration can be verified"
msgstr ""
#: ../../Zotlabs/Module/Admin/Site.php:363
-#: ../../Zotlabs/Module/Admin/Site.php:385 ../../Zotlabs/Module/Invite.php:422
+#: ../../Zotlabs/Module/Admin/Site.php:385 ../../Zotlabs/Module/Invite.php:405
msgid "duration up from now"
msgstr ""
@@ -11915,12 +11883,12 @@ msgstr ""
msgid "Link to source"
msgstr ""
-#: ../../Zotlabs/Module/Cal.php:199 ../../Zotlabs/Module/Photos.php:946
+#: ../../Zotlabs/Module/Cal.php:199 ../../Zotlabs/Module/Photos.php:948
#: ../../Zotlabs/Module/Cdav.php:1026
msgid "Previous"
msgstr ""
-#: ../../Zotlabs/Module/Cal.php:200 ../../Zotlabs/Module/Photos.php:955
+#: ../../Zotlabs/Module/Cal.php:200 ../../Zotlabs/Module/Photos.php:957
#: ../../Zotlabs/Module/Setup.php:278 ../../Zotlabs/Module/Cdav.php:1027
msgid "Next"
msgstr ""
@@ -12196,6 +12164,10 @@ msgstr ""
msgid "Post not found."
msgstr ""
+#: ../../Zotlabs/Module/Tagger.php:83
+msgid "comment"
+msgstr ""
+
#: ../../Zotlabs/Module/Tagger.php:123
#, php-format
msgid "%1$s tagged %2$s's %3$s with %4$s"
@@ -12209,7 +12181,7 @@ msgstr ""
msgid "Register is closed"
msgstr ""
-#: ../../Zotlabs/Module/Invite.php:116 ../../Zotlabs/Module/Invite.php:563
+#: ../../Zotlabs/Module/Invite.php:116 ../../Zotlabs/Module/Invite.php:546
msgid "Note, the invitation code is valid up to"
msgstr ""
@@ -12268,55 +12240,51 @@ msgstr ""
msgid "You have no more invitations available"
msgstr ""
-#: ../../Zotlabs/Module/Invite.php:366
+#: ../../Zotlabs/Module/Invite.php:360
msgid "Not on xchan"
msgstr ""
-#: ../../Zotlabs/Module/Invite.php:399
-msgid "All users invitation limit exceeded."
-msgstr ""
-
-#: ../../Zotlabs/Module/Invite.php:417
+#: ../../Zotlabs/Module/Invite.php:400
msgid "Invitation expires after"
msgstr ""
-#: ../../Zotlabs/Module/Invite.php:518 ../../Zotlabs/Module/Invite.php:557
+#: ../../Zotlabs/Module/Invite.php:501 ../../Zotlabs/Module/Invite.php:540
msgid "Invitation"
msgstr ""
-#: ../../Zotlabs/Module/Invite.php:548
+#: ../../Zotlabs/Module/Invite.php:531
msgid "Send invitations"
msgstr ""
-#: ../../Zotlabs/Module/Invite.php:549
+#: ../../Zotlabs/Module/Invite.php:532
msgid "Invitations I am using"
msgstr ""
-#: ../../Zotlabs/Module/Invite.php:550
+#: ../../Zotlabs/Module/Invite.php:533
msgid "Invitations we are using"
msgstr ""
-#: ../../Zotlabs/Module/Invite.php:551
+#: ../../Zotlabs/Module/Invite.php:534
msgid "§ Note, the email(s) sent will be recorded in the system logs"
msgstr ""
-#: ../../Zotlabs/Module/Invite.php:552
+#: ../../Zotlabs/Module/Invite.php:535
msgid "Enter email addresses, one per line:"
msgstr ""
-#: ../../Zotlabs/Module/Invite.php:553
+#: ../../Zotlabs/Module/Invite.php:536
msgid "Your message:"
msgstr ""
-#: ../../Zotlabs/Module/Invite.php:554
+#: ../../Zotlabs/Module/Invite.php:537
msgid "Invite template"
msgstr ""
-#: ../../Zotlabs/Module/Invite.php:556
+#: ../../Zotlabs/Module/Invite.php:539
msgid "Subject:"
msgstr ""
-#: ../../Zotlabs/Module/Invite.php:562
+#: ../../Zotlabs/Module/Invite.php:545
msgid "Here you may enter personal notes to the recipient(s)"
msgstr ""
@@ -12439,46 +12407,46 @@ msgid ""
"a> on any site containing your channel."
msgstr ""
-#: ../../Zotlabs/Module/Like.php:111
+#: ../../Zotlabs/Module/Like.php:99
msgid "Like/Dislike"
msgstr ""
-#: ../../Zotlabs/Module/Like.php:117
+#: ../../Zotlabs/Module/Like.php:105
msgid "This action is restricted to members."
msgstr ""
-#: ../../Zotlabs/Module/Like.php:118
+#: ../../Zotlabs/Module/Like.php:106
msgid ""
"Please <a href=\"rmagic\">login with your $Projectname ID</a> or <a "
"href=\"register\">register as a new $Projectname member</a> to continue."
msgstr ""
-#: ../../Zotlabs/Module/Like.php:171 ../../Zotlabs/Module/Like.php:197
-#: ../../Zotlabs/Module/Like.php:230
+#: ../../Zotlabs/Module/Like.php:159 ../../Zotlabs/Module/Like.php:185
+#: ../../Zotlabs/Module/Like.php:218
msgid "Invalid request."
msgstr ""
-#: ../../Zotlabs/Module/Like.php:212
+#: ../../Zotlabs/Module/Like.php:200
msgid "thing"
msgstr ""
-#: ../../Zotlabs/Module/Like.php:253
+#: ../../Zotlabs/Module/Like.php:241
msgid "Channel unavailable."
msgstr ""
-#: ../../Zotlabs/Module/Like.php:289
+#: ../../Zotlabs/Module/Like.php:277
msgid "Previous action reversed."
msgstr ""
-#: ../../Zotlabs/Module/Like.php:456
+#: ../../Zotlabs/Module/Like.php:444
msgid "profile"
msgstr ""
-#: ../../Zotlabs/Module/Like.php:623
+#: ../../Zotlabs/Module/Like.php:611
msgid "Action completed."
msgstr ""
-#: ../../Zotlabs/Module/Like.php:624
+#: ../../Zotlabs/Module/Like.php:612
msgid "Thank you."
msgstr ""
@@ -12689,7 +12657,7 @@ msgstr ""
msgid "Delete Album"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:165 ../../Zotlabs/Module/Photos.php:1057
+#: ../../Zotlabs/Module/Photos.php:165 ../../Zotlabs/Module/Photos.php:1058
msgid "Delete Photo"
msgstr ""
@@ -12697,134 +12665,138 @@ msgstr ""
msgid "No photos selected"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:571
+#: ../../Zotlabs/Module/Photos.php:573
msgid "Access to this item is restricted."
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:614
+#: ../../Zotlabs/Module/Photos.php:616
#, php-format
msgid "%1$.2f MB photo storage used."
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:618
+#: ../../Zotlabs/Module/Photos.php:620
#, php-format
msgid "%1$.2f MB of %2$.2f MB photo storage used."
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:660
+#: ../../Zotlabs/Module/Photos.php:662
msgid "Upload Photos"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:664
+#: ../../Zotlabs/Module/Photos.php:666
msgid "Enter an album name"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:665
+#: ../../Zotlabs/Module/Photos.php:667
msgid "or select an existing album (doubleclick)"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:666
+#: ../../Zotlabs/Module/Photos.php:668
msgid "Create a status post for this upload"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:668
+#: ../../Zotlabs/Module/Photos.php:670
msgid "Description (optional)"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:756
+#: ../../Zotlabs/Module/Photos.php:758
msgid "Show Newest First"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:758
+#: ../../Zotlabs/Module/Photos.php:760
msgid "Show Oldest First"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:815 ../../Zotlabs/Module/Photos.php:1355
+#: ../../Zotlabs/Module/Photos.php:817 ../../Zotlabs/Module/Photos.php:1349
msgid "Add Photos"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:867
+#: ../../Zotlabs/Module/Photos.php:869
msgid "Permission denied. Access to this item may be restricted."
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:869
+#: ../../Zotlabs/Module/Photos.php:871
msgid "Photo not available"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:927
+#: ../../Zotlabs/Module/Photos.php:929
msgid "Use as profile photo"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:928
+#: ../../Zotlabs/Module/Photos.php:930
msgid "Use as cover photo"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:935
+#: ../../Zotlabs/Module/Photos.php:937
msgid "Private Photo"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:950
+#: ../../Zotlabs/Module/Photos.php:952
msgid "View Full Size"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:1031
+#: ../../Zotlabs/Module/Photos.php:1032
msgid "Edit photo"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:1033
+#: ../../Zotlabs/Module/Photos.php:1034
msgid "Rotate CW (right)"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:1034
+#: ../../Zotlabs/Module/Photos.php:1035
msgid "Rotate CCW (left)"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:1037
+#: ../../Zotlabs/Module/Photos.php:1038
msgid "Move photo to album"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:1038
+#: ../../Zotlabs/Module/Photos.php:1039
msgid "Enter a new album name"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:1039
+#: ../../Zotlabs/Module/Photos.php:1040
msgid "or select an existing one (doubleclick)"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:1044
+#: ../../Zotlabs/Module/Photos.php:1045
msgid "Add a Tag"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:1052
+#: ../../Zotlabs/Module/Photos.php:1053
msgid "Example: @bob, @Barbara_Jensen, @jim@example.com"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:1055
+#: ../../Zotlabs/Module/Photos.php:1056
msgid "Flag as adult in album view"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:1125 ../../Zotlabs/Module/Photos.php:1137
-msgid "View all"
+#: ../../Zotlabs/Module/Photos.php:1075
+msgid "I like this (toggle)"
+msgstr ""
+
+#: ../../Zotlabs/Module/Photos.php:1076
+msgid "I don't like this (toggle)"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:1238
+#: ../../Zotlabs/Module/Photos.php:1232
msgid "Photo Tools"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:1247
+#: ../../Zotlabs/Module/Photos.php:1241
msgid "In This Photo:"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:1252
+#: ../../Zotlabs/Module/Photos.php:1246
msgid "Map"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:1260
+#: ../../Zotlabs/Module/Photos.php:1254
msgctxt "noun"
msgid "Likes"
msgstr ""
-#: ../../Zotlabs/Module/Photos.php:1261
+#: ../../Zotlabs/Module/Photos.php:1255
msgctxt "noun"
msgid "Dislikes"
msgstr ""
@@ -14027,6 +13999,10 @@ msgstr ""
msgid "Language App"
msgstr ""
+#: ../../Zotlabs/Module/Lang.php:80
+msgid "Select an alternate language"
+msgstr ""
+
#: ../../Zotlabs/Module/Rpost.php:117 ../../Zotlabs/Module/Editpost.php:114
msgid "Edit post"
msgstr ""
@@ -14278,69 +14254,67 @@ msgstr ""
msgid "%s - (Experimental)"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Display.php:183
+#: ../../Zotlabs/Module/Settings/Display.php:182
msgid "Display Settings"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Display.php:184
+#: ../../Zotlabs/Module/Settings/Display.php:183
msgid "Theme Settings"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Display.php:185
+#: ../../Zotlabs/Module/Settings/Display.php:184
msgid "Custom Theme Settings"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Display.php:186
+#: ../../Zotlabs/Module/Settings/Display.php:185
msgid "Content Settings"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Display.php:192
+#: ../../Zotlabs/Module/Settings/Display.php:191
msgid "Display Theme:"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Display.php:193
+#: ../../Zotlabs/Module/Settings/Display.php:192
msgid "Select scheme"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Display.php:195
-msgid "Preload images before rendering the page"
+#: ../../Zotlabs/Module/Settings/Display.php:194
+msgid "Threaded conversation view"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Display.php:195
-msgid ""
-"The subjective page load time will be longer but the page will be ready when "
-"displayed"
+#: ../../Zotlabs/Module/Settings/Display.php:194
+msgid "Display replies below their parent message (default yes)"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Display.php:196
+#: ../../Zotlabs/Module/Settings/Display.php:195
msgid "Enable user zoom on mobile devices"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Display.php:197
+#: ../../Zotlabs/Module/Settings/Display.php:196
msgid "Update browser every xx seconds"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Display.php:197
+#: ../../Zotlabs/Module/Settings/Display.php:196
msgid "Minimum of 10 seconds, no maximum"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Display.php:198
+#: ../../Zotlabs/Module/Settings/Display.php:197
msgid "Maximum number of conversations to load at any time:"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Display.php:198
+#: ../../Zotlabs/Module/Settings/Display.php:197
msgid "Maximum of 30 items"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Display.php:199
+#: ../../Zotlabs/Module/Settings/Display.php:198
msgid "Show emoticons (smilies) as images"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Display.php:200
+#: ../../Zotlabs/Module/Settings/Display.php:199
msgid "Link post titles to source"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Display.php:202
+#: ../../Zotlabs/Module/Settings/Display.php:201
msgid "Display new member quick links menu"
msgstr ""
@@ -14687,10 +14661,6 @@ msgstr ""
msgid "Unseen stream activity"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Channel.php:259
-msgid "Unseen channel activity"
-msgstr ""
-
#: ../../Zotlabs/Module/Settings/Channel.php:260
msgid "Unseen private messages"
msgstr ""
@@ -14730,10 +14700,6 @@ msgstr ""
msgid "System critical alerts"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Channel.php:267
-msgid "New connections"
-msgstr ""
-
#: ../../Zotlabs/Module/Settings/Channel.php:268
msgid "System Registrations"
msgstr ""
@@ -14742,10 +14708,6 @@ msgstr ""
msgid "Unseen shared files"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Channel.php:270
-msgid "Unseen public stream activity"
-msgstr ""
-
#: ../../Zotlabs/Module/Settings/Channel.php:271
msgid "Unseen likes and dislikes"
msgstr ""
diff --git a/util/init_sys_channel b/util/init_sys_channel
new file mode 100755
index 000000000..a97ef295b
--- /dev/null
+++ b/util/init_sys_channel
@@ -0,0 +1,13 @@
+#!/usr/bin/env php
+<?php
+
+if(!file_exists('include/cli_startup.php')) {
+ echo 'Run init_sys_channel from the top level Hubzilla web directory, as util/init_sys_channel' . PHP_EOL;
+ exit(1);
+}
+
+require_once('include/cli_startup.php');
+
+cli_startup();
+
+create_sys_channel();
diff --git a/vendor/bakame/http-structured-fields/LICENSE b/vendor/bakame/http-structured-fields/LICENSE
new file mode 100644
index 000000000..225c502ed
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Nyamagana Butera
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/bakame/http-structured-fields/composer.json b/vendor/bakame/http-structured-fields/composer.json
new file mode 100644
index 000000000..9a8241ee2
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/composer.json
@@ -0,0 +1,106 @@
+{
+ "name": "bakame/http-structured-fields",
+ "description": "A PHP library that parses, validates and serializes HTTP structured fields according to RFC9561 and RFC8941",
+ "type": "library",
+ "keywords": [
+ "http",
+ "http headers",
+ "http trailers",
+ "headers",
+ "trailers",
+ "structured fields",
+ "structured headers",
+ "structured trailers",
+ "structured values",
+ "parser",
+ "serializer",
+ "validation",
+ "rfc8941",
+ "rfc9651"
+ ],
+ "license": "MIT",
+ "authors": [
+ {
+ "name" : "Ignace Nyamagana Butera",
+ "email" : "nyamsprod@gmail.com",
+ "homepage" : "https://github.com/nyamsprod/",
+ "role" : "Developer"
+ }
+ ],
+ "support": {
+ "docs": "https://github.com/bakame-php/http-structured-fields",
+ "issues": "https://github.com/bakame-php/http-structured-fields/issues",
+ "source": "https://github.com/bakame-php/http-structured-fields"
+ },
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nyamsprod"
+ }
+ ],
+ "require": {
+ "php" : "^8.1"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^3.65.0",
+ "httpwg/structured-field-tests": "*@dev",
+ "phpstan/phpstan": "^2.0.3",
+ "phpstan/phpstan-strict-rules": "^2.0",
+ "phpstan/phpstan-phpunit": "^2.0.1",
+ "phpstan/phpstan-deprecation-rules": "^2.0.1",
+ "phpunit/phpunit": "^10.5.38 || ^11.5.0",
+ "symfony/var-dumper": "^6.4.15 || ^v7.2.0",
+ "bakame/aide-base32": "dev-main",
+ "phpbench/phpbench": "^1.3.1"
+ },
+ "autoload": {
+ "psr-4": {
+ "Bakame\\Http\\StructuredFields\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Bakame\\Http\\StructuredFields\\": "tests/"
+ }
+ },
+ "scripts": {
+ "benchmark": "phpbench run --report=default",
+ "phpcs": "php-cs-fixer fix --dry-run --diff -vvv --allow-risky=yes --ansi",
+ "phpcs:fix": "php-cs-fixer fix -vvv --allow-risky=yes --ansi",
+ "phpstan": "phpstan analyse -c phpstan.neon --ansi --memory-limit 192M",
+ "phpunit": "XDEBUG_MODE=coverage phpunit --coverage-text",
+ "phpunit:min": "phpunit --no-coverage",
+ "test": [
+ "@phpunit",
+ "@phpstan",
+ "@phpcs"
+ ]
+ },
+ "scripts-descriptions": {
+ "benchmark": "Runs parser benchmark",
+ "phpstan": "Runs complete codebase static analysis",
+ "phpunit": "Runs unit and functional testing",
+ "phpcs": "Runs coding style testing",
+ "phpcs:fix": "Fix coding style issues",
+ "test": "Runs all tests"
+ },
+ "repositories": [
+ {
+ "type": "package",
+ "package": {
+ "name": "httpwg/structured-field-tests",
+ "version": "dev-main",
+ "source": {
+ "url": "https://github.com/httpwg/structured-field-tests.git",
+ "type": "git",
+ "reference": "main"
+ }
+ }
+ }
+ ],
+ "extra": {
+ "branch-alias": {
+ "dev-develop": "1.x-dev"
+ }
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/Bytes.php b/vendor/bakame/http-structured-fields/src/Bytes.php
new file mode 100644
index 000000000..aa747b76b
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Bytes.php
@@ -0,0 +1,83 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields;
+
+use Stringable;
+use Throwable;
+
+use function base64_decode;
+use function base64_encode;
+use function preg_match;
+
+/**
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-3.3.5
+ */
+final class Bytes
+{
+ private function __construct(private readonly string $value)
+ {
+ }
+
+ /**
+ * Returns a new instance from a Base64 encoded string.
+ */
+ public static function fromEncoded(Stringable|string $encoded): self
+ {
+ $encoded = (string) $encoded;
+ if (1 !== preg_match('/^[a-z\d+\/=]*$/i', $encoded)) {
+ throw new SyntaxError('The byte sequence '.$encoded.' contains invalid characters.');
+ }
+
+ $decoded = base64_decode($encoded, true);
+ if (false === $decoded) {
+ throw new SyntaxError('Unable to base64 decode the byte sequence '.$encoded);
+ }
+
+ return new self($decoded);
+ }
+
+ public static function tryFromEncoded(Stringable|string $encoded): ?self
+ {
+ try {
+ return self::fromEncoded($encoded);
+ } catch (Throwable) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns a new instance from a raw decoded string.
+ */
+ public static function fromDecoded(Stringable|string $decoded): self
+ {
+ return new self((string) $decoded);
+ }
+
+ /**
+ * Returns the decoded string.
+ */
+ public function decoded(): string
+ {
+ return $this->value;
+ }
+
+ /**
+ * Returns the base64 encoded string.
+ */
+ public function encoded(): string
+ {
+ return base64_encode($this->value);
+ }
+
+ public function equals(mixed $other): bool
+ {
+ return $other instanceof self && $other->value === $this->value;
+ }
+
+ public function type(): Type
+ {
+ return Type::Bytes;
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/DataType.php b/vendor/bakame/http-structured-fields/src/DataType.php
new file mode 100644
index 000000000..8cc5f5395
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/DataType.php
@@ -0,0 +1,45 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields;
+
+use Exception;
+use Stringable;
+
+enum DataType: string
+{
+ case List = 'list';
+ case InnerList = 'innerlist';
+ case Parameters = 'parameters';
+ case Dictionary = 'dictionary';
+ case Item = 'item';
+
+ /**
+ * @throws SyntaxError|Exception
+ */
+ public function parse(Stringable|string $httpValue, Ietf $rfc = Ietf::Rfc9651): OuterList|InnerList|Parameters|Dictionary|Item
+ {
+ return match ($this) {
+ self::List => OuterList::fromHttpValue($httpValue, $rfc),
+ self::Dictionary => Dictionary::fromHttpValue($httpValue, $rfc),
+ self::Item => Item::fromHttpValue($httpValue, $rfc),
+ self::InnerList => InnerList::fromHttpValue($httpValue, $rfc),
+ self::Parameters => Parameters::fromHttpValue($httpValue, $rfc),
+ };
+ }
+
+ /**
+ * @throws SyntaxError|Exception
+ */
+ public function serialize(iterable $data, Ietf $rfc = Ietf::Rfc9651): string
+ {
+ return (match ($this) {
+ self::List => OuterList::fromPairs($data),
+ self::Dictionary => Dictionary::fromPairs($data),
+ self::Item => Item::fromPair([...$data]),
+ self::InnerList => InnerList::fromPair([...$data]),
+ self::Parameters => Parameters::fromPairs($data),
+ })->toHttpValue($rfc);
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/Dictionary.php b/vendor/bakame/http-structured-fields/src/Dictionary.php
new file mode 100644
index 000000000..8a318695c
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Dictionary.php
@@ -0,0 +1,788 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields;
+
+use ArrayAccess;
+use Bakame\Http\StructuredFields\Validation\Violation;
+use CallbackFilterIterator;
+use Countable;
+use DateTimeInterface;
+use Exception;
+use Iterator;
+use IteratorAggregate;
+use Stringable;
+use Throwable;
+
+use function array_key_exists;
+use function array_keys;
+use function array_map;
+use function count;
+use function implode;
+use function is_bool;
+use function is_int;
+use function is_iterable;
+use function is_string;
+use function uasort;
+
+/**
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-3.2
+ *
+ * @phpstan-import-type SfMemberInput from StructuredFieldProvider
+ *
+ * @implements ArrayAccess<string, InnerList|Item>
+ * @implements IteratorAggregate<int, array{0:string, 1:InnerList|Item}>
+ */
+final class Dictionary implements ArrayAccess, Countable, IteratorAggregate
+{
+ /** @var array<string, InnerList|Item> */
+ private readonly array $members;
+
+ /**
+ * @param iterable<string, SfMemberInput> $members
+ */
+ private function __construct(iterable $members = [])
+ {
+ $filteredMembers = [];
+ foreach ($members as $key => $member) {
+ $filteredMembers[Key::from($key)->value] = Member::innerListOrItem($member);
+ }
+
+ $this->members = $filteredMembers;
+ }
+
+ /**
+ * Returns a new instance.
+ */
+ public static function new(): self
+ {
+ return new self();
+ }
+
+ /**
+ * Returns a new instance from an associative iterable construct.
+ *
+ * its keys represent the dictionary entry name
+ * its values represent the dictionary entry value
+ *
+ * @param StructuredFieldProvider|iterable<string, SfMemberInput> $members
+ */
+ public static function fromAssociative(StructuredFieldProvider|iterable $members): self
+ {
+ if ($members instanceof StructuredFieldProvider) {
+ $structuredField = $members->toStructuredField();
+
+ return match (true) {
+ $structuredField instanceof Dictionary,
+ $structuredField instanceof Parameters => new self($structuredField->toAssociative()),
+ default => throw new InvalidArgument('The '.StructuredFieldProvider::class.' must provide a structured field container; '.$structuredField::class.' given.'),
+ };
+ }
+
+ return new self($members);
+ }
+
+ /**
+ * Returns a new instance from a pair iterable construct.
+ *
+ * Each member is composed of an array with two elements
+ * the first member represents the instance entry key
+ * the second member represents the instance entry value
+ *
+ * @param StructuredFieldProvider|Dictionary|Parameters|iterable<array{0:string, 1?:SfMemberInput}> $pairs
+ */
+ public static function fromPairs(StructuredFieldProvider|Dictionary|Parameters|iterable $pairs): self
+ {
+ if ($pairs instanceof StructuredFieldProvider) {
+ $pairs = $pairs->toStructuredField();
+ }
+
+ if (!is_iterable($pairs)) {
+ throw new InvalidArgument('The "'.$pairs::class.'" instance can not be used for creating a .'.self::class.' structured field.');
+ }
+
+ return match (true) {
+ $pairs instanceof Dictionary,
+ $pairs instanceof Parameters => new self($pairs->toAssociative()),
+ default => new self((function (iterable $pairs) {
+ foreach ($pairs as [$key, $member]) {
+ yield $key => Member::innerListOrItemFromPair($member);
+ }
+ })($pairs)),
+ };
+ }
+
+ /**
+ * Returns an instance from an HTTP textual representation compliant with RFC9651.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-3.2
+ *
+ * @throws StructuredFieldError|Throwable
+ */
+ public static function fromRfc9651(Stringable|string $httpValue): self
+ {
+ return self::fromHttpValue($httpValue, Ietf::Rfc9651);
+ }
+
+ /**
+ * Returns an instance from an HTTP textual representation compliant with RFC8941.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc8941.html#section-3.2
+ *
+ * @throws StructuredFieldError|Throwable
+ */
+ public static function fromRfc8941(Stringable|string $httpValue): self
+ {
+ return self::fromHttpValue($httpValue, Ietf::Rfc8941);
+ }
+
+ /**
+ * Returns an instance from an HTTP textual representation.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-3.2
+ *
+ * @throws StructuredFieldError|Exception If the string is not a valid
+ */
+ public static function fromHttpValue(Stringable|string $httpValue, Ietf $rfc = Ietf::Rfc9651): self
+ {
+ return self::fromPairs((new Parser($rfc))->parseDictionary($httpValue)); /* @phpstan-ignore-line */
+ }
+
+ public function toRfc9651(): string
+ {
+ return $this->toHttpValue(Ietf::Rfc9651);
+ }
+
+ public function toRfc8941(): string
+ {
+ return $this->toHttpValue(Ietf::Rfc8941);
+ }
+
+ public function toHttpValue(Ietf $rfc = Ietf::Rfc9651): string
+ {
+ $formatter = static fn (Item|InnerList $member, string $offset): string => match (true) {
+ $member instanceof Item && true === $member->value() => $offset.$member->parameters()->toHttpValue($rfc),
+ default => $offset.'='.$member->toHttpValue($rfc),
+ };
+
+ return implode(', ', array_map($formatter, $this->members, array_keys($this->members)));
+ }
+
+ public function __toString(): string
+ {
+ return $this->toHttpValue();
+ }
+
+ public function equals(mixed $other): bool
+ {
+ return $other instanceof self && $other->toHttpValue() === $this->toHttpValue();
+ }
+
+ /**
+ * Apply the callback if the given "condition" is (or resolves to) true.
+ *
+ * @param (callable($this): bool)|bool $condition
+ * @param callable($this): (self|null) $onSuccess
+ * @param ?callable($this): (self|null) $onFail
+ */
+ public function when(callable|bool $condition, callable $onSuccess, ?callable $onFail = null): self
+ {
+ if (!is_bool($condition)) {
+ $condition = $condition($this);
+ }
+
+ return match (true) {
+ $condition => $onSuccess($this),
+ null !== $onFail => $onFail($this),
+ default => $this,
+ } ?? $this;
+ }
+
+ public function count(): int
+ {
+ return count($this->members);
+ }
+
+ /**
+ * Tells whether the instance contains no members.
+ */
+ public function isEmpty(): bool
+ {
+ return !$this->isNotEmpty();
+ }
+
+ /**
+ * Tells whether the instance contains any members.
+ */
+ public function isNotEmpty(): bool
+ {
+ return [] !== $this->members;
+ }
+
+ /**
+ * @return Iterator<string, InnerList|Item>
+ */
+ public function toAssociative(): Iterator
+ {
+ yield from $this->members;
+ }
+
+ /**
+ * Returns an iterable construct of dictionary pairs.
+ *
+ * @return Iterator<int, array{0:string, 1:InnerList|Item}>
+ */
+ public function getIterator(): Iterator
+ {
+ foreach ($this->members as $index => $member) {
+ yield [$index, $member];
+ }
+ }
+
+ /**
+ * Returns an ordered list of the instance keys.
+ *
+ * @return array<string>
+ */
+ public function keys(): array
+ {
+ return array_keys($this->members);
+ }
+
+ /**
+ * @return array<int>
+ */
+ public function indices(): array
+ {
+ return array_keys($this->keys());
+ }
+
+ /**
+ * Tells whether the instance contain a members at the specified offsets.
+ */
+ public function hasKeys(string ...$keys): bool
+ {
+ foreach ($keys as $key) {
+ if (!array_key_exists($key, $this->members)) {
+ return false;
+ }
+ }
+
+ return [] !== $keys;
+ }
+
+ /**
+ * Returns true only if the instance only contains the listed keys, false otherwise.
+ *
+ * @param array<string> $keys
+ */
+ public function allowedKeys(array $keys): bool
+ {
+ foreach ($this->members as $key => $member) {
+ if (!in_array($key, $keys, true)) {
+ return false;
+ }
+ }
+
+ return [] !== $keys;
+ }
+
+ /**
+ * @param ?callable(Item|InnerList): (bool|string) $validate
+ *
+ * @throws InvalidOffset|Violation|StructuredFieldError
+ */
+ public function getByKey(string $key, ?callable $validate = null): Item|InnerList
+ {
+ $value = $this->members[$key] ?? throw InvalidOffset::dueToKeyNotFound($key);
+ if (null === $validate || true === ($exceptionMessage = $validate($value))) {
+ return $value;
+ }
+
+ if (!is_string($exceptionMessage) || '' === trim($exceptionMessage)) {
+ $exceptionMessage = "The parameter '{key}' whose value is '{value}' failed validation.";
+ }
+
+ throw new Violation(strtr($exceptionMessage, ['{key}' => $key, '{value}' => $value->toHttpValue()]));
+ }
+
+ /**
+ * Tells whether a pair is attached to the given index position.
+ */
+ public function hasIndices(int ...$indexes): bool
+ {
+ $max = count($this->members);
+ foreach ($indexes as $index) {
+ if (null === $this->filterIndex($index, $max)) {
+ return false;
+ }
+ }
+
+ return [] !== $indexes;
+ }
+
+ /**
+ * Filters and format instance index.
+ */
+ private function filterIndex(int $index, ?int $max = null): ?int
+ {
+ $max ??= count($this->members);
+
+ return match (true) {
+ [] === $this->members,
+ 0 > $max + $index,
+ 0 > $max - $index - 1 => null,
+ 0 > $index => $max + $index,
+ default => $index,
+ };
+ }
+
+ /**
+ * Returns the item or the inner-list and its key as attached to the given
+ * collection according to their index position otherwise throw.
+ *
+ * @param ?callable(Item|InnerList, string): (bool|string) $validate
+ *
+ * @throws InvalidOffset|Violation|StructuredFieldError
+ *
+ * @return array{0:string, 1:InnerList|Item}
+ */
+ public function getByIndex(int $index, ?callable $validate = null): array
+ {
+ $foundOffset = $this->filterIndex($index) ?? throw InvalidOffset::dueToIndexNotFound($index);
+ foreach ($this as $offset => $pair) {
+ if ($offset === $foundOffset) {
+ break;
+ }
+ }
+
+ if (!isset($pair)) {
+ throw InvalidOffset::dueToIndexNotFound($index);
+ }
+
+ if (null === $validate || true === ($exceptionMessage = $validate($pair[1], $pair[0]))) {
+ return $pair;
+ }
+
+ if (!is_string($exceptionMessage) || '' === trim($exceptionMessage)) {
+ $exceptionMessage = "The member at position '{index}' whose key is '{key}' with the value '{value}' failed validation.";
+ }
+
+ throw new Violation(strtr($exceptionMessage, ['{index}' => $index, '{key}' => $pair[0], '{value}' => $pair[1]->toHttpValue()]));
+ }
+
+ /**
+ * Returns the key associated with the given index or null otherwise.
+ */
+ public function indexByKey(string $key): ?int
+ {
+ foreach ($this as $index => $member) {
+ if ($key === $member[0]) {
+ return $index;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the index associated with the given key or null otherwise.
+ */
+ public function keyByIndex(int $index): ?string
+ {
+ $index = $this->filterIndex($index);
+ if (null === $index) {
+ return null;
+ }
+
+ foreach ($this as $offset => $member) {
+ if ($offset === $index) {
+ return $member[0];
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the first member whether it is an item or an inner-list and its key as attached to the given
+ * collection according to their index position otherwise returns an empty array.
+ *
+ * @return array{0:string, 1:InnerList|Item}|array{}
+ */
+ public function first(): array
+ {
+ try {
+ return $this->getByIndex(0);
+ } catch (StructuredFieldError) {
+ return [];
+ }
+ }
+
+ /**
+ * Returns the first member whether it is an item or an inner-list and its key as attached to the given
+ * collection according to their index position otherwise returns an empty array.
+ *
+ * @return array{0:string, 1:InnerList|Item}|array{}
+ */
+ public function last(): array
+ {
+ try {
+ return $this->getByIndex(-1);
+ } catch (StructuredFieldError) {
+ return [];
+ }
+ }
+
+ /**
+ * Adds a member at the end of the instance otherwise updates the value associated with the key if already present.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified changes.
+ *
+ * @param SfMemberInput $member
+ *
+ * @throws SyntaxError If the string key is not a valid
+ */
+ public function add(
+ string $key,
+ iterable|StructuredFieldProvider|Dictionary|Parameters|Item|Token|Bytes|DisplayString|DateTimeInterface|string|int|float|bool $member
+ ): self {
+ $members = $this->members;
+ $members[Key::from($key)->value] = Member::innerListOrItem($member);
+
+ return $this->newInstance($members);
+ }
+
+ /**
+ * @param array<string, InnerList|Item> $members
+ */
+ private function newInstance(array $members): self
+ {
+ foreach ($members as $offset => $member) {
+ if (!isset($this->members[$offset]) || !$this->members[$offset]->equals($member)) {
+ return new self($members);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Deletes members associated with the list of submitted keys.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified changes.
+ */
+ private function remove(string|int ...$offsets): self
+ {
+ if ([] === $this->members || [] === $offsets) {
+ return $this;
+ }
+
+ $keys = array_keys($this->members);
+ $max = count($keys);
+ $reducer = fn (array $carry, string|int $key): array => match (true) {
+ is_string($key) && (false !== ($position = array_search($key, $keys, true))),
+ is_int($key) && (null !== ($position = $this->filterIndex($key, $max))) => [$position => true] + $carry,
+ default => $carry,
+ };
+
+ $indices = array_reduce($offsets, $reducer, []);
+
+ return match (true) {
+ [] === $indices => $this,
+ $max === count($indices) => self::new(),
+ default => self::fromPairs((function (array $offsets) {
+ foreach ($this->getIterator() as $offset => $pair) {
+ if (!array_key_exists($offset, $offsets)) {
+ yield $pair;
+ }
+ }
+ })($indices)),
+ };
+ }
+
+ /**
+ * Deletes members associated with the list using the member pair offset.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified changes.
+ */
+ public function removeByIndices(int ...$indices): self
+ {
+ return $this->remove(...$indices);
+ }
+
+ /**
+ * Deletes members associated with the list using the member key.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified changes.
+ */
+ public function removeByKeys(string ...$keys): self
+ {
+ return $this->remove(...$keys);
+ }
+
+ /**
+ * Adds a member at the end of the instance and deletes any previous reference to the key if present.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified changes.
+ *
+ * @param SfMemberInput $member
+ * @throws SyntaxError If the string key is not a valid
+ */
+ public function append(
+ string $key,
+ iterable|StructuredFieldProvider|Dictionary|Parameters|Item|Token|Bytes|DisplayString|DateTimeInterface|string|int|float|bool $member
+ ): self {
+ $members = $this->members;
+ unset($members[$key]);
+
+ return $this->newInstance([...$members, Key::from($key)->value => Member::innerListOrItem($member)]);
+ }
+
+ /**
+ * Adds a member at the beginning of the instance and deletes any previous reference to the key if present.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified changes.
+ *
+ * @param SfMemberInput $member
+ *
+ * @throws SyntaxError If the string key is not a valid
+ */
+ public function prepend(
+ string $key,
+ iterable|StructuredFieldProvider|Dictionary|Parameters|Item|Token|Bytes|DisplayString|DateTimeInterface|string|int|float|bool $member
+ ): self {
+ $members = $this->members;
+ unset($members[$key]);
+
+ return $this->newInstance([Key::from($key)->value => Member::innerListOrItem($member), ...$members]);
+ }
+
+ /**
+ * Inserts pairs at the end of the container.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified changes.
+ *
+ * @param array{0:string, 1:SfMemberInput} ...$pairs
+ */
+ public function push(array ...$pairs): self
+ {
+ return match (true) {
+ [] === $pairs => $this,
+ default => self::fromPairs((function (iterable $pairs) {
+ yield from $this->getIterator();
+ yield from $pairs;
+ })($pairs)),
+ };
+ }
+
+ /**
+ * Inserts pairs at the beginning of the container.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified changes.
+ *
+ * @param array{0:string, 1:SfMemberInput} ...$pairs
+ */
+ public function unshift(array ...$pairs): self
+ {
+ return match (true) {
+ [] === $pairs => $this,
+ default => self::fromPairs((function (iterable $pairs) {
+ yield from $pairs;
+ yield from $this->getIterator();
+ })($pairs)),
+ };
+ }
+
+ /**
+ * Insert a member pair using its offset.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified changes.
+ *
+ * @param array{0:string, 1:SfMemberInput} ...$members
+ */
+ public function insert(int $index, array ...$members): self
+ {
+ $offset = $this->filterIndex($index) ?? throw InvalidOffset::dueToIndexNotFound($index);
+
+ return match (true) {
+ [] === $members => $this,
+ 0 === $offset => $this->unshift(...$members),
+ count($this->members) === $offset => $this->push(...$members),
+ default => (function (Iterator $newMembers) use ($offset, $members) {
+ $newMembers = iterator_to_array($newMembers);
+ array_splice($newMembers, $offset, 0, $members);
+
+ return self::fromPairs($newMembers);
+ })($this->getIterator()),
+ };
+ }
+
+ /**
+ * Replace a member pair using its offset.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified changes.
+ *
+ * @param array{0:string, 1:SfMemberInput} $pair
+ */
+ public function replace(int $index, array $pair): self
+ {
+ $offset = $this->filterIndex($index) ?? throw InvalidOffset::dueToIndexNotFound($index);
+ $pair[1] = Member::innerListOrItem($pair[1]);
+ $pairs = iterator_to_array($this->getIterator());
+
+ return match (true) {
+ $pairs[$offset][0] === $pair[0] && $pairs[$offset][1]->equals($pair[1]) => $this,
+ default => self::fromPairs(array_replace($pairs, [$offset => $pair])),
+ };
+ }
+
+ /**
+ * Merges multiple instances using iterable associative structures.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified changes.
+ *
+ * @param StructuredFieldProvider|Dictionary|Parameters|iterable<string, SfMemberInput> ...$others
+ */
+ public function mergeAssociative(StructuredFieldProvider|iterable ...$others): self
+ {
+ $members = $this->members;
+ foreach ($others as $other) {
+ if ($other instanceof StructuredFieldProvider) {
+ $other = $other->toStructuredField();
+ if (!is_iterable($other)) {
+ throw new InvalidArgument('The "'.$other::class.'" instance can not be used for creating a .'.self::class.' structured field.');
+ }
+ }
+
+ if ($other instanceof self || $other instanceof Parameters) {
+ $other = $other->toAssociative();
+ }
+
+ foreach ($other as $key => $value) {
+ $members[$key] = $value;
+ }
+ }
+
+ return new self($members);
+ }
+
+ /**
+ * Merges multiple instances using iterable pairs.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified changes.
+ *
+ * @param StructuredFieldProvider|Dictionary|Parameters|iterable<array{0:string, 1:SfMemberInput}> ...$others
+ */
+ public function mergePairs(StructuredFieldProvider|Dictionary|Parameters|iterable ...$others): self
+ {
+ $members = $this->members;
+ foreach ($others as $other) {
+ if (!$other instanceof self) {
+ $other = self::fromPairs($other);
+ }
+ foreach ($other->toAssociative() as $key => $value) {
+ $members[$key] = $value;
+ }
+ }
+
+ return new self($members);
+ }
+
+ /**
+ * @param string $offset
+ */
+ public function offsetExists(mixed $offset): bool
+ {
+ return $this->hasKeys($offset);
+ }
+
+ /**
+ * @param string $offset
+ *
+ * @throws StructuredFieldError
+ */
+ public function offsetGet(mixed $offset): InnerList|Item
+ {
+ return $this->getByKey($offset);
+ }
+
+ public function offsetUnset(mixed $offset): void
+ {
+ throw new ForbiddenOperation(self::class.' instance can not be updated using '.ArrayAccess::class.' methods.');
+ }
+
+ public function offsetSet(mixed $offset, mixed $value): void
+ {
+ throw new ForbiddenOperation(self::class.' instance can not be updated using '.ArrayAccess::class.' methods.');
+ }
+
+ /**
+ * Run a map over each container members.
+ *
+ * @template TMap
+ *
+ * @param callable(array{0:string, 1:Item|InnerList}, int): TMap $callback
+ *
+ * @return Iterator<TMap>
+ */
+ public function map(callable $callback): Iterator
+ {
+ foreach ($this as $offset => $member) {
+ yield ($callback)($member, $offset);
+ }
+ }
+
+ /**
+ * @param callable(TInitial|null, array{0:string, 1:Item|InnerList}, int): TInitial $callback
+ * @param TInitial|null $initial
+ *
+ * @template TInitial
+ *
+ * @return TInitial|null
+ */
+ public function reduce(callable $callback, mixed $initial = null): mixed
+ {
+ foreach ($this as $offset => $pair) {
+ $initial = $callback($initial, $pair, $offset);
+ }
+
+ return $initial;
+ }
+
+ /**
+ * Run a filter over each container members.
+ *
+ * @param callable(array{0:string, 1:InnerList|Item}, int): bool $callback
+ */
+ public function filter(callable $callback): self
+ {
+ return self::fromPairs(new CallbackFilterIterator($this, $callback));
+ }
+
+ /**
+ * Sort a container by value using a callback.
+ *
+ * @param callable(array{0:string, 1:InnerList|Item}, array{0:string, 1:InnerList|Item}): int $callback
+ */
+ public function sort(callable $callback): self
+ {
+ $members = iterator_to_array($this);
+ uasort($members, $callback);
+
+ return self::fromPairs($members);
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/DisplayString.php b/vendor/bakame/http-structured-fields/src/DisplayString.php
new file mode 100644
index 000000000..361f91ec2
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/DisplayString.php
@@ -0,0 +1,103 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields;
+
+use Stringable;
+use Throwable;
+
+use function preg_match;
+use function preg_replace_callback;
+use function rawurldecode;
+use function rawurlencode;
+use function str_contains;
+
+/**
+ * @see https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-sfbis#section-4.2.10
+ */
+final class DisplayString
+{
+ private function __construct(private readonly string $value)
+ {
+ }
+
+ public static function tryFromEncoded(Stringable|string $encoded): ?self
+ {
+ try {
+ return self::fromEncoded($encoded);
+ } catch (Throwable) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns a new instance from a Base64 encoded string.
+ */
+ public static function fromEncoded(Stringable|string $encoded): self
+ {
+ $encoded = (string) $encoded;
+
+ if (1 === preg_match('/[^\x20-\x7E]/i', $encoded)) {
+ throw new SyntaxError('The display string '.$encoded.' contains invalid characters.');
+ }
+
+ if (!str_contains($encoded, '%')) {
+ return new self($encoded);
+ }
+
+ if (1 === preg_match('/%(?![0-9a-f]{2})/', $encoded)) {
+ throw new SyntaxError('The display string '.$encoded.' contains invalid utf-8 encoded sequence.');
+ }
+
+ $decoded = (string) preg_replace_callback(
+ ',%[a-f0-9]{2},',
+ fn (array $matches): string => rawurldecode($matches[0]),
+ $encoded
+ );
+
+ if (1 !== preg_match('//u', $decoded)) {
+ throw new SyntaxError('The display string '.$encoded.' contains invalid characters.');
+ }
+
+ return new self($decoded);
+ }
+
+ /**
+ * Returns a new instance from a raw decoded string.
+ */
+ public static function fromDecoded(Stringable|string $decoded): self
+ {
+ return new self((string) $decoded);
+ }
+
+ /**
+ * Returns the decoded string.
+ */
+ public function decoded(): string
+ {
+ return $this->value;
+ }
+
+ /**
+ * Returns the base64 encoded string.
+ */
+ public function encoded(): string
+ {
+ return (string) preg_replace_callback(
+ '/[%"\x00-\x1F\x7F-\xFF]/',
+ static fn (array $matches): string => strtolower(rawurlencode($matches[0])),
+ $this->value
+ );
+ }
+
+ public function equals(mixed $other): bool
+ {
+ return $other instanceof self && $other->value === $this->value;
+ }
+
+ public function type(): Type
+ {
+ return Type::DisplayString;
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/ForbiddenOperation.php b/vendor/bakame/http-structured-fields/src/ForbiddenOperation.php
new file mode 100644
index 000000000..f1edbdef4
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/ForbiddenOperation.php
@@ -0,0 +1,11 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields;
+
+use LogicException;
+
+final class ForbiddenOperation extends LogicException implements StructuredFieldError
+{
+}
diff --git a/vendor/bakame/http-structured-fields/src/Ietf.php b/vendor/bakame/http-structured-fields/src/Ietf.php
new file mode 100644
index 000000000..a98e16e20
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Ietf.php
@@ -0,0 +1,73 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields;
+
+use DateTimeImmutable;
+use DateTimeZone;
+
+enum Ietf
+{
+ case Rfc8941;
+ case Rfc9651;
+
+ public function uri(): string
+ {
+ return match ($this) {
+ self::Rfc9651 => 'https://www.rfc-editor.org/rfc/rfc9651.html',
+ self::Rfc8941 => 'https://www.rfc-editor.org/rfc/rfc8941.html',
+ };
+ }
+
+ public function publishedAt(): DateTimeImmutable
+ {
+ return new DateTimeImmutable(match ($this) {
+ self::Rfc9651 => '2024-09-01',
+ self::Rfc8941 => '2021-02-01',
+ }, new DateTimeZone('UTC'));
+ }
+
+ public function isActive(): bool
+ {
+ return self::Rfc9651 === $this;
+ }
+
+ public function isObsolete(): bool
+ {
+ return !$this->isActive();
+ }
+
+ public function supports(mixed $value): bool
+ {
+ if ($value instanceof StructuredFieldProvider) {
+ $value = $value->toStructuredField();
+ }
+
+ if ($value instanceof OuterList ||
+ $value instanceof InnerList ||
+ $value instanceof Dictionary ||
+ $value instanceof Parameters ||
+ $value instanceof Item
+ ) {
+ try {
+ $value->toHttpValue($this);
+
+ return true;
+ } catch (MissingFeature) {
+ return false;
+ }
+ }
+
+ if (!$value instanceof Type) {
+ $value = Type::tryFromVariable($value);
+ }
+
+ return match ($value) {
+ null => false,
+ Type::DisplayString,
+ Type::Date => self::Rfc8941 !== $this,
+ default => true,
+ };
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/InnerList.php b/vendor/bakame/http-structured-fields/src/InnerList.php
new file mode 100644
index 000000000..8a9a3e734
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/InnerList.php
@@ -0,0 +1,478 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields;
+
+use ArrayAccess;
+use Bakame\Http\StructuredFields\Validation\Violation;
+use Countable;
+use DateTimeInterface;
+use Iterator;
+use IteratorAggregate;
+use Stringable;
+
+use function array_filter;
+use function array_is_list;
+use function array_map;
+use function array_replace;
+use function array_splice;
+use function array_values;
+use function count;
+use function implode;
+use function uasort;
+
+use const ARRAY_FILTER_USE_BOTH;
+use const ARRAY_FILTER_USE_KEY;
+
+/**
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-3.1.1
+ *
+ * @phpstan-import-type SfType from StructuredFieldProvider
+ * @phpstan-import-type SfTypeInput from StructuredFieldProvider
+ * @phpstan-import-type SfItemInput from StructuredFieldProvider
+ * @phpstan-import-type SfItemPair from StructuredFieldProvider
+ * @phpstan-import-type SfInnerListPair from StructuredFieldProvider
+ * @phpstan-import-type SfParameterInput from StructuredFieldProvider
+ *
+ * @implements ArrayAccess<int, Item>
+ * @implements IteratorAggregate<int, Item>
+ */
+final class InnerList implements ArrayAccess, Countable, IteratorAggregate
+{
+ use ParameterAccess;
+
+ /** @var list<Item> */
+ private readonly array $members;
+ private readonly Parameters $parameters;
+
+ /**
+ * @param iterable<SfItemInput|SfItemPair> $members
+ */
+ private function __construct(iterable $members, ?Parameters $parameters = null)
+ {
+ $this->members = array_map(Member::item(...), array_values([...$members]));
+ $this->parameters = $parameters ?? Parameters::new();
+ }
+
+ /**
+ * Returns an instance from an HTTP textual representation.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-3.1
+ */
+ public static function fromHttpValue(Stringable|string $httpValue, Ietf $rfc = Ietf::Rfc9651): self
+ {
+ return self::fromPair((new Parser($rfc))->parseInnerList($httpValue));
+ }
+
+ /**
+ * Returns a new instance with an iter.
+ *
+ * @param iterable<SfItemInput> $value
+ * @param Parameters|iterable<string, SfItemInput> $parameters
+ */
+ public static function fromAssociative(
+ iterable $value,
+ StructuredFieldProvider|Parameters|iterable $parameters
+ ): self {
+ if ($parameters instanceof StructuredFieldProvider) {
+ $parameters = $parameters->toStructuredField();
+ if (!$parameters instanceof Parameters) {
+ throw new InvalidArgument('The '.StructuredFieldProvider::class.' must provide a '.Parameters::class.'; '.$parameters::class.' given.');
+ }
+ }
+
+ if (!$parameters instanceof Parameters) {
+ return new self($value, Parameters::fromAssociative($parameters));
+ }
+
+ return new self($value, $parameters);
+ }
+
+ /**
+ * @param array{0:iterable<SfItemInput>, 1?:Parameters|SfParameterInput}|array<mixed> $pair
+ */
+ public static function fromPair(array $pair = []): self
+ {
+ if ([] === $pair) {
+ return self::new();
+ }
+
+ if (!array_is_list($pair) || 2 < count($pair)) {
+ throw new SyntaxError('The pair must be represented by an non-empty array as a list containing at most 2 members.');
+ }
+
+ if (1 === count($pair)) {
+ return new self($pair[0]);
+ }
+
+ if ($pair[1] instanceof StructuredFieldProvider) {
+ $pair[1] = $pair[1]->toStructuredField();
+ if (!$pair[1] instanceof Parameters) {
+ throw new InvalidArgument('The '.StructuredFieldProvider::class.' must provide a '.Parameters::class.'; '.$pair[1]::class.' given.');
+ }
+ }
+
+ if (!$pair[1] instanceof Parameters) {
+ return new self($pair[0], Parameters::fromPairs($pair[1]));
+ }
+
+ return new self($pair[0], $pair[1]);
+ }
+
+ /**
+ * Returns a new instance.
+ *
+ * @param StructuredFieldProvider|Item|SfTypeInput|SfItemPair ...$members
+ */
+ public static function new(
+ StructuredFieldProvider|Item|Token|Bytes|DisplayString|DateTimeInterface|array|string|int|float|bool ...$members
+ ): self {
+ return new self($members);
+ }
+
+ public static function fromRfc9651(Stringable|string $httpValue): self
+ {
+ return self::fromHttpValue($httpValue, Ietf::Rfc9651);
+ }
+
+ public static function fromRfc8941(Stringable|string $httpValue): self
+ {
+ return self::fromHttpValue($httpValue, Ietf::Rfc8941);
+ }
+
+ public function toHttpValue(Ietf $rfc = Ietf::Rfc9651): string
+ {
+ return '('.implode(' ', array_map(fn (Item $value): string => $value->toHttpValue($rfc), $this->members)).')'.$this->parameters->toHttpValue($rfc);
+ }
+
+ public function toRfc9651(): string
+ {
+ return $this->toHttpValue(Ietf::Rfc9651);
+ }
+
+ public function toRfc8941(): string
+ {
+ return $this->toHttpValue(Ietf::Rfc8941);
+ }
+
+ public function __toString(): string
+ {
+ return $this->toHttpValue();
+ }
+
+ public function equals(mixed $other): bool
+ {
+ return $other instanceof self && $other->toHttpValue() === $this->toHttpValue();
+ }
+
+ /**
+ * Apply the callback if the given "condition" is (or resolves to) true.
+ *
+ * @param (callable($this): bool)|bool $condition
+ * @param callable($this): (self|null) $onSuccess
+ * @param ?callable($this): (self|null) $onFail
+ */
+ public function when(callable|bool $condition, callable $onSuccess, ?callable $onFail = null): self
+ {
+ if (!is_bool($condition)) {
+ $condition = $condition($this);
+ }
+
+ return match (true) {
+ $condition => $onSuccess($this),
+ null !== $onFail => $onFail($this),
+ default => $this,
+ } ?? $this;
+ }
+
+ /**
+ * @return array{0:list<Item>, 1:Parameters}
+ */
+ public function toPair(): array
+ {
+ return [$this->members, $this->parameters];
+ }
+
+ public function getIterator(): Iterator
+ {
+ yield from $this->members;
+ }
+
+ public function count(): int
+ {
+ return count($this->members);
+ }
+
+ public function isEmpty(): bool
+ {
+ return !$this->isNotEmpty();
+ }
+
+ public function isNotEmpty(): bool
+ {
+ return [] !== $this->members;
+ }
+
+ /**
+ * @return array<int>
+ */
+ public function indices(): array
+ {
+ return array_keys($this->members);
+ }
+
+ public function hasIndices(int ...$indices): bool
+ {
+ $max = count($this->members);
+ foreach ($indices as $offset) {
+ if (null === $this->filterIndex($offset, $max)) {
+ return false;
+ }
+ }
+
+ return [] !== $indices;
+ }
+
+ private function filterIndex(int $index, ?int $max = null): ?int
+ {
+ $max ??= count($this->members);
+
+ return match (true) {
+ [] === $this->members,
+ 0 > $max + $index,
+ 0 > $max - $index - 1 => null,
+ 0 > $index => $max + $index,
+ default => $index,
+ };
+ }
+
+ /**
+ * @param ?callable(Item): (bool|string) $validate
+ *
+ * @throws SyntaxError|Violation|StructuredFieldError
+ */
+ public function getByIndex(int $index, ?callable $validate = null): Item
+ {
+ $value = $this->members[$this->filterIndex($index) ?? throw InvalidOffset::dueToIndexNotFound($index)];
+ if (null === $validate) {
+ return $value;
+ }
+
+ if (true === ($exceptionMessage = $validate($value))) {
+ return $value;
+ }
+
+ if (!is_string($exceptionMessage) || '' === trim($exceptionMessage)) {
+ $exceptionMessage = "The item at '{index}' whose value is '{value}' failed validation.";
+ }
+
+ throw new Violation(strtr($exceptionMessage, ['{index}' => $index, '{value}' => $value->toHttpValue()]));
+ }
+
+ public function first(): ?Item
+ {
+ return $this->members[0] ?? null;
+ }
+
+ public function last(): ?Item
+ {
+ return $this->members[$this->filterIndex(-1)] ?? null;
+ }
+
+ public function withParameters(StructuredFieldProvider|Parameters $parameters): static
+ {
+ if ($parameters instanceof StructuredFieldProvider) {
+ $parameters = $parameters->toStructuredField();
+ if (!$parameters instanceof Parameters) {
+ throw new InvalidArgument('The '.StructuredFieldProvider::class.' must provide a '.Parameters::class.'; '.$parameters::class.' given.');
+ }
+ }
+
+ return $this->parameters->equals($parameters) ? $this : new self($this->members, $parameters);
+ }
+
+ /**
+ * Inserts members at the beginning of the list.
+ */
+ public function unshift(
+ StructuredFieldProvider|OuterList|Dictionary|InnerList|Parameters|Item|Token|Bytes|DisplayString|DateTimeInterface|string|int|float|bool ...$members
+ ): self {
+ $membersToAdd = array_reduce(
+ $members,
+ function (array $carry, $member) {
+ if ($member instanceof StructuredFieldProvider) {
+ $member = $member->toStructuredField();
+ }
+
+ return [...$carry, ...$member instanceof InnerList ? [...$member] : [$member]];
+ },
+ []
+ );
+
+ return match (true) {
+ [] === $membersToAdd => $this,
+ default => new self([...array_values($membersToAdd), ...$this->members], $this->parameters),
+ };
+ }
+
+ /**
+ * Inserts members at the end of the list.
+ */
+ public function push(
+ StructuredFieldProvider|OuterList|Dictionary|InnerList|Parameters|Item|Token|Bytes|DisplayString|DateTimeInterface|string|int|float|bool ...$members
+ ): self {
+ $membersToAdd = array_reduce(
+ $members,
+ function (array $carry, $member) {
+ if ($member instanceof StructuredFieldProvider) {
+ $member = $member->toStructuredField();
+ }
+
+ return [...$carry, ...$member instanceof InnerList ? [...$member] : [$member]];
+ },
+ []
+ );
+
+ return match (true) {
+ [] === $membersToAdd => $this,
+ default => new self([...$this->members, ...array_values($membersToAdd)], $this->parameters),
+ };
+ }
+
+ /**
+ * Inserts members starting at the given index.
+ *
+ * @throws InvalidOffset If the index does not exist
+ */
+ public function insert(
+ int $index,
+ StructuredFieldProvider|OuterList|Dictionary|InnerList|Parameters|Item|Token|Bytes|DisplayString|DateTimeInterface|string|int|float|bool ...$members
+ ): self {
+ $offset = $this->filterIndex($index) ?? throw InvalidOffset::dueToIndexNotFound($index);
+
+ return match (true) {
+ 0 === $offset => $this->unshift(...$members),
+ count($this->members) === $offset => $this->push(...$members),
+ [] === $members => $this,
+ default => (function (array $newMembers) use ($offset, $members) {
+ array_splice($newMembers, $offset, 0, $members);
+
+ return new self($newMembers, $this->parameters);
+ })($this->members),
+ };
+ }
+
+ public function replace(
+ int $index,
+ StructuredFieldProvider|OuterList|Dictionary|InnerList|Parameters|Item|Token|Bytes|DisplayString|DateTimeInterface|string|int|float|bool $member
+ ): self {
+ $offset = $this->filterIndex($index) ?? throw InvalidOffset::dueToIndexNotFound($index);
+ $member = Member::item($member);
+
+ return match (true) {
+ $member->equals($this->members[$offset]) => $this,
+ default => new self(array_replace($this->members, [$offset => $member]), $this->parameters),
+ };
+ }
+
+ public function removeByIndices(int ...$indices): self
+ {
+ $max = count($this->members);
+ $indices = array_filter(
+ array_map(fn (int $index): ?int => $this->filterIndex($index, $max), $indices),
+ fn (?int $index): bool => null !== $index
+ );
+
+ return match (true) {
+ [] === $indices => $this,
+ count($indices) === $max => self::new(),
+ default => new self(array_filter(
+ $this->members,
+ fn (int $offset): bool => !in_array($offset, $indices, true),
+ ARRAY_FILTER_USE_KEY
+ ), $this->parameters),
+ };
+ }
+
+ /**
+ * @param int $offset
+ */
+ public function offsetExists(mixed $offset): bool
+ {
+ return $this->hasIndices($offset);
+ }
+
+ /**
+ * @param int $offset
+ */
+ public function offsetGet(mixed $offset): Item
+ {
+ return $this->getByIndex($offset);
+ }
+
+ public function offsetUnset(mixed $offset): void
+ {
+ throw new ForbiddenOperation(self::class.' instance can not be updated using '.ArrayAccess::class.' methods.');
+ }
+
+ public function offsetSet(mixed $offset, mixed $value): void
+ {
+ throw new ForbiddenOperation(self::class.' instance can not be updated using '.ArrayAccess::class.' methods.');
+ }
+
+ /**
+ * @param callable(Item, int): TMap $callback
+ *
+ * @template TMap
+ *
+ * @return Iterator<TMap>
+ */
+ public function map(callable $callback): Iterator
+ {
+ foreach ($this->members as $offset => $member) {
+ yield ($callback)($member, $offset);
+ }
+ }
+
+ /**
+ * @param callable(TInitial|null, Item, int=): TInitial $callback
+ * @param TInitial|null $initial
+ *
+ * @template TInitial
+ *
+ * @return TInitial|null
+ */
+ public function reduce(callable $callback, mixed $initial = null): mixed
+ {
+ foreach ($this->members as $offset => $member) {
+ $initial = $callback($initial, $member, $offset);
+ }
+
+ return $initial;
+ }
+
+ /**
+ * @param callable(Item, int): bool $callback
+ */
+ public function filter(callable $callback): self
+ {
+ $members = array_filter($this->members, $callback, ARRAY_FILTER_USE_BOTH);
+ if ($members === $this->members) {
+ return $this;
+ }
+
+ return new self($members, $this->parameters);
+ }
+
+ /**
+ * @param callable(Item, Item): int $callback
+ */
+ public function sort(callable $callback): self
+ {
+ $members = $this->members;
+ uasort($members, $callback);
+
+ return new self($members, $this->parameters);
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/InvalidArgument.php b/vendor/bakame/http-structured-fields/src/InvalidArgument.php
new file mode 100644
index 000000000..6c7c3344f
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/InvalidArgument.php
@@ -0,0 +1,11 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields;
+
+use InvalidArgumentException;
+
+final class InvalidArgument extends InvalidArgumentException implements StructuredFieldError
+{
+}
diff --git a/vendor/bakame/http-structured-fields/src/InvalidOffset.php b/vendor/bakame/http-structured-fields/src/InvalidOffset.php
new file mode 100644
index 000000000..ab878d98d
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/InvalidOffset.php
@@ -0,0 +1,38 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields;
+
+use OutOfBoundsException;
+
+final class InvalidOffset extends OutOfBoundsException implements StructuredFieldError
+{
+ private function __construct(string $message)
+ {
+ parent::__construct($message);
+ }
+
+ public static function dueToIndexNotFound(string|int $index): self
+ {
+ if (is_string($index)) {
+ return new self('The member index can not be the string "'.$index.'".');
+ }
+
+ return new self('No member exists with the index "'.$index.'".');
+ }
+
+ public static function dueToKeyNotFound(string|int $key): self
+ {
+ if (is_int($key)) {
+ return new self('The member key can not be the integer "'.$key.'".');
+ }
+
+ return new self('No member exists with the key "'.$key.'".');
+ }
+
+ public static function dueToMemberNotFound(string|int $offset): self
+ {
+ return new self('No member exists with the '.(is_int($offset) ? 'index' : 'key').' "'.$offset.'".');
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/Item.php b/vendor/bakame/http-structured-fields/src/Item.php
new file mode 100644
index 000000000..6d0434ee5
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Item.php
@@ -0,0 +1,502 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields;
+
+use BadMethodCallException;
+use Bakame\Http\StructuredFields\Validation\Violation;
+use DateTimeImmutable;
+use DateTimeInterface;
+use DateTimeZone;
+use Exception;
+use ReflectionMethod;
+use Stringable;
+use Throwable;
+use TypeError;
+
+use function array_is_list;
+use function count;
+use function in_array;
+
+use const JSON_PRESERVE_ZERO_FRACTION;
+use const PHP_ROUND_HALF_EVEN;
+
+/**
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-3.3
+ *
+ * @phpstan-import-type SfType from StructuredFieldProvider
+ * @phpstan-import-type SfItemInput from StructuredFieldProvider
+ * @phpstan-import-type SfItemPair from StructuredFieldProvider
+ * @phpstan-import-type SfTypeInput from StructuredFieldProvider
+ *
+ * @method static ?Item tryFromPair(array{0: SfItemInput, 1?: Parameters|iterable<array{0:string, 1:SfItemInput}>}|array<mixed> $pair) try to create a new instance from a Pair
+ * @method static ?Item tryFromRfc9651(Stringable|string $httpValue) try to create a new instance from a string using RFC9651
+ * @method static ?Item tryFromRfc8941(Stringable|string $httpValue) try to create a new instance from a string using RFC8941
+ * @method static ?Item tryFromHttpValue(Stringable|string $httpValue) try to create a new instance from a string
+ * @method static ?Item tryFromAssociative(Bytes|Token|DisplayString|DateTimeInterface|string|int|float|bool $value, StructuredFieldProvider|Parameters|iterable<string, SfItemInput> $parameters) try to create a new instance from a value and a parameters as associative construct
+ * @method static ?Item tryNew(mixed $value) try to create a new bare instance from a value
+ * @method static ?Item tryFromEncodedBytes(Stringable|string $value) try to create a new instance from an encoded byte sequence
+ * @method static ?Item tryFromDecodedBytes(Stringable|string $value) try to create a new instance from a decoded byte sequence
+ * @method static ?Item tryFromEncodedDisplayString(Stringable|string $value) try to create a new instance from an encoded display string
+ * @method static ?Item tryFromDecodedDisplayString(Stringable|string $value) try to create a new instance from a decoded display string
+ * @method static ?Item tryFromToken(Stringable|string $value) try to create a new instance from a token string
+ * @method static ?Item tryFromTimestamp(int $timestamp) try to create a new instance from a timestamp
+ * @method static ?Item tryFromDateFormat(string $format, string $datetime) try to create a new instance from a date format
+ * @method static ?Item tryFromDateString(string $datetime, DateTimeZone|string|null $timezone = null) try to create a new instance from a date string
+ * @method static ?Item tryFromDate(DateTimeInterface $datetime) try to create a new instance from a DateTimeInterface object
+ * @method static ?Item tryFromDecimal(int|float $value) try to create a new instance from a float
+ * @method static ?Item tryFromInteger(int|float $value) try to create a new instance from an integer
+ */
+final class Item
+{
+ use ParameterAccess;
+
+ private readonly Token|Bytes|DisplayString|DateTimeImmutable|int|float|string|bool $value;
+ private readonly Parameters $parameters;
+ private readonly Type $type;
+
+ private function __construct(Token|Bytes|DisplayString|DateTimeInterface|int|float|string|bool $value, ?Parameters $parameters = null)
+ {
+ if ($value instanceof DateTimeInterface && !$value instanceof DateTimeImmutable) {
+ $value = DateTimeImmutable::createFromInterface($value);
+ }
+
+ $this->value = $value;
+ $this->parameters = $parameters ?? Parameters::new();
+ $this->type = Type::fromVariable($value);
+ }
+
+ /**
+ * @throws BadMethodCallException
+ */
+ public static function __callStatic(string $name, array $arguments): ?self /* @phpstan-ignore-line */
+ {
+ if (!str_starts_with($name, 'try')) {
+ throw new BadMethodCallException('The method "'.self::class.'::'.$name.'" does not exist.');
+ }
+
+ $namedConstructor = lcfirst(substr($name, strlen('try')));
+ if (!method_exists(self::class, $namedConstructor)) {
+ throw new BadMethodCallException('The method "'.self::class.'::'.$name.'" does not exist.');
+ }
+
+ $method = new ReflectionMethod(self::class, $namedConstructor);
+ if (!$method->isPublic() || !$method->isStatic()) {
+ throw new BadMethodCallException('The method "'.self::class.'::'.$name.'" can not be accessed directly.');
+ }
+
+ try {
+ return self::$namedConstructor(...$arguments); /* @phpstan-ignore-line */
+ } catch (Throwable) { /* @phpstan-ignore-line */
+ return null;
+ }
+ }
+
+ public static function fromRfc9651(Stringable|string $httpValue): self
+ {
+ return self::fromHttpValue($httpValue, Ietf::Rfc9651);
+ }
+
+ public static function fromRfc8941(Stringable|string $httpValue): self
+ {
+ return self::fromHttpValue($httpValue, Ietf::Rfc8941);
+ }
+
+ /**
+ * Returns a new instance from an HTTP Header or Trailer value string
+ * in compliance with a published RFC.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-3.3
+ *
+ * @throws SyntaxError|Exception If the HTTP value can not be parsed
+ */
+ public static function fromHttpValue(Stringable|string $httpValue, Ietf $rfc = Ietf::Rfc9651): self
+ {
+ return self::fromPair((new Parser($rfc))->parseItem($httpValue));
+ }
+
+ /**
+ * Returns a new instance from a value type and an iterable of key-value parameters.
+ *
+ * @param StructuredFieldProvider|Parameters|iterable<string, SfItemInput> $parameters
+ *
+ * @throws SyntaxError If the value or the parameters are not valid
+ */
+ public static function fromAssociative(
+ Bytes|Token|DisplayString|DateTimeInterface|string|int|float|bool $value,
+ StructuredFieldProvider|Parameters|iterable $parameters
+ ): self {
+ if ($parameters instanceof StructuredFieldProvider) {
+ $parameters = $parameters->toStructuredField();
+ if ($parameters instanceof Parameters) {
+ return new self($value, $parameters);
+ }
+
+ throw new InvalidArgument('The '.StructuredFieldProvider::class.' must provide a '.Parameters::class.'; '.$parameters::class.' given.');
+ }
+
+ if (!$parameters instanceof Parameters) {
+ return new self($value, Parameters::fromAssociative($parameters));
+ }
+
+ return new self($value, $parameters);
+ }
+
+ /**
+ * @param array{0: SfItemInput, 1?: Parameters|iterable<array{0:string, 1:SfItemInput}>}|array<mixed> $pair
+ *
+ * @throws SyntaxError If the pair or its content is not valid.
+ */
+ public static function fromPair(array $pair): self
+ {
+ $nbElements = count($pair);
+ if (!in_array($nbElements, [1, 2], true) || !array_is_list($pair)) {
+ throw new SyntaxError('The pair must be represented by an non-empty array as a list containing exactly 1 or 2 members.');
+ }
+
+ if (1 === $nbElements) {
+ return new self($pair[0]);
+ }
+
+ if ($pair[1] instanceof StructuredFieldProvider) {
+ $pair[1] = $pair[1]->toStructuredField();
+ if ($pair[1] instanceof Parameters) {
+ return new self($pair[0], Parameters::fromPairs($pair[1]));
+ }
+
+ throw new InvalidArgument('The '.StructuredFieldProvider::class.' must provide a '.Parameters::class.'; '.$pair[1]::class.' given.');
+ }
+
+ if (!$pair[1] instanceof Parameters) {
+ return new self($pair[0], Parameters::fromPairs($pair[1]));
+ }
+
+ return new self($pair[0], $pair[1]);
+ }
+
+ /**
+ * Returns a new bare instance from value.
+ *
+ * @param SfItemPair|SfItemInput $value
+ *
+ * @throws SyntaxError|TypeError If the value is not valid.
+ */
+ public static function new(mixed $value): self
+ {
+ if (is_array($value)) {
+ return self::fromPair($value);
+ }
+
+ return new self($value); /* @phpstan-ignore-line */
+ }
+
+ /**
+ * Returns a new instance from a string.
+ *
+ * @throws SyntaxError if the string is invalid
+ */
+ public static function fromString(Stringable|string $value): self
+ {
+ return new self((string)$value);
+ }
+
+ /**
+ * Returns a new instance from an encoded byte sequence and an iterable of key-value parameters.
+ *
+ * @throws SyntaxError if the sequence is invalid
+ */
+ public static function fromEncodedBytes(Stringable|string $value): self
+ {
+ return new self(Bytes::fromEncoded($value));
+ }
+
+ /**
+ * Returns a new instance from a decoded byte sequence and an iterable of key-value parameters.
+ *
+ * @throws SyntaxError if the sequence is invalid
+ */
+ public static function fromDecodedBytes(Stringable|string $value): self
+ {
+ return new self(Bytes::fromDecoded($value));
+ }
+
+ /**
+ * Returns a new instance from an encoded byte sequence and an iterable of key-value parameters.
+ *
+ * @throws SyntaxError if the sequence is invalid
+ */
+ public static function fromEncodedDisplayString(Stringable|string $value): self
+ {
+ return new self(DisplayString::fromEncoded($value));
+ }
+
+ /**
+ * Returns a new instance from a decoded byte sequence and an iterable of key-value parameters.
+ *
+ * @throws SyntaxError if the sequence is invalid
+ */
+ public static function fromDecodedDisplayString(Stringable|string $value): self
+ {
+ return new self(DisplayString::fromDecoded($value));
+ }
+
+ /**
+ * Returns a new instance from a Token and an iterable of key-value parameters.
+ *
+ * @throws SyntaxError if the token is invalid
+ */
+ public static function fromToken(Stringable|string $value): self
+ {
+ return new self(Token::fromString($value));
+ }
+
+ /**
+ * Returns a new instance from a timestamp and an iterable of key-value parameters.
+ *
+ * @throws SyntaxError if the timestamp value is not supported
+ */
+ public static function fromTimestamp(int $timestamp): self
+ {
+ return new self((new DateTimeImmutable())->setTimestamp($timestamp));
+ }
+
+ /**
+ * Returns a new instance from a date format its date string representation and an iterable of key-value parameters.
+ *
+ * @throws SyntaxError if the format is invalid
+ */
+ public static function fromDateFormat(string $format, string $datetime): self
+ {
+ try {
+ $value = DateTimeImmutable::createFromFormat($format, $datetime);
+ } catch (Exception $exception) {
+ throw new SyntaxError('The date notation `'.$datetime.'` is incompatible with the date format `'.$format.'`.', 0, $exception);
+ }
+
+ if (!$value instanceof DateTimeImmutable) {
+ throw new SyntaxError('The date notation `'.$datetime.'` is incompatible with the date format `'.$format.'`.');
+ }
+
+ return new self($value);
+ }
+
+ /**
+ * Returns a new instance from a string parsable by DateTimeImmutable constructor, an optional timezone and an iterable of key-value parameters.
+ *
+ * @throws SyntaxError if the format is invalid
+ */
+ public static function fromDateString(string $datetime, DateTimeZone|string|null $timezone = null): self
+ {
+ $timezone ??= date_default_timezone_get();
+ if (!$timezone instanceof DateTimeZone) {
+ try {
+ $timezone = new DateTimeZone($timezone);
+ } catch (Throwable $exception) {
+ throw new SyntaxError('The timezone could not be instantiated.', 0, $exception);
+ }
+ }
+
+ try {
+ return new self(new DateTimeImmutable($datetime, $timezone));
+ } catch (Throwable $exception) {
+ throw new SyntaxError('Unable to create a '.DateTimeImmutable::class.' instance with the date notation `'.$datetime.'.`', 0, $exception);
+ }
+ }
+
+ /**
+ * Returns a new instance from a DateTineInterface implementing object.
+ *
+ * @throws SyntaxError if the format is invalid
+ */
+ public static function fromDate(DateTimeInterface $datetime): self
+ {
+ return new self($datetime);
+ }
+
+ /**
+ * Returns a new instance from a float value.
+ *
+ * @throws SyntaxError if the format is invalid
+ */
+ public static function fromDecimal(int|float $value): self
+ {
+ return new self((float)$value);
+ }
+
+ /**
+ * Returns a new instance from an integer value.
+ *
+ * @throws SyntaxError if the format is invalid
+ */
+ public static function fromInteger(int|float $value): self
+ {
+ return new self((int)$value);
+ }
+
+ /**
+ * Returns a new instance for the boolean true type.
+ */
+ public static function true(): self
+ {
+ return new self(true);
+ }
+
+ /**
+ * Returns a new instance for the boolean false type.
+ */
+ public static function false(): self
+ {
+ return new self(false);
+ }
+
+ /**
+ * Returns the underlying value.
+ * If a validation rule is provided, an exception will be thrown
+ * if the validation rules does not return true.
+ *
+ * if the validation returns false then a default validation message will be return; otherwise the submitted message string will be returned as is.
+ *
+ * @param ?callable(SfType): (string|bool) $validate
+ *
+ * @throws Violation
+ */
+ public function value(?callable $validate = null): Bytes|Token|DisplayString|DateTimeImmutable|string|int|float|bool
+ {
+ if (null === $validate) {
+ return $this->value;
+ }
+
+ $exceptionMessage = $validate($this->value);
+ if (true === $exceptionMessage) {
+ return $this->value;
+ }
+
+ if (!is_string($exceptionMessage) || '' === trim($exceptionMessage)) {
+ $exceptionMessage = "The item value '{value}' failed validation.";
+ }
+
+ throw new Violation(strtr($exceptionMessage, ['{value}' => $this->serialize()]));
+ }
+
+ public function type(): Type
+ {
+ return $this->type;
+ }
+
+ /**
+ * Serialize the Item value according to RFC8941.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-4.1
+ */
+ public function toHttpValue(Ietf $rfc = Ietf::Rfc9651): string
+ {
+ return $this->serialize($rfc).$this->parameters->toHttpValue($rfc);
+ }
+
+ /**
+ * Serialize the Item value according to RFC8941.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-4.1
+ */
+ private function serialize(Ietf $rfc = Ietf::Rfc9651): string
+ {
+ return match (true) {
+ !$rfc->supports($this->type) => throw MissingFeature::dueToLackOfSupport($this->type, $rfc),
+ $this->value instanceof DateTimeImmutable => '@'.$this->value->getTimestamp(),
+ $this->value instanceof Token => $this->value->toString(),
+ $this->value instanceof Bytes => ':'.$this->value->encoded().':',
+ $this->value instanceof DisplayString => '%"'.$this->value->encoded().'"',
+ is_int($this->value) => (string) $this->value,
+ is_float($this->value) => (string) json_encode(round($this->value, 3, PHP_ROUND_HALF_EVEN), JSON_PRESERVE_ZERO_FRACTION),
+ $this->value,
+ false === $this->value => '?'.($this->value ? '1' : '0'),
+ default => '"'.preg_replace('/(["\\\])/', '\\\$1', $this->value).'"',
+ };
+ }
+
+ public function toRfc9651(): string
+ {
+ return $this->toHttpValue(Ietf::Rfc9651);
+ }
+
+ public function toRfc8941(): string
+ {
+ return $this->toHttpValue(Ietf::Rfc8941);
+ }
+
+ public function __toString(): string
+ {
+ return $this->toHttpValue();
+ }
+
+ /**
+ * @return array{0:SfItemInput, 1:Parameters}
+ */
+ public function toPair(): array
+ {
+ return [$this->value, $this->parameters];
+ }
+
+ public function equals(mixed $other): bool
+ {
+ return $other instanceof self && $other->toHttpValue() === $this->toHttpValue();
+ }
+
+ /**
+ * Apply the callback if the given "condition" is (or resolves to) true.
+ *
+ * @param (callable($this): bool)|bool $condition
+ * @param callable($this): (self|null) $onSuccess
+ * @param ?callable($this): (self|null) $onFail
+ */
+ public function when(callable|bool $condition, callable $onSuccess, ?callable $onFail = null): self
+ {
+ if (!is_bool($condition)) {
+ $condition = $condition($this);
+ }
+
+ return match (true) {
+ $condition => $onSuccess($this),
+ null !== $onFail => $onFail($this),
+ default => $this,
+ } ?? $this;
+ }
+
+ /**
+ * Returns a new instance with the newly associated value.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified value change.
+ *
+ * @throws SyntaxError If the value is invalid or not supported
+ */
+ public function withValue(DateTimeInterface|Bytes|Token|DisplayString|string|int|float|bool $value): self
+ {
+ $isEqual = match (true) {
+ $this->value instanceof Bytes,
+ $this->value instanceof Token,
+ $this->value instanceof DisplayString => $this->value->equals($value),
+ $this->value instanceof DateTimeInterface && $value instanceof DateTimeInterface => $value->getTimestamp() === $this->value->getTimestamp(),
+ default => $value === $this->value,
+ };
+
+ if ($isEqual) {
+ return $this;
+ }
+
+ return new self($value, $this->parameters);
+ }
+
+ public function withParameters(StructuredFieldProvider|Parameters $parameters): static
+ {
+ if ($parameters instanceof StructuredFieldProvider) {
+ $parameters = $parameters->toStructuredField();
+ if (!$parameters instanceof Parameters) {
+ throw new InvalidArgument('The '.StructuredFieldProvider::class.' must provide a '.Parameters::class.'; '.$parameters::class.' given.');
+ }
+ }
+
+ return $this->parameters->equals($parameters) ? $this : new self($this->value, $parameters);
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/Key.php b/vendor/bakame/http-structured-fields/src/Key.php
new file mode 100644
index 000000000..e1971426a
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Key.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Bakame\Http\StructuredFields;
+
+use Stringable;
+
+use function preg_match;
+
+/**
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-3.1.2
+ * @internal normalize HTTP field key
+ */
+final class Key
+{
+ private function __construct(public readonly string $value)
+ {
+ }
+
+ /**
+ * @throws SyntaxError If the string is not a valid HTTP value field key
+ */
+ public static function from(Stringable|string|int $httpValue): self
+ {
+ $key = (string) $httpValue;
+ $instance = self::fromStringBeginning($key);
+ if ($instance->value !== $key) {
+ throw new SyntaxError('No valid http value key could be extracted from "'.$httpValue.'".');
+ }
+
+ return $instance;
+ }
+
+ public static function tryFrom(Stringable|string|int $httpValue): ?self
+ {
+ try {
+ return self::from($httpValue);
+ } catch (SyntaxError $e) {
+ return null;
+ }
+ }
+
+ /**
+ * @throws SyntaxError If the string does not start with a valid HTTP value field key
+ */
+ public static function fromStringBeginning(string $httpValue): self
+ {
+ if (1 !== preg_match('/^(?<key>[a-z*][a-z\d.*_-]*)/', $httpValue, $found)) {
+ throw new SyntaxError('No valid http value key could be extracted from "'.$httpValue.'".');
+ }
+
+ return new self($found['key']);
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/Member.php b/vendor/bakame/http-structured-fields/src/Member.php
new file mode 100644
index 000000000..ee315b078
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Member.php
@@ -0,0 +1,116 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields;
+
+use function count;
+use function in_array;
+use function is_array;
+use function is_iterable;
+
+/**
+ * @phpstan-import-type SfMemberInput from StructuredFieldProvider
+ * @phpstan-import-type SfItemInput from StructuredFieldProvider
+ * @phpstan-import-type SfItemPair from StructuredFieldProvider
+ * @phpstan-import-type SfInnerListPair from StructuredFieldProvider
+ * @phpstan-import-type SfTypeInput from StructuredFieldProvider
+ *
+ * @internal Validate containers member
+ */
+final class Member
+{
+ /**
+ * @param SfMemberInput $value
+ */
+ public static function innerListOrItem(mixed $value): InnerList|Item
+ {
+ if ($value instanceof StructuredFieldProvider) {
+ $value = $value->toStructuredField();
+ if ($value instanceof Item || $value instanceof InnerList) {
+ return $value;
+ }
+
+ throw new InvalidArgument('The '.StructuredFieldProvider::class.' must provide a '.Item::class.' or an '.InnerList::class.'; '.$value::class.' given.');
+ }
+
+ return match (true) {
+ $value instanceof InnerList,
+ $value instanceof Item => $value,
+ is_iterable($value) => InnerList::new(...$value),
+ default => Item::new($value),
+ };
+ }
+
+ public static function innerListOrItemFromPair(mixed $value): InnerList|Item
+ {
+ if ($value instanceof StructuredFieldProvider) {
+ $value = $value->toStructuredField();
+ if ($value instanceof Item || $value instanceof InnerList) {
+ return $value;
+ }
+
+ throw new InvalidArgument('The '.StructuredFieldProvider::class.' must provide a '.Item::class.' or an '.InnerList::class.'; '.$value::class.' given.');
+ }
+
+ if ($value instanceof InnerList || $value instanceof Item) {
+ return $value;
+ }
+
+ if (!is_array($value)) {
+ if (is_iterable($value)) {
+ throw new SyntaxError('The value must be an Item value not an iterable.');
+ }
+
+ return Item::new($value); /* @phpstan-ignore-line */
+ }
+
+ if (!array_is_list($value)) {
+ throw new SyntaxError('The pair must be represented by an array as a list.');
+ }
+
+ if ([] === $value) {
+ return InnerList::new();
+ }
+
+ if (!in_array(count($value), [1, 2], true)) {
+ throw new SyntaxError('The pair first member represents its value; the second member is its associated parameters.');
+ }
+
+ return is_iterable($value[0]) ? InnerList::fromPair($value) : Item::fromPair($value);
+ }
+
+ /**
+ * @param SfItemInput|SfItemPair $value
+ */
+ public static function item(mixed $value): Item
+ {
+ if ($value instanceof StructuredFieldProvider) {
+ $value = $value->toStructuredField();
+ if (!$value instanceof Item) {
+ throw new InvalidArgument('The '.StructuredFieldProvider::class.' must provide a '.Item::class.'; '.$value::class.' given.');
+ }
+
+ return $value;
+ }
+
+ if ($value instanceof Item) {
+ return $value;
+ }
+
+ return Item::new($value);
+ }
+
+ /**
+ * @param SfItemInput|SfItemPair $value
+ */
+ public static function bareItem(mixed $value): Item
+ {
+ $bareItem = self::item($value);
+ if ($bareItem->parameters()->isNotEmpty()) {
+ throw new InvalidArgument('The "'.$bareItem::class.'" instance is not a Bare Item.');
+ }
+
+ return $bareItem;
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/MissingFeature.php b/vendor/bakame/http-structured-fields/src/MissingFeature.php
new file mode 100644
index 000000000..03bd8c05f
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/MissingFeature.php
@@ -0,0 +1,13 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields;
+
+final class MissingFeature extends SyntaxError
+{
+ public static function dueToLackOfSupport(Type $type, Ietf $rfc): self
+ {
+ return new self('The \''.$type->value.'\' type is not handled by '.strtoupper($rfc->name));
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/OuterList.php b/vendor/bakame/http-structured-fields/src/OuterList.php
new file mode 100644
index 000000000..644f1990e
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/OuterList.php
@@ -0,0 +1,430 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields;
+
+use ArrayAccess;
+use Bakame\Http\StructuredFields\Validation\Violation;
+use Countable;
+use DateTimeInterface;
+use Exception;
+use Iterator;
+use IteratorAggregate;
+use Stringable;
+
+use function array_filter;
+use function array_map;
+use function array_replace;
+use function array_splice;
+use function array_values;
+use function count;
+use function implode;
+use function is_iterable;
+use function uasort;
+
+use const ARRAY_FILTER_USE_BOTH;
+use const ARRAY_FILTER_USE_KEY;
+
+/**
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#name-lists
+ *
+ * @phpstan-import-type SfMemberInput from StructuredFieldProvider
+ * @phpstan-import-type SfInnerListPair from StructuredFieldProvider
+ * @phpstan-import-type SfItemPair from StructuredFieldProvider
+ *
+ * @implements ArrayAccess<int, InnerList|Item>
+ * @implements IteratorAggregate<int, InnerList|Item>
+ */
+final class OuterList implements ArrayAccess, Countable, IteratorAggregate
+{
+ /** @var list<InnerList|Item> */
+ private readonly array $members;
+
+ /**
+ * @param SfMemberInput ...$members
+ */
+ private function __construct(
+ iterable|StructuredFieldProvider|Item|Token|Bytes|DisplayString|DateTimeInterface|string|int|float|bool ...$members
+ ) {
+ $this->members = array_map(Member::innerListOrItem(...), array_values([...$members]));
+ }
+
+ /**
+ * Returns an instance from an HTTP textual representation.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-3.1
+ *
+ * @throws SyntaxError|Exception
+ */
+ public static function fromHttpValue(Stringable|string $httpValue, Ietf $rfc = Ietf::Rfc9651): self
+ {
+ return self::fromPairs((new Parser($rfc))->parseList($httpValue)); /* @phpstan-ignore-line */
+ }
+
+ /**
+ * @param StructuredFieldProvider|iterable<SfInnerListPair|SfItemPair> $pairs
+ */
+ public static function fromPairs(StructuredFieldProvider|iterable $pairs): self
+ {
+ if ($pairs instanceof StructuredFieldProvider) {
+ $pairs = $pairs->toStructuredField();
+ }
+
+ if (!is_iterable($pairs)) {
+ throw new InvalidArgument('The "'.$pairs::class.'" instance can not be used for creating a .'.self::class.' structured field.');
+ }
+
+ return match (true) {
+ $pairs instanceof OuterList,
+ $pairs instanceof InnerList => new self($pairs),
+ default => new self(...(function (iterable $pairs) {
+ foreach ($pairs as $member) {
+ yield Member::innerListOrItemFromPair($member);
+ }
+ })($pairs)),
+ };
+ }
+
+ /**
+ * @param StructuredFieldProvider|SfInnerListPair|SfItemPair|SfMemberInput ...$members
+ */
+ public static function new(iterable|StructuredFieldProvider|InnerList|Item|Token|Bytes|DisplayString|DateTimeInterface|string|int|float|bool ...$members): self
+ {
+ return self::fromPairs($members); /* @phpstan-ignore-line*/
+ }
+
+ public static function fromRfc9651(Stringable|string $httpValue): self
+ {
+ return self::fromHttpValue($httpValue, Ietf::Rfc9651);
+ }
+
+ public static function fromRfc8941(Stringable|string $httpValue): self
+ {
+ return self::fromHttpValue($httpValue, Ietf::Rfc8941);
+ }
+
+ public function toHttpValue(Ietf $rfc = Ietf::Rfc9651): string
+ {
+ return implode(', ', array_map(fn (Item|InnerList $member): string => $member->toHttpValue($rfc), $this->members));
+ }
+
+ public function toRfc9651(): string
+ {
+ return $this->toHttpValue(Ietf::Rfc9651);
+ }
+
+ public function toRfc8941(): string
+ {
+ return $this->toHttpValue(Ietf::Rfc8941);
+ }
+
+ public function __toString(): string
+ {
+ return $this->toHttpValue();
+ }
+
+ public function equals(mixed $other): bool
+ {
+ return $other instanceof self && $other->toHttpValue() === $this->toHttpValue();
+ }
+
+ /**
+ * Apply the callback if the given "condition" is (or resolves to) true.
+ *
+ * @param (callable($this): bool)|bool $condition
+ * @param callable($this): (self|null) $onSuccess
+ * @param ?callable($this): (self|null) $onFail
+ */
+ public function when(callable|bool $condition, callable $onSuccess, ?callable $onFail = null): self
+ {
+ if (!is_bool($condition)) {
+ $condition = $condition($this);
+ }
+
+ return match (true) {
+ $condition => $onSuccess($this),
+ null !== $onFail => $onFail($this),
+ default => $this,
+ } ?? $this;
+ }
+
+ public function getIterator(): Iterator
+ {
+ yield from $this->members;
+ }
+
+ public function count(): int
+ {
+ return count($this->members);
+ }
+
+ public function isEmpty(): bool
+ {
+ return !$this->isNotEmpty();
+ }
+
+ public function isNotEmpty(): bool
+ {
+ return [] !== $this->members;
+ }
+
+ /**
+ * @return array<int>
+ */
+ public function indices(): array
+ {
+ return array_keys($this->members);
+ }
+
+ public function hasIndices(int ...$indices): bool
+ {
+ $max = count($this->members);
+ foreach ($indices as $index) {
+ if (null === $this->filterIndex($index, $max)) {
+ return false;
+ }
+ }
+
+ return [] !== $indices;
+ }
+
+ private function filterIndex(int $index, ?int $max = null): ?int
+ {
+ $max ??= count($this->members);
+
+ return match (true) {
+ [] === $this->members,
+ 0 > $max + $index,
+ 0 > $max - $index - 1 => null,
+ 0 > $index => $max + $index,
+ default => $index,
+ };
+ }
+
+ /**
+ * @param ?callable(InnerList|Item): (bool|string) $validate
+ *
+ * @throws SyntaxError|Violation|StructuredFieldError
+ */
+ public function getByIndex(int $index, ?callable $validate = null): InnerList|Item
+ {
+ $value = $this->members[$this->filterIndex($index) ?? throw InvalidOffset::dueToIndexNotFound($index)];
+ if (null === $validate) {
+ return $value;
+ }
+
+ if (true === ($exceptionMessage = $validate($value))) {
+ return $value;
+ }
+
+ if (!is_string($exceptionMessage) || '' === trim($exceptionMessage)) {
+ $exceptionMessage = "The member at position '{index}' whose value is '{value}' failed validation.";
+ }
+
+ throw new Violation(strtr($exceptionMessage, ['{index}' => $index, '{value}' => $value->toHttpValue()]));
+ }
+
+ public function first(): InnerList|Item|null
+ {
+ return $this->members[0] ?? null;
+ }
+
+ public function last(): InnerList|Item|null
+ {
+ return $this->members[$this->filterIndex(-1)] ?? null;
+ }
+
+ /**
+ * Inserts members at the beginning of the list.
+ *
+ * @param SfMemberInput ...$members
+ */
+ public function unshift(
+ StructuredFieldProvider|InnerList|Item|iterable|Token|Bytes|DisplayString|DateTimeInterface|string|int|float|bool ...$members
+ ): self {
+ $membersToAdd = array_reduce(
+ $members,
+ function (array $carry, $member) {
+ if ($member instanceof StructuredFieldProvider) {
+ $member = $member->toStructuredField();
+ }
+
+ return [...$carry, ...$member instanceof InnerList ? [...$member] : [$member]];
+ },
+ []
+ );
+
+ return match (true) {
+ [] === $membersToAdd => $this,
+ default => new self(...array_values($membersToAdd), ...$this->members),
+ };
+ }
+
+ /**
+ * Inserts members at the end of the list.
+ *
+ * @param SfMemberInput ...$members
+ */
+ public function push(
+ iterable|StructuredFieldProvider|InnerList|Item|Token|Bytes|DisplayString|DateTimeInterface|string|int|float|bool ...$members
+ ): self {
+ $membersToAdd = array_reduce(
+ $members,
+ function (array $carry, $member) {
+ if ($member instanceof StructuredFieldProvider) {
+ $member = $member->toStructuredField();
+ }
+
+ return [...$carry, ...$member instanceof InnerList ? [...$member] : [$member]];
+ },
+ []
+ );
+
+ return match (true) {
+ [] === $membersToAdd => $this,
+ default => new self(...$this->members, ...array_values($membersToAdd)),
+ };
+ }
+
+ /**
+ * Inserts members starting at the given index.
+ *
+ * @param SfMemberInput ...$members
+ *
+ * @throws InvalidOffset If the index does not exist
+ */
+ public function insert(
+ int $index,
+ iterable|StructuredFieldProvider|InnerList|Item|Token|Bytes|DisplayString|DateTimeInterface|string|int|float|bool ...$members
+ ): self {
+ $offset = $this->filterIndex($index) ?? throw InvalidOffset::dueToIndexNotFound($index);
+
+ return match (true) {
+ 0 === $offset => $this->unshift(...$members),
+ count($this->members) === $offset => $this->push(...$members),
+ [] === $members => $this,
+ default => (function (array $newMembers) use ($offset, $members) {
+ array_splice($newMembers, $offset, 0, $members);
+
+ return new self(...$newMembers);
+ })($this->members),
+ };
+ }
+
+ /**
+ * @param SfMemberInput $member
+ */
+ public function replace(
+ int $index,
+ iterable|StructuredFieldProvider|InnerList|Item|Token|Bytes|DisplayString|DateTimeInterface|string|int|float|bool $member
+ ): self {
+ $offset = $this->filterIndex($index) ?? throw InvalidOffset::dueToIndexNotFound($index);
+ $member = Member::innerListOrItem($member);
+
+ return match (true) {
+ $member->equals($this->members[$offset]) => $this,
+ default => new self(...array_replace($this->members, [$offset => $member])),
+ };
+ }
+
+ public function removeByIndices(int ...$indices): self
+ {
+ $max = count($this->members);
+ $offsets = array_filter(
+ array_map(fn (int $index): ?int => $this->filterIndex($index, $max), $indices),
+ fn (?int $index): bool => null !== $index
+ );
+
+ return match (true) {
+ [] === $offsets => $this,
+ $max === count($offsets) => new self(),
+ default => new self(...array_filter(
+ $this->members,
+ fn (int $index): bool => !in_array($index, $offsets, true),
+ ARRAY_FILTER_USE_KEY
+ )),
+ };
+ }
+
+ /**
+ * @param int $offset
+ */
+ public function offsetExists(mixed $offset): bool
+ {
+ return $this->hasIndices($offset);
+ }
+
+ /**
+ * @param int $offset
+ */
+ public function offsetGet(mixed $offset): InnerList|Item
+ {
+ return $this->getByIndex($offset);
+ }
+
+ public function offsetUnset(mixed $offset): void
+ {
+ throw new ForbiddenOperation(self::class.' instance can not be updated using '.ArrayAccess::class.' methods.');
+ }
+
+ public function offsetSet(mixed $offset, mixed $value): void
+ {
+ throw new ForbiddenOperation(self::class.' instance can not be updated using '.ArrayAccess::class.' methods.');
+ }
+
+ /**
+ * @param callable(InnerList|Item, int): TMap $callback
+ *
+ * @template TMap
+ *
+ * @return Iterator<TMap>
+ */
+ public function map(callable $callback): Iterator
+ {
+ foreach ($this->members as $offset => $member) {
+ yield ($callback)($member, $offset);
+ }
+ }
+
+ /**
+ * @param callable(TInitial|null, InnerList|Item, int): TInitial $callback
+ * @param TInitial|null $initial
+ *
+ * @template TInitial
+ *
+ * @return TInitial|null
+ */
+ public function reduce(callable $callback, mixed $initial = null): mixed
+ {
+ foreach ($this->members as $offset => $member) {
+ $initial = $callback($initial, $member, $offset);
+ }
+
+ return $initial;
+ }
+
+ /**
+ * @param callable(InnerList|Item, int): bool $callback
+ */
+ public function filter(callable $callback): self
+ {
+ $members = array_filter($this->members, $callback, ARRAY_FILTER_USE_BOTH);
+ if ($members === $this->members) {
+ return $this;
+ }
+
+ return new self(...$members);
+ }
+
+ /**
+ * @param callable(InnerList|Item, InnerList|Item): int $callback
+ */
+ public function sort(callable $callback): self
+ {
+ $members = $this->members;
+ uasort($members, $callback);
+
+ return new self(...$members);
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/ParameterAccess.php b/vendor/bakame/http-structured-fields/src/ParameterAccess.php
new file mode 100644
index 000000000..9c71f0955
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/ParameterAccess.php
@@ -0,0 +1,260 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields;
+
+use Bakame\Http\StructuredFields\Validation\Violation;
+use DateTimeImmutable;
+use DateTimeInterface;
+
+/**
+ * Common manipulation methods used when interacting with an object
+ * with a Parameters instance attached to it.
+ *
+ * @phpstan-import-type SfType from StructuredFieldProvider
+ * @phpstan-import-type SfItemInput from StructuredFieldProvider
+ */
+trait ParameterAccess
+{
+ /**
+ * Returns a copy of the associated parameter instance.
+ */
+ public function parameters(): Parameters
+ {
+ return $this->parameters;
+ }
+
+ /**
+ * Returns the member value or null if no members value exists.
+ *
+ * @param ?callable(SfType): (bool|string) $validate
+ *
+ * @throws Violation if the validation fails
+ *
+ * @return SfType|null
+ */
+ public function parameterByKey(
+ string $key,
+ ?callable $validate = null,
+ bool|string $required = false,
+ Bytes|Token|DisplayString|DateTimeImmutable|string|int|float|bool|null $default = null
+ ): Bytes|Token|DisplayString|DateTimeImmutable|string|int|float|bool|null {
+ return $this->parameters->valueByKey($key, $validate, $required, $default);
+ }
+
+ /**
+ * Returns the member value and key as pair or an empty array if no members value exists.
+ *
+ * @param ?callable(SfType, string): (bool|string) $validate
+ * @param array{0:string, 1:SfType}|array{} $default
+ *
+ * @throws Violation if the validation fails
+ *
+ * @return array{0:string, 1:SfType}|array{}
+ */
+ public function parameterByIndex(
+ int $index,
+ ?callable $validate = null,
+ bool|string $required = false,
+ array $default = []
+ ): array {
+ return $this->parameters->valueByIndex($index, $validate, $required, $default);
+ }
+
+ /**
+ * Returns a new instance with the newly associated parameter instance.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified parameter change.
+ */
+ abstract public function withParameters(Parameters $parameters): static;
+
+ /**
+ * Adds a member if its key is not present at the of the associated parameter instance or update the instance at the given key.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified parameter change.
+ *
+ * @param StructuredFieldProvider|Item|SfType $member
+ *
+ * @throws SyntaxError If the string key is not a valid
+ */
+ public function addParameter(
+ string $key,
+ StructuredFieldProvider|Item|Token|Bytes|DisplayString|DateTimeInterface|string|int|float|bool $member
+ ): static {
+ return $this->withParameters($this->parameters()->add($key, $member));
+ }
+
+ /**
+ * Adds a member at the start of the associated parameter instance and deletes any previous reference to the key if present.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified parameter change.
+ *
+ * @param StructuredFieldProvider|Item|SfType $member
+ *
+ * @throws SyntaxError If the string key is not a valid
+ */
+ public function prependParameter(
+ string $key,
+ StructuredFieldProvider|Item|Token|Bytes|DisplayString|DateTimeInterface|string|int|float|bool $member
+ ): static {
+ return $this->withParameters($this->parameters()->prepend($key, $member));
+ }
+
+ /**
+ * Adds a member at the end of the associated parameter instance and deletes any previous reference to the key if present.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified parameter change.
+ *
+ * @param StructuredFieldProvider|Item|SfType $member
+ *
+ * @throws SyntaxError If the string key is not a valid
+ */
+ public function appendParameter(
+ string $key,
+ StructuredFieldProvider|Item|Token|Bytes|DisplayString|DateTimeInterface|string|int|float|bool $member
+ ): static {
+ return $this->withParameters($this->parameters()->append($key, $member));
+ }
+
+ /**
+ * Removes all parameters members associated with the list of submitted keys in the associated parameter instance.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified parameter change.
+ */
+ public function withoutAnyParameter(): static
+ {
+ return $this->withParameters(Parameters::new());
+ }
+
+ /**
+ * Inserts pair at the end of the member list.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified parameter change.
+ *
+ * @param array{0:string, 1:SfItemInput} ...$pairs
+ */
+ public function pushParameters(array ...$pairs): static
+ {
+ return $this->withParameters($this->parameters()->push(...$pairs));
+ }
+
+ /**
+ * Inserts pair at the beginning of the member list.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified parameter change.
+ *
+ * @param array{0:string, 1:SfItemInput} ...$pairs
+ */
+ public function unshiftParameters(array ...$pairs): static
+ {
+ return $this->withParameters($this->parameters()->unshift(...$pairs));
+ }
+
+ /**
+ * Delete member based on their key.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified parameter change.
+ */
+ public function withoutParameterByKeys(string ...$keys): static
+ {
+ return $this->withParameters($this->parameters()->removeByKeys(...$keys));
+ }
+
+ /**
+ * Delete member based on their offsets.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified parameter change.
+ */
+ public function withoutParameterByIndices(int ...$indices): static
+ {
+ return $this->withParameters($this->parameters()->removeByIndices(...$indices));
+ }
+
+ /**
+ * Inserts members at the specified index.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified parameter change.
+ *
+ * @param array{0:string, 1:SfType} ...$pairs
+ */
+ public function insertParameters(int $index, array ...$pairs): static
+ {
+ return $this->withParameters($this->parameters()->insert($index, ...$pairs));
+ }
+
+ /**
+ * Replace the member at the specified index.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified parameter change.
+ *
+ * @param array{0:string, 1:SfType} $pair
+ */
+ public function replaceParameter(int $index, array $pair): static
+ {
+ return $this->withParameters($this->parameters()->replace($index, $pair));
+ }
+
+ /**
+ * Sort the object parameters by value using a callback.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified parameter change.
+ *
+ * @param callable(array{0:string, 1:Item}, array{0:string, 1:Item}): int $callback
+ */
+ public function sortParameters(callable $callback): static
+ {
+ return $this->withParameters($this->parameters()->sort($callback));
+ }
+
+ /**
+ * Filter the object parameters using a callback.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified parameter change.
+ *
+ * @param callable(array{0:string, 1:Item}, int): bool $callback
+ */
+ public function filterParameters(callable $callback): static
+ {
+ return $this->withParameters($this->parameters()->filter($callback));
+ }
+
+ /**
+ * Merges multiple instances using iterable pairs.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified changes.
+ *
+ * @param StructuredFieldProvider|Parameters|Dictionary|iterable<array{0:string, 1:SfItemInput}> ...$others
+ */
+ public function mergeParametersByPairs(...$others): static
+ {
+ return $this->withParameters($this->parameters()->mergePairs(...$others));
+ }
+
+ /**
+ * Merges multiple instances using iterable associative.
+ *
+ * This method MUST retain the state of the current instance, and return
+ * an instance that contains the specified changes.
+ *
+ * @param StructuredFieldProvider|Dictionary|Parameters|iterable<string, SfItemInput> ...$others
+ */
+ public function mergeParametersByAssociative(...$others): static
+ {
+ return $this->withParameters($this->parameters()->mergeAssociative(...$others));
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/Parameters.php b/vendor/bakame/http-structured-fields/src/Parameters.php
new file mode 100644
index 000000000..9fd9a4e2b
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Parameters.php
@@ -0,0 +1,763 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields;
+
+use ArrayAccess;
+use Bakame\Http\StructuredFields\Validation\Violation;
+use CallbackFilterIterator;
+use Countable;
+use DateTimeImmutable;
+use DateTimeInterface;
+use Exception;
+use Iterator;
+use IteratorAggregate;
+use Stringable;
+
+use function array_key_exists;
+use function array_keys;
+use function array_map;
+use function array_replace;
+use function count;
+use function implode;
+use function is_int;
+use function is_string;
+use function trim;
+use function uasort;
+
+/**
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-3.1.2
+ *
+ * @phpstan-import-type SfItemInput from StructuredFieldProvider
+ * @phpstan-import-type SfType from StructuredFieldProvider
+ *
+ * @implements ArrayAccess<string, Item>
+ * @implements IteratorAggregate<int, array{0:string, 1:Item}>
+ */
+final class Parameters implements ArrayAccess, Countable, IteratorAggregate
+{
+ /** @var array<string, Item> */
+ private readonly array $members;
+
+ /**
+ * @param iterable<string, SfItemInput> $members
+ */
+ private function __construct(iterable $members = [])
+ {
+ $filteredMembers = [];
+ foreach ($members as $key => $member) {
+ $filteredMembers[Key::from($key)->value] = Member::bareItem($member);
+ }
+
+ $this->members = $filteredMembers;
+ }
+
+ /**
+ * Returns a new instance.
+ */
+ public static function new(): self
+ {
+ return new self();
+ }
+
+ /**
+ * Returns a new instance from an associative iterable construct.
+ *
+ * its keys represent the dictionary entry key
+ * its values represent the dictionary entry value
+ *
+ * @param StructuredFieldProvider|iterable<string, SfItemInput> $members
+ */
+ public static function fromAssociative(StructuredFieldProvider|iterable $members): self
+ {
+ if ($members instanceof StructuredFieldProvider) {
+ $structuredField = $members->toStructuredField();
+
+ return match (true) {
+ $structuredField instanceof Dictionary,
+ $structuredField instanceof Parameters => new self($structuredField->toAssociative()),
+ default => throw new InvalidArgument('The '.StructuredFieldProvider::class.' must provide a structured field container; '.$structuredField::class.' given.'),
+ };
+ }
+
+ return new self($members);
+ }
+
+ /**
+ * Returns a new instance from a pair iterable construct.
+ *
+ * Each member is composed of an array with two elements
+ * the first member represents the instance entry key
+ * the second member represents the instance entry value
+ *
+ * @param StructuredFieldProvider|iterable<array{0:string, 1:SfItemInput}> $pairs
+ */
+ public static function fromPairs(StructuredFieldProvider|iterable $pairs): self
+ {
+ if ($pairs instanceof StructuredFieldProvider) {
+ $pairs = $pairs->toStructuredField();
+ }
+
+ if (!is_iterable($pairs)) {
+ throw new InvalidArgument('The "'.$pairs::class.'" instance can not be used for creating a .'.self::class.' structured field.');
+ }
+
+ return match (true) {
+ $pairs instanceof Parameters,
+ $pairs instanceof Dictionary => new self($pairs->toAssociative()),
+ default => new self((function (iterable $pairs) {
+ foreach ($pairs as [$key, $member]) {
+ yield $key => $member;
+ }
+ })($pairs)),
+ };
+ }
+
+ /**
+ * Returns an instance from an HTTP textual representation.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-3.1.2
+ *
+ * @throws SyntaxError|Exception If the string is not a valid
+ */
+ public static function fromHttpValue(Stringable|string $httpValue, Ietf $rfc = Ietf::Rfc9651): self
+ {
+ return self::fromPairs((new Parser($rfc))->parseParameters($httpValue)); /* @phpstan-ignore-line */
+ }
+
+ public static function fromRfc9651(Stringable|string $httpValue): self
+ {
+ return self::fromHttpValue($httpValue, Ietf::Rfc9651);
+ }
+
+ public static function fromRfc8941(Stringable|string $httpValue): self
+ {
+ return self::fromHttpValue($httpValue, Ietf::Rfc8941);
+ }
+
+ public function toHttpValue(Ietf $rfc = Ietf::Rfc9651): string
+ {
+ $formatter = static fn (Item $member, string $offset): string => match ($member->value()) {
+ true => ';'.$offset,
+ default => ';'.$offset.'='.$member->toHttpValue($rfc),
+ };
+
+ return implode('', array_map($formatter, $this->members, array_keys($this->members)));
+ }
+
+ public function toRfc9651(): string
+ {
+ return $this->toHttpValue(Ietf::Rfc9651);
+ }
+
+ public function toRfc8941(): string
+ {
+ return $this->toHttpValue(Ietf::Rfc8941);
+ }
+
+ public function __toString(): string
+ {
+ return $this->toHttpValue();
+ }
+
+ public function equals(mixed $other): bool
+ {
+ return $other instanceof self && $other->toHttpValue() === $this->toHttpValue();
+ }
+
+ /**
+ * Apply the callback if the given "condition" is (or resolves to) true.
+ *
+ * @param (callable($this): bool)|bool $condition
+ * @param callable($this): (self|null) $onSuccess
+ * @param ?callable($this): (self|null) $onFail
+ */
+ public function when(callable|bool $condition, callable $onSuccess, ?callable $onFail = null): self
+ {
+ if (!is_bool($condition)) {
+ $condition = $condition($this);
+ }
+
+ return match (true) {
+ $condition => $onSuccess($this),
+ null !== $onFail => $onFail($this),
+ default => $this,
+ } ?? $this;
+ }
+
+ public function count(): int
+ {
+ return count($this->members);
+ }
+
+ public function isEmpty(): bool
+ {
+ return !$this->isNotEmpty();
+ }
+
+ public function isNotEmpty(): bool
+ {
+ return [] !== $this->members;
+ }
+
+ /**
+ * @return Iterator<string, Item>
+ */
+ public function toAssociative(): Iterator
+ {
+ yield from $this->members;
+ }
+
+ /**
+ * @return Iterator<int, array{0:string, 1:Item}>
+ */
+ public function getIterator(): Iterator
+ {
+ foreach ($this->members as $index => $member) {
+ yield [$index, $member];
+ }
+ }
+
+ /**
+ * @return array<string>
+ */
+ public function keys(): array
+ {
+ return array_keys($this->members);
+ }
+
+ /**
+ * Tells whether the instance contain a members at the specified offsets.
+ */
+ public function hasKeys(string ...$keys): bool
+ {
+ foreach ($keys as $key) {
+ if (!array_key_exists($key, $this->members)) {
+ return false;
+ }
+ }
+
+ return [] !== $keys;
+ }
+
+ /**
+ * @param ?callable(SfType): (bool|string) $validate
+ *
+ * @throws Violation|InvalidOffset
+ */
+ public function getByKey(string $key, ?callable $validate = null): Item
+ {
+ $value = $this->members[$key] ?? throw InvalidOffset::dueToKeyNotFound($key);
+ if (null === $validate || true === ($exceptionMessage = $validate($value->value()))) {
+ return $value;
+ }
+
+ if (!is_string($exceptionMessage) || '' === trim($exceptionMessage)) {
+ $exceptionMessage = "The parameter '{key}' whose value is '{value}' failed validation.";
+ }
+
+ throw new Violation(strtr($exceptionMessage, ['{key}' => $key, '{value}' => $value->toHttpValue()]));
+ }
+
+ /**
+ * @return array<int>
+ */
+ public function indices(): array
+ {
+ return array_keys($this->keys());
+ }
+
+ public function hasIndices(int ...$indices): bool
+ {
+ $max = count($this->members);
+ foreach ($indices as $index) {
+ if (null === $this->filterIndex($index, $max)) {
+ return false;
+ }
+ }
+
+ return [] !== $indices;
+ }
+
+ /**
+ * Filters and format instance index.
+ */
+ private function filterIndex(int $index, int|null $max = null): int|null
+ {
+ $max ??= count($this->members);
+
+ return match (true) {
+ [] === $this->members,
+ 0 > $max + $index,
+ 0 > $max - $index - 1 => null,
+ 0 > $index => $max + $index,
+ default => $index,
+ };
+ }
+
+ /**
+ * @param ?callable(SfType, string): (bool|string) $validate
+ *
+ * @throws InvalidOffset|Violation
+ *
+ * @return array{0:string, 1:Item}
+ */
+ public function getByIndex(int $index, ?callable $validate = null): array
+ {
+ $foundOffset = $this->filterIndex($index) ?? throw InvalidOffset::dueToIndexNotFound($index);
+ foreach ($this as $offset => $pair) {
+ if ($offset === $foundOffset) {
+ break;
+ }
+ }
+
+ if (!isset($pair)) {
+ throw InvalidOffset::dueToIndexNotFound($index);
+ }
+
+ if (null === $validate || true === ($exceptionMessage = $validate($pair[1]->value(), $pair[0]))) {
+ return $pair;
+ }
+
+ if (!is_string($exceptionMessage) || '' === trim($exceptionMessage)) {
+ $exceptionMessage = "The parameter at position '{index}' whose key is '{key}' with the value '{value}' failed validation.";
+ }
+
+ throw new Violation(strtr($exceptionMessage, ['{index}' => $index, '{key}' => $pair[0], '{value}' => $pair[1]->toHttpValue()]));
+ }
+
+ /**
+ * Returns the key associated with the given index or null otherwise.
+ */
+ public function indexByKey(string $key): ?int
+ {
+ foreach ($this as $index => $member) {
+ if ($key === $member[0]) {
+ return $index;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the index associated with the given key or null otherwise.
+ */
+ public function keyByIndex(int $index): ?string
+ {
+ $index = $this->filterIndex($index);
+ if (null === $index) {
+ return null;
+ }
+
+ foreach ($this as $offset => $member) {
+ if ($offset === $index) {
+ return $member[0];
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return array{0:string, 1:Item}|array{}
+ */
+ public function first(): array
+ {
+ try {
+ return $this->getByIndex(0);
+ } catch (InvalidOffset) {
+ return [];
+ }
+ }
+
+ /**
+ * @return array{0:string, 1:Item}|array{}
+ */
+ public function last(): array
+ {
+ try {
+ return $this->getByIndex(-1);
+ } catch (InvalidOffset) {
+ return [];
+ }
+ }
+
+ /**
+ * Returns true only if the instance only contains the listed keys, false otherwise.
+ *
+ * @param array<string> $keys
+ */
+ public function allowedKeys(array $keys): bool
+ {
+ foreach ($this->members as $key => $member) {
+ if (!in_array($key, $keys, true)) {
+ return false;
+ }
+ }
+
+ return [] !== $keys;
+ }
+
+ /**
+ * Returns the member value or null if no members value exists.
+ *
+ * @param ?callable(SfType): (bool|string) $validate
+ *
+ * @throws Violation if the validation fails
+ *
+ * @return SfType|null
+ */
+ public function valueByKey(
+ string $key,
+ ?callable $validate = null,
+ bool|string $required = false,
+ Bytes|Token|DisplayString|DateTimeImmutable|string|int|float|bool|null $default = null
+ ): Bytes|Token|DisplayString|DateTimeImmutable|string|int|float|bool|null {
+ if (null !== $default && null === Type::tryFromVariable($default)) {
+ throw new SyntaxError('The default parameter is invalid.');
+ }
+
+ try {
+ return $this->getByKey($key, $validate)->value();
+ } catch (InvalidOffset $exception) {
+ if (false === $required) {
+ return $default;
+ }
+
+ $message = $required;
+ if (!is_string($message) || '' === trim($message)) {
+ $message = "The required parameter '{key}' is missing.";
+ }
+
+ throw new Violation(strtr($message, ['{key}' => $key]), previous: $exception);
+ }
+ }
+
+ /**
+ * Returns the member value and key as pair or an empty array if no members value exists.
+ *
+ * @param ?callable(SfType, string): (bool|string) $validate
+ * @param array{0:string, 1:SfType}|array{} $default
+ *
+ * @throws Violation if the validation fails
+ *
+ * @return array{0:string, 1:SfType}|array{}
+ */
+ public function valueByIndex(int $index, ?callable $validate = null, bool|string $required = false, array $default = []): array
+ {
+ $default = match (true) {
+ [] === $default => [],
+ !array_is_list($default) => throw new SyntaxError('The pair must be represented by an array as a list.'), /* @phpstan-ignore-line */
+ 2 !== count($default) => throw new SyntaxError('The pair first member is the key; its second member is its value.'), /* @phpstan-ignore-line */
+ null === ($key = Key::tryFrom($default[0])?->value) => throw new SyntaxError('The pair first member is invalid.'),
+ null === ($value = Item::tryNew($default[1])?->value()) => throw new SyntaxError('The pair second member is invalid.'),
+ default => [$key, $value],
+ };
+
+ try {
+ $tuple = $this->getByIndex($index, $validate);
+
+ return [$tuple[0], $tuple[1]->value()];
+ } catch (InvalidOffset $exception) {
+ if (false === $required) {
+ return $default;
+ }
+
+ $message = $required;
+ if (!is_string($message) || '' === trim($message)) {
+ $message = "The required parameter at position '{index}' is missing.";
+ }
+
+ throw new Violation(strtr($message, ['{index}' => $index]), previous: $exception);
+ }
+ }
+
+ /**
+ * @param StructuredFieldProvider|Item|SfType $member
+ */
+ public function add(
+ string $key,
+ StructuredFieldProvider|Item|Token|Bytes|DisplayString|DateTimeInterface|string|int|float|bool $member
+ ): self {
+ $key = Key::from($key)->value;
+ $member = Member::bareItem($member);
+ $oldMember = $this->members[$key] ?? null;
+ if (null === $oldMember || !$oldMember->equals($member)) {
+ $members = $this->members;
+ $members[$key] = $member;
+
+ return new self($members);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @param array<string, Item> $members
+ */
+ private function newInstance(array $members): self
+ {
+ foreach ($members as $offset => $member) {
+ if (!isset($this->members[$offset]) || !$this->members[$offset]->equals($member)) {
+ return new self($members);
+ }
+ }
+
+ return $this;
+ }
+
+ private function remove(string|int ...$offsets): self
+ {
+ if ([] === $this->members || [] === $offsets) {
+ return $this;
+ }
+
+ $keys = array_keys($this->members);
+ $max = count($keys);
+ $reducer = fn (array $carry, string|int $key): array => match (true) {
+ is_string($key) && (false !== ($position = array_search($key, $keys, true))),
+ is_int($key) && (null !== ($position = $this->filterIndex($key, $max))) => [$position => true] + $carry,
+ default => $carry,
+ };
+
+ $indices = array_reduce($offsets, $reducer, []);
+
+ return match (true) {
+ [] === $indices => $this,
+ $max === count($indices) => self::new(),
+ default => self::fromPairs((function (array $offsets) {
+ foreach ($this as $offset => $pair) {
+ if (!array_key_exists($offset, $offsets)) {
+ yield $pair;
+ }
+ }
+ })($indices)),
+ };
+ }
+
+ public function removeByIndices(int ...$indices): self
+ {
+ return $this->remove(...$indices);
+ }
+
+ public function removeByKeys(string ...$keys): self
+ {
+ return $this->remove(...$keys);
+ }
+
+ /**
+ * @param StructuredFieldProvider|Item|SfType $member
+ */
+ public function append(
+ string $key,
+ StructuredFieldProvider|Item|Token|Bytes|DisplayString|DateTimeInterface|string|int|float|bool $member
+ ): self {
+ $key = Key::from($key)->value;
+ $member = Member::bareItem($member);
+ $members = $this->members;
+ unset($members[$key]);
+ $members[$key] = $member;
+
+ return $this->newInstance($members);
+ }
+
+ /**
+ * @param StructuredFieldProvider|Item|SfType $member
+ */
+ public function prepend(
+ string $key,
+ StructuredFieldProvider|Item|Token|Bytes|DisplayString|DateTimeInterface|string|int|float|bool $member
+ ): self {
+ $key = Key::from($key)->value;
+ $member = Member::bareItem($member);
+ $members = $this->members;
+ unset($members[$key]);
+
+ return $this->newInstance([$key => $member, ...$members]);
+ }
+
+ /**
+ * @param array{0:string, 1:SfItemInput} ...$pairs
+ */
+ public function push(array ...$pairs): self
+ {
+ return match (true) {
+ [] === $pairs => $this,
+ default => self::fromPairs((function (iterable $pairs) {
+ yield from $this->getIterator();
+ yield from $pairs;
+ })($pairs)),
+ };
+ }
+
+ /**
+ * @param array{0:string, 1:SfItemInput} ...$pairs
+ */
+ public function unshift(array ...$pairs): self
+ {
+ return match (true) {
+ [] === $pairs => $this,
+ default => self::fromPairs((function (iterable $pairs) {
+ yield from $pairs;
+ yield from $this->getIterator();
+ })($pairs)),
+ };
+ }
+
+ /**
+ * @param array{0:string, 1:SfItemInput} ...$members
+ */
+ public function insert(int $index, array ...$members): self
+ {
+ $offset = $this->filterIndex($index) ?? throw InvalidOffset::dueToIndexNotFound($index);
+
+ return match (true) {
+ [] === $members => $this,
+ 0 === $offset => $this->unshift(...$members),
+ count($this->members) === $offset => $this->push(...$members),
+ default => (function (Iterator $newMembers) use ($offset, $members) {
+ $newMembers = iterator_to_array($newMembers);
+ array_splice($newMembers, $offset, 0, $members);
+
+ return self::fromPairs($newMembers);
+ })($this->getIterator()),
+ };
+ }
+
+ /**
+ * @param array{0:string, 1:SfItemInput} $pair
+ */
+ public function replace(int $index, array $pair): self
+ {
+ $offset = $this->filterIndex($index) ?? throw InvalidOffset::dueToIndexNotFound($index);
+ $pair[1] = Member::bareItem($pair[1]);
+ $pairs = iterator_to_array($this);
+
+ return match (true) {
+ $pairs[$offset][0] === $pair[0] && $pairs[$offset][1]->equals($pair[1]) => $this,
+ default => self::fromPairs(array_replace($pairs, [$offset => $pair])),
+ };
+ }
+
+ /**
+ * @param StructuredFieldProvider|Dictionary|Parameters|iterable<string, SfItemInput> ...$others
+ */
+ public function mergeAssociative(StructuredFieldProvider|iterable ...$others): self
+ {
+ $members = $this->members;
+ foreach ($others as $other) {
+ if ($other instanceof StructuredFieldProvider) {
+ $other = $other->toStructuredField();
+ if (!$other instanceof Dictionary && !$other instanceof Parameters) {
+ throw new InvalidArgument('The "'.$other::class.'" instance can not be used for creating a .'.self::class.' structured field.');
+ }
+ }
+
+ if ($other instanceof self || $other instanceof Dictionary) {
+ $other = $other->toAssociative();
+ }
+
+ foreach ($other as $key => $value) {
+ $members[$key] = $value;
+ }
+ }
+
+ return new self($members);
+ }
+
+ /**
+ * @param StructuredFieldProvider|Parameters|Dictionary|iterable<array{0:string, 1:SfItemInput}> ...$others
+ */
+ public function mergePairs(Dictionary|Parameters|StructuredFieldProvider|iterable ...$others): self
+ {
+ $members = $this->members;
+ foreach ($others as $other) {
+ if (!$other instanceof self) {
+ $other = self::fromPairs($other);
+ }
+ foreach ($other->toAssociative() as $key => $value) {
+ $members[$key] = $value;
+ }
+ }
+
+ return new self($members);
+ }
+
+ /**
+ * @param string $offset
+ */
+ public function offsetExists(mixed $offset): bool
+ {
+ return $this->hasKeys($offset);
+ }
+
+ /**
+ * @param string $offset
+ */
+ public function offsetGet(mixed $offset): Item
+ {
+ return $this->getByKey($offset);
+ }
+
+ public function offsetUnset(mixed $offset): void
+ {
+ throw new ForbiddenOperation(self::class.' instance can not be updated using '.ArrayAccess::class.' methods.');
+ }
+
+ public function offsetSet(mixed $offset, mixed $value): void
+ {
+ throw new ForbiddenOperation(self::class.' instance can not be updated using '.ArrayAccess::class.' methods.');
+ }
+
+ /**
+ * @param callable(array{0:string, 1:Item}, int): TMap $callback
+ *
+ * @template TMap
+ *
+ * @return Iterator<TMap>
+ */
+ public function map(callable $callback): Iterator
+ {
+ foreach ($this as $offset => $pair) {
+ yield ($callback)($pair, $offset);
+ }
+ }
+
+ /**
+ * @param callable(TInitial|null, array{0:string, 1:Item}, int): TInitial $callback
+ * @param TInitial|null $initial
+ *
+ * @template TInitial
+ *
+ * @return TInitial|null
+ */
+ public function reduce(callable $callback, mixed $initial = null): mixed
+ {
+ foreach ($this as $offset => $pair) {
+ $initial = $callback($initial, $pair, $offset);
+ }
+
+ return $initial;
+ }
+
+ /**
+ * @param callable(array{0:string, 1:Item}, int): bool $callback
+ */
+ public function filter(callable $callback): self
+ {
+ return self::fromPairs(new CallbackFilterIterator($this->getIterator(), $callback));
+ }
+
+ /**
+ * @param callable(array{0:string, 1:Item}, array{0:string, 1:Item}): int $callback
+ */
+ public function sort(callable $callback): self
+ {
+ $members = iterator_to_array($this);
+ uasort($members, $callback);
+
+ return self::fromPairs($members);
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/Parser.php b/vendor/bakame/http-structured-fields/src/Parser.php
new file mode 100644
index 000000000..4c7448c15
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Parser.php
@@ -0,0 +1,523 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields;
+
+use DateTimeImmutable;
+use Exception;
+use Stringable;
+
+use function in_array;
+use function ltrim;
+use function preg_match;
+use function str_contains;
+use function strlen;
+use function substr;
+use function trim;
+
+/**
+ * A class to parse HTTP Structured Fields from their HTTP textual representation according to RFC8941.
+ *
+ * Based on gapple\StructuredFields\Parser class in Structured Field Values for PHP v1.0.0.
+ *
+ * @link https://github.com/gapple/structured-fields/blob/v1.0.0/src/Parser.php
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-4.2
+ *
+ * @see Dictionary::fromHttpValue()
+ * @see Parameters::fromHttpValue()
+ * @see OuterList::fromHttpValue()
+ * @see InnerList::fromHttpValue()
+ * @see Item::fromHttpValue()
+ *
+ * @internal Do not use directly this class as it's behaviour and return type
+ * MAY change significantly even during a major release cycle.
+ *
+ * @phpstan-type SfValue Bytes|Token|DisplayString|DateTimeImmutable|string|int|float|bool
+ * @phpstan-type SfParameter array<array{0:string, 1:SfValue}>
+ * @phpstan-type SfItem array{0:SfValue, 1: SfParameter}
+ * @phpstan-type SfInnerList array{0:array<SfItem>, 1: SfParameter}
+ */
+final class Parser
+{
+ private const REGEXP_BYTES = '/^(?<sequence>:(?<byte>[a-z\d+\/=]*):)/i';
+ private const REGEXP_BOOLEAN = '/^\?[01]/';
+ private const REGEXP_DATE = '/^@(?<date>-?\d{1,15})(?:[^\d.]|$)/';
+ private const REGEXP_DECIMAL = '/^-?\d{1,12}\.\d{1,3}$/';
+ private const REGEXP_INTEGER = '/^-?\d{1,15}$/';
+ private const REGEXP_TOKEN = "/^(?<token>[a-z*][a-z\d:\/!#\$%&'*+\-.^_`|~]*)/i";
+ private const REGEXP_INVALID_CHARACTERS = "/[\r\t\n]|[^\x20-\x7E]/";
+ private const REGEXP_VALID_NUMBER = '/^(?<number>-?\d+(?:\.\d+)?)(?:[^\d.]|$)/';
+ private const REGEXP_VALID_SPACE = '/^(?<space>,[ \t]*)/';
+ private const FIRST_CHARACTER_RANGE_NUMBER = '-1234567890';
+ private const FIRST_CHARACTER_RANGE_TOKEN = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ*';
+
+ public function __construct(private readonly Ietf $rfc)
+ {
+ }
+
+ /**
+ * Returns an Item as a PHP list array from an HTTP textual representation.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#name-parsing-an-item
+ *
+ *
+ * @throws Exception|SyntaxError
+ *
+ * @return SfItem
+ */
+ public function parseItem(Stringable|string $httpValue): array
+ {
+ $remainder = trim((string) $httpValue, ' ');
+ if ('' === $remainder || 1 === preg_match(self::REGEXP_INVALID_CHARACTERS, $remainder)) {
+ throw new SyntaxError("The HTTP textual representation \"$httpValue\" for an item contains invalid characters.");
+ }
+
+ [$value, $offset] = $this->extractValue($remainder);
+ $remainder = substr($remainder, $offset);
+ if ('' !== $remainder && !str_contains($remainder, ';')) {
+ throw new SyntaxError("The HTTP textual representation \"$httpValue\" for an item contains invalid characters.");
+ }
+
+ return [$value, $this->parseParameters($remainder)]; /* @phpstan-ignore-line */
+ }
+
+ /**
+ * Returns a Parameters ordered map container as a PHP list array from an HTTP textual representation.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-3.1.2
+ *
+ * @throws SyntaxError|Exception
+ *
+ * @return array<SfParameter>
+ */
+ public function parseParameters(Stringable|string $httpValue): array
+ {
+ $remainder = trim((string) $httpValue);
+ [$parameters, $offset] = $this->extractParametersValues($remainder);
+ if (strlen($remainder) !== $offset) {
+ throw new SyntaxError("The HTTP textual representation \"$httpValue\" for Parameters contains invalid characters.");
+ }
+
+ return $parameters; /* @phpstan-ignore-line */
+ }
+
+ /**
+ * Returns an ordered list represented as a PHP list array from an HTTP textual representation.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-4.2.1
+ *
+ * @throws SyntaxError|Exception
+ *
+ * @return array<SfInnerList|SfItem>
+ */
+ public function parseList(Stringable|string $httpValue): array
+ {
+ $list = [];
+ $remainder = ltrim((string) $httpValue, ' ');
+ while ('' !== $remainder) {
+ [$list[], $offset] = $this->extractItemOrInnerList($remainder);
+ $remainder = self::removeCommaSeparatedWhiteSpaces($remainder, $offset);
+ }
+
+ return $list;
+ }
+
+ /**
+ * Returns a Dictionary represented as a PHP list array from an HTTP textual representation.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-4.2.2
+ *
+ * @throws SyntaxError|Exception
+ *
+ * @return array<array{0:string, 1:SfInnerList|SfItem}>
+ */
+ public function parseDictionary(Stringable|string $httpValue): array
+ {
+ $map = [];
+ $remainder = ltrim((string) $httpValue, ' ');
+ while ('' !== $remainder) {
+ $key = Key::fromStringBeginning($remainder)->value;
+ $remainder = substr($remainder, strlen($key));
+ if ('' === $remainder || '=' !== $remainder[0]) {
+ $remainder = '=?1'.$remainder;
+ }
+ $member = [$key];
+
+ [$member[1], $offset] = $this->extractItemOrInnerList(substr($remainder, 1));
+ $remainder = self::removeCommaSeparatedWhiteSpaces($remainder, ++$offset);
+ $map[] = $member;
+ }
+
+ return $map;
+ }
+
+ /**
+ * Returns an inner list represented as a PHP list array from an HTTP textual representation.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-4.2.1.2
+ *
+ * @throws SyntaxError|Exception
+ *
+ * @return SfInnerList
+ */
+ public function parseInnerList(Stringable|string $httpValue): array
+ {
+ $remainder = ltrim((string) $httpValue, ' ');
+ if ('(' !== $remainder[0]) {
+ throw new SyntaxError("The HTTP textual representation \"$httpValue\" for a inner list is missing a parenthesis.");
+ }
+
+ [$list, $offset] = $this->extractInnerList($remainder);
+ $remainder = self::removeOptionalWhiteSpaces(substr($remainder, $offset));
+ if ('' !== $remainder) {
+ throw new SyntaxError("The HTTP textual representation \"$httpValue\" for a inner list contains invalid data.");
+ }
+
+ return $list;
+ }
+
+ /**
+ * Filter optional white spaces before and after comma.
+ *
+ * @see https://tools.ietf.org/html/rfc7230#section-3.2.3
+ */
+ private static function removeCommaSeparatedWhiteSpaces(string $remainder, int $offset): string
+ {
+ $remainder = self::removeOptionalWhiteSpaces(substr($remainder, $offset));
+ if ('' === $remainder) {
+ return '';
+ }
+
+ if (1 !== preg_match(self::REGEXP_VALID_SPACE, $remainder, $found)) {
+ throw new SyntaxError('The HTTP textual representation is missing an excepted comma.');
+ }
+
+ $remainder = substr($remainder, strlen($found['space']));
+
+ if ('' === $remainder) {
+ throw new SyntaxError('The HTTP textual representation has an unexpected end of line.');
+ }
+
+ return $remainder;
+ }
+
+ /**
+ * Remove optional white spaces before field value.
+ *
+ * @see https://tools.ietf.org/html/rfc7230#section-3.2.3
+ */
+ private static function removeOptionalWhiteSpaces(string $httpValue): string
+ {
+ return ltrim($httpValue, " \t");
+ }
+
+ /**
+ * Returns an item or an inner list as a PHP list array from an HTTP textual representation.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-4.2.1.1
+ *
+ * @throws SyntaxError|Exception
+ *
+ * @return array{0: SfInnerList|SfItem, 1:int}
+ */
+ private function extractItemOrInnerList(string $httpValue): array
+ {
+ if ('(' === $httpValue[0]) {
+ return $this->extractInnerList($httpValue);
+ }
+
+ [$item, $remainder] = $this->extractItem($httpValue);
+
+ return [$item, strlen($httpValue) - strlen($remainder)];
+ }
+
+ /**
+ * Returns an inner list represented as a PHP list array from an HTTP textual representation and the consumed offset in a tuple.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-4.2.1.2
+ *
+ * @throws SyntaxError|Exception
+ *
+ * @return array{0: SfInnerList, 1 :int}
+ */
+ private function extractInnerList(string $httpValue): array
+ {
+ $list = [];
+ $remainder = substr($httpValue, 1);
+ while ('' !== $remainder) {
+ $remainder = ltrim($remainder, ' ');
+
+ if (')' === $remainder[0]) {
+ $remainder = substr($remainder, 1);
+ [$parameters, $offset] = $this->extractParametersValues($remainder);
+ $remainder = substr($remainder, $offset);
+
+ return [[$list, $parameters], strlen($httpValue) - strlen($remainder)];
+ }
+
+ [$list[], $remainder] = $this->extractItem($remainder);
+
+ if ('' !== $remainder && !in_array($remainder[0], [' ', ')'], true)) {
+ throw new SyntaxError("The HTTP textual representation \"$httpValue\" for a inner list is using invalid characters.");
+ }
+ }
+
+ throw new SyntaxError("The HTTP textual representation \"$httpValue\" for a inner list has an unexpected end of line.");
+ }
+
+ /**
+ * Returns an item represented as a PHP array from an HTTP textual representation and the consumed offset in a tuple.
+ *
+ * @throws SyntaxError|Exception
+ *
+ * @return array{0:SfItem, 1:string}
+ */
+ private function extractItem(string $remainder): array
+ {
+ [$value, $offset] = $this->extractValue($remainder);
+ $remainder = substr($remainder, $offset);
+ [$parameters, $offset] = $this->extractParametersValues($remainder);
+
+ return [[$value, $parameters], substr($remainder, $offset)];
+ }
+
+ /**
+ * Returns an item value from an HTTP textual representation and the consumed offset in a tuple.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-4.2.3.1
+ *
+ * @throws SyntaxError|Exception
+ *
+ * @return array{0:SfValue, 1:int}
+ */
+ private function extractValue(string $httpValue): array
+ {
+ return match (true) {
+ '"' === $httpValue[0] => self::extractString($httpValue),
+ ':' === $httpValue[0] => self::extractBytes($httpValue),
+ '?' === $httpValue[0] => self::extractBoolean($httpValue),
+ '@' === $httpValue[0] => self::extractDate($httpValue, $this->rfc),
+ str_starts_with($httpValue, '%"') => self::extractDisplayString($httpValue, $this->rfc),
+ str_contains(self::FIRST_CHARACTER_RANGE_NUMBER, $httpValue[0]) => self::extractNumber($httpValue),
+ str_contains(self::FIRST_CHARACTER_RANGE_TOKEN, $httpValue[0]) => self::extractToken($httpValue),
+ default => throw new SyntaxError("The HTTP textual representation \"$httpValue\" for an item value is unknown or unsupported."),
+ };
+ }
+
+ /**
+ * Returns a parameters container represented as a PHP associative array from an HTTP textual representation and the consumed offset in a tuple.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-4.2.3.2
+ *
+ * @throws SyntaxError|Exception
+ *
+ * @return array{0:SfParameter, 1:int}
+ */
+ private function extractParametersValues(Stringable|string $httpValue): array
+ {
+ $map = [];
+ $httpValue = (string) $httpValue;
+ $remainder = $httpValue;
+ while ('' !== $remainder && ';' === $remainder[0]) {
+ $remainder = ltrim(substr($remainder, 1), ' ');
+ $key = Key::fromStringBeginning($remainder)->value;
+ $member = [$key, true];
+ $remainder = substr($remainder, strlen($key));
+ if ('' !== $remainder && '=' === $remainder[0]) {
+ $remainder = substr($remainder, 1);
+ [$member[1], $offset] = $this->extractValue($remainder);
+ $remainder = substr($remainder, $offset);
+ }
+
+ $map[] = $member;
+ }
+
+ return [$map, strlen($httpValue) - strlen($remainder)];
+ }
+
+ /**
+ * Returns a boolean from an HTTP textual representation and the consumed offset in a tuple.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-4.2.8
+ *
+ * @return array{0:bool, 1:int}
+ */
+ private static function extractBoolean(string $httpValue): array
+ {
+ return match (1) {
+ preg_match(self::REGEXP_BOOLEAN, $httpValue) => ['1' === $httpValue[1], 2],
+ default => throw new SyntaxError("The HTTP textual representation \"$httpValue\" for a Boolean contains invalid characters."),
+ };
+ }
+
+ /**
+ * Returns an int or a float from an HTTP textual representation and the consumed offset in a tuple.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-4.2.4
+ *
+ * @return array{0:int|float, 1:int}
+ */
+ private static function extractNumber(string $httpValue): array
+ {
+ if (1 !== preg_match(self::REGEXP_VALID_NUMBER, $httpValue, $found)) {
+ throw new SyntaxError("The HTTP textual representation \"$httpValue\" for a Number contains invalid characters.");
+ }
+
+ return match (1) {
+ preg_match(self::REGEXP_DECIMAL, $found['number']) => [(float) $found['number'], strlen($found['number'])],
+ preg_match(self::REGEXP_INTEGER, $found['number']) => [(int) $found['number'], strlen($found['number'])],
+ default => throw new SyntaxError("The HTTP textual representation \"$httpValue\" for a Number contains too much digit."),
+ };
+ }
+
+ /**
+ * Returns DateTimeImmutable instance from an HTTP textual representation and the consumed offset in a tuple.
+ *
+ * @see https://httpwg.org/http-extensions/draft-ietf-httpbis-sfbis.html#name-dates
+ *
+ * @throws SyntaxError
+ * @throws Exception
+ *
+ * @return array{0:DateTimeImmutable, 1:int}
+ */
+ private static function extractDate(string $httpValue, Ietf $rfc): array
+ {
+ if (!$rfc->supports(Type::Date)) {
+ throw MissingFeature::dueToLackOfSupport(Type::Date, $rfc);
+ }
+
+ if (1 !== preg_match(self::REGEXP_DATE, $httpValue, $found)) {
+ throw new SyntaxError("The HTTP textual representation \"$httpValue\" for a Date contains invalid characters.");
+ }
+
+ return [new DateTimeImmutable('@'.$found['date']), strlen($found['date']) + 1];
+ }
+
+ /**
+ * Returns a string from an HTTP textual representation and the consumed offset in a tuple.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-4.2.5
+ *
+ * @return array{0:string, 1:int}
+ */
+ private static function extractString(string $httpValue): array
+ {
+ $offset = 1;
+ $remainder = substr($httpValue, $offset);
+ $output = '';
+
+ if (1 === preg_match(self::REGEXP_INVALID_CHARACTERS, $remainder)) {
+ throw new SyntaxError("The HTTP textual representation \"$httpValue\" for a String contains an invalid end string.");
+ }
+
+ while ('' !== $remainder) {
+ $char = $remainder[0];
+ $offset += 1;
+
+ if ('"' === $char) {
+ return [$output, $offset];
+ }
+
+ $remainder = substr($remainder, 1);
+
+ if ('\\' !== $char) {
+ $output .= $char;
+ continue;
+ }
+
+ $char = $remainder[0] ?? '';
+ $offset += 1;
+ $remainder = substr($remainder, 1);
+
+ if (!in_array($char, ['"', '\\'], true)) {
+ throw new SyntaxError("The HTTP textual representation \"$httpValue\" for a String contains an invalid end string.");
+ }
+
+ $output .= $char;
+ }
+
+ throw new SyntaxError("The HTTP textual representation \"$httpValue\" for a String contains an invalid end string.");
+ }
+
+ /**
+ * Returns a string from an HTTP textual representation and the consumed offset in a tuple.
+ *
+ * @see https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-sfbis#section-4.2.10
+ *
+ * @return array{0:DisplayString, 1:int}
+ */
+ private static function extractDisplayString(string $httpValue, Ietf $rfc): array
+ {
+ if (!$rfc->supports(Type::DisplayString)) {
+ throw MissingFeature::dueToLackOfSupport(Type::DisplayString, $rfc);
+ }
+
+ $offset = 2;
+ $remainder = substr($httpValue, $offset);
+ $output = '';
+
+ if (1 === preg_match(self::REGEXP_INVALID_CHARACTERS, $remainder)) {
+ throw new SyntaxError("The HTTP textual representation \"$httpValue\" for a DisplayString contains an invalid character string.");
+ }
+
+ while ('' !== $remainder) {
+ $char = $remainder[0];
+ $offset += 1;
+
+ if ('"' === $char) {
+ return [DisplayString::fromEncoded($output), $offset];
+ }
+
+ $remainder = substr($remainder, 1);
+ if ('%' !== $char) {
+ $output .= $char;
+ continue;
+ }
+
+ $octet = substr($remainder, 0, 2);
+ $offset += 2;
+ if (1 === preg_match('/^[0-9a-f]]{2}$/', $octet)) {
+ throw new SyntaxError("The HTTP textual representation '$httpValue' for a DisplayString contains uppercased percent encoding sequence.");
+ }
+
+ $remainder = substr($remainder, 2);
+ $output .= $char.$octet;
+ }
+
+ throw new SyntaxError("The HTTP textual representation \"$httpValue\" for a DisplayString contains an invalid end string.");
+ }
+
+ /**
+ * Returns a Token from an HTTP textual representation and the consumed offset in a tuple.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-4.2.6
+ *
+ * @return array{0:Token, 1:int}
+ */
+ private static function extractToken(string $httpValue): array
+ {
+ preg_match(self::REGEXP_TOKEN, $httpValue, $found);
+
+ $token = $found['token'] ?? throw new SyntaxError("The HTTP textual representation \"$httpValue\" for a Token contains invalid characters.");
+
+ return [Token::fromString($token), strlen($token)];
+ }
+
+ /**
+ * Returns a Byte Sequence from an HTTP textual representation and the consumed offset in a tuple.
+ *
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-4.2.7
+ *
+ * @return array{0:Bytes, 1:int}
+ */
+ private static function extractBytes(string $httpValue): array
+ {
+ if (1 !== preg_match(self::REGEXP_BYTES, $httpValue, $found)) {
+ throw new SyntaxError("The HTTP textual representation \"$httpValue\" for a Byte Sequence contains invalid characters.");
+ }
+
+ return [Bytes::fromEncoded($found['byte']), strlen($found['sequence'])];
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/StructuredFieldError.php b/vendor/bakame/http-structured-fields/src/StructuredFieldError.php
new file mode 100644
index 000000000..fa823f6c8
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/StructuredFieldError.php
@@ -0,0 +1,11 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields;
+
+use Throwable;
+
+interface StructuredFieldError extends Throwable
+{
+}
diff --git a/vendor/bakame/http-structured-fields/src/StructuredFieldProvider.php b/vendor/bakame/http-structured-fields/src/StructuredFieldProvider.php
new file mode 100644
index 000000000..fd9e2be99
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/StructuredFieldProvider.php
@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields;
+
+use DateTimeImmutable;
+use DateTimeInterface;
+
+/**
+ * @phpstan-type SfType Bytes|Token|DisplayString|DateTimeImmutable|string|int|float|bool
+ * @phpstan-type SfTypeInput SfType|DateTimeInterface
+ * @phpstan-type SfList InnerList|OuterList
+ * @phpstan-type SfOrderedMap Dictionary|Parameters
+ * @phpstan-type SfDataType SfList|SfOrderedMap|Item
+ * @phpstan-type SfItemInput SfTypeInput|SfDataType|StructuredFieldProvider
+ * @phpstan-type SfMemberInput iterable<SfItemInput>|SfItemInput
+ * @phpstan-type SfParameterInput iterable<array{0:string, 1?:SfItemInput}>
+ * @phpstan-type SfInnerListPair array{0:iterable<SfItemInput>, 1?:Parameters|SfParameterInput}
+ * @phpstan-type SfItemPair array{0:SfTypeInput, 1?:Parameters|SfParameterInput}
+ */
+interface StructuredFieldProvider
+{
+ /**
+ * Returns one of the StructuredField Data Type class.
+ */
+ public function toStructuredField(): Dictionary|InnerList|Item|OuterList|Parameters;
+}
diff --git a/vendor/bakame/http-structured-fields/src/SyntaxError.php b/vendor/bakame/http-structured-fields/src/SyntaxError.php
new file mode 100644
index 000000000..304671fe9
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/SyntaxError.php
@@ -0,0 +1,11 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields;
+
+use InvalidArgumentException;
+
+class SyntaxError extends InvalidArgumentException implements StructuredFieldError
+{
+}
diff --git a/vendor/bakame/http-structured-fields/src/Token.php b/vendor/bakame/http-structured-fields/src/Token.php
new file mode 100644
index 000000000..88f41dbe8
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Token.php
@@ -0,0 +1,52 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields;
+
+use Stringable;
+use Throwable;
+
+use function preg_match;
+
+/**
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#name-tokens
+ */
+final class Token
+{
+ private function __construct(private readonly string $value)
+ {
+ if (1 !== preg_match("/^([a-z*][a-z\d:\/!#\$%&'*+\-.^_`|~]*)$/i", $this->value)) {
+ throw new SyntaxError('The token '.$this->value.' contains invalid characters.');
+ }
+ }
+
+ public function toString(): string
+ {
+ return $this->value;
+ }
+
+ public static function tryFromString(Stringable|string $value): ?self
+ {
+ try {
+ return self::fromString($value);
+ } catch (Throwable) {
+ return null;
+ }
+ }
+
+ public static function fromString(Stringable|string $value): self
+ {
+ return new self((string)$value);
+ }
+
+ public function equals(mixed $other): bool
+ {
+ return $other instanceof self && $other->value === $this->value;
+ }
+
+ public function type(): Type
+ {
+ return Type::Token;
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/Type.php b/vendor/bakame/http-structured-fields/src/Type.php
new file mode 100644
index 000000000..44eb8764d
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Type.php
@@ -0,0 +1,88 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields;
+
+use DateTimeInterface;
+
+use function abs;
+use function floor;
+use function gettype;
+use function is_float;
+use function is_int;
+use function is_object;
+use function is_string;
+use function preg_match;
+
+/**
+ * @see https://www.rfc-editor.org/rfc/rfc9651.html#section-3.3
+ */
+enum Type: string
+{
+ private const MAXIMUM_INT = 999_999_999_999_999;
+ private const MAXIMUM_FLOAT = 999_999_999_999;
+
+ case Integer = 'integer';
+ case Decimal = 'decimal';
+ case String = 'string';
+ case Token = 'token';
+ case Bytes = 'binary';
+ case DisplayString = 'displaystring';
+ case Boolean = 'boolean';
+ case Date = 'date';
+
+ public function equals(mixed $other): bool
+ {
+ return match (true) {
+ $other instanceof Item => $other->type() === $this,
+ default => $other instanceof self && $other === $this,
+ };
+ }
+
+ public function isOneOf(mixed ...$other): bool
+ {
+ foreach ($other as $item) {
+ if ($this->equals($item)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @throws SyntaxError if the value can not be resolved into a supported HTTP structured field data type
+ */
+ public static function fromVariable(Item|Token|DisplayString|Bytes|DateTimeInterface|int|float|bool|string $value): self
+ {
+ return self::tryFromVariable($value) ?? throw new SyntaxError(match (true) {
+ $value instanceof DateTimeInterface => 'The integer representation of a date is limited to 15 digits for a HTTP structured field date type.',
+ is_int($value) => 'The integer is limited to 15 digits for a HTTP structured field integer type.',
+ is_float($value) => 'The integer portion of decimals is limited to 12 digits for a HTTP structured field decimal type.',
+ is_string($value) => 'The string contains characters that are invalid for a HTTP structured field string type',
+ default => (is_object($value) ? 'An instance of "'.$value::class.'"' : 'A value of type "'.gettype($value).'"').' can not be used as an HTTP structured field value type.',
+ });
+ }
+
+ public static function tryFromVariable(mixed $variable): ?self
+ {
+ return match (true) {
+ $variable instanceof Item,
+ $variable instanceof Token,
+ $variable instanceof DisplayString,
+ $variable instanceof Bytes => $variable->type(),
+ $variable instanceof DateTimeInterface && self::MAXIMUM_INT >= abs($variable->getTimestamp()) => Type::Date,
+ is_int($variable) && self::MAXIMUM_INT >= abs($variable) => Type::Integer,
+ is_float($variable) && self::MAXIMUM_FLOAT >= abs(floor($variable)) => Type::Decimal,
+ is_bool($variable) => Type::Boolean,
+ is_string($variable) && 1 !== preg_match('/[^\x20-\x7f]/', $variable) => Type::String,
+ default => null,
+ };
+ }
+
+ public function supports(mixed $value): bool
+ {
+ return self::tryFromVariable($value)?->equals($this) ?? false;
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/Validation/ErrorCode.php b/vendor/bakame/http-structured-fields/src/Validation/ErrorCode.php
new file mode 100644
index 000000000..3f6ed43ae
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Validation/ErrorCode.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Bakame\Http\StructuredFields\Validation;
+
+/**
+ * General Error Code-.
+ *
+ * When adding new codes the name MUST be prefixed with
+ * a `@` to avoid conflicting with parameters keys.
+ */
+enum ErrorCode: string
+{
+ case ItemFailedParsing = '@item.failed.parsing';
+ case ItemValueFailedValidation = '@item.value.failed.validation';
+ case ParametersFailedParsing = '@parameters.failed.parsing';
+ case ParametersMissingConstraints = '@parameters.missing.constraints';
+ case ParametersFailedCriteria = '@parameters.failed.criteria';
+
+ /**
+ * @return array<string>
+ */
+ public static function list(): array
+ {
+ return array_map(fn (self $case) => $case->value, self::cases());
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/Validation/ItemValidator.php b/vendor/bakame/http-structured-fields/src/Validation/ItemValidator.php
new file mode 100644
index 000000000..21d8cdd2f
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Validation/ItemValidator.php
@@ -0,0 +1,103 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields\Validation;
+
+use Bakame\Http\StructuredFields\Item;
+use Bakame\Http\StructuredFields\StructuredFieldProvider;
+use Bakame\Http\StructuredFields\SyntaxError;
+use Stringable;
+
+/**
+ * Structured field Item validator.
+ *
+ * @phpstan-import-type SfType from StructuredFieldProvider
+ */
+final class ItemValidator
+{
+ /** @var callable(SfType): (string|bool) */
+ private mixed $valueConstraint;
+ private ParametersValidator $parametersConstraint;
+
+ /**
+ * @param callable(SfType): (string|bool) $valueConstraint
+ */
+ private function __construct(
+ callable $valueConstraint,
+ ParametersValidator $parametersConstraint,
+ ) {
+ $this->valueConstraint = $valueConstraint;
+ $this->parametersConstraint = $parametersConstraint;
+ }
+
+ public static function new(): self
+ {
+ return new self(fn (mixed $value) => false, ParametersValidator::new());
+ }
+
+ /**
+ * Validates the Item value.
+ *
+ * On success populate the result item property
+ * On failure populates the result errors property
+ *
+ * @param callable(SfType): (string|bool) $constraint
+ */
+ public function value(callable $constraint): self
+ {
+ return new self($constraint, $this->parametersConstraint);
+ }
+
+ /**
+ * Validates the Item parameters as a whole.
+ *
+ * On failure populates the result errors property
+ */
+ public function parameters(ParametersValidator $constraint): self
+ {
+ return new self($this->valueConstraint, $constraint);
+ }
+
+ public function __invoke(Item|Stringable|string $item): bool|string
+ {
+ $result = $this->validate($item);
+
+ return $result->isSuccess() ? true : (string) $result->errors;
+ }
+
+ /**
+ * Validates the structured field Item.
+ */
+ public function validate(Item|Stringable|string $item): Result
+ {
+ $violations = new ViolationList();
+ if (!$item instanceof Item) {
+ try {
+ $item = Item::fromHttpValue($item);
+ } catch (SyntaxError $exception) {
+ $violations->add(ErrorCode::ItemFailedParsing->value, new Violation('The item string could not be parsed.', previous: $exception));
+
+ return Result::failed($violations);
+ }
+ }
+
+ try {
+ $itemValue = $item->value($this->valueConstraint);
+ } catch (Violation $exception) {
+ $itemValue = null;
+ $violations->add(ErrorCode::ItemValueFailedValidation->value, $exception);
+ }
+
+ $validate = $this->parametersConstraint->validate($item->parameters());
+ $violations->addAll($validate->errors);
+ if ($violations->isNotEmpty()) {
+ return Result::failed($violations);
+ }
+
+ /** @var ValidatedParameters $validatedParameters */
+ $validatedParameters = $validate->data;
+
+ return Result::success(new ValidatedItem($itemValue, $validatedParameters));
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/Validation/ParametersValidator.php b/vendor/bakame/http-structured-fields/src/Validation/ParametersValidator.php
new file mode 100644
index 000000000..e851b9686
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Validation/ParametersValidator.php
@@ -0,0 +1,244 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields\Validation;
+
+use Bakame\Http\StructuredFields\Parameters;
+use Bakame\Http\StructuredFields\StructuredFieldProvider;
+use Bakame\Http\StructuredFields\SyntaxError;
+use Stringable;
+
+/**
+ * Structured field Item validator.
+ *
+ * @phpstan-import-type SfType from StructuredFieldProvider
+ *
+ * @phpstan-type SfParameterKeyRule array{validate?:callable(SfType): (bool|string), required?:bool|string, default?:SfType|null}
+ * @phpstan-type SfParameterIndexRule array{validate?:callable(SfType, string): (bool|string), required?:bool|string, default?:array{0:string, 1:SfType}|array{}}
+ */
+final class ParametersValidator
+{
+ public const USE_KEYS = 1;
+ public const USE_INDICES = 2;
+
+ /** @var ?callable(Parameters): (string|bool) */
+ private mixed $criteria;
+ private int $type;
+ /** @var array<string, SfParameterKeyRule>|array<int, SfParameterIndexRule> */
+ private array $filterConstraints;
+
+ /**
+ * @param ?callable(Parameters): (string|bool) $criteria
+ * @param array<string, SfParameterKeyRule>|array<int, SfParameterIndexRule> $filterConstraints
+ */
+ private function __construct(
+ ?callable $criteria = null,
+ int $type = self::USE_KEYS,
+ array $filterConstraints = [],
+ ) {
+ $this->criteria = $criteria;
+ $this->type = $type;
+ $this->filterConstraints = $filterConstraints;
+ }
+
+ public static function new(): self
+ {
+ return new self();
+ }
+
+ /**
+ * Validates the Item parameters as a whole.
+ *
+ * On failure populates the result errors property
+ *
+ * @param ?callable(Parameters): (string|bool) $criteria
+ */
+ public function filterByCriteria(?callable $criteria, int $type = self::USE_KEYS): self
+ {
+ return new self($criteria, [] === $this->filterConstraints ? $type : $this->type, $this->filterConstraints);
+ }
+
+ /**
+ * Validate each parameters value per name.
+ *
+ * On success populate the result item property
+ * On failure populates the result errors property
+ *
+ * @param array<string, SfParameterKeyRule> $constraints
+ */
+ public function filterByKeys(array $constraints): self
+ {
+ return new self($this->criteria, self::USE_KEYS, $constraints);
+ }
+
+ /**
+ * Validate each parameters value per indices.
+ *
+ * On success populate the result item property
+ * On failure populates the result errors property
+ *
+ * @param array<int, SfParameterIndexRule> $constraints
+ */
+ public function filterByIndices(array $constraints): self
+ {
+ return new self($this->criteria, self::USE_INDICES, $constraints);
+ }
+
+ public function __invoke(Parameters|Stringable|string $parameters): bool|string
+ {
+ $result = $this->validate($parameters);
+
+ return $result->isSuccess() ? true : (string) $result->errors;
+ }
+
+ /**
+ * Validates the structured field Item.
+ */
+ public function validate(Parameters|Stringable|string $parameters): Result
+ {
+ $violations = new ViolationList();
+ if (!$parameters instanceof Parameters) {
+ try {
+ $parameters = Parameters::fromHttpValue($parameters);
+ } catch (SyntaxError $exception) {
+ $violations->add(ErrorCode::ParametersFailedParsing->value, new Violation('The parameters string could not be parsed.', previous: $exception));
+
+ return Result::failed($violations);
+ }
+ }
+
+ if ([] === $this->filterConstraints && null === $this->criteria) {
+ $violations->add(ErrorCode::ParametersMissingConstraints->value, new Violation('The parameters constraints are missing.'));
+ }
+
+ $parsedParameters = new ValidatedParameters();
+ if ([] !== $this->filterConstraints) {
+ $parsedParameters = match ($this->type) {
+ self::USE_INDICES => $this->validateByIndices($parameters),
+ default => $this->validateByKeys($parameters),
+ };
+
+ if ($parsedParameters->isFailed()) {
+ $violations->addAll($parsedParameters->errors);
+ } else {
+ $parsedParameters = $parsedParameters->data;
+ }
+ }
+
+ $errorMessage = $this->validateByCriteria($parameters);
+ if (!is_bool($errorMessage)) {
+ $violations->add(ErrorCode::ParametersFailedCriteria->value, new Violation($errorMessage));
+ }
+
+ /** @var ValidatedParameters $parsedParameters */
+ $parsedParameters = $parsedParameters ?? new ValidatedParameters();
+ if ([] === $this->filterConstraints && true === $errorMessage) {
+ $parsedParameters = new ValidatedParameters(match ($this->type) {
+ self::USE_KEYS => $this->toAssociative($parameters),
+ default => $this->toList($parameters),
+ });
+ }
+
+ return match ($violations->isNotEmpty()) {
+ true => Result::failed($violations),
+ default => Result::success($parsedParameters),
+ };
+ }
+
+ private function validateByCriteria(Parameters $parameters): bool|string
+ {
+ if (null === $this->criteria) {
+ return true;
+ }
+
+ $errorMessage = ($this->criteria)($parameters);
+ if (true === $errorMessage) {
+ return true;
+ }
+
+ if (!is_string($errorMessage) || '' === trim($errorMessage)) {
+ $errorMessage = 'The parameters constraints are not met.';
+ }
+
+ return $errorMessage;
+ }
+
+ /**
+ * Validate the current parameter object using its keys and return the parsed values and the errors.
+ *
+ * @return Result<ValidatedParameters>|Result<null>
+ */
+ private function validateByKeys(Parameters $parameters): Result /* @phpstan-ignore-line */
+ {
+ $data = [];
+ $violations = new ViolationList();
+ /**
+ * @var string $key
+ * @var SfParameterKeyRule $rule
+ */
+ foreach ($this->filterConstraints as $key => $rule) {
+ try {
+ $data[$key] = $parameters->valueByKey($key, $rule['validate'] ?? null, $rule['required'] ?? false, $rule['default'] ?? null);
+ } catch (Violation $exception) {
+ $violations[$key] = $exception;
+ }
+ }
+
+ return match ($violations->isNotEmpty()) {
+ true => Result::failed($violations),
+ default => Result::success(new ValidatedParameters($data)),
+ };
+ }
+
+ /**
+ * Validate the current parameter object using its indices and return the parsed values and the errors.
+ */
+ public function validateByIndices(Parameters $parameters): Result
+ {
+ $data = [];
+ $violations = new ViolationList();
+ /**
+ * @var int $index
+ * @var SfParameterIndexRule $rule
+ */
+ foreach ($this->filterConstraints as $index => $rule) {
+ try {
+ $data[$index] = $parameters->valueByIndex($index, $rule['validate'] ?? null, $rule['required'] ?? false, $rule['default'] ?? []);
+ } catch (Violation $exception) {
+ $violations[$index] = $exception;
+ }
+ }
+
+ return match ($violations->isNotEmpty()) {
+ true => Result::failed($violations),
+ default => Result::success(new ValidatedParameters($data)),
+ };
+ }
+
+ /**
+ * @return array<string,SfType>
+ */
+ private function toAssociative(Parameters $parameters): array
+ {
+ $assoc = [];
+ foreach ($parameters as $parameter) {
+ $assoc[$parameter[0]] = $parameter[1]->value();
+ }
+
+ return $assoc;
+ }
+
+ /**
+ * @return array<int, array{0:string, 1:SfType}>
+ */
+ private function toList(Parameters $parameters): array
+ {
+ $list = [];
+ foreach ($parameters as $index => $parameter) {
+ $list[$index] = [$parameter[0], $parameter[1]->value()];
+ }
+
+ return $list;
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/Validation/Result.php b/vendor/bakame/http-structured-fields/src/Validation/Result.php
new file mode 100644
index 000000000..f765efda0
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Validation/Result.php
@@ -0,0 +1,34 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields\Validation;
+
+final class Result
+{
+ private function __construct(
+ public readonly ValidatedParameters|ValidatedItem|null $data,
+ public readonly ViolationList $errors,
+ ) {
+ }
+
+ public function isSuccess(): bool
+ {
+ return $this->errors->isEmpty();
+ }
+
+ public function isFailed(): bool
+ {
+ return $this->errors->isNotEmpty();
+ }
+
+ public static function success(ValidatedItem|ValidatedParameters $data): self
+ {
+ return new self($data, new ViolationList());
+ }
+
+ public static function failed(ViolationList $errors): self
+ {
+ return new self(null, $errors);
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/Validation/ValidatedItem.php b/vendor/bakame/http-structured-fields/src/Validation/ValidatedItem.php
new file mode 100644
index 000000000..df61a1aa8
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Validation/ValidatedItem.php
@@ -0,0 +1,19 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields\Validation;
+
+use Bakame\Http\StructuredFields\Bytes;
+use Bakame\Http\StructuredFields\DisplayString;
+use Bakame\Http\StructuredFields\Token;
+use DateTimeImmutable;
+
+final class ValidatedItem
+{
+ public function __construct(
+ public readonly Bytes|Token|DisplayString|DateTimeImmutable|string|int|float|bool|null $value,
+ public readonly ValidatedParameters $parameters = new ValidatedParameters(),
+ ) {
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/Validation/ValidatedParameters.php b/vendor/bakame/http-structured-fields/src/Validation/ValidatedParameters.php
new file mode 100644
index 000000000..c7f5b5eb6
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Validation/ValidatedParameters.php
@@ -0,0 +1,68 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields\Validation;
+
+use ArrayAccess;
+use Bakame\Http\StructuredFields\ForbiddenOperation;
+use Bakame\Http\StructuredFields\InvalidOffset;
+use Bakame\Http\StructuredFields\StructuredFieldProvider;
+use Countable;
+use Iterator;
+use IteratorAggregate;
+
+/**
+ * @phpstan-import-type SfType from StructuredFieldProvider
+ *
+ * @implements ArrayAccess<array-key, array{0:string, 1:SfType}|array{}|SfType|null>
+ * @implements IteratorAggregate<array-key, array{0:string, 1:SfType}|array{}|SfType|null>
+ */
+final class ValidatedParameters implements ArrayAccess, Countable, IteratorAggregate
+{
+ /**
+ * @param array<array-key, array{0:string, 1:SfType}|array{}|SfType|null> $values
+ */
+ public function __construct(
+ private readonly array $values = [],
+ ) {
+ }
+
+ public function count(): int
+ {
+ return count($this->values);
+ }
+
+ public function getIterator(): Iterator
+ {
+ yield from $this->values;
+ }
+
+ public function offsetExists($offset): bool
+ {
+ return array_key_exists($offset, $this->values);
+ }
+
+ public function offsetGet($offset): mixed
+ {
+ return $this->offsetExists($offset) ? $this->values[$offset] : throw InvalidOffset::dueToMemberNotFound($offset);
+ }
+
+ public function offsetUnset(mixed $offset): void
+ {
+ throw new ForbiddenOperation(self::class.' instance can not be updated using '.ArrayAccess::class.' methods.');
+ }
+
+ public function offsetSet(mixed $offset, mixed $value): void
+ {
+ throw new ForbiddenOperation(self::class.' instance can not be updated using '.ArrayAccess::class.' methods.');
+ }
+
+ /**
+ * @return array<array-key, array{0:string, 1:SfType}|array{}|SfType|null>
+ */
+ public function all(): array
+ {
+ return $this->values;
+ }
+}
diff --git a/vendor/bakame/http-structured-fields/src/Validation/Violation.php b/vendor/bakame/http-structured-fields/src/Validation/Violation.php
new file mode 100644
index 000000000..ec666de98
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Validation/Violation.php
@@ -0,0 +1,12 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields\Validation;
+
+use Bakame\Http\StructuredFields\StructuredFieldError;
+use LogicException;
+
+final class Violation extends LogicException implements StructuredFieldError
+{
+}
diff --git a/vendor/bakame/http-structured-fields/src/Validation/ViolationList.php b/vendor/bakame/http-structured-fields/src/Validation/ViolationList.php
new file mode 100644
index 000000000..8cbf5e741
--- /dev/null
+++ b/vendor/bakame/http-structured-fields/src/Validation/ViolationList.php
@@ -0,0 +1,169 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Bakame\Http\StructuredFields\Validation;
+
+use ArrayAccess;
+use Bakame\Http\StructuredFields\InvalidOffset;
+use Countable;
+use Iterator;
+use IteratorAggregate;
+use Stringable;
+use TypeError;
+
+use function array_filter;
+use function array_map;
+use function count;
+use function implode;
+use function is_int;
+
+use const ARRAY_FILTER_USE_BOTH;
+
+/**
+ * @implements IteratorAggregate<array-key,Violation>
+ * @implements ArrayAccess<array-key,Violation>
+ */
+final class ViolationList implements IteratorAggregate, Countable, ArrayAccess, Stringable
+{
+ /** @var array<Violation> */
+ private array $errors = [];
+
+ /**
+ * @param iterable<array-key, Violation> $errors
+ */
+ public function __construct(iterable $errors = [])
+ {
+ $this->addAll($errors);
+ }
+
+ public function count(): int
+ {
+ return count($this->errors);
+ }
+
+ public function getIterator(): Iterator
+ {
+ yield from $this->errors;
+ }
+
+ public function __toString(): string
+ {
+ return implode(PHP_EOL, array_map(fn (Violation $e): string => $e->getMessage(), $this->errors));
+ }
+
+ /**
+ * @return array<array-key, string>
+ */
+ public function summary(): array
+ {
+ return array_map(fn (Violation $e): string => $e->getMessage(), $this->errors);
+ }
+
+ public function isEmpty(): bool
+ {
+ return [] === $this->errors;
+ }
+
+ public function isNotEmpty(): bool
+ {
+ return ! $this->isEmpty();
+ }
+
+ /**
+ * @param string|int $offset
+ */
+ public function offsetExists(mixed $offset): bool
+ {
+ return $this->has($offset);
+ }
+
+ /**
+ * @param string|int $offset
+ *
+ * @return Violation
+ */
+ public function offsetGet(mixed $offset): mixed
+ {
+ return $this->get($offset);
+ }
+
+ /**
+ * @param string|int $offset
+ */
+ public function offsetUnset(mixed $offset): void
+ {
+ unset($this->errors[$offset]);
+ }
+
+ /**
+ * @param string|int|null $offset
+ * @param Violation $value
+ */
+ public function offsetSet(mixed $offset, mixed $value): void
+ {
+ if (null === $offset) {
+ throw new TypeError('null can not be used as a valid offset value.');
+ }
+ $this->add($offset, $value);
+ }
+
+ public function has(string|int $offset): bool
+ {
+ if (is_int($offset)) {
+ return null !== $this->filterIndex($offset);
+ }
+
+ return array_key_exists($offset, $this->errors);
+ }
+
+ public function get(string|int $offset): Violation
+ {
+ return $this->errors[$this->filterIndex($offset) ?? throw InvalidOffset::dueToIndexNotFound($offset)];
+ }
+
+ public function add(string|int $offset, Violation $error): void
+ {
+ $this->errors[$offset] = $error;
+ }
+
+ /**
+ * @param iterable<array-key, Violation> $errors
+ */
+ public function addAll(iterable $errors): void
+ {
+ foreach ($errors as $offset => $error) {
+ $this->add($offset, $error);
+ }
+ }
+
+ private function filterIndex(string|int $index, int|null $max = null): string|int|null
+ {
+ if (!is_int($index)) {
+ return $index;
+ }
+
+ $max ??= count($this->errors);
+
+ return match (true) {
+ [] === $this->errors,
+ 0 > $max + $index,
+ 0 > $max - $index - 1 => null,
+ 0 > $index => $max + $index,
+ default => $index,
+ };
+ }
+
+ /**
+ * @param callable(Violation, array-key): bool $callback
+ */
+ public function filter(callable $callback): self
+ {
+ return new self(array_filter($this->errors, $callback, ARRAY_FILTER_USE_BOTH));
+ }
+
+ public function toException(): Violation
+ {
+ return new Violation((string) $this);
+ }
+}
diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php
index c00fec51e..ec901b895 100644
--- a/vendor/composer/autoload_classmap.php
+++ b/vendor/composer/autoload_classmap.php
@@ -6,6 +6,36 @@ $vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
+ 'Bakame\\Http\\StructuredFields\\Bytes' => $vendorDir . '/bakame/http-structured-fields/src/Bytes.php',
+ 'Bakame\\Http\\StructuredFields\\DataType' => $vendorDir . '/bakame/http-structured-fields/src/DataType.php',
+ 'Bakame\\Http\\StructuredFields\\Dictionary' => $vendorDir . '/bakame/http-structured-fields/src/Dictionary.php',
+ 'Bakame\\Http\\StructuredFields\\DisplayString' => $vendorDir . '/bakame/http-structured-fields/src/DisplayString.php',
+ 'Bakame\\Http\\StructuredFields\\ForbiddenOperation' => $vendorDir . '/bakame/http-structured-fields/src/ForbiddenOperation.php',
+ 'Bakame\\Http\\StructuredFields\\Ietf' => $vendorDir . '/bakame/http-structured-fields/src/Ietf.php',
+ 'Bakame\\Http\\StructuredFields\\InnerList' => $vendorDir . '/bakame/http-structured-fields/src/InnerList.php',
+ 'Bakame\\Http\\StructuredFields\\InvalidArgument' => $vendorDir . '/bakame/http-structured-fields/src/InvalidArgument.php',
+ 'Bakame\\Http\\StructuredFields\\InvalidOffset' => $vendorDir . '/bakame/http-structured-fields/src/InvalidOffset.php',
+ 'Bakame\\Http\\StructuredFields\\Item' => $vendorDir . '/bakame/http-structured-fields/src/Item.php',
+ 'Bakame\\Http\\StructuredFields\\Key' => $vendorDir . '/bakame/http-structured-fields/src/Key.php',
+ 'Bakame\\Http\\StructuredFields\\Member' => $vendorDir . '/bakame/http-structured-fields/src/Member.php',
+ 'Bakame\\Http\\StructuredFields\\MissingFeature' => $vendorDir . '/bakame/http-structured-fields/src/MissingFeature.php',
+ 'Bakame\\Http\\StructuredFields\\OuterList' => $vendorDir . '/bakame/http-structured-fields/src/OuterList.php',
+ 'Bakame\\Http\\StructuredFields\\ParameterAccess' => $vendorDir . '/bakame/http-structured-fields/src/ParameterAccess.php',
+ 'Bakame\\Http\\StructuredFields\\Parameters' => $vendorDir . '/bakame/http-structured-fields/src/Parameters.php',
+ 'Bakame\\Http\\StructuredFields\\Parser' => $vendorDir . '/bakame/http-structured-fields/src/Parser.php',
+ 'Bakame\\Http\\StructuredFields\\StructuredFieldError' => $vendorDir . '/bakame/http-structured-fields/src/StructuredFieldError.php',
+ 'Bakame\\Http\\StructuredFields\\StructuredFieldProvider' => $vendorDir . '/bakame/http-structured-fields/src/StructuredFieldProvider.php',
+ 'Bakame\\Http\\StructuredFields\\SyntaxError' => $vendorDir . '/bakame/http-structured-fields/src/SyntaxError.php',
+ 'Bakame\\Http\\StructuredFields\\Token' => $vendorDir . '/bakame/http-structured-fields/src/Token.php',
+ 'Bakame\\Http\\StructuredFields\\Type' => $vendorDir . '/bakame/http-structured-fields/src/Type.php',
+ 'Bakame\\Http\\StructuredFields\\Validation\\ErrorCode' => $vendorDir . '/bakame/http-structured-fields/src/Validation/ErrorCode.php',
+ 'Bakame\\Http\\StructuredFields\\Validation\\ItemValidator' => $vendorDir . '/bakame/http-structured-fields/src/Validation/ItemValidator.php',
+ 'Bakame\\Http\\StructuredFields\\Validation\\ParametersValidator' => $vendorDir . '/bakame/http-structured-fields/src/Validation/ParametersValidator.php',
+ 'Bakame\\Http\\StructuredFields\\Validation\\Result' => $vendorDir . '/bakame/http-structured-fields/src/Validation/Result.php',
+ 'Bakame\\Http\\StructuredFields\\Validation\\ValidatedItem' => $vendorDir . '/bakame/http-structured-fields/src/Validation/ValidatedItem.php',
+ 'Bakame\\Http\\StructuredFields\\Validation\\ValidatedParameters' => $vendorDir . '/bakame/http-structured-fields/src/Validation/ValidatedParameters.php',
+ 'Bakame\\Http\\StructuredFields\\Validation\\Violation' => $vendorDir . '/bakame/http-structured-fields/src/Validation/Violation.php',
+ 'Bakame\\Http\\StructuredFields\\Validation\\ViolationList' => $vendorDir . '/bakame/http-structured-fields/src/Validation/ViolationList.php',
'Brick\\Math\\BigDecimal' => $vendorDir . '/brick/math/src/BigDecimal.php',
'Brick\\Math\\BigInteger' => $vendorDir . '/brick/math/src/BigInteger.php',
'Brick\\Math\\BigNumber' => $vendorDir . '/brick/math/src/BigNumber.php',
@@ -44,6 +74,37 @@ return array(
'CommerceGuys\\Intl\\NumberFormat\\NumberFormatRepository' => $vendorDir . '/commerceguys/intl/src/NumberFormat/NumberFormatRepository.php',
'CommerceGuys\\Intl\\NumberFormat\\NumberFormatRepositoryInterface' => $vendorDir . '/commerceguys/intl/src/NumberFormat/NumberFormatRepositoryInterface.php',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
+ 'GuzzleHttp\\Psr7\\AppendStream' => $vendorDir . '/guzzlehttp/psr7/src/AppendStream.php',
+ 'GuzzleHttp\\Psr7\\BufferStream' => $vendorDir . '/guzzlehttp/psr7/src/BufferStream.php',
+ 'GuzzleHttp\\Psr7\\CachingStream' => $vendorDir . '/guzzlehttp/psr7/src/CachingStream.php',
+ 'GuzzleHttp\\Psr7\\DroppingStream' => $vendorDir . '/guzzlehttp/psr7/src/DroppingStream.php',
+ 'GuzzleHttp\\Psr7\\Exception\\MalformedUriException' => $vendorDir . '/guzzlehttp/psr7/src/Exception/MalformedUriException.php',
+ 'GuzzleHttp\\Psr7\\FnStream' => $vendorDir . '/guzzlehttp/psr7/src/FnStream.php',
+ 'GuzzleHttp\\Psr7\\Header' => $vendorDir . '/guzzlehttp/psr7/src/Header.php',
+ 'GuzzleHttp\\Psr7\\HttpFactory' => $vendorDir . '/guzzlehttp/psr7/src/HttpFactory.php',
+ 'GuzzleHttp\\Psr7\\InflateStream' => $vendorDir . '/guzzlehttp/psr7/src/InflateStream.php',
+ 'GuzzleHttp\\Psr7\\LazyOpenStream' => $vendorDir . '/guzzlehttp/psr7/src/LazyOpenStream.php',
+ 'GuzzleHttp\\Psr7\\LimitStream' => $vendorDir . '/guzzlehttp/psr7/src/LimitStream.php',
+ 'GuzzleHttp\\Psr7\\Message' => $vendorDir . '/guzzlehttp/psr7/src/Message.php',
+ 'GuzzleHttp\\Psr7\\MessageTrait' => $vendorDir . '/guzzlehttp/psr7/src/MessageTrait.php',
+ 'GuzzleHttp\\Psr7\\MimeType' => $vendorDir . '/guzzlehttp/psr7/src/MimeType.php',
+ 'GuzzleHttp\\Psr7\\MultipartStream' => $vendorDir . '/guzzlehttp/psr7/src/MultipartStream.php',
+ 'GuzzleHttp\\Psr7\\NoSeekStream' => $vendorDir . '/guzzlehttp/psr7/src/NoSeekStream.php',
+ 'GuzzleHttp\\Psr7\\PumpStream' => $vendorDir . '/guzzlehttp/psr7/src/PumpStream.php',
+ 'GuzzleHttp\\Psr7\\Query' => $vendorDir . '/guzzlehttp/psr7/src/Query.php',
+ 'GuzzleHttp\\Psr7\\Request' => $vendorDir . '/guzzlehttp/psr7/src/Request.php',
+ 'GuzzleHttp\\Psr7\\Response' => $vendorDir . '/guzzlehttp/psr7/src/Response.php',
+ 'GuzzleHttp\\Psr7\\Rfc7230' => $vendorDir . '/guzzlehttp/psr7/src/Rfc7230.php',
+ 'GuzzleHttp\\Psr7\\ServerRequest' => $vendorDir . '/guzzlehttp/psr7/src/ServerRequest.php',
+ 'GuzzleHttp\\Psr7\\Stream' => $vendorDir . '/guzzlehttp/psr7/src/Stream.php',
+ 'GuzzleHttp\\Psr7\\StreamDecoratorTrait' => $vendorDir . '/guzzlehttp/psr7/src/StreamDecoratorTrait.php',
+ 'GuzzleHttp\\Psr7\\StreamWrapper' => $vendorDir . '/guzzlehttp/psr7/src/StreamWrapper.php',
+ 'GuzzleHttp\\Psr7\\UploadedFile' => $vendorDir . '/guzzlehttp/psr7/src/UploadedFile.php',
+ 'GuzzleHttp\\Psr7\\Uri' => $vendorDir . '/guzzlehttp/psr7/src/Uri.php',
+ 'GuzzleHttp\\Psr7\\UriComparator' => $vendorDir . '/guzzlehttp/psr7/src/UriComparator.php',
+ 'GuzzleHttp\\Psr7\\UriNormalizer' => $vendorDir . '/guzzlehttp/psr7/src/UriNormalizer.php',
+ 'GuzzleHttp\\Psr7\\UriResolver' => $vendorDir . '/guzzlehttp/psr7/src/UriResolver.php',
+ 'GuzzleHttp\\Psr7\\Utils' => $vendorDir . '/guzzlehttp/psr7/src/Utils.php',
'HTMLPurifier' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.php',
'HTMLPurifier_Arborize' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Arborize.php',
'HTMLPurifier_AttrCollections' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrCollections.php',
@@ -276,6 +337,9 @@ return array(
'HTMLPurifier_VarParser_Flexible' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php',
'HTMLPurifier_VarParser_Native' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php',
'HTMLPurifier_Zipper' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier/Zipper.php',
+ 'HttpSignature\\HttpMessageSigner' => $vendorDir . '/macgirvin/http-message-signer/src/HttpMessageSigner.php',
+ 'HttpSignature\\StructuredFieldTypes' => $vendorDir . '/macgirvin/http-message-signer/src/StructuredFieldTypes.php',
+ 'HttpSignature\\UnProcessableSignatureException' => $vendorDir . '/macgirvin/http-message-signer/src/UnProcessableSignatureException.php',
'ID3Parser\\ID3Parser' => $vendorDir . '/lukasreschke/id3parser/src/ID3Parser.php',
'ID3Parser\\getID3\\Tags\\getid3_id3v1' => $vendorDir . '/lukasreschke/id3parser/src/getID3/Tags/getid3_id3v1.php',
'ID3Parser\\getID3\\Tags\\getid3_id3v2' => $vendorDir . '/lukasreschke/id3parser/src/getID3/Tags/getid3_id3v2.php',
@@ -1619,6 +1683,7 @@ return array(
'Zotlabs\\Lib\\Img_filesize' => $baseDir . '/Zotlabs/Lib/Img_filesize.php',
'Zotlabs\\Lib\\JSalmon' => $baseDir . '/Zotlabs/Lib/JSalmon.php',
'Zotlabs\\Lib\\JcsEddsa2022' => $baseDir . '/Zotlabs/Lib/JcsEddsa2022.php',
+ 'Zotlabs\\Lib\\JcsEddsa2022SignException' => $baseDir . '/Zotlabs/Lib/JcsEddsa2022SignException.php',
'Zotlabs\\Lib\\Keyutils' => $baseDir . '/Zotlabs/Lib/Keyutils.php',
'Zotlabs\\Lib\\LDSignatures' => $baseDir . '/Zotlabs/Lib/LDSignatures.php',
'Zotlabs\\Lib\\Libsync' => $baseDir . '/Zotlabs/Lib/Libsync.php',
@@ -1682,14 +1747,12 @@ return array(
'Zotlabs\\Module\\Branchtopic' => $baseDir . '/Zotlabs/Module/Branchtopic.php',
'Zotlabs\\Module\\Cal' => $baseDir . '/Zotlabs/Module/Cal.php',
'Zotlabs\\Module\\Cdav' => $baseDir . '/Zotlabs/Module/Cdav.php',
- 'Zotlabs\\Module\\Ceditor' => $baseDir . '/Zotlabs/Module/Ceditor.php',
'Zotlabs\\Module\\Changeaddr' => $baseDir . '/Zotlabs/Module/Changeaddr.php',
'Zotlabs\\Module\\Channel' => $baseDir . '/Zotlabs/Module/Channel.php',
'Zotlabs\\Module\\Channel_calendar' => $baseDir . '/Zotlabs/Module/Channel_calendar.php',
'Zotlabs\\Module\\Chanview' => $baseDir . '/Zotlabs/Module/Chanview.php',
'Zotlabs\\Module\\Chat' => $baseDir . '/Zotlabs/Module/Chat.php',
'Zotlabs\\Module\\Chatsvc' => $baseDir . '/Zotlabs/Module/Chatsvc.php',
- 'Zotlabs\\Module\\Cleditor' => $baseDir . '/Zotlabs/Module/Cleditor.php',
'Zotlabs\\Module\\Cloud' => $baseDir . '/Zotlabs/Module/Cloud.php',
'Zotlabs\\Module\\Cloud_tiles' => $baseDir . '/Zotlabs/Module/Cloud_tiles.php',
'Zotlabs\\Module\\Common' => $baseDir . '/Zotlabs/Module/Common.php',
@@ -1802,6 +1865,7 @@ return array(
'Zotlabs\\Module\\Regver' => $baseDir . '/Zotlabs/Module/Regver.php',
'Zotlabs\\Module\\Removeaccount' => $baseDir . '/Zotlabs/Module/Removeaccount.php',
'Zotlabs\\Module\\Removeme' => $baseDir . '/Zotlabs/Module/Removeme.php',
+ 'Zotlabs\\Module\\Request' => $baseDir . '/Zotlabs/Module/Request.php',
'Zotlabs\\Module\\Rmagic' => $baseDir . '/Zotlabs/Module/Rmagic.php',
'Zotlabs\\Module\\Rpost' => $baseDir . '/Zotlabs/Module/Rpost.php',
'Zotlabs\\Module\\Search' => $baseDir . '/Zotlabs/Module/Search.php',
@@ -1864,7 +1928,6 @@ return array(
'Zotlabs\\Module\\Xchan' => $baseDir . '/Zotlabs/Module/Xchan.php',
'Zotlabs\\Module\\Xpoco' => $baseDir . '/Zotlabs/Module/Xpoco.php',
'Zotlabs\\Module\\Xrd' => $baseDir . '/Zotlabs/Module/Xrd.php',
- 'Zotlabs\\Module\\Xref' => $baseDir . '/Zotlabs/Module/Xref.php',
'Zotlabs\\Module\\Z6trans' => $baseDir . '/Zotlabs/Module/Z6trans.php',
'Zotlabs\\Module\\Zot' => $baseDir . '/Zotlabs/Module/Zot.php',
'Zotlabs\\Module\\Zot_probe' => $baseDir . '/Zotlabs/Module/Zot_probe.php',
@@ -2291,30 +2354,363 @@ return array(
'chillerlan\\QRCode\\QROptionsTrait' => $vendorDir . '/chillerlan/php-qrcode/src/QROptionsTrait.php',
'chillerlan\\Settings\\SettingsContainerAbstract' => $vendorDir . '/chillerlan/php-settings-container/src/SettingsContainerAbstract.php',
'chillerlan\\Settings\\SettingsContainerInterface' => $vendorDir . '/chillerlan/php-settings-container/src/SettingsContainerInterface.php',
- 'phpseclib\\Crypt\\AES' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/AES.php',
- 'phpseclib\\Crypt\\Base' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Base.php',
- 'phpseclib\\Crypt\\Blowfish' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Blowfish.php',
- 'phpseclib\\Crypt\\DES' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/DES.php',
- 'phpseclib\\Crypt\\Hash' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Hash.php',
- 'phpseclib\\Crypt\\RC2' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/RC2.php',
- 'phpseclib\\Crypt\\RC4' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/RC4.php',
- 'phpseclib\\Crypt\\RSA' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/RSA.php',
- 'phpseclib\\Crypt\\Random' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Random.php',
- 'phpseclib\\Crypt\\Rijndael' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Rijndael.php',
- 'phpseclib\\Crypt\\TripleDES' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/TripleDES.php',
- 'phpseclib\\Crypt\\Twofish' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Twofish.php',
- 'phpseclib\\File\\ANSI' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ANSI.php',
- 'phpseclib\\File\\ASN1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1.php',
- 'phpseclib\\File\\ASN1\\Element' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Element.php',
- 'phpseclib\\File\\X509' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/X509.php',
- 'phpseclib\\Math\\BigInteger' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger.php',
- 'phpseclib\\Net\\SCP' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Net/SCP.php',
- 'phpseclib\\Net\\SFTP' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Net/SFTP.php',
- 'phpseclib\\Net\\SFTP\\Stream' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Net/SFTP/Stream.php',
- 'phpseclib\\Net\\SSH1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Net/SSH1.php',
- 'phpseclib\\Net\\SSH2' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Net/SSH2.php',
- 'phpseclib\\System\\SSH\\Agent' => $vendorDir . '/phpseclib/phpseclib/phpseclib/System/SSH/Agent.php',
- 'phpseclib\\System\\SSH\\Agent\\Identity' => $vendorDir . '/phpseclib/phpseclib/phpseclib/System/SSH/Agent/Identity.php',
+ 'phpseclib3\\Common\\Functions\\Strings' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Common/Functions/Strings.php',
+ 'phpseclib3\\Crypt\\AES' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/AES.php',
+ 'phpseclib3\\Crypt\\Blowfish' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Blowfish.php',
+ 'phpseclib3\\Crypt\\ChaCha20' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/ChaCha20.php',
+ 'phpseclib3\\Crypt\\Common\\AsymmetricKey' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Common/AsymmetricKey.php',
+ 'phpseclib3\\Crypt\\Common\\BlockCipher' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Common/BlockCipher.php',
+ 'phpseclib3\\Crypt\\Common\\Formats\\Keys\\JWK' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/JWK.php',
+ 'phpseclib3\\Crypt\\Common\\Formats\\Keys\\OpenSSH' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/OpenSSH.php',
+ 'phpseclib3\\Crypt\\Common\\Formats\\Keys\\PKCS' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS.php',
+ 'phpseclib3\\Crypt\\Common\\Formats\\Keys\\PKCS1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS1.php',
+ 'phpseclib3\\Crypt\\Common\\Formats\\Keys\\PKCS8' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS8.php',
+ 'phpseclib3\\Crypt\\Common\\Formats\\Keys\\PuTTY' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PuTTY.php',
+ 'phpseclib3\\Crypt\\Common\\Formats\\Signature\\Raw' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Signature/Raw.php',
+ 'phpseclib3\\Crypt\\Common\\PrivateKey' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Common/PrivateKey.php',
+ 'phpseclib3\\Crypt\\Common\\PublicKey' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Common/PublicKey.php',
+ 'phpseclib3\\Crypt\\Common\\StreamCipher' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Common/StreamCipher.php',
+ 'phpseclib3\\Crypt\\Common\\SymmetricKey' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Common/SymmetricKey.php',
+ 'phpseclib3\\Crypt\\Common\\Traits\\Fingerprint' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Common/Traits/Fingerprint.php',
+ 'phpseclib3\\Crypt\\Common\\Traits\\PasswordProtected' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Common/Traits/PasswordProtected.php',
+ 'phpseclib3\\Crypt\\DES' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/DES.php',
+ 'phpseclib3\\Crypt\\DH' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/DH.php',
+ 'phpseclib3\\Crypt\\DH\\Formats\\Keys\\PKCS1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/DH/Formats/Keys/PKCS1.php',
+ 'phpseclib3\\Crypt\\DH\\Formats\\Keys\\PKCS8' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/DH/Formats/Keys/PKCS8.php',
+ 'phpseclib3\\Crypt\\DH\\Parameters' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/DH/Parameters.php',
+ 'phpseclib3\\Crypt\\DH\\PrivateKey' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/DH/PrivateKey.php',
+ 'phpseclib3\\Crypt\\DH\\PublicKey' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/DH/PublicKey.php',
+ 'phpseclib3\\Crypt\\DSA' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/DSA.php',
+ 'phpseclib3\\Crypt\\DSA\\Formats\\Keys\\OpenSSH' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/OpenSSH.php',
+ 'phpseclib3\\Crypt\\DSA\\Formats\\Keys\\PKCS1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PKCS1.php',
+ 'phpseclib3\\Crypt\\DSA\\Formats\\Keys\\PKCS8' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PKCS8.php',
+ 'phpseclib3\\Crypt\\DSA\\Formats\\Keys\\PuTTY' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PuTTY.php',
+ 'phpseclib3\\Crypt\\DSA\\Formats\\Keys\\Raw' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/Raw.php',
+ 'phpseclib3\\Crypt\\DSA\\Formats\\Keys\\XML' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/XML.php',
+ 'phpseclib3\\Crypt\\DSA\\Formats\\Signature\\ASN1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/ASN1.php',
+ 'phpseclib3\\Crypt\\DSA\\Formats\\Signature\\Raw' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/Raw.php',
+ 'phpseclib3\\Crypt\\DSA\\Formats\\Signature\\SSH2' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/SSH2.php',
+ 'phpseclib3\\Crypt\\DSA\\Parameters' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/DSA/Parameters.php',
+ 'phpseclib3\\Crypt\\DSA\\PrivateKey' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/DSA/PrivateKey.php',
+ 'phpseclib3\\Crypt\\DSA\\PublicKey' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/DSA/PublicKey.php',
+ 'phpseclib3\\Crypt\\EC' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC.php',
+ 'phpseclib3\\Crypt\\EC\\BaseCurves\\Base' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Base.php',
+ 'phpseclib3\\Crypt\\EC\\BaseCurves\\Binary' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Binary.php',
+ 'phpseclib3\\Crypt\\EC\\BaseCurves\\KoblitzPrime' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/KoblitzPrime.php',
+ 'phpseclib3\\Crypt\\EC\\BaseCurves\\Montgomery' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Montgomery.php',
+ 'phpseclib3\\Crypt\\EC\\BaseCurves\\Prime' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Prime.php',
+ 'phpseclib3\\Crypt\\EC\\BaseCurves\\TwistedEdwards' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/TwistedEdwards.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\Curve25519' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Curve25519.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\Curve448' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Curve448.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\Ed25519' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Ed25519.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\Ed448' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Ed448.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP160r1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP160r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP160t1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP160t1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP192r1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP192r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP192t1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP192t1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP224r1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP224r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP224t1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP224t1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP256r1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP256r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP256t1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP256t1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP320r1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP320r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP320t1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP320t1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP384r1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP384r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP384t1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP384t1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP512r1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP512r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP512t1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP512t1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\nistb233' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistb233.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\nistb409' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistb409.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\nistk163' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk163.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\nistk233' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk233.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\nistk283' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk283.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\nistk409' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk409.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\nistp192' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp192.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\nistp224' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp224.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\nistp256' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp256.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\nistp384' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp384.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\nistp521' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp521.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\nistt571' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistt571.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\prime192v1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\prime192v2' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v2.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\prime192v3' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v3.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\prime239v1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\prime239v2' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v2.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\prime239v3' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v3.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\prime256v1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime256v1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp112r1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp112r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp112r2' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp112r2.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp128r1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp128r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp128r2' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp128r2.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp160k1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160k1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp160r1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp160r2' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160r2.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp192k1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp192k1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp192r1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp192r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp224k1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp224k1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp224r1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp224r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp256k1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp256k1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp256r1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp256r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp384r1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp384r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp521r1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp521r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect113r1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect113r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect113r2' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect113r2.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect131r1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect131r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect131r2' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect131r2.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect163k1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163k1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect163r1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect163r2' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163r2.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect193r1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect193r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect193r2' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect193r2.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect233k1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect233k1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect233r1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect233r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect239k1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect239k1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect283k1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect283k1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect283r1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect283r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect409k1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect409k1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect409r1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect409r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect571k1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect571k1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect571r1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect571r1.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Keys\\Common' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/Common.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Keys\\JWK' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/JWK.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Keys\\MontgomeryPrivate' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/MontgomeryPrivate.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Keys\\MontgomeryPublic' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/MontgomeryPublic.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Keys\\OpenSSH' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/OpenSSH.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Keys\\PKCS1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PKCS1.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Keys\\PKCS8' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PKCS8.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Keys\\PuTTY' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PuTTY.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Keys\\XML' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/XML.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Keys\\libsodium' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/libsodium.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Signature\\ASN1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/ASN1.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Signature\\IEEE' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/IEEE.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Signature\\Raw' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/Raw.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Signature\\SSH2' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/SSH2.php',
+ 'phpseclib3\\Crypt\\EC\\Parameters' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Parameters.php',
+ 'phpseclib3\\Crypt\\EC\\PrivateKey' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/PrivateKey.php',
+ 'phpseclib3\\Crypt\\EC\\PublicKey' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/EC/PublicKey.php',
+ 'phpseclib3\\Crypt\\Hash' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Hash.php',
+ 'phpseclib3\\Crypt\\PublicKeyLoader' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/PublicKeyLoader.php',
+ 'phpseclib3\\Crypt\\RC2' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/RC2.php',
+ 'phpseclib3\\Crypt\\RC4' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/RC4.php',
+ 'phpseclib3\\Crypt\\RSA' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/RSA.php',
+ 'phpseclib3\\Crypt\\RSA\\Formats\\Keys\\JWK' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/JWK.php',
+ 'phpseclib3\\Crypt\\RSA\\Formats\\Keys\\MSBLOB' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/MSBLOB.php',
+ 'phpseclib3\\Crypt\\RSA\\Formats\\Keys\\OpenSSH' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/OpenSSH.php',
+ 'phpseclib3\\Crypt\\RSA\\Formats\\Keys\\PKCS1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PKCS1.php',
+ 'phpseclib3\\Crypt\\RSA\\Formats\\Keys\\PKCS8' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PKCS8.php',
+ 'phpseclib3\\Crypt\\RSA\\Formats\\Keys\\PSS' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PSS.php',
+ 'phpseclib3\\Crypt\\RSA\\Formats\\Keys\\PuTTY' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PuTTY.php',
+ 'phpseclib3\\Crypt\\RSA\\Formats\\Keys\\Raw' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/Raw.php',
+ 'phpseclib3\\Crypt\\RSA\\Formats\\Keys\\XML' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/XML.php',
+ 'phpseclib3\\Crypt\\RSA\\PrivateKey' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/RSA/PrivateKey.php',
+ 'phpseclib3\\Crypt\\RSA\\PublicKey' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/RSA/PublicKey.php',
+ 'phpseclib3\\Crypt\\Random' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Random.php',
+ 'phpseclib3\\Crypt\\Rijndael' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Rijndael.php',
+ 'phpseclib3\\Crypt\\Salsa20' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Salsa20.php',
+ 'phpseclib3\\Crypt\\TripleDES' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/TripleDES.php',
+ 'phpseclib3\\Crypt\\Twofish' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Crypt/Twofish.php',
+ 'phpseclib3\\Exception\\BadConfigurationException' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Exception/BadConfigurationException.php',
+ 'phpseclib3\\Exception\\BadDecryptionException' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Exception/BadDecryptionException.php',
+ 'phpseclib3\\Exception\\BadModeException' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Exception/BadModeException.php',
+ 'phpseclib3\\Exception\\ConnectionClosedException' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Exception/ConnectionClosedException.php',
+ 'phpseclib3\\Exception\\FileNotFoundException' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Exception/FileNotFoundException.php',
+ 'phpseclib3\\Exception\\InconsistentSetupException' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Exception/InconsistentSetupException.php',
+ 'phpseclib3\\Exception\\InsufficientSetupException' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Exception/InsufficientSetupException.php',
+ 'phpseclib3\\Exception\\InvalidPacketLengthException' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Exception/InvalidPacketLengthException.php',
+ 'phpseclib3\\Exception\\NoKeyLoadedException' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Exception/NoKeyLoadedException.php',
+ 'phpseclib3\\Exception\\NoSupportedAlgorithmsException' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Exception/NoSupportedAlgorithmsException.php',
+ 'phpseclib3\\Exception\\TimeoutException' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Exception/TimeoutException.php',
+ 'phpseclib3\\Exception\\UnableToConnectException' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Exception/UnableToConnectException.php',
+ 'phpseclib3\\Exception\\UnsupportedAlgorithmException' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Exception/UnsupportedAlgorithmException.php',
+ 'phpseclib3\\Exception\\UnsupportedCurveException' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Exception/UnsupportedCurveException.php',
+ 'phpseclib3\\Exception\\UnsupportedFormatException' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Exception/UnsupportedFormatException.php',
+ 'phpseclib3\\Exception\\UnsupportedOperationException' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Exception/UnsupportedOperationException.php',
+ 'phpseclib3\\File\\ANSI' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ANSI.php',
+ 'phpseclib3\\File\\ASN1' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1.php',
+ 'phpseclib3\\File\\ASN1\\Element' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Element.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\AccessDescription' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AccessDescription.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\AdministrationDomainName' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AdministrationDomainName.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\AlgorithmIdentifier' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AlgorithmIdentifier.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\AnotherName' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AnotherName.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Attribute' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Attribute.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\AttributeType' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeType.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\AttributeTypeAndValue' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeTypeAndValue.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\AttributeValue' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeValue.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Attributes' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Attributes.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\AuthorityInfoAccessSyntax' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AuthorityInfoAccessSyntax.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\AuthorityKeyIdentifier' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AuthorityKeyIdentifier.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\BaseDistance' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BaseDistance.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\BasicConstraints' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BasicConstraints.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\BuiltInDomainDefinedAttribute' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttribute.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\BuiltInDomainDefinedAttributes' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttributes.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\BuiltInStandardAttributes' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInStandardAttributes.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\CPSuri' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CPSuri.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\CRLDistributionPoints' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLDistributionPoints.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\CRLNumber' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLNumber.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\CRLReason' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLReason.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\CertPolicyId' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertPolicyId.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Certificate' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Certificate.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\CertificateIssuer' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateIssuer.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\CertificateList' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateList.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\CertificatePolicies' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificatePolicies.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\CertificateSerialNumber' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateSerialNumber.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\CertificationRequest' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificationRequest.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\CertificationRequestInfo' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificationRequestInfo.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Characteristic_two' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Characteristic_two.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\CountryName' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CountryName.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Curve' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Curve.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\DHParameter' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DHParameter.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\DSAParams' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAParams.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\DSAPrivateKey' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAPrivateKey.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\DSAPublicKey' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAPublicKey.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\DigestInfo' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DigestInfo.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\DirectoryString' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DirectoryString.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\DisplayText' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DisplayText.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\DistributionPoint' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DistributionPoint.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\DistributionPointName' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DistributionPointName.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\DssSigValue' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DssSigValue.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\ECParameters' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECParameters.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\ECPoint' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECPoint.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\ECPrivateKey' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECPrivateKey.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\EDIPartyName' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EDIPartyName.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\EcdsaSigValue' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EcdsaSigValue.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\EncryptedData' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EncryptedData.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\EncryptedPrivateKeyInfo' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EncryptedPrivateKeyInfo.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\ExtKeyUsageSyntax' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtKeyUsageSyntax.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Extension' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Extension.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\ExtensionAttribute' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtensionAttribute.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\ExtensionAttributes' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtensionAttributes.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Extensions' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Extensions.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\FieldElement' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/FieldElement.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\FieldID' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/FieldID.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\GeneralName' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralName.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\GeneralNames' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralNames.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\GeneralSubtree' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralSubtree.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\GeneralSubtrees' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralSubtrees.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\HashAlgorithm' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/HashAlgorithm.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\HoldInstructionCode' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/HoldInstructionCode.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\InvalidityDate' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/InvalidityDate.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\IssuerAltName' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/IssuerAltName.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\IssuingDistributionPoint' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/IssuingDistributionPoint.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\KeyIdentifier' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyIdentifier.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\KeyPurposeId' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyPurposeId.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\KeyUsage' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyUsage.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\MaskGenAlgorithm' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/MaskGenAlgorithm.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Name' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Name.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\NameConstraints' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NameConstraints.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\NetworkAddress' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NetworkAddress.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\NoticeReference' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NoticeReference.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\NumericUserIdentifier' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NumericUserIdentifier.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\ORAddress' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ORAddress.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\OneAsymmetricKey' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OneAsymmetricKey.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\OrganizationName' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OrganizationName.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\OrganizationalUnitNames' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OrganizationalUnitNames.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\OtherPrimeInfo' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OtherPrimeInfo.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\OtherPrimeInfos' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OtherPrimeInfos.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PBEParameter' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBEParameter.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PBES2params' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBES2params.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PBKDF2params' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBKDF2params.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PBMAC1params' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBMAC1params.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PKCS9String' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PKCS9String.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Pentanomial' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Pentanomial.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PersonalName' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PersonalName.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PolicyInformation' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyInformation.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PolicyMappings' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyMappings.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PolicyQualifierId' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyQualifierId.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PolicyQualifierInfo' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyQualifierInfo.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PostalAddress' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PostalAddress.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Prime_p' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Prime_p.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PrivateDomainName' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateDomainName.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PrivateKey' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKey.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PrivateKeyInfo' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKeyInfo.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PrivateKeyUsagePeriod' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKeyUsagePeriod.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PublicKey' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKey.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PublicKeyAndChallenge' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKeyAndChallenge.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PublicKeyInfo' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKeyInfo.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\RC2CBCParameter' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RC2CBCParameter.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\RDNSequence' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RDNSequence.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\RSAPrivateKey' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSAPrivateKey.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\RSAPublicKey' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSAPublicKey.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\RSASSA_PSS_params' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSASSA_PSS_params.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\ReasonFlags' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ReasonFlags.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\RelativeDistinguishedName' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RelativeDistinguishedName.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\RevokedCertificate' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RevokedCertificate.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\SignedPublicKeyAndChallenge' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SignedPublicKeyAndChallenge.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\SpecifiedECDomain' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SpecifiedECDomain.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\SubjectAltName' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectAltName.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\SubjectDirectoryAttributes' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectDirectoryAttributes.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\SubjectInfoAccessSyntax' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectInfoAccessSyntax.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\SubjectPublicKeyInfo' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectPublicKeyInfo.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\TBSCertList' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TBSCertList.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\TBSCertificate' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TBSCertificate.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\TerminalIdentifier' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TerminalIdentifier.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Time' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Time.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Trinomial' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Trinomial.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\UniqueIdentifier' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/UniqueIdentifier.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\UserNotice' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/UserNotice.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Validity' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Validity.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\netscape_ca_policy_url' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_ca_policy_url.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\netscape_cert_type' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_cert_type.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\netscape_comment' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_comment.php',
+ 'phpseclib3\\File\\X509' => $vendorDir . '/phpseclib/phpseclib/phpseclib/File/X509.php',
+ 'phpseclib3\\Math\\BigInteger' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\BCMath' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\BCMath\\Base' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Base.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\BCMath\\BuiltIn' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/BuiltIn.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\BCMath\\DefaultEngine' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/DefaultEngine.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\BCMath\\OpenSSL' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/OpenSSL.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\BCMath\\Reductions\\Barrett' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Reductions/Barrett.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\BCMath\\Reductions\\EvalBarrett' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Reductions/EvalBarrett.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\Engine' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/Engine.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\GMP' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/GMP.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\GMP\\DefaultEngine' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/GMP/DefaultEngine.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\OpenSSL' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/OpenSSL.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP32' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP32.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP64' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP64.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP\\Base' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Base.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP\\DefaultEngine' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/DefaultEngine.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP\\Montgomery' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Montgomery.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP\\OpenSSL' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/OpenSSL.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP\\Reductions\\Barrett' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Barrett.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP\\Reductions\\Classic' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Classic.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP\\Reductions\\EvalBarrett' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/EvalBarrett.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP\\Reductions\\Montgomery' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Montgomery.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP\\Reductions\\MontgomeryMult' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/MontgomeryMult.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP\\Reductions\\PowerOfTwo' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/PowerOfTwo.php',
+ 'phpseclib3\\Math\\BinaryField' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BinaryField.php',
+ 'phpseclib3\\Math\\BinaryField\\Integer' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/BinaryField/Integer.php',
+ 'phpseclib3\\Math\\Common\\FiniteField' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/Common/FiniteField.php',
+ 'phpseclib3\\Math\\Common\\FiniteField\\Integer' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/Common/FiniteField/Integer.php',
+ 'phpseclib3\\Math\\PrimeField' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/PrimeField.php',
+ 'phpseclib3\\Math\\PrimeField\\Integer' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Math/PrimeField/Integer.php',
+ 'phpseclib3\\Net\\SFTP' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Net/SFTP.php',
+ 'phpseclib3\\Net\\SFTP\\Stream' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Net/SFTP/Stream.php',
+ 'phpseclib3\\Net\\SSH2' => $vendorDir . '/phpseclib/phpseclib/phpseclib/Net/SSH2.php',
+ 'phpseclib3\\System\\SSH\\Agent' => $vendorDir . '/phpseclib/phpseclib/phpseclib/System/SSH/Agent.php',
+ 'phpseclib3\\System\\SSH\\Agent\\Identity' => $vendorDir . '/phpseclib/phpseclib/phpseclib/System/SSH/Agent/Identity.php',
+ 'phpseclib3\\System\\SSH\\Common\\Traits\\ReadBytes' => $vendorDir . '/phpseclib/phpseclib/phpseclib/System/SSH/Common/Traits/ReadBytes.php',
+ 'phpseclib\\Crypt\\AES' => $vendorDir . '/phpseclib/phpseclib2_compat/src/Crypt/AES.php',
+ 'phpseclib\\Crypt\\Base' => $vendorDir . '/phpseclib/phpseclib2_compat/src/Crypt/Base.php',
+ 'phpseclib\\Crypt\\Blowfish' => $vendorDir . '/phpseclib/phpseclib2_compat/src/Crypt/Blowfish.php',
+ 'phpseclib\\Crypt\\DES' => $vendorDir . '/phpseclib/phpseclib2_compat/src/Crypt/DES.php',
+ 'phpseclib\\Crypt\\Hash' => $vendorDir . '/phpseclib/phpseclib2_compat/src/Crypt/Hash.php',
+ 'phpseclib\\Crypt\\RC2' => $vendorDir . '/phpseclib/phpseclib2_compat/src/Crypt/RC2.php',
+ 'phpseclib\\Crypt\\RC4' => $vendorDir . '/phpseclib/phpseclib2_compat/src/Crypt/RC4.php',
+ 'phpseclib\\Crypt\\RSA' => $vendorDir . '/phpseclib/phpseclib2_compat/src/Crypt/RSA.php',
+ 'phpseclib\\Crypt\\Random' => $vendorDir . '/phpseclib/phpseclib2_compat/src/Crypt/Random.php',
+ 'phpseclib\\Crypt\\Rijndael' => $vendorDir . '/phpseclib/phpseclib2_compat/src/Crypt/Rijndael.php',
+ 'phpseclib\\Crypt\\TripleDES' => $vendorDir . '/phpseclib/phpseclib2_compat/src/Crypt/TripleDES.php',
+ 'phpseclib\\Crypt\\Twofish' => $vendorDir . '/phpseclib/phpseclib2_compat/src/Crypt/Twofish.php',
+ 'phpseclib\\File\\ANSI' => $vendorDir . '/phpseclib/phpseclib2_compat/src/File/ANSI.php',
+ 'phpseclib\\File\\ASN1' => $vendorDir . '/phpseclib/phpseclib2_compat/src/File/ASN1.php',
+ 'phpseclib\\File\\ASN1\\Element' => $vendorDir . '/phpseclib/phpseclib2_compat/src/File/ASN1/Element.php',
+ 'phpseclib\\File\\X509' => $vendorDir . '/phpseclib/phpseclib2_compat/src/File/X509.php',
+ 'phpseclib\\Math\\BigInteger' => $vendorDir . '/phpseclib/phpseclib2_compat/src/Math/BigInteger.php',
+ 'phpseclib\\Net\\SFTP' => $vendorDir . '/phpseclib/phpseclib2_compat/src/Net/SFTP.php',
+ 'phpseclib\\Net\\SFTP\\Stream' => $vendorDir . '/phpseclib/phpseclib2_compat/src/Net/SFTP/Stream.php',
+ 'phpseclib\\Net\\SSH2' => $vendorDir . '/phpseclib/phpseclib2_compat/src/Net/SSH2.php',
+ 'phpseclib\\System\\SSH\\Agent' => $vendorDir . '/phpseclib/phpseclib2_compat/src/System/SSH/Agent.php',
+ 'phpseclib\\System\\SSH\\Agent\\Identity' => $vendorDir . '/phpseclib/phpseclib2_compat/src/System/SSH/Agent/Identity.php',
'voku\\helper\\ASCII' => $vendorDir . '/voku/portable-ascii/src/voku/helper/ASCII.php',
'voku\\helper\\StopWords' => $vendorDir . '/voku/stop-words/src/voku/helper/StopWords.php',
'voku\\helper\\StopWordsLanguageNotExists' => $vendorDir . '/voku/stop-words/src/voku/helper/StopWordsLanguageNotExists.php',
diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php
index 070ea14ba..47b457816 100644
--- a/vendor/composer/autoload_files.php
+++ b/vendor/composer/autoload_files.php
@@ -8,16 +8,17 @@ $baseDir = dirname($vendorDir);
return array(
'383eaff206634a77a1be54e64e6459c7' => $vendorDir . '/sabre/uri/lib/functions.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
+ 'decc78cc4436b1292c6c0d151b19445c' => $vendorDir . '/phpseclib/phpseclib/phpseclib/bootstrap.php',
'2b9d0f43f9552984cfa82fee95491826' => $vendorDir . '/sabre/event/lib/coroutine.php',
'd81bab31d3feb45bfe2f283ea3c8fdf7' => $vendorDir . '/sabre/event/lib/Loop/functions.php',
'a1cce3d26cc15c00fcd0b3354bd72c88' => $vendorDir . '/sabre/event/lib/Promise/functions.php',
'3569eecfeed3bcf0bad3c998a494ecb8' => $vendorDir . '/sabre/xml/lib/Deserializer/functions.php',
'93aa591bc4ca510c520999e34229ee79' => $vendorDir . '/sabre/xml/lib/Serializer/functions.php',
+ '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'ebdb698ed4152ae445614b69b5e4bb6a' => $vendorDir . '/sabre/http/lib/functions.php',
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
'2cffec82183ee1cea088009cef9a6fc3' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
- 'decc78cc4436b1292c6c0d151b19445c' => $vendorDir . '/phpseclib/phpseclib/phpseclib/bootstrap.php',
'e39a8b23c42d4e1452234d762b03835a' => $vendorDir . '/ramsey/uuid/src/functions.php',
'c15d4a1253e33e055d05e547c61dcb71' => $vendorDir . '/smarty/smarty/src/functions.php',
);
diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php
index 3f54528b2..3a0f148bf 100644
--- a/vendor/composer/autoload_psr4.php
+++ b/vendor/composer/autoload_psr4.php
@@ -7,7 +7,8 @@ $baseDir = dirname($vendorDir);
return array(
'voku\\' => array($vendorDir . '/voku/stop-words/src/voku', $vendorDir . '/voku/portable-ascii/src/voku'),
- 'phpseclib\\' => array($vendorDir . '/phpseclib/phpseclib/phpseclib'),
+ 'phpseclib\\' => array($vendorDir . '/phpseclib/phpseclib2_compat/src'),
+ 'phpseclib3\\' => array($vendorDir . '/phpseclib/phpseclib/phpseclib'),
'chillerlan\\Settings\\' => array($vendorDir . '/chillerlan/php-settings-container/src'),
'chillerlan\\QRCode\\' => array($vendorDir . '/chillerlan/php-qrcode/src'),
'Zotlabs\\' => array($baseDir . '/Zotlabs'),
@@ -40,6 +41,9 @@ return array(
'LanguageDetection\\' => array($vendorDir . '/patrickschur/language-detection/src/LanguageDetection'),
'ID3Parser\\' => array($vendorDir . '/lukasreschke/id3parser/src'),
'Hubzilla\\' => array($baseDir . '/include'),
+ 'HttpSignature\\' => array($vendorDir . '/macgirvin/http-message-signer/src'),
+ 'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
'CommerceGuys\\Intl\\' => array($vendorDir . '/commerceguys/intl/src'),
'Brick\\Math\\' => array($vendorDir . '/brick/math/src'),
+ 'Bakame\\Http\\StructuredFields\\' => array($vendorDir . '/bakame/http-structured-fields/src'),
);
diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php
index c8860e571..7ae0881e4 100644
--- a/vendor/composer/autoload_static.php
+++ b/vendor/composer/autoload_static.php
@@ -9,16 +9,17 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
public static $files = array (
'383eaff206634a77a1be54e64e6459c7' => __DIR__ . '/..' . '/sabre/uri/lib/functions.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
+ 'decc78cc4436b1292c6c0d151b19445c' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/bootstrap.php',
'2b9d0f43f9552984cfa82fee95491826' => __DIR__ . '/..' . '/sabre/event/lib/coroutine.php',
'd81bab31d3feb45bfe2f283ea3c8fdf7' => __DIR__ . '/..' . '/sabre/event/lib/Loop/functions.php',
'a1cce3d26cc15c00fcd0b3354bd72c88' => __DIR__ . '/..' . '/sabre/event/lib/Promise/functions.php',
'3569eecfeed3bcf0bad3c998a494ecb8' => __DIR__ . '/..' . '/sabre/xml/lib/Deserializer/functions.php',
'93aa591bc4ca510c520999e34229ee79' => __DIR__ . '/..' . '/sabre/xml/lib/Serializer/functions.php',
+ '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
'ebdb698ed4152ae445614b69b5e4bb6a' => __DIR__ . '/..' . '/sabre/http/lib/functions.php',
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
'2cffec82183ee1cea088009cef9a6fc3' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
- 'decc78cc4436b1292c6c0d151b19445c' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/bootstrap.php',
'e39a8b23c42d4e1452234d762b03835a' => __DIR__ . '/..' . '/ramsey/uuid/src/functions.php',
'c15d4a1253e33e055d05e547c61dcb71' => __DIR__ . '/..' . '/smarty/smarty/src/functions.php',
);
@@ -31,6 +32,7 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'p' =>
array (
'phpseclib\\' => 10,
+ 'phpseclib3\\' => 11,
),
'c' =>
array (
@@ -93,6 +95,11 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'H' =>
array (
'Hubzilla\\' => 9,
+ 'HttpSignature\\' => 14,
+ ),
+ 'G' =>
+ array (
+ 'GuzzleHttp\\Psr7\\' => 16,
),
'C' =>
array (
@@ -101,6 +108,7 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'B' =>
array (
'Brick\\Math\\' => 11,
+ 'Bakame\\Http\\StructuredFields\\' => 29,
),
);
@@ -112,6 +120,10 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
),
'phpseclib\\' =>
array (
+ 0 => __DIR__ . '/..' . '/phpseclib/phpseclib2_compat/src',
+ ),
+ 'phpseclib3\\' =>
+ array (
0 => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib',
),
'chillerlan\\Settings\\' =>
@@ -244,6 +256,14 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
array (
0 => __DIR__ . '/../..' . '/include',
),
+ 'HttpSignature\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/macgirvin/http-message-signer/src',
+ ),
+ 'GuzzleHttp\\Psr7\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
+ ),
'CommerceGuys\\Intl\\' =>
array (
0 => __DIR__ . '/..' . '/commerceguys/intl/src',
@@ -252,6 +272,10 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
array (
0 => __DIR__ . '/..' . '/brick/math/src',
),
+ 'Bakame\\Http\\StructuredFields\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/bakame/http-structured-fields/src',
+ ),
);
public static $prefixesPsr0 = array (
@@ -293,6 +317,36 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
);
public static $classMap = array (
+ 'Bakame\\Http\\StructuredFields\\Bytes' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/Bytes.php',
+ 'Bakame\\Http\\StructuredFields\\DataType' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/DataType.php',
+ 'Bakame\\Http\\StructuredFields\\Dictionary' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/Dictionary.php',
+ 'Bakame\\Http\\StructuredFields\\DisplayString' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/DisplayString.php',
+ 'Bakame\\Http\\StructuredFields\\ForbiddenOperation' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/ForbiddenOperation.php',
+ 'Bakame\\Http\\StructuredFields\\Ietf' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/Ietf.php',
+ 'Bakame\\Http\\StructuredFields\\InnerList' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/InnerList.php',
+ 'Bakame\\Http\\StructuredFields\\InvalidArgument' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/InvalidArgument.php',
+ 'Bakame\\Http\\StructuredFields\\InvalidOffset' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/InvalidOffset.php',
+ 'Bakame\\Http\\StructuredFields\\Item' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/Item.php',
+ 'Bakame\\Http\\StructuredFields\\Key' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/Key.php',
+ 'Bakame\\Http\\StructuredFields\\Member' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/Member.php',
+ 'Bakame\\Http\\StructuredFields\\MissingFeature' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/MissingFeature.php',
+ 'Bakame\\Http\\StructuredFields\\OuterList' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/OuterList.php',
+ 'Bakame\\Http\\StructuredFields\\ParameterAccess' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/ParameterAccess.php',
+ 'Bakame\\Http\\StructuredFields\\Parameters' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/Parameters.php',
+ 'Bakame\\Http\\StructuredFields\\Parser' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/Parser.php',
+ 'Bakame\\Http\\StructuredFields\\StructuredFieldError' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/StructuredFieldError.php',
+ 'Bakame\\Http\\StructuredFields\\StructuredFieldProvider' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/StructuredFieldProvider.php',
+ 'Bakame\\Http\\StructuredFields\\SyntaxError' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/SyntaxError.php',
+ 'Bakame\\Http\\StructuredFields\\Token' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/Token.php',
+ 'Bakame\\Http\\StructuredFields\\Type' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/Type.php',
+ 'Bakame\\Http\\StructuredFields\\Validation\\ErrorCode' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/Validation/ErrorCode.php',
+ 'Bakame\\Http\\StructuredFields\\Validation\\ItemValidator' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/Validation/ItemValidator.php',
+ 'Bakame\\Http\\StructuredFields\\Validation\\ParametersValidator' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/Validation/ParametersValidator.php',
+ 'Bakame\\Http\\StructuredFields\\Validation\\Result' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/Validation/Result.php',
+ 'Bakame\\Http\\StructuredFields\\Validation\\ValidatedItem' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/Validation/ValidatedItem.php',
+ 'Bakame\\Http\\StructuredFields\\Validation\\ValidatedParameters' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/Validation/ValidatedParameters.php',
+ 'Bakame\\Http\\StructuredFields\\Validation\\Violation' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/Validation/Violation.php',
+ 'Bakame\\Http\\StructuredFields\\Validation\\ViolationList' => __DIR__ . '/..' . '/bakame/http-structured-fields/src/Validation/ViolationList.php',
'Brick\\Math\\BigDecimal' => __DIR__ . '/..' . '/brick/math/src/BigDecimal.php',
'Brick\\Math\\BigInteger' => __DIR__ . '/..' . '/brick/math/src/BigInteger.php',
'Brick\\Math\\BigNumber' => __DIR__ . '/..' . '/brick/math/src/BigNumber.php',
@@ -331,6 +385,37 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'CommerceGuys\\Intl\\NumberFormat\\NumberFormatRepository' => __DIR__ . '/..' . '/commerceguys/intl/src/NumberFormat/NumberFormatRepository.php',
'CommerceGuys\\Intl\\NumberFormat\\NumberFormatRepositoryInterface' => __DIR__ . '/..' . '/commerceguys/intl/src/NumberFormat/NumberFormatRepositoryInterface.php',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
+ 'GuzzleHttp\\Psr7\\AppendStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/AppendStream.php',
+ 'GuzzleHttp\\Psr7\\BufferStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/BufferStream.php',
+ 'GuzzleHttp\\Psr7\\CachingStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/CachingStream.php',
+ 'GuzzleHttp\\Psr7\\DroppingStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/DroppingStream.php',
+ 'GuzzleHttp\\Psr7\\Exception\\MalformedUriException' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Exception/MalformedUriException.php',
+ 'GuzzleHttp\\Psr7\\FnStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/FnStream.php',
+ 'GuzzleHttp\\Psr7\\Header' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Header.php',
+ 'GuzzleHttp\\Psr7\\HttpFactory' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/HttpFactory.php',
+ 'GuzzleHttp\\Psr7\\InflateStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/InflateStream.php',
+ 'GuzzleHttp\\Psr7\\LazyOpenStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/LazyOpenStream.php',
+ 'GuzzleHttp\\Psr7\\LimitStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/LimitStream.php',
+ 'GuzzleHttp\\Psr7\\Message' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Message.php',
+ 'GuzzleHttp\\Psr7\\MessageTrait' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/MessageTrait.php',
+ 'GuzzleHttp\\Psr7\\MimeType' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/MimeType.php',
+ 'GuzzleHttp\\Psr7\\MultipartStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/MultipartStream.php',
+ 'GuzzleHttp\\Psr7\\NoSeekStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/NoSeekStream.php',
+ 'GuzzleHttp\\Psr7\\PumpStream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/PumpStream.php',
+ 'GuzzleHttp\\Psr7\\Query' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Query.php',
+ 'GuzzleHttp\\Psr7\\Request' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Request.php',
+ 'GuzzleHttp\\Psr7\\Response' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Response.php',
+ 'GuzzleHttp\\Psr7\\Rfc7230' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Rfc7230.php',
+ 'GuzzleHttp\\Psr7\\ServerRequest' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/ServerRequest.php',
+ 'GuzzleHttp\\Psr7\\Stream' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Stream.php',
+ 'GuzzleHttp\\Psr7\\StreamDecoratorTrait' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/StreamDecoratorTrait.php',
+ 'GuzzleHttp\\Psr7\\StreamWrapper' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/StreamWrapper.php',
+ 'GuzzleHttp\\Psr7\\UploadedFile' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/UploadedFile.php',
+ 'GuzzleHttp\\Psr7\\Uri' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Uri.php',
+ 'GuzzleHttp\\Psr7\\UriComparator' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/UriComparator.php',
+ 'GuzzleHttp\\Psr7\\UriNormalizer' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/UriNormalizer.php',
+ 'GuzzleHttp\\Psr7\\UriResolver' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/UriResolver.php',
+ 'GuzzleHttp\\Psr7\\Utils' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/Utils.php',
'HTMLPurifier' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier.php',
'HTMLPurifier_Arborize' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Arborize.php',
'HTMLPurifier_AttrCollections' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/AttrCollections.php',
@@ -563,6 +648,9 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'HTMLPurifier_VarParser_Flexible' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php',
'HTMLPurifier_VarParser_Native' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php',
'HTMLPurifier_Zipper' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier/Zipper.php',
+ 'HttpSignature\\HttpMessageSigner' => __DIR__ . '/..' . '/macgirvin/http-message-signer/src/HttpMessageSigner.php',
+ 'HttpSignature\\StructuredFieldTypes' => __DIR__ . '/..' . '/macgirvin/http-message-signer/src/StructuredFieldTypes.php',
+ 'HttpSignature\\UnProcessableSignatureException' => __DIR__ . '/..' . '/macgirvin/http-message-signer/src/UnProcessableSignatureException.php',
'ID3Parser\\ID3Parser' => __DIR__ . '/..' . '/lukasreschke/id3parser/src/ID3Parser.php',
'ID3Parser\\getID3\\Tags\\getid3_id3v1' => __DIR__ . '/..' . '/lukasreschke/id3parser/src/getID3/Tags/getid3_id3v1.php',
'ID3Parser\\getID3\\Tags\\getid3_id3v2' => __DIR__ . '/..' . '/lukasreschke/id3parser/src/getID3/Tags/getid3_id3v2.php',
@@ -1906,6 +1994,7 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'Zotlabs\\Lib\\Img_filesize' => __DIR__ . '/../..' . '/Zotlabs/Lib/Img_filesize.php',
'Zotlabs\\Lib\\JSalmon' => __DIR__ . '/../..' . '/Zotlabs/Lib/JSalmon.php',
'Zotlabs\\Lib\\JcsEddsa2022' => __DIR__ . '/../..' . '/Zotlabs/Lib/JcsEddsa2022.php',
+ 'Zotlabs\\Lib\\JcsEddsa2022SignException' => __DIR__ . '/../..' . '/Zotlabs/Lib/JcsEddsa2022SignException.php',
'Zotlabs\\Lib\\Keyutils' => __DIR__ . '/../..' . '/Zotlabs/Lib/Keyutils.php',
'Zotlabs\\Lib\\LDSignatures' => __DIR__ . '/../..' . '/Zotlabs/Lib/LDSignatures.php',
'Zotlabs\\Lib\\Libsync' => __DIR__ . '/../..' . '/Zotlabs/Lib/Libsync.php',
@@ -1969,14 +2058,12 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'Zotlabs\\Module\\Branchtopic' => __DIR__ . '/../..' . '/Zotlabs/Module/Branchtopic.php',
'Zotlabs\\Module\\Cal' => __DIR__ . '/../..' . '/Zotlabs/Module/Cal.php',
'Zotlabs\\Module\\Cdav' => __DIR__ . '/../..' . '/Zotlabs/Module/Cdav.php',
- 'Zotlabs\\Module\\Ceditor' => __DIR__ . '/../..' . '/Zotlabs/Module/Ceditor.php',
'Zotlabs\\Module\\Changeaddr' => __DIR__ . '/../..' . '/Zotlabs/Module/Changeaddr.php',
'Zotlabs\\Module\\Channel' => __DIR__ . '/../..' . '/Zotlabs/Module/Channel.php',
'Zotlabs\\Module\\Channel_calendar' => __DIR__ . '/../..' . '/Zotlabs/Module/Channel_calendar.php',
'Zotlabs\\Module\\Chanview' => __DIR__ . '/../..' . '/Zotlabs/Module/Chanview.php',
'Zotlabs\\Module\\Chat' => __DIR__ . '/../..' . '/Zotlabs/Module/Chat.php',
'Zotlabs\\Module\\Chatsvc' => __DIR__ . '/../..' . '/Zotlabs/Module/Chatsvc.php',
- 'Zotlabs\\Module\\Cleditor' => __DIR__ . '/../..' . '/Zotlabs/Module/Cleditor.php',
'Zotlabs\\Module\\Cloud' => __DIR__ . '/../..' . '/Zotlabs/Module/Cloud.php',
'Zotlabs\\Module\\Cloud_tiles' => __DIR__ . '/../..' . '/Zotlabs/Module/Cloud_tiles.php',
'Zotlabs\\Module\\Common' => __DIR__ . '/../..' . '/Zotlabs/Module/Common.php',
@@ -2089,6 +2176,7 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'Zotlabs\\Module\\Regver' => __DIR__ . '/../..' . '/Zotlabs/Module/Regver.php',
'Zotlabs\\Module\\Removeaccount' => __DIR__ . '/../..' . '/Zotlabs/Module/Removeaccount.php',
'Zotlabs\\Module\\Removeme' => __DIR__ . '/../..' . '/Zotlabs/Module/Removeme.php',
+ 'Zotlabs\\Module\\Request' => __DIR__ . '/../..' . '/Zotlabs/Module/Request.php',
'Zotlabs\\Module\\Rmagic' => __DIR__ . '/../..' . '/Zotlabs/Module/Rmagic.php',
'Zotlabs\\Module\\Rpost' => __DIR__ . '/../..' . '/Zotlabs/Module/Rpost.php',
'Zotlabs\\Module\\Search' => __DIR__ . '/../..' . '/Zotlabs/Module/Search.php',
@@ -2151,7 +2239,6 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'Zotlabs\\Module\\Xchan' => __DIR__ . '/../..' . '/Zotlabs/Module/Xchan.php',
'Zotlabs\\Module\\Xpoco' => __DIR__ . '/../..' . '/Zotlabs/Module/Xpoco.php',
'Zotlabs\\Module\\Xrd' => __DIR__ . '/../..' . '/Zotlabs/Module/Xrd.php',
- 'Zotlabs\\Module\\Xref' => __DIR__ . '/../..' . '/Zotlabs/Module/Xref.php',
'Zotlabs\\Module\\Z6trans' => __DIR__ . '/../..' . '/Zotlabs/Module/Z6trans.php',
'Zotlabs\\Module\\Zot' => __DIR__ . '/../..' . '/Zotlabs/Module/Zot.php',
'Zotlabs\\Module\\Zot_probe' => __DIR__ . '/../..' . '/Zotlabs/Module/Zot_probe.php',
@@ -2578,30 +2665,363 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'chillerlan\\QRCode\\QROptionsTrait' => __DIR__ . '/..' . '/chillerlan/php-qrcode/src/QROptionsTrait.php',
'chillerlan\\Settings\\SettingsContainerAbstract' => __DIR__ . '/..' . '/chillerlan/php-settings-container/src/SettingsContainerAbstract.php',
'chillerlan\\Settings\\SettingsContainerInterface' => __DIR__ . '/..' . '/chillerlan/php-settings-container/src/SettingsContainerInterface.php',
- 'phpseclib\\Crypt\\AES' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/AES.php',
- 'phpseclib\\Crypt\\Base' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Base.php',
- 'phpseclib\\Crypt\\Blowfish' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Blowfish.php',
- 'phpseclib\\Crypt\\DES' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/DES.php',
- 'phpseclib\\Crypt\\Hash' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Hash.php',
- 'phpseclib\\Crypt\\RC2' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/RC2.php',
- 'phpseclib\\Crypt\\RC4' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/RC4.php',
- 'phpseclib\\Crypt\\RSA' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/RSA.php',
- 'phpseclib\\Crypt\\Random' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Random.php',
- 'phpseclib\\Crypt\\Rijndael' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Rijndael.php',
- 'phpseclib\\Crypt\\TripleDES' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/TripleDES.php',
- 'phpseclib\\Crypt\\Twofish' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Twofish.php',
- 'phpseclib\\File\\ANSI' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ANSI.php',
- 'phpseclib\\File\\ASN1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1.php',
- 'phpseclib\\File\\ASN1\\Element' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Element.php',
- 'phpseclib\\File\\X509' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/X509.php',
- 'phpseclib\\Math\\BigInteger' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger.php',
- 'phpseclib\\Net\\SCP' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Net/SCP.php',
- 'phpseclib\\Net\\SFTP' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Net/SFTP.php',
- 'phpseclib\\Net\\SFTP\\Stream' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Net/SFTP/Stream.php',
- 'phpseclib\\Net\\SSH1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Net/SSH1.php',
- 'phpseclib\\Net\\SSH2' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Net/SSH2.php',
- 'phpseclib\\System\\SSH\\Agent' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/System/SSH/Agent.php',
- 'phpseclib\\System\\SSH\\Agent\\Identity' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/System/SSH/Agent/Identity.php',
+ 'phpseclib3\\Common\\Functions\\Strings' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Common/Functions/Strings.php',
+ 'phpseclib3\\Crypt\\AES' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/AES.php',
+ 'phpseclib3\\Crypt\\Blowfish' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Blowfish.php',
+ 'phpseclib3\\Crypt\\ChaCha20' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/ChaCha20.php',
+ 'phpseclib3\\Crypt\\Common\\AsymmetricKey' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Common/AsymmetricKey.php',
+ 'phpseclib3\\Crypt\\Common\\BlockCipher' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Common/BlockCipher.php',
+ 'phpseclib3\\Crypt\\Common\\Formats\\Keys\\JWK' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/JWK.php',
+ 'phpseclib3\\Crypt\\Common\\Formats\\Keys\\OpenSSH' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/OpenSSH.php',
+ 'phpseclib3\\Crypt\\Common\\Formats\\Keys\\PKCS' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS.php',
+ 'phpseclib3\\Crypt\\Common\\Formats\\Keys\\PKCS1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS1.php',
+ 'phpseclib3\\Crypt\\Common\\Formats\\Keys\\PKCS8' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS8.php',
+ 'phpseclib3\\Crypt\\Common\\Formats\\Keys\\PuTTY' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PuTTY.php',
+ 'phpseclib3\\Crypt\\Common\\Formats\\Signature\\Raw' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Signature/Raw.php',
+ 'phpseclib3\\Crypt\\Common\\PrivateKey' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Common/PrivateKey.php',
+ 'phpseclib3\\Crypt\\Common\\PublicKey' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Common/PublicKey.php',
+ 'phpseclib3\\Crypt\\Common\\StreamCipher' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Common/StreamCipher.php',
+ 'phpseclib3\\Crypt\\Common\\SymmetricKey' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Common/SymmetricKey.php',
+ 'phpseclib3\\Crypt\\Common\\Traits\\Fingerprint' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Common/Traits/Fingerprint.php',
+ 'phpseclib3\\Crypt\\Common\\Traits\\PasswordProtected' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Common/Traits/PasswordProtected.php',
+ 'phpseclib3\\Crypt\\DES' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/DES.php',
+ 'phpseclib3\\Crypt\\DH' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/DH.php',
+ 'phpseclib3\\Crypt\\DH\\Formats\\Keys\\PKCS1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/DH/Formats/Keys/PKCS1.php',
+ 'phpseclib3\\Crypt\\DH\\Formats\\Keys\\PKCS8' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/DH/Formats/Keys/PKCS8.php',
+ 'phpseclib3\\Crypt\\DH\\Parameters' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/DH/Parameters.php',
+ 'phpseclib3\\Crypt\\DH\\PrivateKey' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/DH/PrivateKey.php',
+ 'phpseclib3\\Crypt\\DH\\PublicKey' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/DH/PublicKey.php',
+ 'phpseclib3\\Crypt\\DSA' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/DSA.php',
+ 'phpseclib3\\Crypt\\DSA\\Formats\\Keys\\OpenSSH' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/OpenSSH.php',
+ 'phpseclib3\\Crypt\\DSA\\Formats\\Keys\\PKCS1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PKCS1.php',
+ 'phpseclib3\\Crypt\\DSA\\Formats\\Keys\\PKCS8' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PKCS8.php',
+ 'phpseclib3\\Crypt\\DSA\\Formats\\Keys\\PuTTY' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PuTTY.php',
+ 'phpseclib3\\Crypt\\DSA\\Formats\\Keys\\Raw' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/Raw.php',
+ 'phpseclib3\\Crypt\\DSA\\Formats\\Keys\\XML' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/XML.php',
+ 'phpseclib3\\Crypt\\DSA\\Formats\\Signature\\ASN1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/ASN1.php',
+ 'phpseclib3\\Crypt\\DSA\\Formats\\Signature\\Raw' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/Raw.php',
+ 'phpseclib3\\Crypt\\DSA\\Formats\\Signature\\SSH2' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/SSH2.php',
+ 'phpseclib3\\Crypt\\DSA\\Parameters' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/DSA/Parameters.php',
+ 'phpseclib3\\Crypt\\DSA\\PrivateKey' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/DSA/PrivateKey.php',
+ 'phpseclib3\\Crypt\\DSA\\PublicKey' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/DSA/PublicKey.php',
+ 'phpseclib3\\Crypt\\EC' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC.php',
+ 'phpseclib3\\Crypt\\EC\\BaseCurves\\Base' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Base.php',
+ 'phpseclib3\\Crypt\\EC\\BaseCurves\\Binary' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Binary.php',
+ 'phpseclib3\\Crypt\\EC\\BaseCurves\\KoblitzPrime' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/KoblitzPrime.php',
+ 'phpseclib3\\Crypt\\EC\\BaseCurves\\Montgomery' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Montgomery.php',
+ 'phpseclib3\\Crypt\\EC\\BaseCurves\\Prime' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Prime.php',
+ 'phpseclib3\\Crypt\\EC\\BaseCurves\\TwistedEdwards' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/TwistedEdwards.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\Curve25519' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Curve25519.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\Curve448' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Curve448.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\Ed25519' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Ed25519.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\Ed448' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Ed448.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP160r1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP160r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP160t1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP160t1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP192r1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP192r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP192t1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP192t1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP224r1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP224r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP224t1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP224t1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP256r1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP256r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP256t1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP256t1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP320r1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP320r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP320t1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP320t1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP384r1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP384r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP384t1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP384t1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP512r1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP512r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\brainpoolP512t1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP512t1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\nistb233' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistb233.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\nistb409' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistb409.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\nistk163' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk163.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\nistk233' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk233.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\nistk283' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk283.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\nistk409' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk409.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\nistp192' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp192.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\nistp224' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp224.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\nistp256' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp256.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\nistp384' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp384.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\nistp521' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp521.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\nistt571' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistt571.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\prime192v1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\prime192v2' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v2.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\prime192v3' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v3.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\prime239v1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\prime239v2' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v2.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\prime239v3' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v3.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\prime256v1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime256v1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp112r1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp112r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp112r2' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp112r2.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp128r1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp128r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp128r2' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp128r2.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp160k1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160k1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp160r1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp160r2' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160r2.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp192k1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp192k1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp192r1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp192r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp224k1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp224k1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp224r1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp224r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp256k1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp256k1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp256r1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp256r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp384r1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp384r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\secp521r1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp521r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect113r1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect113r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect113r2' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect113r2.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect131r1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect131r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect131r2' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect131r2.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect163k1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163k1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect163r1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect163r2' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163r2.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect193r1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect193r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect193r2' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect193r2.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect233k1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect233k1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect233r1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect233r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect239k1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect239k1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect283k1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect283k1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect283r1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect283r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect409k1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect409k1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect409r1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect409r1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect571k1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect571k1.php',
+ 'phpseclib3\\Crypt\\EC\\Curves\\sect571r1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect571r1.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Keys\\Common' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/Common.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Keys\\JWK' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/JWK.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Keys\\MontgomeryPrivate' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/MontgomeryPrivate.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Keys\\MontgomeryPublic' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/MontgomeryPublic.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Keys\\OpenSSH' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/OpenSSH.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Keys\\PKCS1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PKCS1.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Keys\\PKCS8' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PKCS8.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Keys\\PuTTY' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PuTTY.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Keys\\XML' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/XML.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Keys\\libsodium' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/libsodium.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Signature\\ASN1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/ASN1.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Signature\\IEEE' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/IEEE.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Signature\\Raw' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/Raw.php',
+ 'phpseclib3\\Crypt\\EC\\Formats\\Signature\\SSH2' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/SSH2.php',
+ 'phpseclib3\\Crypt\\EC\\Parameters' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/Parameters.php',
+ 'phpseclib3\\Crypt\\EC\\PrivateKey' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/PrivateKey.php',
+ 'phpseclib3\\Crypt\\EC\\PublicKey' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/EC/PublicKey.php',
+ 'phpseclib3\\Crypt\\Hash' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Hash.php',
+ 'phpseclib3\\Crypt\\PublicKeyLoader' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/PublicKeyLoader.php',
+ 'phpseclib3\\Crypt\\RC2' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/RC2.php',
+ 'phpseclib3\\Crypt\\RC4' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/RC4.php',
+ 'phpseclib3\\Crypt\\RSA' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/RSA.php',
+ 'phpseclib3\\Crypt\\RSA\\Formats\\Keys\\JWK' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/JWK.php',
+ 'phpseclib3\\Crypt\\RSA\\Formats\\Keys\\MSBLOB' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/MSBLOB.php',
+ 'phpseclib3\\Crypt\\RSA\\Formats\\Keys\\OpenSSH' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/OpenSSH.php',
+ 'phpseclib3\\Crypt\\RSA\\Formats\\Keys\\PKCS1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PKCS1.php',
+ 'phpseclib3\\Crypt\\RSA\\Formats\\Keys\\PKCS8' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PKCS8.php',
+ 'phpseclib3\\Crypt\\RSA\\Formats\\Keys\\PSS' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PSS.php',
+ 'phpseclib3\\Crypt\\RSA\\Formats\\Keys\\PuTTY' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PuTTY.php',
+ 'phpseclib3\\Crypt\\RSA\\Formats\\Keys\\Raw' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/Raw.php',
+ 'phpseclib3\\Crypt\\RSA\\Formats\\Keys\\XML' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/XML.php',
+ 'phpseclib3\\Crypt\\RSA\\PrivateKey' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/RSA/PrivateKey.php',
+ 'phpseclib3\\Crypt\\RSA\\PublicKey' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/RSA/PublicKey.php',
+ 'phpseclib3\\Crypt\\Random' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Random.php',
+ 'phpseclib3\\Crypt\\Rijndael' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Rijndael.php',
+ 'phpseclib3\\Crypt\\Salsa20' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Salsa20.php',
+ 'phpseclib3\\Crypt\\TripleDES' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/TripleDES.php',
+ 'phpseclib3\\Crypt\\Twofish' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Crypt/Twofish.php',
+ 'phpseclib3\\Exception\\BadConfigurationException' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Exception/BadConfigurationException.php',
+ 'phpseclib3\\Exception\\BadDecryptionException' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Exception/BadDecryptionException.php',
+ 'phpseclib3\\Exception\\BadModeException' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Exception/BadModeException.php',
+ 'phpseclib3\\Exception\\ConnectionClosedException' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Exception/ConnectionClosedException.php',
+ 'phpseclib3\\Exception\\FileNotFoundException' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Exception/FileNotFoundException.php',
+ 'phpseclib3\\Exception\\InconsistentSetupException' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Exception/InconsistentSetupException.php',
+ 'phpseclib3\\Exception\\InsufficientSetupException' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Exception/InsufficientSetupException.php',
+ 'phpseclib3\\Exception\\InvalidPacketLengthException' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Exception/InvalidPacketLengthException.php',
+ 'phpseclib3\\Exception\\NoKeyLoadedException' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Exception/NoKeyLoadedException.php',
+ 'phpseclib3\\Exception\\NoSupportedAlgorithmsException' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Exception/NoSupportedAlgorithmsException.php',
+ 'phpseclib3\\Exception\\TimeoutException' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Exception/TimeoutException.php',
+ 'phpseclib3\\Exception\\UnableToConnectException' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Exception/UnableToConnectException.php',
+ 'phpseclib3\\Exception\\UnsupportedAlgorithmException' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Exception/UnsupportedAlgorithmException.php',
+ 'phpseclib3\\Exception\\UnsupportedCurveException' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Exception/UnsupportedCurveException.php',
+ 'phpseclib3\\Exception\\UnsupportedFormatException' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Exception/UnsupportedFormatException.php',
+ 'phpseclib3\\Exception\\UnsupportedOperationException' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Exception/UnsupportedOperationException.php',
+ 'phpseclib3\\File\\ANSI' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ANSI.php',
+ 'phpseclib3\\File\\ASN1' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1.php',
+ 'phpseclib3\\File\\ASN1\\Element' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Element.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\AccessDescription' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AccessDescription.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\AdministrationDomainName' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AdministrationDomainName.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\AlgorithmIdentifier' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AlgorithmIdentifier.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\AnotherName' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AnotherName.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Attribute' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Attribute.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\AttributeType' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeType.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\AttributeTypeAndValue' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeTypeAndValue.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\AttributeValue' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeValue.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Attributes' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Attributes.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\AuthorityInfoAccessSyntax' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AuthorityInfoAccessSyntax.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\AuthorityKeyIdentifier' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AuthorityKeyIdentifier.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\BaseDistance' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BaseDistance.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\BasicConstraints' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BasicConstraints.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\BuiltInDomainDefinedAttribute' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttribute.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\BuiltInDomainDefinedAttributes' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttributes.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\BuiltInStandardAttributes' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInStandardAttributes.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\CPSuri' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CPSuri.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\CRLDistributionPoints' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLDistributionPoints.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\CRLNumber' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLNumber.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\CRLReason' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLReason.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\CertPolicyId' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertPolicyId.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Certificate' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Certificate.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\CertificateIssuer' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateIssuer.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\CertificateList' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateList.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\CertificatePolicies' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificatePolicies.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\CertificateSerialNumber' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateSerialNumber.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\CertificationRequest' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificationRequest.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\CertificationRequestInfo' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificationRequestInfo.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Characteristic_two' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Characteristic_two.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\CountryName' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CountryName.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Curve' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Curve.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\DHParameter' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DHParameter.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\DSAParams' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAParams.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\DSAPrivateKey' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAPrivateKey.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\DSAPublicKey' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAPublicKey.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\DigestInfo' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DigestInfo.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\DirectoryString' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DirectoryString.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\DisplayText' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DisplayText.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\DistributionPoint' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DistributionPoint.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\DistributionPointName' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DistributionPointName.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\DssSigValue' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DssSigValue.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\ECParameters' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECParameters.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\ECPoint' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECPoint.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\ECPrivateKey' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECPrivateKey.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\EDIPartyName' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EDIPartyName.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\EcdsaSigValue' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EcdsaSigValue.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\EncryptedData' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EncryptedData.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\EncryptedPrivateKeyInfo' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EncryptedPrivateKeyInfo.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\ExtKeyUsageSyntax' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtKeyUsageSyntax.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Extension' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Extension.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\ExtensionAttribute' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtensionAttribute.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\ExtensionAttributes' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtensionAttributes.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Extensions' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Extensions.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\FieldElement' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/FieldElement.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\FieldID' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/FieldID.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\GeneralName' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralName.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\GeneralNames' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralNames.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\GeneralSubtree' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralSubtree.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\GeneralSubtrees' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralSubtrees.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\HashAlgorithm' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/HashAlgorithm.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\HoldInstructionCode' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/HoldInstructionCode.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\InvalidityDate' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/InvalidityDate.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\IssuerAltName' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/IssuerAltName.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\IssuingDistributionPoint' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/IssuingDistributionPoint.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\KeyIdentifier' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyIdentifier.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\KeyPurposeId' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyPurposeId.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\KeyUsage' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyUsage.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\MaskGenAlgorithm' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/MaskGenAlgorithm.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Name' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Name.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\NameConstraints' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NameConstraints.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\NetworkAddress' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NetworkAddress.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\NoticeReference' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NoticeReference.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\NumericUserIdentifier' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NumericUserIdentifier.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\ORAddress' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ORAddress.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\OneAsymmetricKey' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OneAsymmetricKey.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\OrganizationName' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OrganizationName.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\OrganizationalUnitNames' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OrganizationalUnitNames.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\OtherPrimeInfo' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OtherPrimeInfo.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\OtherPrimeInfos' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OtherPrimeInfos.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PBEParameter' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBEParameter.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PBES2params' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBES2params.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PBKDF2params' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBKDF2params.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PBMAC1params' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBMAC1params.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PKCS9String' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PKCS9String.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Pentanomial' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Pentanomial.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PersonalName' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PersonalName.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PolicyInformation' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyInformation.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PolicyMappings' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyMappings.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PolicyQualifierId' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyQualifierId.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PolicyQualifierInfo' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyQualifierInfo.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PostalAddress' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PostalAddress.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Prime_p' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Prime_p.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PrivateDomainName' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateDomainName.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PrivateKey' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKey.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PrivateKeyInfo' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKeyInfo.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PrivateKeyUsagePeriod' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKeyUsagePeriod.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PublicKey' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKey.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PublicKeyAndChallenge' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKeyAndChallenge.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\PublicKeyInfo' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKeyInfo.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\RC2CBCParameter' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RC2CBCParameter.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\RDNSequence' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RDNSequence.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\RSAPrivateKey' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSAPrivateKey.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\RSAPublicKey' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSAPublicKey.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\RSASSA_PSS_params' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSASSA_PSS_params.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\ReasonFlags' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ReasonFlags.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\RelativeDistinguishedName' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RelativeDistinguishedName.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\RevokedCertificate' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RevokedCertificate.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\SignedPublicKeyAndChallenge' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SignedPublicKeyAndChallenge.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\SpecifiedECDomain' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SpecifiedECDomain.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\SubjectAltName' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectAltName.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\SubjectDirectoryAttributes' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectDirectoryAttributes.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\SubjectInfoAccessSyntax' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectInfoAccessSyntax.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\SubjectPublicKeyInfo' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectPublicKeyInfo.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\TBSCertList' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TBSCertList.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\TBSCertificate' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TBSCertificate.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\TerminalIdentifier' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TerminalIdentifier.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Time' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Time.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Trinomial' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Trinomial.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\UniqueIdentifier' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/UniqueIdentifier.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\UserNotice' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/UserNotice.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\Validity' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Validity.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\netscape_ca_policy_url' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_ca_policy_url.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\netscape_cert_type' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_cert_type.php',
+ 'phpseclib3\\File\\ASN1\\Maps\\netscape_comment' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_comment.php',
+ 'phpseclib3\\File\\X509' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/File/X509.php',
+ 'phpseclib3\\Math\\BigInteger' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\BCMath' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\BCMath\\Base' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Base.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\BCMath\\BuiltIn' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/BuiltIn.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\BCMath\\DefaultEngine' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/DefaultEngine.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\BCMath\\OpenSSL' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/OpenSSL.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\BCMath\\Reductions\\Barrett' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Reductions/Barrett.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\BCMath\\Reductions\\EvalBarrett' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Reductions/EvalBarrett.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\Engine' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/Engine.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\GMP' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/GMP.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\GMP\\DefaultEngine' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/GMP/DefaultEngine.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\OpenSSL' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/OpenSSL.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP32' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP32.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP64' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP64.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP\\Base' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Base.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP\\DefaultEngine' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/DefaultEngine.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP\\Montgomery' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Montgomery.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP\\OpenSSL' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/OpenSSL.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP\\Reductions\\Barrett' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Barrett.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP\\Reductions\\Classic' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Classic.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP\\Reductions\\EvalBarrett' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/EvalBarrett.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP\\Reductions\\Montgomery' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Montgomery.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP\\Reductions\\MontgomeryMult' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/MontgomeryMult.php',
+ 'phpseclib3\\Math\\BigInteger\\Engines\\PHP\\Reductions\\PowerOfTwo' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/PowerOfTwo.php',
+ 'phpseclib3\\Math\\BinaryField' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BinaryField.php',
+ 'phpseclib3\\Math\\BinaryField\\Integer' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/BinaryField/Integer.php',
+ 'phpseclib3\\Math\\Common\\FiniteField' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/Common/FiniteField.php',
+ 'phpseclib3\\Math\\Common\\FiniteField\\Integer' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/Common/FiniteField/Integer.php',
+ 'phpseclib3\\Math\\PrimeField' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/PrimeField.php',
+ 'phpseclib3\\Math\\PrimeField\\Integer' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Math/PrimeField/Integer.php',
+ 'phpseclib3\\Net\\SFTP' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Net/SFTP.php',
+ 'phpseclib3\\Net\\SFTP\\Stream' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Net/SFTP/Stream.php',
+ 'phpseclib3\\Net\\SSH2' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/Net/SSH2.php',
+ 'phpseclib3\\System\\SSH\\Agent' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/System/SSH/Agent.php',
+ 'phpseclib3\\System\\SSH\\Agent\\Identity' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/System/SSH/Agent/Identity.php',
+ 'phpseclib3\\System\\SSH\\Common\\Traits\\ReadBytes' => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib/System/SSH/Common/Traits/ReadBytes.php',
+ 'phpseclib\\Crypt\\AES' => __DIR__ . '/..' . '/phpseclib/phpseclib2_compat/src/Crypt/AES.php',
+ 'phpseclib\\Crypt\\Base' => __DIR__ . '/..' . '/phpseclib/phpseclib2_compat/src/Crypt/Base.php',
+ 'phpseclib\\Crypt\\Blowfish' => __DIR__ . '/..' . '/phpseclib/phpseclib2_compat/src/Crypt/Blowfish.php',
+ 'phpseclib\\Crypt\\DES' => __DIR__ . '/..' . '/phpseclib/phpseclib2_compat/src/Crypt/DES.php',
+ 'phpseclib\\Crypt\\Hash' => __DIR__ . '/..' . '/phpseclib/phpseclib2_compat/src/Crypt/Hash.php',
+ 'phpseclib\\Crypt\\RC2' => __DIR__ . '/..' . '/phpseclib/phpseclib2_compat/src/Crypt/RC2.php',
+ 'phpseclib\\Crypt\\RC4' => __DIR__ . '/..' . '/phpseclib/phpseclib2_compat/src/Crypt/RC4.php',
+ 'phpseclib\\Crypt\\RSA' => __DIR__ . '/..' . '/phpseclib/phpseclib2_compat/src/Crypt/RSA.php',
+ 'phpseclib\\Crypt\\Random' => __DIR__ . '/..' . '/phpseclib/phpseclib2_compat/src/Crypt/Random.php',
+ 'phpseclib\\Crypt\\Rijndael' => __DIR__ . '/..' . '/phpseclib/phpseclib2_compat/src/Crypt/Rijndael.php',
+ 'phpseclib\\Crypt\\TripleDES' => __DIR__ . '/..' . '/phpseclib/phpseclib2_compat/src/Crypt/TripleDES.php',
+ 'phpseclib\\Crypt\\Twofish' => __DIR__ . '/..' . '/phpseclib/phpseclib2_compat/src/Crypt/Twofish.php',
+ 'phpseclib\\File\\ANSI' => __DIR__ . '/..' . '/phpseclib/phpseclib2_compat/src/File/ANSI.php',
+ 'phpseclib\\File\\ASN1' => __DIR__ . '/..' . '/phpseclib/phpseclib2_compat/src/File/ASN1.php',
+ 'phpseclib\\File\\ASN1\\Element' => __DIR__ . '/..' . '/phpseclib/phpseclib2_compat/src/File/ASN1/Element.php',
+ 'phpseclib\\File\\X509' => __DIR__ . '/..' . '/phpseclib/phpseclib2_compat/src/File/X509.php',
+ 'phpseclib\\Math\\BigInteger' => __DIR__ . '/..' . '/phpseclib/phpseclib2_compat/src/Math/BigInteger.php',
+ 'phpseclib\\Net\\SFTP' => __DIR__ . '/..' . '/phpseclib/phpseclib2_compat/src/Net/SFTP.php',
+ 'phpseclib\\Net\\SFTP\\Stream' => __DIR__ . '/..' . '/phpseclib/phpseclib2_compat/src/Net/SFTP/Stream.php',
+ 'phpseclib\\Net\\SSH2' => __DIR__ . '/..' . '/phpseclib/phpseclib2_compat/src/Net/SSH2.php',
+ 'phpseclib\\System\\SSH\\Agent' => __DIR__ . '/..' . '/phpseclib/phpseclib2_compat/src/System/SSH/Agent.php',
+ 'phpseclib\\System\\SSH\\Agent\\Identity' => __DIR__ . '/..' . '/phpseclib/phpseclib2_compat/src/System/SSH/Agent/Identity.php',
'voku\\helper\\ASCII' => __DIR__ . '/..' . '/voku/portable-ascii/src/voku/helper/ASCII.php',
'voku\\helper\\StopWords' => __DIR__ . '/..' . '/voku/stop-words/src/voku/helper/StopWords.php',
'voku\\helper\\StopWordsLanguageNotExists' => __DIR__ . '/..' . '/voku/stop-words/src/voku/helper/StopWordsLanguageNotExists.php',
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index 17c42b0a7..de089f123 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -1,6 +1,91 @@
{
"packages": [
{
+ "name": "bakame/http-structured-fields",
+ "version": "2.0.0",
+ "version_normalized": "2.0.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/bakame-php/http-structured-fields.git",
+ "reference": "d0fc193e5b173a4e90f2fa589d5b97b2b653b323"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/bakame-php/http-structured-fields/zipball/d0fc193e5b173a4e90f2fa589d5b97b2b653b323",
+ "reference": "d0fc193e5b173a4e90f2fa589d5b97b2b653b323",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.1"
+ },
+ "require-dev": {
+ "bakame/aide-base32": "dev-main",
+ "friendsofphp/php-cs-fixer": "^3.65.0",
+ "httpwg/structured-field-tests": "*@dev",
+ "phpbench/phpbench": "^1.3.1",
+ "phpstan/phpstan": "^2.0.3",
+ "phpstan/phpstan-deprecation-rules": "^2.0.1",
+ "phpstan/phpstan-phpunit": "^2.0.1",
+ "phpstan/phpstan-strict-rules": "^2.0",
+ "phpunit/phpunit": "^10.5.38 || ^11.5.0",
+ "symfony/var-dumper": "^6.4.15 || ^v7.2.0"
+ },
+ "time": "2024-12-12T08:25:42+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-develop": "1.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Bakame\\Http\\StructuredFields\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ignace Nyamagana Butera",
+ "email": "nyamsprod@gmail.com",
+ "homepage": "https://github.com/nyamsprod/",
+ "role": "Developer"
+ }
+ ],
+ "description": "A PHP library that parses, validates and serializes HTTP structured fields according to RFC9561 and RFC8941",
+ "keywords": [
+ "headers",
+ "http",
+ "http headers",
+ "http trailers",
+ "parser",
+ "rfc8941",
+ "rfc9651",
+ "serializer",
+ "structured fields",
+ "structured headers",
+ "structured trailers",
+ "structured values",
+ "trailers",
+ "validation"
+ ],
+ "support": {
+ "docs": "https://github.com/bakame-php/http-structured-fields",
+ "issues": "https://github.com/bakame-php/http-structured-fields/issues",
+ "source": "https://github.com/bakame-php/http-structured-fields"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sponsors/nyamsprod",
+ "type": "github"
+ }
+ ],
+ "install-path": "../bakame/http-structured-fields"
+ },
+ {
"name": "blueimp/jquery-file-upload",
"version": "v10.32.0",
"version_normalized": "10.32.0.0",
@@ -522,6 +607,125 @@
"install-path": "../ezyang/htmlpurifier"
},
{
+ "name": "guzzlehttp/psr7",
+ "version": "2.7.1",
+ "version_normalized": "2.7.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/psr7.git",
+ "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16",
+ "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5 || ^8.0",
+ "psr/http-factory": "^1.0",
+ "psr/http-message": "^1.1 || ^2.0",
+ "ralouphie/getallheaders": "^3.0"
+ },
+ "provide": {
+ "psr/http-factory-implementation": "1.0",
+ "psr/http-message-implementation": "1.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "http-interop/http-factory-tests": "0.9.0",
+ "phpunit/phpunit": "^8.5.39 || ^9.6.20"
+ },
+ "suggest": {
+ "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
+ },
+ "time": "2025-03-27T12:30:47+00:00",
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Psr7\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "George Mponos",
+ "email": "gmponos@gmail.com",
+ "homepage": "https://github.com/gmponos"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://github.com/sagikazarmark"
+ },
+ {
+ "name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
+ "homepage": "https://github.com/Tobion"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://sagikazarmark.hu"
+ }
+ ],
+ "description": "PSR-7 message implementation that also provides common utility methods",
+ "keywords": [
+ "http",
+ "message",
+ "psr-7",
+ "request",
+ "response",
+ "stream",
+ "uri",
+ "url"
+ ],
+ "support": {
+ "issues": "https://github.com/guzzle/psr7/issues",
+ "source": "https://github.com/guzzle/psr7/tree/2.7.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/Nyholm",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
+ "type": "tidelift"
+ }
+ ],
+ "install-path": "../guzzlehttp/psr7"
+ },
+ {
"name": "jbroadway/urlify",
"version": "1.2.5-stable",
"version_normalized": "1.2.5.0",
@@ -907,6 +1111,52 @@
"install-path": "../lukasreschke/id3parser"
},
{
+ "name": "macgirvin/http-message-signer",
+ "version": "v0.2.2",
+ "version_normalized": "0.2.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/macgirvin/HTTP-Message-Signer.git",
+ "reference": "47604de860b822cd202dcd8b1da910d6c84720ab"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/macgirvin/HTTP-Message-Signer/zipball/47604de860b822cd202dcd8b1da910d6c84720ab",
+ "reference": "47604de860b822cd202dcd8b1da910d6c84720ab",
+ "shasum": ""
+ },
+ "require": {
+ "bakame/http-structured-fields": "^2.0",
+ "ext-openssl": "*",
+ "guzzlehttp/psr7": "^2.0",
+ "php": "^8.1",
+ "phpseclib/phpseclib": "~3.0",
+ "phpseclib/phpseclib2_compat": "^1.0",
+ "psr/http-message": "^2.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "time": "2025-07-10T01:13:05+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "HttpSignature\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "description": "RFC 9421 HTTP Message Signer and Verifier for PSR-7 requests",
+ "support": {
+ "issues": "https://github.com/macgirvin/HTTP-Message-Signer/issues",
+ "source": "https://github.com/macgirvin/HTTP-Message-Signer/tree/v0.2.2"
+ },
+ "install-path": "../macgirvin/http-message-signer"
+ },
+ {
"name": "michelf/php-markdown",
"version": "2.0.0",
"version_normalized": "2.0.0.0",
@@ -1097,6 +1347,59 @@
"install-path": "../paragonie/constant_time_encoding"
},
{
+ "name": "paragonie/random_compat",
+ "version": "v9.99.100",
+ "version_normalized": "9.99.100.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/paragonie/random_compat.git",
+ "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
+ "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">= 7"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "4.*|5.*",
+ "vimeo/psalm": "^1"
+ },
+ "suggest": {
+ "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
+ },
+ "time": "2020-10-15T08:29:30+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Paragon Initiative Enterprises",
+ "email": "security@paragonie.com",
+ "homepage": "https://paragonie.com"
+ }
+ ],
+ "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
+ "keywords": [
+ "csprng",
+ "polyfill",
+ "pseudorandom",
+ "random"
+ ],
+ "support": {
+ "email": "info@paragonie.com",
+ "issues": "https://github.com/paragonie/random_compat/issues",
+ "source": "https://github.com/paragonie/random_compat"
+ },
+ "install-path": "../paragonie/random_compat"
+ },
+ {
"name": "patrickschur/language-detection",
"version": "v5.3.1",
"version_normalized": "5.3.1.0",
@@ -1203,35 +1506,35 @@
},
{
"name": "phpseclib/phpseclib",
- "version": "2.0.48",
- "version_normalized": "2.0.48.0",
+ "version": "3.0.46",
+ "version_normalized": "3.0.46.0",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
- "reference": "eaa7be704b8b93a6913b69eb7f645a59d7731b61"
+ "reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/eaa7be704b8b93a6913b69eb7f645a59d7731b61",
- "reference": "eaa7be704b8b93a6913b69eb7f645a59d7731b61",
+ "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6",
+ "reference": "56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6",
"shasum": ""
},
"require": {
- "php": ">=5.3.3"
+ "paragonie/constant_time_encoding": "^1|^2|^3",
+ "paragonie/random_compat": "^1.4|^2.0|^9.99.99",
+ "php": ">=5.6.1"
},
"require-dev": {
- "phing/phing": "~2.7",
- "phpunit/phpunit": "^4.8.35|^5.7|^6.0|^9.4",
- "squizlabs/php_codesniffer": "~2.0"
+ "phpunit/phpunit": "*"
},
"suggest": {
+ "ext-dom": "Install the DOM extension to load XML formatted public keys.",
"ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
"ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
- "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations.",
- "ext-xml": "Install the XML extension to load XML formatted public keys."
+ "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
},
- "time": "2024-12-14T21:03:54+00:00",
+ "time": "2025-06-26T16:29:55+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@@ -1239,7 +1542,7 @@
"phpseclib/bootstrap.php"
],
"psr-4": {
- "phpseclib\\": "phpseclib/"
+ "phpseclib3\\": "phpseclib/"
}
},
"notification-url": "https://packagist.org/downloads/",
@@ -1296,7 +1599,7 @@
],
"support": {
"issues": "https://github.com/phpseclib/phpseclib/issues",
- "source": "https://github.com/phpseclib/phpseclib/tree/2.0.48"
+ "source": "https://github.com/phpseclib/phpseclib/tree/3.0.46"
},
"funding": [
{
@@ -1315,6 +1618,57 @@
"install-path": "../phpseclib/phpseclib"
},
{
+ "name": "phpseclib/phpseclib2_compat",
+ "version": "1.0.6",
+ "version_normalized": "1.0.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpseclib/phpseclib2_compat.git",
+ "reference": "90976f25d6c2ff936878624b9cfaa322db11dde7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpseclib/phpseclib2_compat/zipball/90976f25d6c2ff936878624b9cfaa322db11dde7",
+ "reference": "90976f25d6c2ff936878624b9cfaa322db11dde7",
+ "shasum": ""
+ },
+ "require": {
+ "phpseclib/phpseclib": "^3.0"
+ },
+ "provide": {
+ "phpseclib/phpseclib": "2.0.47"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5.7|^6.0|^9.4"
+ },
+ "time": "2024-02-26T14:37:15+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "phpseclib\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jim Wigginton",
+ "email": "terrafrost@php.net",
+ "role": "Lead Developer"
+ }
+ ],
+ "description": "phpseclib 2.0 polyfill built with phpseclib 3.0",
+ "homepage": "https://github.com/phpseclib/phpseclib2_compat",
+ "support": {
+ "issues": "https://github.com/phpseclib/phpseclib2_compat/issues",
+ "source": "https://github.com/phpseclib/phpseclib2_compat"
+ },
+ "install-path": "../phpseclib/phpseclib2_compat"
+ },
+ {
"name": "psr/clock",
"version": "1.0.0",
"version_normalized": "1.0.0.0",
@@ -1533,6 +1887,53 @@
"install-path": "../psr/log"
},
{
+ "name": "ralouphie/getallheaders",
+ "version": "3.0.3",
+ "version_normalized": "3.0.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ralouphie/getallheaders.git",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "^2.1",
+ "phpunit/phpunit": "^5 || ^6.5"
+ },
+ "time": "2019-03-08T08:55:37+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "src/getallheaders.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ralph Khattar",
+ "email": "ralph.khattar@gmail.com"
+ }
+ ],
+ "description": "A polyfill for getallheaders.",
+ "support": {
+ "issues": "https://github.com/ralouphie/getallheaders/issues",
+ "source": "https://github.com/ralouphie/getallheaders/tree/develop"
+ },
+ "install-path": "../ralouphie/getallheaders"
+ },
+ {
"name": "ramsey/collection",
"version": "2.1.1",
"version_normalized": "2.1.1.0",
diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php
index 073738a4d..fe56b9d2d 100644
--- a/vendor/composer/installed.php
+++ b/vendor/composer/installed.php
@@ -1,15 +1,24 @@
<?php return array(
'root' => array(
'name' => 'zotlabs/hubzilla',
- 'pretty_version' => 'dev-10.2RC',
- 'version' => 'dev-10.2RC',
- 'reference' => '0d51ff1906df6e02f2aa028b753f25ae988d5d64',
+ 'pretty_version' => 'dev-10.4RC',
+ 'version' => 'dev-10.4RC',
+ 'reference' => '43ebf69d09a9cd779a9fcc70ea642632bb0aeeae',
'type' => 'application',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev' => false,
),
'versions' => array(
+ 'bakame/http-structured-fields' => array(
+ 'pretty_version' => '2.0.0',
+ 'version' => '2.0.0.0',
+ 'reference' => 'd0fc193e5b173a4e90f2fa589d5b97b2b653b323',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../bakame/http-structured-fields',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
'blueimp/jquery-file-upload' => array(
'pretty_version' => 'v10.32.0',
'version' => '10.32.0.0',
@@ -82,6 +91,15 @@
'aliases' => array(),
'dev_requirement' => false,
),
+ 'guzzlehttp/psr7' => array(
+ 'pretty_version' => '2.7.1',
+ 'version' => '2.7.1.0',
+ 'reference' => 'c2270caaabe631b3b44c85f99e5a04bbb8060d16',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../guzzlehttp/psr7',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
'jbroadway/urlify' => array(
'pretty_version' => '1.2.5-stable',
'version' => '1.2.5.0',
@@ -127,6 +145,15 @@
'aliases' => array(),
'dev_requirement' => false,
),
+ 'macgirvin/http-message-signer' => array(
+ 'pretty_version' => 'v0.2.2',
+ 'version' => '0.2.2.0',
+ 'reference' => '47604de860b822cd202dcd8b1da910d6c84720ab',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../macgirvin/http-message-signer',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
'michelf/php-markdown' => array(
'pretty_version' => '2.0.0',
'version' => '2.0.0.0',
@@ -154,6 +181,15 @@
'aliases' => array(),
'dev_requirement' => false,
),
+ 'paragonie/random_compat' => array(
+ 'pretty_version' => 'v9.99.100',
+ 'version' => '9.99.100.0',
+ 'reference' => '996434e5492cb4c3edcb9168db6fbb1359ef965a',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../paragonie/random_compat',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
'patrickschur/language-detection' => array(
'pretty_version' => 'v5.3.1',
'version' => '5.3.1.0',
@@ -173,13 +209,25 @@
'dev_requirement' => false,
),
'phpseclib/phpseclib' => array(
- 'pretty_version' => '2.0.48',
- 'version' => '2.0.48.0',
- 'reference' => 'eaa7be704b8b93a6913b69eb7f645a59d7731b61',
+ 'pretty_version' => '3.0.46',
+ 'version' => '3.0.46.0',
+ 'reference' => '56483a7de62a6c2a6635e42e93b8a9e25d4f0ec6',
'type' => 'library',
'install_path' => __DIR__ . '/../phpseclib/phpseclib',
'aliases' => array(),
'dev_requirement' => false,
+ 'provided' => array(
+ 0 => '2.0.47',
+ ),
+ ),
+ 'phpseclib/phpseclib2_compat' => array(
+ 'pretty_version' => '1.0.6',
+ 'version' => '1.0.6.0',
+ 'reference' => '90976f25d6c2ff936878624b9cfaa322db11dde7',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../phpseclib/phpseclib2_compat',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
),
'psr/clock' => array(
'pretty_version' => '1.0.0',
@@ -199,6 +247,12 @@
'aliases' => array(),
'dev_requirement' => false,
),
+ 'psr/http-factory-implementation' => array(
+ 'dev_requirement' => false,
+ 'provided' => array(
+ 0 => '1.0',
+ ),
+ ),
'psr/http-message' => array(
'pretty_version' => '2.0',
'version' => '2.0.0.0',
@@ -208,6 +262,12 @@
'aliases' => array(),
'dev_requirement' => false,
),
+ 'psr/http-message-implementation' => array(
+ 'dev_requirement' => false,
+ 'provided' => array(
+ 0 => '1.0',
+ ),
+ ),
'psr/log' => array(
'pretty_version' => '3.0.2',
'version' => '3.0.2.0',
@@ -217,6 +277,15 @@
'aliases' => array(),
'dev_requirement' => false,
),
+ 'ralouphie/getallheaders' => array(
+ 'pretty_version' => '3.0.3',
+ 'version' => '3.0.3.0',
+ 'reference' => '120b605dfeb996808c31b6477290a714d356e822',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../ralouphie/getallheaders',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
'ramsey/collection' => array(
'pretty_version' => '2.1.1',
'version' => '2.1.1.0',
@@ -428,9 +497,9 @@
'dev_requirement' => false,
),
'zotlabs/hubzilla' => array(
- 'pretty_version' => 'dev-10.2RC',
- 'version' => 'dev-10.2RC',
- 'reference' => '0d51ff1906df6e02f2aa028b753f25ae988d5d64',
+ 'pretty_version' => 'dev-10.4RC',
+ 'version' => 'dev-10.4RC',
+ 'reference' => '43ebf69d09a9cd779a9fcc70ea642632bb0aeeae',
'type' => 'application',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
diff --git a/vendor/guzzlehttp/psr7/CHANGELOG.md b/vendor/guzzlehttp/psr7/CHANGELOG.md
new file mode 100644
index 000000000..a85929521
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/CHANGELOG.md
@@ -0,0 +1,475 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## 2.7.1 - 2025-03-27
+
+### Fixed
+
+- Fixed uppercase IPv6 addresses in URI
+
+### Changed
+
+- Improve uploaded file error message
+
+## 2.7.0 - 2024-07-18
+
+### Added
+
+- Add `Utils::redactUserInfo()` method
+- Add ability to encode bools as ints in `Query::build`
+
+## 2.6.3 - 2024-07-18
+
+### Fixed
+
+- Make `StreamWrapper::stream_stat()` return `false` if inner stream's size is `null`
+
+### Changed
+
+- PHP 8.4 support
+
+## 2.6.2 - 2023-12-03
+
+### Fixed
+
+- Fixed another issue with the fact that PHP transforms numeric strings in array keys to ints
+
+### Changed
+
+- Updated links in docs to their canonical versions
+- Replaced `call_user_func*` with native calls
+
+## 2.6.1 - 2023-08-27
+
+### Fixed
+
+- Properly handle the fact that PHP transforms numeric strings in array keys to ints
+
+## 2.6.0 - 2023-08-03
+
+### Changed
+
+- Updated the mime type map to add some new entries, fix a couple of invalid entries, and remove an invalid entry
+- Fallback to `application/octet-stream` if we are unable to guess the content type for a multipart file upload
+
+## 2.5.1 - 2023-08-03
+
+### Fixed
+
+- Corrected mime type for `.acc` files to `audio/aac`
+
+### Changed
+
+- PHP 8.3 support
+
+## 2.5.0 - 2023-04-17
+
+### Changed
+
+- Adjusted `psr/http-message` version constraint to `^1.1 || ^2.0`
+
+## 2.4.5 - 2023-04-17
+
+### Fixed
+
+- Prevent possible warnings on unset variables in `ServerRequest::normalizeNestedFileSpec`
+- Fixed `Message::bodySummary` when `preg_match` fails
+- Fixed header validation issue
+
+## 2.4.4 - 2023-03-09
+
+### Changed
+
+- Removed the need for `AllowDynamicProperties` in `LazyOpenStream`
+
+## 2.4.3 - 2022-10-26
+
+### Changed
+
+- Replaced `sha1(uniqid())` by `bin2hex(random_bytes(20))`
+
+## 2.4.2 - 2022-10-25
+
+### Fixed
+
+- Fixed erroneous behaviour when combining host and relative path
+
+## 2.4.1 - 2022-08-28
+
+### Fixed
+
+- Rewind body before reading in `Message::bodySummary`
+
+## 2.4.0 - 2022-06-20
+
+### Added
+
+- Added provisional PHP 8.2 support
+- Added `UriComparator::isCrossOrigin` method
+
+## 2.3.0 - 2022-06-09
+
+### Fixed
+
+- Added `Header::splitList` method
+- Added `Utils::tryGetContents` method
+- Improved `Stream::getContents` method
+- Updated mimetype mappings
+
+## 2.2.2 - 2022-06-08
+
+### Fixed
+
+- Fix `Message::parseRequestUri` for numeric headers
+- Re-wrap exceptions thrown in `fread` into runtime exceptions
+- Throw an exception when multipart options is misformatted
+
+## 2.2.1 - 2022-03-20
+
+### Fixed
+
+- Correct header value validation
+
+## 2.2.0 - 2022-03-20
+
+### Added
+
+- A more compressive list of mime types
+- Add JsonSerializable to Uri
+- Missing return types
+
+### Fixed
+
+- Bug MultipartStream no `uri` metadata
+- Bug MultipartStream with filename for `data://` streams
+- Fixed new line handling in MultipartStream
+- Reduced RAM usage when copying streams
+- Updated parsing in `Header::normalize()`
+
+## 2.1.1 - 2022-03-20
+
+### Fixed
+
+- Validate header values properly
+
+## 2.1.0 - 2021-10-06
+
+### Changed
+
+- Attempting to create a `Uri` object from a malformed URI will no longer throw a generic
+ `InvalidArgumentException`, but rather a `MalformedUriException`, which inherits from the former
+ for backwards compatibility. Callers relying on the exception being thrown to detect invalid
+ URIs should catch the new exception.
+
+### Fixed
+
+- Return `null` in caching stream size if remote size is `null`
+
+## 2.0.0 - 2021-06-30
+
+Identical to the RC release.
+
+## 2.0.0@RC-1 - 2021-04-29
+
+### Fixed
+
+- Handle possibly unset `url` in `stream_get_meta_data`
+
+## 2.0.0@beta-1 - 2021-03-21
+
+### Added
+
+- PSR-17 factories
+- Made classes final
+- PHP7 type hints
+
+### Changed
+
+- When building a query string, booleans are represented as 1 and 0.
+
+### Removed
+
+- PHP < 7.2 support
+- All functions in the `GuzzleHttp\Psr7` namespace
+
+## 1.8.1 - 2021-03-21
+
+### Fixed
+
+- Issue parsing IPv6 URLs
+- Issue modifying ServerRequest lost all its attributes
+
+## 1.8.0 - 2021-03-21
+
+### Added
+
+- Locale independent URL parsing
+- Most classes got a `@final` annotation to prepare for 2.0
+
+### Fixed
+
+- Issue when creating stream from `php://input` and curl-ext is not installed
+- Broken `Utils::tryFopen()` on PHP 8
+
+## 1.7.0 - 2020-09-30
+
+### Added
+
+- Replaced functions by static methods
+
+### Fixed
+
+- Converting a non-seekable stream to a string
+- Handle multiple Set-Cookie correctly
+- Ignore array keys in header values when merging
+- Allow multibyte characters to be parsed in `Message:bodySummary()`
+
+### Changed
+
+- Restored partial HHVM 3 support
+
+
+## [1.6.1] - 2019-07-02
+
+### Fixed
+
+- Accept null and bool header values again
+
+
+## [1.6.0] - 2019-06-30
+
+### Added
+
+- Allowed version `^3.0` of `ralouphie/getallheaders` dependency (#244)
+- Added MIME type for WEBP image format (#246)
+- Added more validation of values according to PSR-7 and RFC standards, e.g. status code range (#250, #272)
+
+### Changed
+
+- Tests don't pass with HHVM 4.0, so HHVM support got dropped. Other libraries like composer have done the same. (#262)
+- Accept port number 0 to be valid (#270)
+
+### Fixed
+
+- Fixed subsequent reads from `php://input` in ServerRequest (#247)
+- Fixed readable/writable detection for certain stream modes (#248)
+- Fixed encoding of special characters in the `userInfo` component of an URI (#253)
+
+
+## [1.5.2] - 2018-12-04
+
+### Fixed
+
+- Check body size when getting the message summary
+
+
+## [1.5.1] - 2018-12-04
+
+### Fixed
+
+- Get the summary of a body only if it is readable
+
+
+## [1.5.0] - 2018-12-03
+
+### Added
+
+- Response first-line to response string exception (fixes #145)
+- A test for #129 behavior
+- `get_message_body_summary` function in order to get the message summary
+- `3gp` and `mkv` mime types
+
+### Changed
+
+- Clarify exception message when stream is detached
+
+### Deprecated
+
+- Deprecated parsing folded header lines as per RFC 7230
+
+### Fixed
+
+- Fix `AppendStream::detach` to not close streams
+- `InflateStream` preserves `isSeekable` attribute of the underlying stream
+- `ServerRequest::getUriFromGlobals` to support URLs in query parameters
+
+
+Several other fixes and improvements.
+
+
+## [1.4.2] - 2017-03-20
+
+### Fixed
+
+- Reverted BC break to `Uri::resolve` and `Uri::removeDotSegments` by removing
+ calls to `trigger_error` when deprecated methods are invoked.
+
+
+## [1.4.1] - 2017-02-27
+
+### Added
+
+- Rriggering of silenced deprecation warnings.
+
+### Fixed
+
+- Reverted BC break by reintroducing behavior to automagically fix a URI with a
+ relative path and an authority by adding a leading slash to the path. It's only
+ deprecated now.
+
+
+## [1.4.0] - 2017-02-21
+
+### Added
+
+- Added common URI utility methods based on RFC 3986 (see documentation in the readme):
+ - `Uri::isDefaultPort`
+ - `Uri::isAbsolute`
+ - `Uri::isNetworkPathReference`
+ - `Uri::isAbsolutePathReference`
+ - `Uri::isRelativePathReference`
+ - `Uri::isSameDocumentReference`
+ - `Uri::composeComponents`
+ - `UriNormalizer::normalize`
+ - `UriNormalizer::isEquivalent`
+ - `UriResolver::relativize`
+
+### Changed
+
+- Ensure `ServerRequest::getUriFromGlobals` returns a URI in absolute form.
+- Allow `parse_response` to parse a response without delimiting space and reason.
+- Ensure each URI modification results in a valid URI according to PSR-7 discussions.
+ Invalid modifications will throw an exception instead of returning a wrong URI or
+ doing some magic.
+ - `(new Uri)->withPath('foo')->withHost('example.com')` will throw an exception
+ because the path of a URI with an authority must start with a slash "/" or be empty
+ - `(new Uri())->withScheme('http')` will return `'http://localhost'`
+
+### Deprecated
+
+- `Uri::resolve` in favor of `UriResolver::resolve`
+- `Uri::removeDotSegments` in favor of `UriResolver::removeDotSegments`
+
+### Fixed
+
+- `Stream::read` when length parameter <= 0.
+- `copy_to_stream` reads bytes in chunks instead of `maxLen` into memory.
+- `ServerRequest::getUriFromGlobals` when `Host` header contains port.
+- Compatibility of URIs with `file` scheme and empty host.
+
+
+## [1.3.1] - 2016-06-25
+
+### Fixed
+
+- `Uri::__toString` for network path references, e.g. `//example.org`.
+- Missing lowercase normalization for host.
+- Handling of URI components in case they are `'0'` in a lot of places,
+ e.g. as a user info password.
+- `Uri::withAddedHeader` to correctly merge headers with different case.
+- Trimming of header values in `Uri::withAddedHeader`. Header values may
+ be surrounded by whitespace which should be ignored according to RFC 7230
+ Section 3.2.4. This does not apply to header names.
+- `Uri::withAddedHeader` with an array of header values.
+- `Uri::resolve` when base path has no slash and handling of fragment.
+- Handling of encoding in `Uri::with(out)QueryValue` so one can pass the
+ key/value both in encoded as well as decoded form to those methods. This is
+ consistent with withPath, withQuery etc.
+- `ServerRequest::withoutAttribute` when attribute value is null.
+
+
+## [1.3.0] - 2016-04-13
+
+### Added
+
+- Remaining interfaces needed for full PSR7 compatibility
+ (ServerRequestInterface, UploadedFileInterface, etc.).
+- Support for stream_for from scalars.
+
+### Changed
+
+- Can now extend Uri.
+
+### Fixed
+- A bug in validating request methods by making it more permissive.
+
+
+## [1.2.3] - 2016-02-18
+
+### Fixed
+
+- Support in `GuzzleHttp\Psr7\CachingStream` for seeking forward on remote
+ streams, which can sometimes return fewer bytes than requested with `fread`.
+- Handling of gzipped responses with FNAME headers.
+
+
+## [1.2.2] - 2016-01-22
+
+### Added
+
+- Support for URIs without any authority.
+- Support for HTTP 451 'Unavailable For Legal Reasons.'
+- Support for using '0' as a filename.
+- Support for including non-standard ports in Host headers.
+
+
+## [1.2.1] - 2015-11-02
+
+### Changes
+
+- Now supporting negative offsets when seeking to SEEK_END.
+
+
+## [1.2.0] - 2015-08-15
+
+### Changed
+
+- Body as `"0"` is now properly added to a response.
+- Now allowing forward seeking in CachingStream.
+- Now properly parsing HTTP requests that contain proxy targets in
+ `parse_request`.
+- functions.php is now conditionally required.
+- user-info is no longer dropped when resolving URIs.
+
+
+## [1.1.0] - 2015-06-24
+
+### Changed
+
+- URIs can now be relative.
+- `multipart/form-data` headers are now overridden case-insensitively.
+- URI paths no longer encode the following characters because they are allowed
+ in URIs: "(", ")", "*", "!", "'"
+- A port is no longer added to a URI when the scheme is missing and no port is
+ present.
+
+
+## 1.0.0 - 2015-05-19
+
+Initial release.
+
+Currently unsupported:
+
+- `Psr\Http\Message\ServerRequestInterface`
+- `Psr\Http\Message\UploadedFileInterface`
+
+
+
+[1.6.0]: https://github.com/guzzle/psr7/compare/1.5.2...1.6.0
+[1.5.2]: https://github.com/guzzle/psr7/compare/1.5.1...1.5.2
+[1.5.1]: https://github.com/guzzle/psr7/compare/1.5.0...1.5.1
+[1.5.0]: https://github.com/guzzle/psr7/compare/1.4.2...1.5.0
+[1.4.2]: https://github.com/guzzle/psr7/compare/1.4.1...1.4.2
+[1.4.1]: https://github.com/guzzle/psr7/compare/1.4.0...1.4.1
+[1.4.0]: https://github.com/guzzle/psr7/compare/1.3.1...1.4.0
+[1.3.1]: https://github.com/guzzle/psr7/compare/1.3.0...1.3.1
+[1.3.0]: https://github.com/guzzle/psr7/compare/1.2.3...1.3.0
+[1.2.3]: https://github.com/guzzle/psr7/compare/1.2.2...1.2.3
+[1.2.2]: https://github.com/guzzle/psr7/compare/1.2.1...1.2.2
+[1.2.1]: https://github.com/guzzle/psr7/compare/1.2.0...1.2.1
+[1.2.0]: https://github.com/guzzle/psr7/compare/1.1.0...1.2.0
+[1.1.0]: https://github.com/guzzle/psr7/compare/1.0.0...1.1.0
diff --git a/vendor/guzzlehttp/psr7/LICENSE b/vendor/guzzlehttp/psr7/LICENSE
new file mode 100644
index 000000000..51c7ec81c
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/LICENSE
@@ -0,0 +1,26 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Michael Dowling <mtdowling@gmail.com>
+Copyright (c) 2015 Márk Sági-Kazár <mark.sagikazar@gmail.com>
+Copyright (c) 2015 Graham Campbell <hello@gjcampbell.co.uk>
+Copyright (c) 2016 Tobias Schultze <webmaster@tubo-world.de>
+Copyright (c) 2016 George Mponos <gmponos@gmail.com>
+Copyright (c) 2018 Tobias Nyholm <tobias.nyholm@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/guzzlehttp/psr7/README.md b/vendor/guzzlehttp/psr7/README.md
new file mode 100644
index 000000000..2e9bb0b9b
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/README.md
@@ -0,0 +1,887 @@
+# PSR-7 Message Implementation
+
+This repository contains a full [PSR-7](https://www.php-fig.org/psr/psr-7/)
+message implementation, several stream decorators, and some helpful
+functionality like query string parsing.
+
+![CI](https://github.com/guzzle/psr7/workflows/CI/badge.svg)
+![Static analysis](https://github.com/guzzle/psr7/workflows/Static%20analysis/badge.svg)
+
+
+## Features
+
+This package comes with a number of stream implementations and stream
+decorators.
+
+
+## Installation
+
+```shell
+composer require guzzlehttp/psr7
+```
+
+## Version Guidance
+
+| Version | Status | PHP Version |
+|---------|---------------------|--------------|
+| 1.x | EOL (2024-06-30) | >=5.4,<8.2 |
+| 2.x | Latest | >=7.2.5,<8.5 |
+
+
+## AppendStream
+
+`GuzzleHttp\Psr7\AppendStream`
+
+Reads from multiple streams, one after the other.
+
+```php
+use GuzzleHttp\Psr7;
+
+$a = Psr7\Utils::streamFor('abc, ');
+$b = Psr7\Utils::streamFor('123.');
+$composed = new Psr7\AppendStream([$a, $b]);
+
+$composed->addStream(Psr7\Utils::streamFor(' Above all listen to me'));
+
+echo $composed; // abc, 123. Above all listen to me.
+```
+
+
+## BufferStream
+
+`GuzzleHttp\Psr7\BufferStream`
+
+Provides a buffer stream that can be written to fill a buffer, and read
+from to remove bytes from the buffer.
+
+This stream returns a "hwm" metadata value that tells upstream consumers
+what the configured high water mark of the stream is, or the maximum
+preferred size of the buffer.
+
+```php
+use GuzzleHttp\Psr7;
+
+// When more than 1024 bytes are in the buffer, it will begin returning
+// false to writes. This is an indication that writers should slow down.
+$buffer = new Psr7\BufferStream(1024);
+```
+
+
+## CachingStream
+
+The CachingStream is used to allow seeking over previously read bytes on
+non-seekable streams. This can be useful when transferring a non-seekable
+entity body fails due to needing to rewind the stream (for example, resulting
+from a redirect). Data that is read from the remote stream will be buffered in
+a PHP temp stream so that previously read bytes are cached first in memory,
+then on disk.
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\Utils::streamFor(fopen('http://www.google.com', 'r'));
+$stream = new Psr7\CachingStream($original);
+
+$stream->read(1024);
+echo $stream->tell();
+// 1024
+
+$stream->seek(0);
+echo $stream->tell();
+// 0
+```
+
+
+## DroppingStream
+
+`GuzzleHttp\Psr7\DroppingStream`
+
+Stream decorator that begins dropping data once the size of the underlying
+stream becomes too full.
+
+```php
+use GuzzleHttp\Psr7;
+
+// Create an empty stream
+$stream = Psr7\Utils::streamFor();
+
+// Start dropping data when the stream has more than 10 bytes
+$dropping = new Psr7\DroppingStream($stream, 10);
+
+$dropping->write('01234567890123456789');
+echo $stream; // 0123456789
+```
+
+
+## FnStream
+
+`GuzzleHttp\Psr7\FnStream`
+
+Compose stream implementations based on a hash of functions.
+
+Allows for easy testing and extension of a provided stream without needing
+to create a concrete class for a simple extension point.
+
+```php
+
+use GuzzleHttp\Psr7;
+
+$stream = Psr7\Utils::streamFor('hi');
+$fnStream = Psr7\FnStream::decorate($stream, [
+ 'rewind' => function () use ($stream) {
+ echo 'About to rewind - ';
+ $stream->rewind();
+ echo 'rewound!';
+ }
+]);
+
+$fnStream->rewind();
+// Outputs: About to rewind - rewound!
+```
+
+
+## InflateStream
+
+`GuzzleHttp\Psr7\InflateStream`
+
+Uses PHP's zlib.inflate filter to inflate zlib (HTTP deflate, RFC1950) or gzipped (RFC1952) content.
+
+This stream decorator converts the provided stream to a PHP stream resource,
+then appends the zlib.inflate filter. The stream is then converted back
+to a Guzzle stream resource to be used as a Guzzle stream.
+
+
+## LazyOpenStream
+
+`GuzzleHttp\Psr7\LazyOpenStream`
+
+Lazily reads or writes to a file that is opened only after an IO operation
+take place on the stream.
+
+```php
+use GuzzleHttp\Psr7;
+
+$stream = new Psr7\LazyOpenStream('/path/to/file', 'r');
+// The file has not yet been opened...
+
+echo $stream->read(10);
+// The file is opened and read from only when needed.
+```
+
+
+## LimitStream
+
+`GuzzleHttp\Psr7\LimitStream`
+
+LimitStream can be used to read a subset or slice of an existing stream object.
+This can be useful for breaking a large file into smaller pieces to be sent in
+chunks (e.g. Amazon S3's multipart upload API).
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\Utils::streamFor(fopen('/tmp/test.txt', 'r+'));
+echo $original->getSize();
+// >>> 1048576
+
+// Limit the size of the body to 1024 bytes and start reading from byte 2048
+$stream = new Psr7\LimitStream($original, 1024, 2048);
+echo $stream->getSize();
+// >>> 1024
+echo $stream->tell();
+// >>> 0
+```
+
+
+## MultipartStream
+
+`GuzzleHttp\Psr7\MultipartStream`
+
+Stream that when read returns bytes for a streaming multipart or
+multipart/form-data stream.
+
+
+## NoSeekStream
+
+`GuzzleHttp\Psr7\NoSeekStream`
+
+NoSeekStream wraps a stream and does not allow seeking.
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\Utils::streamFor('foo');
+$noSeek = new Psr7\NoSeekStream($original);
+
+echo $noSeek->read(3);
+// foo
+var_export($noSeek->isSeekable());
+// false
+$noSeek->seek(0);
+var_export($noSeek->read(3));
+// NULL
+```
+
+
+## PumpStream
+
+`GuzzleHttp\Psr7\PumpStream`
+
+Provides a read only stream that pumps data from a PHP callable.
+
+When invoking the provided callable, the PumpStream will pass the amount of
+data requested to read to the callable. The callable can choose to ignore
+this value and return fewer or more bytes than requested. Any extra data
+returned by the provided callable is buffered internally until drained using
+the read() function of the PumpStream. The provided callable MUST return
+false when there is no more data to read.
+
+
+## Implementing stream decorators
+
+Creating a stream decorator is very easy thanks to the
+`GuzzleHttp\Psr7\StreamDecoratorTrait`. This trait provides methods that
+implement `Psr\Http\Message\StreamInterface` by proxying to an underlying
+stream. Just `use` the `StreamDecoratorTrait` and implement your custom
+methods.
+
+For example, let's say we wanted to call a specific function each time the last
+byte is read from a stream. This could be implemented by overriding the
+`read()` method.
+
+```php
+use Psr\Http\Message\StreamInterface;
+use GuzzleHttp\Psr7\StreamDecoratorTrait;
+
+class EofCallbackStream implements StreamInterface
+{
+ use StreamDecoratorTrait;
+
+ private $callback;
+
+ private $stream;
+
+ public function __construct(StreamInterface $stream, callable $cb)
+ {
+ $this->stream = $stream;
+ $this->callback = $cb;
+ }
+
+ public function read($length)
+ {
+ $result = $this->stream->read($length);
+
+ // Invoke the callback when EOF is hit.
+ if ($this->eof()) {
+ ($this->callback)();
+ }
+
+ return $result;
+ }
+}
+```
+
+This decorator could be added to any existing stream and used like so:
+
+```php
+use GuzzleHttp\Psr7;
+
+$original = Psr7\Utils::streamFor('foo');
+
+$eofStream = new EofCallbackStream($original, function () {
+ echo 'EOF!';
+});
+
+$eofStream->read(2);
+$eofStream->read(1);
+// echoes "EOF!"
+$eofStream->seek(0);
+$eofStream->read(3);
+// echoes "EOF!"
+```
+
+
+## PHP StreamWrapper
+
+You can use the `GuzzleHttp\Psr7\StreamWrapper` class if you need to use a
+PSR-7 stream as a PHP stream resource.
+
+Use the `GuzzleHttp\Psr7\StreamWrapper::getResource()` method to create a PHP
+stream from a PSR-7 stream.
+
+```php
+use GuzzleHttp\Psr7\StreamWrapper;
+
+$stream = GuzzleHttp\Psr7\Utils::streamFor('hello!');
+$resource = StreamWrapper::getResource($stream);
+echo fread($resource, 6); // outputs hello!
+```
+
+
+# Static API
+
+There are various static methods available under the `GuzzleHttp\Psr7` namespace.
+
+
+## `GuzzleHttp\Psr7\Message::toString`
+
+`public static function toString(MessageInterface $message): string`
+
+Returns the string representation of an HTTP message.
+
+```php
+$request = new GuzzleHttp\Psr7\Request('GET', 'http://example.com');
+echo GuzzleHttp\Psr7\Message::toString($request);
+```
+
+
+## `GuzzleHttp\Psr7\Message::bodySummary`
+
+`public static function bodySummary(MessageInterface $message, int $truncateAt = 120): string|null`
+
+Get a short summary of the message body.
+
+Will return `null` if the response is not printable.
+
+
+## `GuzzleHttp\Psr7\Message::rewindBody`
+
+`public static function rewindBody(MessageInterface $message): void`
+
+Attempts to rewind a message body and throws an exception on failure.
+
+The body of the message will only be rewound if a call to `tell()`
+returns a value other than `0`.
+
+
+## `GuzzleHttp\Psr7\Message::parseMessage`
+
+`public static function parseMessage(string $message): array`
+
+Parses an HTTP message into an associative array.
+
+The array contains the "start-line" key containing the start line of
+the message, "headers" key containing an associative array of header
+array values, and a "body" key containing the body of the message.
+
+
+## `GuzzleHttp\Psr7\Message::parseRequestUri`
+
+`public static function parseRequestUri(string $path, array $headers): string`
+
+Constructs a URI for an HTTP request message.
+
+
+## `GuzzleHttp\Psr7\Message::parseRequest`
+
+`public static function parseRequest(string $message): Request`
+
+Parses a request message string into a request object.
+
+
+## `GuzzleHttp\Psr7\Message::parseResponse`
+
+`public static function parseResponse(string $message): Response`
+
+Parses a response message string into a response object.
+
+
+## `GuzzleHttp\Psr7\Header::parse`
+
+`public static function parse(string|array $header): array`
+
+Parse an array of header values containing ";" separated data into an
+array of associative arrays representing the header key value pair data
+of the header. When a parameter does not contain a value, but just
+contains a key, this function will inject a key with a '' string value.
+
+
+## `GuzzleHttp\Psr7\Header::splitList`
+
+`public static function splitList(string|string[] $header): string[]`
+
+Splits a HTTP header defined to contain a comma-separated list into
+each individual value:
+
+```
+$knownEtags = Header::splitList($request->getHeader('if-none-match'));
+```
+
+Example headers include `accept`, `cache-control` and `if-none-match`.
+
+
+## `GuzzleHttp\Psr7\Header::normalize` (deprecated)
+
+`public static function normalize(string|array $header): array`
+
+`Header::normalize()` is deprecated in favor of [`Header::splitList()`](README.md#guzzlehttppsr7headersplitlist)
+which performs the same operation with a cleaned up API and improved
+documentation.
+
+Converts an array of header values that may contain comma separated
+headers into an array of headers with no comma separated values.
+
+
+## `GuzzleHttp\Psr7\Query::parse`
+
+`public static function parse(string $str, int|bool $urlEncoding = true): array`
+
+Parse a query string into an associative array.
+
+If multiple values are found for the same key, the value of that key
+value pair will become an array. This function does not parse nested
+PHP style arrays into an associative array (e.g., `foo[a]=1&foo[b]=2`
+will be parsed into `['foo[a]' => '1', 'foo[b]' => '2'])`.
+
+
+## `GuzzleHttp\Psr7\Query::build`
+
+`public static function build(array $params, int|false $encoding = PHP_QUERY_RFC3986, bool $treatBoolsAsInts = true): string`
+
+Build a query string from an array of key value pairs.
+
+This function can use the return value of `parse()` to build a query
+string. This function does not modify the provided keys when an array is
+encountered (like `http_build_query()` would).
+
+
+## `GuzzleHttp\Psr7\Utils::caselessRemove`
+
+`public static function caselessRemove(iterable<string> $keys, $keys, array $data): array`
+
+Remove the items given by the keys, case insensitively from the data.
+
+
+## `GuzzleHttp\Psr7\Utils::copyToStream`
+
+`public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void`
+
+Copy the contents of a stream into another stream until the given number
+of bytes have been read.
+
+
+## `GuzzleHttp\Psr7\Utils::copyToString`
+
+`public static function copyToString(StreamInterface $stream, int $maxLen = -1): string`
+
+Copy the contents of a stream into a string until the given number of
+bytes have been read.
+
+
+## `GuzzleHttp\Psr7\Utils::hash`
+
+`public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string`
+
+Calculate a hash of a stream.
+
+This method reads the entire stream to calculate a rolling hash, based on
+PHP's `hash_init` functions.
+
+
+## `GuzzleHttp\Psr7\Utils::modifyRequest`
+
+`public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface`
+
+Clone and modify a request with the given changes.
+
+This method is useful for reducing the number of clones needed to mutate
+a message.
+
+- method: (string) Changes the HTTP method.
+- set_headers: (array) Sets the given headers.
+- remove_headers: (array) Remove the given headers.
+- body: (mixed) Sets the given body.
+- uri: (UriInterface) Set the URI.
+- query: (string) Set the query string value of the URI.
+- version: (string) Set the protocol version.
+
+
+## `GuzzleHttp\Psr7\Utils::readLine`
+
+`public static function readLine(StreamInterface $stream, ?int $maxLength = null): string`
+
+Read a line from the stream up to the maximum allowed buffer length.
+
+
+## `GuzzleHttp\Psr7\Utils::redactUserInfo`
+
+`public static function redactUserInfo(UriInterface $uri): UriInterface`
+
+Redact the password in the user info part of a URI.
+
+
+## `GuzzleHttp\Psr7\Utils::streamFor`
+
+`public static function streamFor(resource|string|null|int|float|bool|StreamInterface|callable|\Iterator $resource = '', array $options = []): StreamInterface`
+
+Create a new stream based on the input type.
+
+Options is an associative array that can contain the following keys:
+
+- metadata: Array of custom metadata.
+- size: Size of the stream.
+
+This method accepts the following `$resource` types:
+
+- `Psr\Http\Message\StreamInterface`: Returns the value as-is.
+- `string`: Creates a stream object that uses the given string as the contents.
+- `resource`: Creates a stream object that wraps the given PHP stream resource.
+- `Iterator`: If the provided value implements `Iterator`, then a read-only
+ stream object will be created that wraps the given iterable. Each time the
+ stream is read from, data from the iterator will fill a buffer and will be
+ continuously called until the buffer is equal to the requested read size.
+ Subsequent read calls will first read from the buffer and then call `next`
+ on the underlying iterator until it is exhausted.
+- `object` with `__toString()`: If the object has the `__toString()` method,
+ the object will be cast to a string and then a stream will be returned that
+ uses the string value.
+- `NULL`: When `null` is passed, an empty stream object is returned.
+- `callable` When a callable is passed, a read-only stream object will be
+ created that invokes the given callable. The callable is invoked with the
+ number of suggested bytes to read. The callable can return any number of
+ bytes, but MUST return `false` when there is no more data to return. The
+ stream object that wraps the callable will invoke the callable until the
+ number of requested bytes are available. Any additional bytes will be
+ buffered and used in subsequent reads.
+
+```php
+$stream = GuzzleHttp\Psr7\Utils::streamFor('foo');
+$stream = GuzzleHttp\Psr7\Utils::streamFor(fopen('/path/to/file', 'r'));
+
+$generator = function ($bytes) {
+ for ($i = 0; $i < $bytes; $i++) {
+ yield ' ';
+ }
+}
+
+$stream = GuzzleHttp\Psr7\Utils::streamFor($generator(100));
+```
+
+
+## `GuzzleHttp\Psr7\Utils::tryFopen`
+
+`public static function tryFopen(string $filename, string $mode): resource`
+
+Safely opens a PHP stream resource using a filename.
+
+When fopen fails, PHP normally raises a warning. This function adds an
+error handler that checks for errors and throws an exception instead.
+
+
+## `GuzzleHttp\Psr7\Utils::tryGetContents`
+
+`public static function tryGetContents(resource $stream): string`
+
+Safely gets the contents of a given stream.
+
+When stream_get_contents fails, PHP normally raises a warning. This
+function adds an error handler that checks for errors and throws an
+exception instead.
+
+
+## `GuzzleHttp\Psr7\Utils::uriFor`
+
+`public static function uriFor(string|UriInterface $uri): UriInterface`
+
+Returns a UriInterface for the given value.
+
+This function accepts a string or UriInterface and returns a
+UriInterface for the given value. If the value is already a
+UriInterface, it is returned as-is.
+
+
+## `GuzzleHttp\Psr7\MimeType::fromFilename`
+
+`public static function fromFilename(string $filename): string|null`
+
+Determines the mimetype of a file by looking at its extension.
+
+
+## `GuzzleHttp\Psr7\MimeType::fromExtension`
+
+`public static function fromExtension(string $extension): string|null`
+
+Maps a file extensions to a mimetype.
+
+
+## Upgrading from Function API
+
+The static API was first introduced in 1.7.0, in order to mitigate problems with functions conflicting between global and local copies of the package. The function API was removed in 2.0.0. A migration table has been provided here for your convenience:
+
+| Original Function | Replacement Method |
+|----------------|----------------|
+| `str` | `Message::toString` |
+| `uri_for` | `Utils::uriFor` |
+| `stream_for` | `Utils::streamFor` |
+| `parse_header` | `Header::parse` |
+| `normalize_header` | `Header::normalize` |
+| `modify_request` | `Utils::modifyRequest` |
+| `rewind_body` | `Message::rewindBody` |
+| `try_fopen` | `Utils::tryFopen` |
+| `copy_to_string` | `Utils::copyToString` |
+| `copy_to_stream` | `Utils::copyToStream` |
+| `hash` | `Utils::hash` |
+| `readline` | `Utils::readLine` |
+| `parse_request` | `Message::parseRequest` |
+| `parse_response` | `Message::parseResponse` |
+| `parse_query` | `Query::parse` |
+| `build_query` | `Query::build` |
+| `mimetype_from_filename` | `MimeType::fromFilename` |
+| `mimetype_from_extension` | `MimeType::fromExtension` |
+| `_parse_message` | `Message::parseMessage` |
+| `_parse_request_uri` | `Message::parseRequestUri` |
+| `get_message_body_summary` | `Message::bodySummary` |
+| `_caseless_remove` | `Utils::caselessRemove` |
+
+
+# Additional URI Methods
+
+Aside from the standard `Psr\Http\Message\UriInterface` implementation in form of the `GuzzleHttp\Psr7\Uri` class,
+this library also provides additional functionality when working with URIs as static methods.
+
+## URI Types
+
+An instance of `Psr\Http\Message\UriInterface` can either be an absolute URI or a relative reference.
+An absolute URI has a scheme. A relative reference is used to express a URI relative to another URI,
+the base URI. Relative references can be divided into several forms according to
+[RFC 3986 Section 4.2](https://datatracker.ietf.org/doc/html/rfc3986#section-4.2):
+
+- network-path references, e.g. `//example.com/path`
+- absolute-path references, e.g. `/path`
+- relative-path references, e.g. `subpath`
+
+The following methods can be used to identify the type of the URI.
+
+### `GuzzleHttp\Psr7\Uri::isAbsolute`
+
+`public static function isAbsolute(UriInterface $uri): bool`
+
+Whether the URI is absolute, i.e. it has a scheme.
+
+### `GuzzleHttp\Psr7\Uri::isNetworkPathReference`
+
+`public static function isNetworkPathReference(UriInterface $uri): bool`
+
+Whether the URI is a network-path reference. A relative reference that begins with two slash characters is
+termed an network-path reference.
+
+### `GuzzleHttp\Psr7\Uri::isAbsolutePathReference`
+
+`public static function isAbsolutePathReference(UriInterface $uri): bool`
+
+Whether the URI is a absolute-path reference. A relative reference that begins with a single slash character is
+termed an absolute-path reference.
+
+### `GuzzleHttp\Psr7\Uri::isRelativePathReference`
+
+`public static function isRelativePathReference(UriInterface $uri): bool`
+
+Whether the URI is a relative-path reference. A relative reference that does not begin with a slash character is
+termed a relative-path reference.
+
+### `GuzzleHttp\Psr7\Uri::isSameDocumentReference`
+
+`public static function isSameDocumentReference(UriInterface $uri, ?UriInterface $base = null): bool`
+
+Whether the URI is a same-document reference. A same-document reference refers to a URI that is, aside from its
+fragment component, identical to the base URI. When no base URI is given, only an empty URI reference
+(apart from its fragment) is considered a same-document reference.
+
+## URI Components
+
+Additional methods to work with URI components.
+
+### `GuzzleHttp\Psr7\Uri::isDefaultPort`
+
+`public static function isDefaultPort(UriInterface $uri): bool`
+
+Whether the URI has the default port of the current scheme. `Psr\Http\Message\UriInterface::getPort` may return null
+or the standard port. This method can be used independently of the implementation.
+
+### `GuzzleHttp\Psr7\Uri::composeComponents`
+
+`public static function composeComponents($scheme, $authority, $path, $query, $fragment): string`
+
+Composes a URI reference string from its various components according to
+[RFC 3986 Section 5.3](https://datatracker.ietf.org/doc/html/rfc3986#section-5.3). Usually this method does not need
+to be called manually but instead is used indirectly via `Psr\Http\Message\UriInterface::__toString`.
+
+### `GuzzleHttp\Psr7\Uri::fromParts`
+
+`public static function fromParts(array $parts): UriInterface`
+
+Creates a URI from a hash of [`parse_url`](https://www.php.net/manual/en/function.parse-url.php) components.
+
+
+### `GuzzleHttp\Psr7\Uri::withQueryValue`
+
+`public static function withQueryValue(UriInterface $uri, $key, $value): UriInterface`
+
+Creates a new URI with a specific query string value. Any existing query string values that exactly match the
+provided key are removed and replaced with the given key value pair. A value of null will set the query string
+key without a value, e.g. "key" instead of "key=value".
+
+### `GuzzleHttp\Psr7\Uri::withQueryValues`
+
+`public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface`
+
+Creates a new URI with multiple query string values. It has the same behavior as `withQueryValue()` but for an
+associative array of key => value.
+
+### `GuzzleHttp\Psr7\Uri::withoutQueryValue`
+
+`public static function withoutQueryValue(UriInterface $uri, $key): UriInterface`
+
+Creates a new URI with a specific query string value removed. Any existing query string values that exactly match the
+provided key are removed.
+
+## Cross-Origin Detection
+
+`GuzzleHttp\Psr7\UriComparator` provides methods to determine if a modified URL should be considered cross-origin.
+
+### `GuzzleHttp\Psr7\UriComparator::isCrossOrigin`
+
+`public static function isCrossOrigin(UriInterface $original, UriInterface $modified): bool`
+
+Determines if a modified URL should be considered cross-origin with respect to an original URL.
+
+## Reference Resolution
+
+`GuzzleHttp\Psr7\UriResolver` provides methods to resolve a URI reference in the context of a base URI according
+to [RFC 3986 Section 5](https://datatracker.ietf.org/doc/html/rfc3986#section-5). This is for example also what web
+browsers do when resolving a link in a website based on the current request URI.
+
+### `GuzzleHttp\Psr7\UriResolver::resolve`
+
+`public static function resolve(UriInterface $base, UriInterface $rel): UriInterface`
+
+Converts the relative URI into a new URI that is resolved against the base URI.
+
+### `GuzzleHttp\Psr7\UriResolver::removeDotSegments`
+
+`public static function removeDotSegments(string $path): string`
+
+Removes dot segments from a path and returns the new path according to
+[RFC 3986 Section 5.2.4](https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4).
+
+### `GuzzleHttp\Psr7\UriResolver::relativize`
+
+`public static function relativize(UriInterface $base, UriInterface $target): UriInterface`
+
+Returns the target URI as a relative reference from the base URI. This method is the counterpart to resolve():
+
+```php
+(string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
+```
+
+One use-case is to use the current request URI as base URI and then generate relative links in your documents
+to reduce the document size or offer self-contained downloadable document archives.
+
+```php
+$base = new Uri('http://example.com/a/b/');
+echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'.
+echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'.
+echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
+echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'.
+```
+
+## Normalization and Comparison
+
+`GuzzleHttp\Psr7\UriNormalizer` provides methods to normalize and compare URIs according to
+[RFC 3986 Section 6](https://datatracker.ietf.org/doc/html/rfc3986#section-6).
+
+### `GuzzleHttp\Psr7\UriNormalizer::normalize`
+
+`public static function normalize(UriInterface $uri, $flags = self::PRESERVING_NORMALIZATIONS): UriInterface`
+
+Returns a normalized URI. The scheme and host component are already normalized to lowercase per PSR-7 UriInterface.
+This methods adds additional normalizations that can be configured with the `$flags` parameter which is a bitmask
+of normalizations to apply. The following normalizations are available:
+
+- `UriNormalizer::PRESERVING_NORMALIZATIONS`
+
+ Default normalizations which only include the ones that preserve semantics.
+
+- `UriNormalizer::CAPITALIZE_PERCENT_ENCODING`
+
+ All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized.
+
+ Example: `http://example.org/a%c2%b1b` → `http://example.org/a%C2%B1b`
+
+- `UriNormalizer::DECODE_UNRESERVED_CHARACTERS`
+
+ Decodes percent-encoded octets of unreserved characters. For consistency, percent-encoded octets in the ranges of
+ ALPHA (%41–%5A and %61–%7A), DIGIT (%30–%39), hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should
+ not be created by URI producers and, when found in a URI, should be decoded to their corresponding unreserved
+ characters by URI normalizers.
+
+ Example: `http://example.org/%7Eusern%61me/` → `http://example.org/~username/`
+
+- `UriNormalizer::CONVERT_EMPTY_PATH`
+
+ Converts the empty path to "/" for http and https URIs.
+
+ Example: `http://example.org` → `http://example.org/`
+
+- `UriNormalizer::REMOVE_DEFAULT_HOST`
+
+ Removes the default host of the given URI scheme from the URI. Only the "file" scheme defines the default host
+ "localhost". All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile` are equivalent according to
+ RFC 3986.
+
+ Example: `file://localhost/myfile` → `file:///myfile`
+
+- `UriNormalizer::REMOVE_DEFAULT_PORT`
+
+ Removes the default port of the given URI scheme from the URI.
+
+ Example: `http://example.org:80/` → `http://example.org/`
+
+- `UriNormalizer::REMOVE_DOT_SEGMENTS`
+
+ Removes unnecessary dot-segments. Dot-segments in relative-path references are not removed as it would
+ change the semantics of the URI reference.
+
+ Example: `http://example.org/../a/b/../c/./d.html` → `http://example.org/a/c/d.html`
+
+- `UriNormalizer::REMOVE_DUPLICATE_SLASHES`
+
+ Paths which include two or more adjacent slashes are converted to one. Webservers usually ignore duplicate slashes
+ and treat those URIs equivalent. But in theory those URIs do not need to be equivalent. So this normalization
+ may change the semantics. Encoded slashes (%2F) are not removed.
+
+ Example: `http://example.org//foo///bar.html` → `http://example.org/foo/bar.html`
+
+- `UriNormalizer::SORT_QUERY_PARAMETERS`
+
+ Sort query parameters with their values in alphabetical order. However, the order of parameters in a URI may be
+ significant (this is not defined by the standard). So this normalization is not safe and may change the semantics
+ of the URI.
+
+ Example: `?lang=en&article=fred` → `?article=fred&lang=en`
+
+### `GuzzleHttp\Psr7\UriNormalizer::isEquivalent`
+
+`public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, $normalizations = self::PRESERVING_NORMALIZATIONS): bool`
+
+Whether two URIs can be considered equivalent. Both URIs are normalized automatically before comparison with the given
+`$normalizations` bitmask. The method also accepts relative URI references and returns true when they are equivalent.
+This of course assumes they will be resolved against the same base URI. If this is not the case, determination of
+equivalence or difference of relative references does not mean anything.
+
+
+## Security
+
+If you discover a security vulnerability within this package, please send an email to security@tidelift.com. All security vulnerabilities will be promptly addressed. Please do not disclose security-related issues publicly until a fix has been announced. Please see [Security Policy](https://github.com/guzzle/psr7/security/policy) for more information.
+
+
+## License
+
+Guzzle is made available under the MIT License (MIT). Please see [License File](LICENSE) for more information.
+
+
+## For Enterprise
+
+Available as part of the Tidelift Subscription
+
+The maintainers of Guzzle and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/packagist-guzzlehttp-psr7?utm_source=packagist-guzzlehttp-psr7&utm_medium=referral&utm_campaign=enterprise&utm_term=repo)
diff --git a/vendor/guzzlehttp/psr7/composer.json b/vendor/guzzlehttp/psr7/composer.json
new file mode 100644
index 000000000..28d15f571
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/composer.json
@@ -0,0 +1,93 @@
+{
+ "name": "guzzlehttp/psr7",
+ "description": "PSR-7 message implementation that also provides common utility methods",
+ "keywords": [
+ "request",
+ "response",
+ "message",
+ "stream",
+ "http",
+ "uri",
+ "url",
+ "psr-7"
+ ],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "George Mponos",
+ "email": "gmponos@gmail.com",
+ "homepage": "https://github.com/gmponos"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/Nyholm"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://github.com/sagikazarmark"
+ },
+ {
+ "name": "Tobias Schultze",
+ "email": "webmaster@tubo-world.de",
+ "homepage": "https://github.com/Tobion"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://sagikazarmark.hu"
+ }
+ ],
+ "require": {
+ "php": "^7.2.5 || ^8.0",
+ "psr/http-factory": "^1.0",
+ "psr/http-message": "^1.1 || ^2.0",
+ "ralouphie/getallheaders": "^3.0"
+ },
+ "provide": {
+ "psr/http-factory-implementation": "1.0",
+ "psr/http-message-implementation": "1.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "http-interop/http-factory-tests": "0.9.0",
+ "phpunit/phpunit": "^8.5.39 || ^9.6.20"
+ },
+ "suggest": {
+ "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Psr7\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "GuzzleHttp\\Tests\\Psr7\\": "tests/"
+ }
+ },
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ }
+ },
+ "config": {
+ "allow-plugins": {
+ "bamarni/composer-bin-plugin": true
+ },
+ "preferred-install": "dist",
+ "sort-packages": true
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/AppendStream.php b/vendor/guzzlehttp/psr7/src/AppendStream.php
new file mode 100644
index 000000000..ee8f37882
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/AppendStream.php
@@ -0,0 +1,248 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * Reads from multiple streams, one after the other.
+ *
+ * This is a read-only stream decorator.
+ */
+final class AppendStream implements StreamInterface
+{
+ /** @var StreamInterface[] Streams being decorated */
+ private $streams = [];
+
+ /** @var bool */
+ private $seekable = true;
+
+ /** @var int */
+ private $current = 0;
+
+ /** @var int */
+ private $pos = 0;
+
+ /**
+ * @param StreamInterface[] $streams Streams to decorate. Each stream must
+ * be readable.
+ */
+ public function __construct(array $streams = [])
+ {
+ foreach ($streams as $stream) {
+ $this->addStream($stream);
+ }
+ }
+
+ public function __toString(): string
+ {
+ try {
+ $this->rewind();
+
+ return $this->getContents();
+ } catch (\Throwable $e) {
+ if (\PHP_VERSION_ID >= 70400) {
+ throw $e;
+ }
+ trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);
+
+ return '';
+ }
+ }
+
+ /**
+ * Add a stream to the AppendStream
+ *
+ * @param StreamInterface $stream Stream to append. Must be readable.
+ *
+ * @throws \InvalidArgumentException if the stream is not readable
+ */
+ public function addStream(StreamInterface $stream): void
+ {
+ if (!$stream->isReadable()) {
+ throw new \InvalidArgumentException('Each stream must be readable');
+ }
+
+ // The stream is only seekable if all streams are seekable
+ if (!$stream->isSeekable()) {
+ $this->seekable = false;
+ }
+
+ $this->streams[] = $stream;
+ }
+
+ public function getContents(): string
+ {
+ return Utils::copyToString($this);
+ }
+
+ /**
+ * Closes each attached stream.
+ */
+ public function close(): void
+ {
+ $this->pos = $this->current = 0;
+ $this->seekable = true;
+
+ foreach ($this->streams as $stream) {
+ $stream->close();
+ }
+
+ $this->streams = [];
+ }
+
+ /**
+ * Detaches each attached stream.
+ *
+ * Returns null as it's not clear which underlying stream resource to return.
+ */
+ public function detach()
+ {
+ $this->pos = $this->current = 0;
+ $this->seekable = true;
+
+ foreach ($this->streams as $stream) {
+ $stream->detach();
+ }
+
+ $this->streams = [];
+
+ return null;
+ }
+
+ public function tell(): int
+ {
+ return $this->pos;
+ }
+
+ /**
+ * Tries to calculate the size by adding the size of each stream.
+ *
+ * If any of the streams do not return a valid number, then the size of the
+ * append stream cannot be determined and null is returned.
+ */
+ public function getSize(): ?int
+ {
+ $size = 0;
+
+ foreach ($this->streams as $stream) {
+ $s = $stream->getSize();
+ if ($s === null) {
+ return null;
+ }
+ $size += $s;
+ }
+
+ return $size;
+ }
+
+ public function eof(): bool
+ {
+ return !$this->streams
+ || ($this->current >= count($this->streams) - 1
+ && $this->streams[$this->current]->eof());
+ }
+
+ public function rewind(): void
+ {
+ $this->seek(0);
+ }
+
+ /**
+ * Attempts to seek to the given position. Only supports SEEK_SET.
+ */
+ public function seek($offset, $whence = SEEK_SET): void
+ {
+ if (!$this->seekable) {
+ throw new \RuntimeException('This AppendStream is not seekable');
+ } elseif ($whence !== SEEK_SET) {
+ throw new \RuntimeException('The AppendStream can only seek with SEEK_SET');
+ }
+
+ $this->pos = $this->current = 0;
+
+ // Rewind each stream
+ foreach ($this->streams as $i => $stream) {
+ try {
+ $stream->rewind();
+ } catch (\Exception $e) {
+ throw new \RuntimeException('Unable to seek stream '
+ .$i.' of the AppendStream', 0, $e);
+ }
+ }
+
+ // Seek to the actual position by reading from each stream
+ while ($this->pos < $offset && !$this->eof()) {
+ $result = $this->read(min(8096, $offset - $this->pos));
+ if ($result === '') {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Reads from all of the appended streams until the length is met or EOF.
+ */
+ public function read($length): string
+ {
+ $buffer = '';
+ $total = count($this->streams) - 1;
+ $remaining = $length;
+ $progressToNext = false;
+
+ while ($remaining > 0) {
+ // Progress to the next stream if needed.
+ if ($progressToNext || $this->streams[$this->current]->eof()) {
+ $progressToNext = false;
+ if ($this->current === $total) {
+ break;
+ }
+ ++$this->current;
+ }
+
+ $result = $this->streams[$this->current]->read($remaining);
+
+ if ($result === '') {
+ $progressToNext = true;
+ continue;
+ }
+
+ $buffer .= $result;
+ $remaining = $length - strlen($buffer);
+ }
+
+ $this->pos += strlen($buffer);
+
+ return $buffer;
+ }
+
+ public function isReadable(): bool
+ {
+ return true;
+ }
+
+ public function isWritable(): bool
+ {
+ return false;
+ }
+
+ public function isSeekable(): bool
+ {
+ return $this->seekable;
+ }
+
+ public function write($string): int
+ {
+ throw new \RuntimeException('Cannot write to an AppendStream');
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getMetadata($key = null)
+ {
+ return $key ? null : [];
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/BufferStream.php b/vendor/guzzlehttp/psr7/src/BufferStream.php
new file mode 100644
index 000000000..2b0eb77be
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/BufferStream.php
@@ -0,0 +1,147 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * Provides a buffer stream that can be written to to fill a buffer, and read
+ * from to remove bytes from the buffer.
+ *
+ * This stream returns a "hwm" metadata value that tells upstream consumers
+ * what the configured high water mark of the stream is, or the maximum
+ * preferred size of the buffer.
+ */
+final class BufferStream implements StreamInterface
+{
+ /** @var int */
+ private $hwm;
+
+ /** @var string */
+ private $buffer = '';
+
+ /**
+ * @param int $hwm High water mark, representing the preferred maximum
+ * buffer size. If the size of the buffer exceeds the high
+ * water mark, then calls to write will continue to succeed
+ * but will return 0 to inform writers to slow down
+ * until the buffer has been drained by reading from it.
+ */
+ public function __construct(int $hwm = 16384)
+ {
+ $this->hwm = $hwm;
+ }
+
+ public function __toString(): string
+ {
+ return $this->getContents();
+ }
+
+ public function getContents(): string
+ {
+ $buffer = $this->buffer;
+ $this->buffer = '';
+
+ return $buffer;
+ }
+
+ public function close(): void
+ {
+ $this->buffer = '';
+ }
+
+ public function detach()
+ {
+ $this->close();
+
+ return null;
+ }
+
+ public function getSize(): ?int
+ {
+ return strlen($this->buffer);
+ }
+
+ public function isReadable(): bool
+ {
+ return true;
+ }
+
+ public function isWritable(): bool
+ {
+ return true;
+ }
+
+ public function isSeekable(): bool
+ {
+ return false;
+ }
+
+ public function rewind(): void
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET): void
+ {
+ throw new \RuntimeException('Cannot seek a BufferStream');
+ }
+
+ public function eof(): bool
+ {
+ return strlen($this->buffer) === 0;
+ }
+
+ public function tell(): int
+ {
+ throw new \RuntimeException('Cannot determine the position of a BufferStream');
+ }
+
+ /**
+ * Reads data from the buffer.
+ */
+ public function read($length): string
+ {
+ $currentLength = strlen($this->buffer);
+
+ if ($length >= $currentLength) {
+ // No need to slice the buffer because we don't have enough data.
+ $result = $this->buffer;
+ $this->buffer = '';
+ } else {
+ // Slice up the result to provide a subset of the buffer.
+ $result = substr($this->buffer, 0, $length);
+ $this->buffer = substr($this->buffer, $length);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Writes data to the buffer.
+ */
+ public function write($string): int
+ {
+ $this->buffer .= $string;
+
+ if (strlen($this->buffer) >= $this->hwm) {
+ return 0;
+ }
+
+ return strlen($string);
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getMetadata($key = null)
+ {
+ if ($key === 'hwm') {
+ return $this->hwm;
+ }
+
+ return $key ? null : [];
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/CachingStream.php b/vendor/guzzlehttp/psr7/src/CachingStream.php
new file mode 100644
index 000000000..7e4554d5c
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/CachingStream.php
@@ -0,0 +1,153 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * Stream decorator that can cache previously read bytes from a sequentially
+ * read stream.
+ */
+final class CachingStream implements StreamInterface
+{
+ use StreamDecoratorTrait;
+
+ /** @var StreamInterface Stream being wrapped */
+ private $remoteStream;
+
+ /** @var int Number of bytes to skip reading due to a write on the buffer */
+ private $skipReadBytes = 0;
+
+ /**
+ * @var StreamInterface
+ */
+ private $stream;
+
+ /**
+ * We will treat the buffer object as the body of the stream
+ *
+ * @param StreamInterface $stream Stream to cache. The cursor is assumed to be at the beginning of the stream.
+ * @param StreamInterface $target Optionally specify where data is cached
+ */
+ public function __construct(
+ StreamInterface $stream,
+ ?StreamInterface $target = null
+ ) {
+ $this->remoteStream = $stream;
+ $this->stream = $target ?: new Stream(Utils::tryFopen('php://temp', 'r+'));
+ }
+
+ public function getSize(): ?int
+ {
+ $remoteSize = $this->remoteStream->getSize();
+
+ if (null === $remoteSize) {
+ return null;
+ }
+
+ return max($this->stream->getSize(), $remoteSize);
+ }
+
+ public function rewind(): void
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET): void
+ {
+ if ($whence === SEEK_SET) {
+ $byte = $offset;
+ } elseif ($whence === SEEK_CUR) {
+ $byte = $offset + $this->tell();
+ } elseif ($whence === SEEK_END) {
+ $size = $this->remoteStream->getSize();
+ if ($size === null) {
+ $size = $this->cacheEntireStream();
+ }
+ $byte = $size + $offset;
+ } else {
+ throw new \InvalidArgumentException('Invalid whence');
+ }
+
+ $diff = $byte - $this->stream->getSize();
+
+ if ($diff > 0) {
+ // Read the remoteStream until we have read in at least the amount
+ // of bytes requested, or we reach the end of the file.
+ while ($diff > 0 && !$this->remoteStream->eof()) {
+ $this->read($diff);
+ $diff = $byte - $this->stream->getSize();
+ }
+ } else {
+ // We can just do a normal seek since we've already seen this byte.
+ $this->stream->seek($byte);
+ }
+ }
+
+ public function read($length): string
+ {
+ // Perform a regular read on any previously read data from the buffer
+ $data = $this->stream->read($length);
+ $remaining = $length - strlen($data);
+
+ // More data was requested so read from the remote stream
+ if ($remaining) {
+ // If data was written to the buffer in a position that would have
+ // been filled from the remote stream, then we must skip bytes on
+ // the remote stream to emulate overwriting bytes from that
+ // position. This mimics the behavior of other PHP stream wrappers.
+ $remoteData = $this->remoteStream->read(
+ $remaining + $this->skipReadBytes
+ );
+
+ if ($this->skipReadBytes) {
+ $len = strlen($remoteData);
+ $remoteData = substr($remoteData, $this->skipReadBytes);
+ $this->skipReadBytes = max(0, $this->skipReadBytes - $len);
+ }
+
+ $data .= $remoteData;
+ $this->stream->write($remoteData);
+ }
+
+ return $data;
+ }
+
+ public function write($string): int
+ {
+ // When appending to the end of the currently read stream, you'll want
+ // to skip bytes from being read from the remote stream to emulate
+ // other stream wrappers. Basically replacing bytes of data of a fixed
+ // length.
+ $overflow = (strlen($string) + $this->tell()) - $this->remoteStream->tell();
+ if ($overflow > 0) {
+ $this->skipReadBytes += $overflow;
+ }
+
+ return $this->stream->write($string);
+ }
+
+ public function eof(): bool
+ {
+ return $this->stream->eof() && $this->remoteStream->eof();
+ }
+
+ /**
+ * Close both the remote stream and buffer stream
+ */
+ public function close(): void
+ {
+ $this->remoteStream->close();
+ $this->stream->close();
+ }
+
+ private function cacheEntireStream(): int
+ {
+ $target = new FnStream(['write' => 'strlen']);
+ Utils::copyToStream($this, $target);
+
+ return $this->tell();
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/DroppingStream.php b/vendor/guzzlehttp/psr7/src/DroppingStream.php
new file mode 100644
index 000000000..6e3d209d0
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/DroppingStream.php
@@ -0,0 +1,49 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * Stream decorator that begins dropping data once the size of the underlying
+ * stream becomes too full.
+ */
+final class DroppingStream implements StreamInterface
+{
+ use StreamDecoratorTrait;
+
+ /** @var int */
+ private $maxLength;
+
+ /** @var StreamInterface */
+ private $stream;
+
+ /**
+ * @param StreamInterface $stream Underlying stream to decorate.
+ * @param int $maxLength Maximum size before dropping data.
+ */
+ public function __construct(StreamInterface $stream, int $maxLength)
+ {
+ $this->stream = $stream;
+ $this->maxLength = $maxLength;
+ }
+
+ public function write($string): int
+ {
+ $diff = $this->maxLength - $this->stream->getSize();
+
+ // Begin returning 0 when the underlying stream is too large.
+ if ($diff <= 0) {
+ return 0;
+ }
+
+ // Write the stream or a subset of the stream if needed.
+ if (strlen($string) < $diff) {
+ return $this->stream->write($string);
+ }
+
+ return $this->stream->write(substr($string, 0, $diff));
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Exception/MalformedUriException.php b/vendor/guzzlehttp/psr7/src/Exception/MalformedUriException.php
new file mode 100644
index 000000000..3a084779a
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Exception/MalformedUriException.php
@@ -0,0 +1,14 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7\Exception;
+
+use InvalidArgumentException;
+
+/**
+ * Exception thrown if a URI cannot be parsed because it's malformed.
+ */
+class MalformedUriException extends InvalidArgumentException
+{
+}
diff --git a/vendor/guzzlehttp/psr7/src/FnStream.php b/vendor/guzzlehttp/psr7/src/FnStream.php
new file mode 100644
index 000000000..9e6a7f31a
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/FnStream.php
@@ -0,0 +1,180 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * Compose stream implementations based on a hash of functions.
+ *
+ * Allows for easy testing and extension of a provided stream without needing
+ * to create a concrete class for a simple extension point.
+ */
+#[\AllowDynamicProperties]
+final class FnStream implements StreamInterface
+{
+ private const SLOTS = [
+ '__toString', 'close', 'detach', 'rewind',
+ 'getSize', 'tell', 'eof', 'isSeekable', 'seek', 'isWritable', 'write',
+ 'isReadable', 'read', 'getContents', 'getMetadata',
+ ];
+
+ /** @var array<string, callable> */
+ private $methods;
+
+ /**
+ * @param array<string, callable> $methods Hash of method name to a callable.
+ */
+ public function __construct(array $methods)
+ {
+ $this->methods = $methods;
+
+ // Create the functions on the class
+ foreach ($methods as $name => $fn) {
+ $this->{'_fn_'.$name} = $fn;
+ }
+ }
+
+ /**
+ * Lazily determine which methods are not implemented.
+ *
+ * @throws \BadMethodCallException
+ */
+ public function __get(string $name): void
+ {
+ throw new \BadMethodCallException(str_replace('_fn_', '', $name)
+ .'() is not implemented in the FnStream');
+ }
+
+ /**
+ * The close method is called on the underlying stream only if possible.
+ */
+ public function __destruct()
+ {
+ if (isset($this->_fn_close)) {
+ ($this->_fn_close)();
+ }
+ }
+
+ /**
+ * An unserialize would allow the __destruct to run when the unserialized value goes out of scope.
+ *
+ * @throws \LogicException
+ */
+ public function __wakeup(): void
+ {
+ throw new \LogicException('FnStream should never be unserialized');
+ }
+
+ /**
+ * Adds custom functionality to an underlying stream by intercepting
+ * specific method calls.
+ *
+ * @param StreamInterface $stream Stream to decorate
+ * @param array<string, callable> $methods Hash of method name to a closure
+ *
+ * @return FnStream
+ */
+ public static function decorate(StreamInterface $stream, array $methods)
+ {
+ // If any of the required methods were not provided, then simply
+ // proxy to the decorated stream.
+ foreach (array_diff(self::SLOTS, array_keys($methods)) as $diff) {
+ /** @var callable $callable */
+ $callable = [$stream, $diff];
+ $methods[$diff] = $callable;
+ }
+
+ return new self($methods);
+ }
+
+ public function __toString(): string
+ {
+ try {
+ /** @var string */
+ return ($this->_fn___toString)();
+ } catch (\Throwable $e) {
+ if (\PHP_VERSION_ID >= 70400) {
+ throw $e;
+ }
+ trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);
+
+ return '';
+ }
+ }
+
+ public function close(): void
+ {
+ ($this->_fn_close)();
+ }
+
+ public function detach()
+ {
+ return ($this->_fn_detach)();
+ }
+
+ public function getSize(): ?int
+ {
+ return ($this->_fn_getSize)();
+ }
+
+ public function tell(): int
+ {
+ return ($this->_fn_tell)();
+ }
+
+ public function eof(): bool
+ {
+ return ($this->_fn_eof)();
+ }
+
+ public function isSeekable(): bool
+ {
+ return ($this->_fn_isSeekable)();
+ }
+
+ public function rewind(): void
+ {
+ ($this->_fn_rewind)();
+ }
+
+ public function seek($offset, $whence = SEEK_SET): void
+ {
+ ($this->_fn_seek)($offset, $whence);
+ }
+
+ public function isWritable(): bool
+ {
+ return ($this->_fn_isWritable)();
+ }
+
+ public function write($string): int
+ {
+ return ($this->_fn_write)($string);
+ }
+
+ public function isReadable(): bool
+ {
+ return ($this->_fn_isReadable)();
+ }
+
+ public function read($length): string
+ {
+ return ($this->_fn_read)($length);
+ }
+
+ public function getContents(): string
+ {
+ return ($this->_fn_getContents)();
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getMetadata($key = null)
+ {
+ return ($this->_fn_getMetadata)($key);
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Header.php b/vendor/guzzlehttp/psr7/src/Header.php
new file mode 100644
index 000000000..bbce8b03d
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Header.php
@@ -0,0 +1,134 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+final class Header
+{
+ /**
+ * Parse an array of header values containing ";" separated data into an
+ * array of associative arrays representing the header key value pair data
+ * of the header. When a parameter does not contain a value, but just
+ * contains a key, this function will inject a key with a '' string value.
+ *
+ * @param string|array $header Header to parse into components.
+ */
+ public static function parse($header): array
+ {
+ static $trimmed = "\"' \n\t\r";
+ $params = $matches = [];
+
+ foreach ((array) $header as $value) {
+ foreach (self::splitList($value) as $val) {
+ $part = [];
+ foreach (preg_split('/;(?=([^"]*"[^"]*")*[^"]*$)/', $val) ?: [] as $kvp) {
+ if (preg_match_all('/<[^>]+>|[^=]+/', $kvp, $matches)) {
+ $m = $matches[0];
+ if (isset($m[1])) {
+ $part[trim($m[0], $trimmed)] = trim($m[1], $trimmed);
+ } else {
+ $part[] = trim($m[0], $trimmed);
+ }
+ }
+ }
+ if ($part) {
+ $params[] = $part;
+ }
+ }
+ }
+
+ return $params;
+ }
+
+ /**
+ * Converts an array of header values that may contain comma separated
+ * headers into an array of headers with no comma separated values.
+ *
+ * @param string|array $header Header to normalize.
+ *
+ * @deprecated Use self::splitList() instead.
+ */
+ public static function normalize($header): array
+ {
+ $result = [];
+ foreach ((array) $header as $value) {
+ foreach (self::splitList($value) as $parsed) {
+ $result[] = $parsed;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Splits a HTTP header defined to contain a comma-separated list into
+ * each individual value. Empty values will be removed.
+ *
+ * Example headers include 'accept', 'cache-control' and 'if-none-match'.
+ *
+ * This method must not be used to parse headers that are not defined as
+ * a list, such as 'user-agent' or 'set-cookie'.
+ *
+ * @param string|string[] $values Header value as returned by MessageInterface::getHeader()
+ *
+ * @return string[]
+ */
+ public static function splitList($values): array
+ {
+ if (!\is_array($values)) {
+ $values = [$values];
+ }
+
+ $result = [];
+ foreach ($values as $value) {
+ if (!\is_string($value)) {
+ throw new \TypeError('$header must either be a string or an array containing strings.');
+ }
+
+ $v = '';
+ $isQuoted = false;
+ $isEscaped = false;
+ for ($i = 0, $max = \strlen($value); $i < $max; ++$i) {
+ if ($isEscaped) {
+ $v .= $value[$i];
+ $isEscaped = false;
+
+ continue;
+ }
+
+ if (!$isQuoted && $value[$i] === ',') {
+ $v = \trim($v);
+ if ($v !== '') {
+ $result[] = $v;
+ }
+
+ $v = '';
+ continue;
+ }
+
+ if ($isQuoted && $value[$i] === '\\') {
+ $isEscaped = true;
+ $v .= $value[$i];
+
+ continue;
+ }
+ if ($value[$i] === '"') {
+ $isQuoted = !$isQuoted;
+ $v .= $value[$i];
+
+ continue;
+ }
+
+ $v .= $value[$i];
+ }
+
+ $v = \trim($v);
+ if ($v !== '') {
+ $result[] = $v;
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/HttpFactory.php b/vendor/guzzlehttp/psr7/src/HttpFactory.php
new file mode 100644
index 000000000..3ef15103a
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/HttpFactory.php
@@ -0,0 +1,94 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\RequestFactoryInterface;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseFactoryInterface;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestFactoryInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\StreamFactoryInterface;
+use Psr\Http\Message\StreamInterface;
+use Psr\Http\Message\UploadedFileFactoryInterface;
+use Psr\Http\Message\UploadedFileInterface;
+use Psr\Http\Message\UriFactoryInterface;
+use Psr\Http\Message\UriInterface;
+
+/**
+ * Implements all of the PSR-17 interfaces.
+ *
+ * Note: in consuming code it is recommended to require the implemented interfaces
+ * and inject the instance of this class multiple times.
+ */
+final class HttpFactory implements RequestFactoryInterface, ResponseFactoryInterface, ServerRequestFactoryInterface, StreamFactoryInterface, UploadedFileFactoryInterface, UriFactoryInterface
+{
+ public function createUploadedFile(
+ StreamInterface $stream,
+ ?int $size = null,
+ int $error = \UPLOAD_ERR_OK,
+ ?string $clientFilename = null,
+ ?string $clientMediaType = null
+ ): UploadedFileInterface {
+ if ($size === null) {
+ $size = $stream->getSize();
+ }
+
+ return new UploadedFile($stream, $size, $error, $clientFilename, $clientMediaType);
+ }
+
+ public function createStream(string $content = ''): StreamInterface
+ {
+ return Utils::streamFor($content);
+ }
+
+ public function createStreamFromFile(string $file, string $mode = 'r'): StreamInterface
+ {
+ try {
+ $resource = Utils::tryFopen($file, $mode);
+ } catch (\RuntimeException $e) {
+ if ('' === $mode || false === \in_array($mode[0], ['r', 'w', 'a', 'x', 'c'], true)) {
+ throw new \InvalidArgumentException(sprintf('Invalid file opening mode "%s"', $mode), 0, $e);
+ }
+
+ throw $e;
+ }
+
+ return Utils::streamFor($resource);
+ }
+
+ public function createStreamFromResource($resource): StreamInterface
+ {
+ return Utils::streamFor($resource);
+ }
+
+ public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface
+ {
+ if (empty($method)) {
+ if (!empty($serverParams['REQUEST_METHOD'])) {
+ $method = $serverParams['REQUEST_METHOD'];
+ } else {
+ throw new \InvalidArgumentException('Cannot determine HTTP method');
+ }
+ }
+
+ return new ServerRequest($method, $uri, [], null, '1.1', $serverParams);
+ }
+
+ public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface
+ {
+ return new Response($code, [], null, '1.1', $reasonPhrase);
+ }
+
+ public function createRequest(string $method, $uri): RequestInterface
+ {
+ return new Request($method, $uri);
+ }
+
+ public function createUri(string $uri = ''): UriInterface
+ {
+ return new Uri($uri);
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/InflateStream.php b/vendor/guzzlehttp/psr7/src/InflateStream.php
new file mode 100644
index 000000000..e674c9ab6
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/InflateStream.php
@@ -0,0 +1,37 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * Uses PHP's zlib.inflate filter to inflate zlib (HTTP deflate, RFC1950) or gzipped (RFC1952) content.
+ *
+ * This stream decorator converts the provided stream to a PHP stream resource,
+ * then appends the zlib.inflate filter. The stream is then converted back
+ * to a Guzzle stream resource to be used as a Guzzle stream.
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc1950
+ * @see https://datatracker.ietf.org/doc/html/rfc1952
+ * @see https://www.php.net/manual/en/filters.compression.php
+ */
+final class InflateStream implements StreamInterface
+{
+ use StreamDecoratorTrait;
+
+ /** @var StreamInterface */
+ private $stream;
+
+ public function __construct(StreamInterface $stream)
+ {
+ $resource = StreamWrapper::getResource($stream);
+ // Specify window=15+32, so zlib will use header detection to both gzip (with header) and zlib data
+ // See https://www.zlib.net/manual.html#Advanced definition of inflateInit2
+ // "Add 32 to windowBits to enable zlib and gzip decoding with automatic header detection"
+ // Default window size is 15.
+ stream_filter_append($resource, 'zlib.inflate', STREAM_FILTER_READ, ['window' => 15 + 32]);
+ $this->stream = $stream->isSeekable() ? new Stream($resource) : new NoSeekStream(new Stream($resource));
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/LazyOpenStream.php b/vendor/guzzlehttp/psr7/src/LazyOpenStream.php
new file mode 100644
index 000000000..f6c84904e
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/LazyOpenStream.php
@@ -0,0 +1,49 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * Lazily reads or writes to a file that is opened only after an IO operation
+ * take place on the stream.
+ */
+final class LazyOpenStream implements StreamInterface
+{
+ use StreamDecoratorTrait;
+
+ /** @var string */
+ private $filename;
+
+ /** @var string */
+ private $mode;
+
+ /**
+ * @var StreamInterface
+ */
+ private $stream;
+
+ /**
+ * @param string $filename File to lazily open
+ * @param string $mode fopen mode to use when opening the stream
+ */
+ public function __construct(string $filename, string $mode)
+ {
+ $this->filename = $filename;
+ $this->mode = $mode;
+
+ // unsetting the property forces the first access to go through
+ // __get().
+ unset($this->stream);
+ }
+
+ /**
+ * Creates the underlying stream lazily when required.
+ */
+ protected function createStream(): StreamInterface
+ {
+ return Utils::streamFor(Utils::tryFopen($this->filename, $this->mode));
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/LimitStream.php b/vendor/guzzlehttp/psr7/src/LimitStream.php
new file mode 100644
index 000000000..fb2232557
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/LimitStream.php
@@ -0,0 +1,157 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * Decorator used to return only a subset of a stream.
+ */
+final class LimitStream implements StreamInterface
+{
+ use StreamDecoratorTrait;
+
+ /** @var int Offset to start reading from */
+ private $offset;
+
+ /** @var int Limit the number of bytes that can be read */
+ private $limit;
+
+ /** @var StreamInterface */
+ private $stream;
+
+ /**
+ * @param StreamInterface $stream Stream to wrap
+ * @param int $limit Total number of bytes to allow to be read
+ * from the stream. Pass -1 for no limit.
+ * @param int $offset Position to seek to before reading (only
+ * works on seekable streams).
+ */
+ public function __construct(
+ StreamInterface $stream,
+ int $limit = -1,
+ int $offset = 0
+ ) {
+ $this->stream = $stream;
+ $this->setLimit($limit);
+ $this->setOffset($offset);
+ }
+
+ public function eof(): bool
+ {
+ // Always return true if the underlying stream is EOF
+ if ($this->stream->eof()) {
+ return true;
+ }
+
+ // No limit and the underlying stream is not at EOF
+ if ($this->limit === -1) {
+ return false;
+ }
+
+ return $this->stream->tell() >= $this->offset + $this->limit;
+ }
+
+ /**
+ * Returns the size of the limited subset of data
+ */
+ public function getSize(): ?int
+ {
+ if (null === ($length = $this->stream->getSize())) {
+ return null;
+ } elseif ($this->limit === -1) {
+ return $length - $this->offset;
+ } else {
+ return min($this->limit, $length - $this->offset);
+ }
+ }
+
+ /**
+ * Allow for a bounded seek on the read limited stream
+ */
+ public function seek($offset, $whence = SEEK_SET): void
+ {
+ if ($whence !== SEEK_SET || $offset < 0) {
+ throw new \RuntimeException(sprintf(
+ 'Cannot seek to offset %s with whence %s',
+ $offset,
+ $whence
+ ));
+ }
+
+ $offset += $this->offset;
+
+ if ($this->limit !== -1) {
+ if ($offset > $this->offset + $this->limit) {
+ $offset = $this->offset + $this->limit;
+ }
+ }
+
+ $this->stream->seek($offset);
+ }
+
+ /**
+ * Give a relative tell()
+ */
+ public function tell(): int
+ {
+ return $this->stream->tell() - $this->offset;
+ }
+
+ /**
+ * Set the offset to start limiting from
+ *
+ * @param int $offset Offset to seek to and begin byte limiting from
+ *
+ * @throws \RuntimeException if the stream cannot be seeked.
+ */
+ public function setOffset(int $offset): void
+ {
+ $current = $this->stream->tell();
+
+ if ($current !== $offset) {
+ // If the stream cannot seek to the offset position, then read to it
+ if ($this->stream->isSeekable()) {
+ $this->stream->seek($offset);
+ } elseif ($current > $offset) {
+ throw new \RuntimeException("Could not seek to stream offset $offset");
+ } else {
+ $this->stream->read($offset - $current);
+ }
+ }
+
+ $this->offset = $offset;
+ }
+
+ /**
+ * Set the limit of bytes that the decorator allows to be read from the
+ * stream.
+ *
+ * @param int $limit Number of bytes to allow to be read from the stream.
+ * Use -1 for no limit.
+ */
+ public function setLimit(int $limit): void
+ {
+ $this->limit = $limit;
+ }
+
+ public function read($length): string
+ {
+ if ($this->limit === -1) {
+ return $this->stream->read($length);
+ }
+
+ // Check if the current position is less than the total allowed
+ // bytes + original offset
+ $remaining = ($this->offset + $this->limit) - $this->stream->tell();
+ if ($remaining > 0) {
+ // Only return the amount of requested data, ensuring that the byte
+ // limit is not exceeded
+ return $this->stream->read(min($remaining, $length));
+ }
+
+ return '';
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Message.php b/vendor/guzzlehttp/psr7/src/Message.php
new file mode 100644
index 000000000..5561a5130
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Message.php
@@ -0,0 +1,246 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\MessageInterface;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+
+final class Message
+{
+ /**
+ * Returns the string representation of an HTTP message.
+ *
+ * @param MessageInterface $message Message to convert to a string.
+ */
+ public static function toString(MessageInterface $message): string
+ {
+ if ($message instanceof RequestInterface) {
+ $msg = trim($message->getMethod().' '
+ .$message->getRequestTarget())
+ .' HTTP/'.$message->getProtocolVersion();
+ if (!$message->hasHeader('host')) {
+ $msg .= "\r\nHost: ".$message->getUri()->getHost();
+ }
+ } elseif ($message instanceof ResponseInterface) {
+ $msg = 'HTTP/'.$message->getProtocolVersion().' '
+ .$message->getStatusCode().' '
+ .$message->getReasonPhrase();
+ } else {
+ throw new \InvalidArgumentException('Unknown message type');
+ }
+
+ foreach ($message->getHeaders() as $name => $values) {
+ if (is_string($name) && strtolower($name) === 'set-cookie') {
+ foreach ($values as $value) {
+ $msg .= "\r\n{$name}: ".$value;
+ }
+ } else {
+ $msg .= "\r\n{$name}: ".implode(', ', $values);
+ }
+ }
+
+ return "{$msg}\r\n\r\n".$message->getBody();
+ }
+
+ /**
+ * Get a short summary of the message body.
+ *
+ * Will return `null` if the response is not printable.
+ *
+ * @param MessageInterface $message The message to get the body summary
+ * @param int $truncateAt The maximum allowed size of the summary
+ */
+ public static function bodySummary(MessageInterface $message, int $truncateAt = 120): ?string
+ {
+ $body = $message->getBody();
+
+ if (!$body->isSeekable() || !$body->isReadable()) {
+ return null;
+ }
+
+ $size = $body->getSize();
+
+ if ($size === 0) {
+ return null;
+ }
+
+ $body->rewind();
+ $summary = $body->read($truncateAt);
+ $body->rewind();
+
+ if ($size > $truncateAt) {
+ $summary .= ' (truncated...)';
+ }
+
+ // Matches any printable character, including unicode characters:
+ // letters, marks, numbers, punctuation, spacing, and separators.
+ if (preg_match('/[^\pL\pM\pN\pP\pS\pZ\n\r\t]/u', $summary) !== 0) {
+ return null;
+ }
+
+ return $summary;
+ }
+
+ /**
+ * Attempts to rewind a message body and throws an exception on failure.
+ *
+ * The body of the message will only be rewound if a call to `tell()`
+ * returns a value other than `0`.
+ *
+ * @param MessageInterface $message Message to rewind
+ *
+ * @throws \RuntimeException
+ */
+ public static function rewindBody(MessageInterface $message): void
+ {
+ $body = $message->getBody();
+
+ if ($body->tell()) {
+ $body->rewind();
+ }
+ }
+
+ /**
+ * Parses an HTTP message into an associative array.
+ *
+ * The array contains the "start-line" key containing the start line of
+ * the message, "headers" key containing an associative array of header
+ * array values, and a "body" key containing the body of the message.
+ *
+ * @param string $message HTTP request or response to parse.
+ */
+ public static function parseMessage(string $message): array
+ {
+ if (!$message) {
+ throw new \InvalidArgumentException('Invalid message');
+ }
+
+ $message = ltrim($message, "\r\n");
+
+ $messageParts = preg_split("/\r?\n\r?\n/", $message, 2);
+
+ if ($messageParts === false || count($messageParts) !== 2) {
+ throw new \InvalidArgumentException('Invalid message: Missing header delimiter');
+ }
+
+ [$rawHeaders, $body] = $messageParts;
+ $rawHeaders .= "\r\n"; // Put back the delimiter we split previously
+ $headerParts = preg_split("/\r?\n/", $rawHeaders, 2);
+
+ if ($headerParts === false || count($headerParts) !== 2) {
+ throw new \InvalidArgumentException('Invalid message: Missing status line');
+ }
+
+ [$startLine, $rawHeaders] = $headerParts;
+
+ if (preg_match("/(?:^HTTP\/|^[A-Z]+ \S+ HTTP\/)(\d+(?:\.\d+)?)/i", $startLine, $matches) && $matches[1] === '1.0') {
+ // Header folding is deprecated for HTTP/1.1, but allowed in HTTP/1.0
+ $rawHeaders = preg_replace(Rfc7230::HEADER_FOLD_REGEX, ' ', $rawHeaders);
+ }
+
+ /** @var array[] $headerLines */
+ $count = preg_match_all(Rfc7230::HEADER_REGEX, $rawHeaders, $headerLines, PREG_SET_ORDER);
+
+ // If these aren't the same, then one line didn't match and there's an invalid header.
+ if ($count !== substr_count($rawHeaders, "\n")) {
+ // Folding is deprecated, see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4
+ if (preg_match(Rfc7230::HEADER_FOLD_REGEX, $rawHeaders)) {
+ throw new \InvalidArgumentException('Invalid header syntax: Obsolete line folding');
+ }
+
+ throw new \InvalidArgumentException('Invalid header syntax');
+ }
+
+ $headers = [];
+
+ foreach ($headerLines as $headerLine) {
+ $headers[$headerLine[1]][] = $headerLine[2];
+ }
+
+ return [
+ 'start-line' => $startLine,
+ 'headers' => $headers,
+ 'body' => $body,
+ ];
+ }
+
+ /**
+ * Constructs a URI for an HTTP request message.
+ *
+ * @param string $path Path from the start-line
+ * @param array $headers Array of headers (each value an array).
+ */
+ public static function parseRequestUri(string $path, array $headers): string
+ {
+ $hostKey = array_filter(array_keys($headers), function ($k) {
+ // Numeric array keys are converted to int by PHP.
+ $k = (string) $k;
+
+ return strtolower($k) === 'host';
+ });
+
+ // If no host is found, then a full URI cannot be constructed.
+ if (!$hostKey) {
+ return $path;
+ }
+
+ $host = $headers[reset($hostKey)][0];
+ $scheme = substr($host, -4) === ':443' ? 'https' : 'http';
+
+ return $scheme.'://'.$host.'/'.ltrim($path, '/');
+ }
+
+ /**
+ * Parses a request message string into a request object.
+ *
+ * @param string $message Request message string.
+ */
+ public static function parseRequest(string $message): RequestInterface
+ {
+ $data = self::parseMessage($message);
+ $matches = [];
+ if (!preg_match('/^[\S]+\s+([a-zA-Z]+:\/\/|\/).*/', $data['start-line'], $matches)) {
+ throw new \InvalidArgumentException('Invalid request string');
+ }
+ $parts = explode(' ', $data['start-line'], 3);
+ $version = isset($parts[2]) ? explode('/', $parts[2])[1] : '1.1';
+
+ $request = new Request(
+ $parts[0],
+ $matches[1] === '/' ? self::parseRequestUri($parts[1], $data['headers']) : $parts[1],
+ $data['headers'],
+ $data['body'],
+ $version
+ );
+
+ return $matches[1] === '/' ? $request : $request->withRequestTarget($parts[1]);
+ }
+
+ /**
+ * Parses a response message string into a response object.
+ *
+ * @param string $message Response message string.
+ */
+ public static function parseResponse(string $message): ResponseInterface
+ {
+ $data = self::parseMessage($message);
+ // According to https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2
+ // the space between status-code and reason-phrase is required. But
+ // browsers accept responses without space and reason as well.
+ if (!preg_match('/^HTTP\/.* [0-9]{3}( .*|$)/', $data['start-line'])) {
+ throw new \InvalidArgumentException('Invalid response string: '.$data['start-line']);
+ }
+ $parts = explode(' ', $data['start-line'], 3);
+
+ return new Response(
+ (int) $parts[1],
+ $data['headers'],
+ $data['body'],
+ explode('/', $parts[0])[1],
+ $parts[2] ?? null
+ );
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/MessageTrait.php b/vendor/guzzlehttp/psr7/src/MessageTrait.php
new file mode 100644
index 000000000..65dbc4ba0
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/MessageTrait.php
@@ -0,0 +1,265 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\MessageInterface;
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * Trait implementing functionality common to requests and responses.
+ */
+trait MessageTrait
+{
+ /** @var string[][] Map of all registered headers, as original name => array of values */
+ private $headers = [];
+
+ /** @var string[] Map of lowercase header name => original name at registration */
+ private $headerNames = [];
+
+ /** @var string */
+ private $protocol = '1.1';
+
+ /** @var StreamInterface|null */
+ private $stream;
+
+ public function getProtocolVersion(): string
+ {
+ return $this->protocol;
+ }
+
+ public function withProtocolVersion($version): MessageInterface
+ {
+ if ($this->protocol === $version) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->protocol = $version;
+
+ return $new;
+ }
+
+ public function getHeaders(): array
+ {
+ return $this->headers;
+ }
+
+ public function hasHeader($header): bool
+ {
+ return isset($this->headerNames[strtolower($header)]);
+ }
+
+ public function getHeader($header): array
+ {
+ $header = strtolower($header);
+
+ if (!isset($this->headerNames[$header])) {
+ return [];
+ }
+
+ $header = $this->headerNames[$header];
+
+ return $this->headers[$header];
+ }
+
+ public function getHeaderLine($header): string
+ {
+ return implode(', ', $this->getHeader($header));
+ }
+
+ public function withHeader($header, $value): MessageInterface
+ {
+ $this->assertHeader($header);
+ $value = $this->normalizeHeaderValue($value);
+ $normalized = strtolower($header);
+
+ $new = clone $this;
+ if (isset($new->headerNames[$normalized])) {
+ unset($new->headers[$new->headerNames[$normalized]]);
+ }
+ $new->headerNames[$normalized] = $header;
+ $new->headers[$header] = $value;
+
+ return $new;
+ }
+
+ public function withAddedHeader($header, $value): MessageInterface
+ {
+ $this->assertHeader($header);
+ $value = $this->normalizeHeaderValue($value);
+ $normalized = strtolower($header);
+
+ $new = clone $this;
+ if (isset($new->headerNames[$normalized])) {
+ $header = $this->headerNames[$normalized];
+ $new->headers[$header] = array_merge($this->headers[$header], $value);
+ } else {
+ $new->headerNames[$normalized] = $header;
+ $new->headers[$header] = $value;
+ }
+
+ return $new;
+ }
+
+ public function withoutHeader($header): MessageInterface
+ {
+ $normalized = strtolower($header);
+
+ if (!isset($this->headerNames[$normalized])) {
+ return $this;
+ }
+
+ $header = $this->headerNames[$normalized];
+
+ $new = clone $this;
+ unset($new->headers[$header], $new->headerNames[$normalized]);
+
+ return $new;
+ }
+
+ public function getBody(): StreamInterface
+ {
+ if (!$this->stream) {
+ $this->stream = Utils::streamFor('');
+ }
+
+ return $this->stream;
+ }
+
+ public function withBody(StreamInterface $body): MessageInterface
+ {
+ if ($body === $this->stream) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->stream = $body;
+
+ return $new;
+ }
+
+ /**
+ * @param (string|string[])[] $headers
+ */
+ private function setHeaders(array $headers): void
+ {
+ $this->headerNames = $this->headers = [];
+ foreach ($headers as $header => $value) {
+ // Numeric array keys are converted to int by PHP.
+ $header = (string) $header;
+
+ $this->assertHeader($header);
+ $value = $this->normalizeHeaderValue($value);
+ $normalized = strtolower($header);
+ if (isset($this->headerNames[$normalized])) {
+ $header = $this->headerNames[$normalized];
+ $this->headers[$header] = array_merge($this->headers[$header], $value);
+ } else {
+ $this->headerNames[$normalized] = $header;
+ $this->headers[$header] = $value;
+ }
+ }
+ }
+
+ /**
+ * @param mixed $value
+ *
+ * @return string[]
+ */
+ private function normalizeHeaderValue($value): array
+ {
+ if (!is_array($value)) {
+ return $this->trimAndValidateHeaderValues([$value]);
+ }
+
+ if (count($value) === 0) {
+ throw new \InvalidArgumentException('Header value can not be an empty array.');
+ }
+
+ return $this->trimAndValidateHeaderValues($value);
+ }
+
+ /**
+ * Trims whitespace from the header values.
+ *
+ * Spaces and tabs ought to be excluded by parsers when extracting the field value from a header field.
+ *
+ * header-field = field-name ":" OWS field-value OWS
+ * OWS = *( SP / HTAB )
+ *
+ * @param mixed[] $values Header values
+ *
+ * @return string[] Trimmed header values
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.4
+ */
+ private function trimAndValidateHeaderValues(array $values): array
+ {
+ return array_map(function ($value) {
+ if (!is_scalar($value) && null !== $value) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Header value must be scalar or null but %s provided.',
+ is_object($value) ? get_class($value) : gettype($value)
+ ));
+ }
+
+ $trimmed = trim((string) $value, " \t");
+ $this->assertValue($trimmed);
+
+ return $trimmed;
+ }, array_values($values));
+ }
+
+ /**
+ * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
+ *
+ * @param mixed $header
+ */
+ private function assertHeader($header): void
+ {
+ if (!is_string($header)) {
+ throw new \InvalidArgumentException(sprintf(
+ 'Header name must be a string but %s provided.',
+ is_object($header) ? get_class($header) : gettype($header)
+ ));
+ }
+
+ if (!preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $header)) {
+ throw new \InvalidArgumentException(
+ sprintf('"%s" is not valid header name.', $header)
+ );
+ }
+ }
+
+ /**
+ * @see https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
+ *
+ * field-value = *( field-content / obs-fold )
+ * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
+ * field-vchar = VCHAR / obs-text
+ * VCHAR = %x21-7E
+ * obs-text = %x80-FF
+ * obs-fold = CRLF 1*( SP / HTAB )
+ */
+ private function assertValue(string $value): void
+ {
+ // The regular expression intentionally does not support the obs-fold production, because as
+ // per RFC 7230#3.2.4:
+ //
+ // A sender MUST NOT generate a message that includes
+ // line folding (i.e., that has any field-value that contains a match to
+ // the obs-fold rule) unless the message is intended for packaging
+ // within the message/http media type.
+ //
+ // Clients must not send a request with line folding and a server sending folded headers is
+ // likely very rare. Line folding is a fairly obscure feature of HTTP/1.1 and thus not accepting
+ // folding is not likely to break any legitimate use case.
+ if (!preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/D', $value)) {
+ throw new \InvalidArgumentException(
+ sprintf('"%s" is not valid header value.', $value)
+ );
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/MimeType.php b/vendor/guzzlehttp/psr7/src/MimeType.php
new file mode 100644
index 000000000..b131bdbe7
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/MimeType.php
@@ -0,0 +1,1259 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+final class MimeType
+{
+ private const MIME_TYPES = [
+ '1km' => 'application/vnd.1000minds.decision-model+xml',
+ '3dml' => 'text/vnd.in3d.3dml',
+ '3ds' => 'image/x-3ds',
+ '3g2' => 'video/3gpp2',
+ '3gp' => 'video/3gp',
+ '3gpp' => 'video/3gpp',
+ '3mf' => 'model/3mf',
+ '7z' => 'application/x-7z-compressed',
+ '7zip' => 'application/x-7z-compressed',
+ '123' => 'application/vnd.lotus-1-2-3',
+ 'aab' => 'application/x-authorware-bin',
+ 'aac' => 'audio/aac',
+ 'aam' => 'application/x-authorware-map',
+ 'aas' => 'application/x-authorware-seg',
+ 'abw' => 'application/x-abiword',
+ 'ac' => 'application/vnd.nokia.n-gage.ac+xml',
+ 'ac3' => 'audio/ac3',
+ 'acc' => 'application/vnd.americandynamics.acc',
+ 'ace' => 'application/x-ace-compressed',
+ 'acu' => 'application/vnd.acucobol',
+ 'acutc' => 'application/vnd.acucorp',
+ 'adp' => 'audio/adpcm',
+ 'adts' => 'audio/aac',
+ 'aep' => 'application/vnd.audiograph',
+ 'afm' => 'application/x-font-type1',
+ 'afp' => 'application/vnd.ibm.modcap',
+ 'age' => 'application/vnd.age',
+ 'ahead' => 'application/vnd.ahead.space',
+ 'ai' => 'application/pdf',
+ 'aif' => 'audio/x-aiff',
+ 'aifc' => 'audio/x-aiff',
+ 'aiff' => 'audio/x-aiff',
+ 'air' => 'application/vnd.adobe.air-application-installer-package+zip',
+ 'ait' => 'application/vnd.dvb.ait',
+ 'ami' => 'application/vnd.amiga.ami',
+ 'aml' => 'application/automationml-aml+xml',
+ 'amlx' => 'application/automationml-amlx+zip',
+ 'amr' => 'audio/amr',
+ 'apk' => 'application/vnd.android.package-archive',
+ 'apng' => 'image/apng',
+ 'appcache' => 'text/cache-manifest',
+ 'appinstaller' => 'application/appinstaller',
+ 'application' => 'application/x-ms-application',
+ 'appx' => 'application/appx',
+ 'appxbundle' => 'application/appxbundle',
+ 'apr' => 'application/vnd.lotus-approach',
+ 'arc' => 'application/x-freearc',
+ 'arj' => 'application/x-arj',
+ 'asc' => 'application/pgp-signature',
+ 'asf' => 'video/x-ms-asf',
+ 'asm' => 'text/x-asm',
+ 'aso' => 'application/vnd.accpac.simply.aso',
+ 'asx' => 'video/x-ms-asf',
+ 'atc' => 'application/vnd.acucorp',
+ 'atom' => 'application/atom+xml',
+ 'atomcat' => 'application/atomcat+xml',
+ 'atomdeleted' => 'application/atomdeleted+xml',
+ 'atomsvc' => 'application/atomsvc+xml',
+ 'atx' => 'application/vnd.antix.game-component',
+ 'au' => 'audio/x-au',
+ 'avci' => 'image/avci',
+ 'avcs' => 'image/avcs',
+ 'avi' => 'video/x-msvideo',
+ 'avif' => 'image/avif',
+ 'aw' => 'application/applixware',
+ 'azf' => 'application/vnd.airzip.filesecure.azf',
+ 'azs' => 'application/vnd.airzip.filesecure.azs',
+ 'azv' => 'image/vnd.airzip.accelerator.azv',
+ 'azw' => 'application/vnd.amazon.ebook',
+ 'b16' => 'image/vnd.pco.b16',
+ 'bat' => 'application/x-msdownload',
+ 'bcpio' => 'application/x-bcpio',
+ 'bdf' => 'application/x-font-bdf',
+ 'bdm' => 'application/vnd.syncml.dm+wbxml',
+ 'bdoc' => 'application/x-bdoc',
+ 'bed' => 'application/vnd.realvnc.bed',
+ 'bh2' => 'application/vnd.fujitsu.oasysprs',
+ 'bin' => 'application/octet-stream',
+ 'blb' => 'application/x-blorb',
+ 'blorb' => 'application/x-blorb',
+ 'bmi' => 'application/vnd.bmi',
+ 'bmml' => 'application/vnd.balsamiq.bmml+xml',
+ 'bmp' => 'image/bmp',
+ 'book' => 'application/vnd.framemaker',
+ 'box' => 'application/vnd.previewsystems.box',
+ 'boz' => 'application/x-bzip2',
+ 'bpk' => 'application/octet-stream',
+ 'bpmn' => 'application/octet-stream',
+ 'bsp' => 'model/vnd.valve.source.compiled-map',
+ 'btf' => 'image/prs.btif',
+ 'btif' => 'image/prs.btif',
+ 'buffer' => 'application/octet-stream',
+ 'bz' => 'application/x-bzip',
+ 'bz2' => 'application/x-bzip2',
+ 'c' => 'text/x-c',
+ 'c4d' => 'application/vnd.clonk.c4group',
+ 'c4f' => 'application/vnd.clonk.c4group',
+ 'c4g' => 'application/vnd.clonk.c4group',
+ 'c4p' => 'application/vnd.clonk.c4group',
+ 'c4u' => 'application/vnd.clonk.c4group',
+ 'c11amc' => 'application/vnd.cluetrust.cartomobile-config',
+ 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg',
+ 'cab' => 'application/vnd.ms-cab-compressed',
+ 'caf' => 'audio/x-caf',
+ 'cap' => 'application/vnd.tcpdump.pcap',
+ 'car' => 'application/vnd.curl.car',
+ 'cat' => 'application/vnd.ms-pki.seccat',
+ 'cb7' => 'application/x-cbr',
+ 'cba' => 'application/x-cbr',
+ 'cbr' => 'application/x-cbr',
+ 'cbt' => 'application/x-cbr',
+ 'cbz' => 'application/x-cbr',
+ 'cc' => 'text/x-c',
+ 'cco' => 'application/x-cocoa',
+ 'cct' => 'application/x-director',
+ 'ccxml' => 'application/ccxml+xml',
+ 'cdbcmsg' => 'application/vnd.contact.cmsg',
+ 'cdf' => 'application/x-netcdf',
+ 'cdfx' => 'application/cdfx+xml',
+ 'cdkey' => 'application/vnd.mediastation.cdkey',
+ 'cdmia' => 'application/cdmi-capability',
+ 'cdmic' => 'application/cdmi-container',
+ 'cdmid' => 'application/cdmi-domain',
+ 'cdmio' => 'application/cdmi-object',
+ 'cdmiq' => 'application/cdmi-queue',
+ 'cdr' => 'application/cdr',
+ 'cdx' => 'chemical/x-cdx',
+ 'cdxml' => 'application/vnd.chemdraw+xml',
+ 'cdy' => 'application/vnd.cinderella',
+ 'cer' => 'application/pkix-cert',
+ 'cfs' => 'application/x-cfs-compressed',
+ 'cgm' => 'image/cgm',
+ 'chat' => 'application/x-chat',
+ 'chm' => 'application/vnd.ms-htmlhelp',
+ 'chrt' => 'application/vnd.kde.kchart',
+ 'cif' => 'chemical/x-cif',
+ 'cii' => 'application/vnd.anser-web-certificate-issue-initiation',
+ 'cil' => 'application/vnd.ms-artgalry',
+ 'cjs' => 'application/node',
+ 'cla' => 'application/vnd.claymore',
+ 'class' => 'application/octet-stream',
+ 'cld' => 'model/vnd.cld',
+ 'clkk' => 'application/vnd.crick.clicker.keyboard',
+ 'clkp' => 'application/vnd.crick.clicker.palette',
+ 'clkt' => 'application/vnd.crick.clicker.template',
+ 'clkw' => 'application/vnd.crick.clicker.wordbank',
+ 'clkx' => 'application/vnd.crick.clicker',
+ 'clp' => 'application/x-msclip',
+ 'cmc' => 'application/vnd.cosmocaller',
+ 'cmdf' => 'chemical/x-cmdf',
+ 'cml' => 'chemical/x-cml',
+ 'cmp' => 'application/vnd.yellowriver-custom-menu',
+ 'cmx' => 'image/x-cmx',
+ 'cod' => 'application/vnd.rim.cod',
+ 'coffee' => 'text/coffeescript',
+ 'com' => 'application/x-msdownload',
+ 'conf' => 'text/plain',
+ 'cpio' => 'application/x-cpio',
+ 'cpl' => 'application/cpl+xml',
+ 'cpp' => 'text/x-c',
+ 'cpt' => 'application/mac-compactpro',
+ 'crd' => 'application/x-mscardfile',
+ 'crl' => 'application/pkix-crl',
+ 'crt' => 'application/x-x509-ca-cert',
+ 'crx' => 'application/x-chrome-extension',
+ 'cryptonote' => 'application/vnd.rig.cryptonote',
+ 'csh' => 'application/x-csh',
+ 'csl' => 'application/vnd.citationstyles.style+xml',
+ 'csml' => 'chemical/x-csml',
+ 'csp' => 'application/vnd.commonspace',
+ 'csr' => 'application/octet-stream',
+ 'css' => 'text/css',
+ 'cst' => 'application/x-director',
+ 'csv' => 'text/csv',
+ 'cu' => 'application/cu-seeme',
+ 'curl' => 'text/vnd.curl',
+ 'cwl' => 'application/cwl',
+ 'cww' => 'application/prs.cww',
+ 'cxt' => 'application/x-director',
+ 'cxx' => 'text/x-c',
+ 'dae' => 'model/vnd.collada+xml',
+ 'daf' => 'application/vnd.mobius.daf',
+ 'dart' => 'application/vnd.dart',
+ 'dataless' => 'application/vnd.fdsn.seed',
+ 'davmount' => 'application/davmount+xml',
+ 'dbf' => 'application/vnd.dbf',
+ 'dbk' => 'application/docbook+xml',
+ 'dcr' => 'application/x-director',
+ 'dcurl' => 'text/vnd.curl.dcurl',
+ 'dd2' => 'application/vnd.oma.dd2+xml',
+ 'ddd' => 'application/vnd.fujixerox.ddd',
+ 'ddf' => 'application/vnd.syncml.dmddf+xml',
+ 'dds' => 'image/vnd.ms-dds',
+ 'deb' => 'application/x-debian-package',
+ 'def' => 'text/plain',
+ 'deploy' => 'application/octet-stream',
+ 'der' => 'application/x-x509-ca-cert',
+ 'dfac' => 'application/vnd.dreamfactory',
+ 'dgc' => 'application/x-dgc-compressed',
+ 'dib' => 'image/bmp',
+ 'dic' => 'text/x-c',
+ 'dir' => 'application/x-director',
+ 'dis' => 'application/vnd.mobius.dis',
+ 'disposition-notification' => 'message/disposition-notification',
+ 'dist' => 'application/octet-stream',
+ 'distz' => 'application/octet-stream',
+ 'djv' => 'image/vnd.djvu',
+ 'djvu' => 'image/vnd.djvu',
+ 'dll' => 'application/octet-stream',
+ 'dmg' => 'application/x-apple-diskimage',
+ 'dmn' => 'application/octet-stream',
+ 'dmp' => 'application/vnd.tcpdump.pcap',
+ 'dms' => 'application/octet-stream',
+ 'dna' => 'application/vnd.dna',
+ 'doc' => 'application/msword',
+ 'docm' => 'application/vnd.ms-word.template.macroEnabled.12',
+ 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ 'dot' => 'application/msword',
+ 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12',
+ 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
+ 'dp' => 'application/vnd.osgi.dp',
+ 'dpg' => 'application/vnd.dpgraph',
+ 'dpx' => 'image/dpx',
+ 'dra' => 'audio/vnd.dra',
+ 'drle' => 'image/dicom-rle',
+ 'dsc' => 'text/prs.lines.tag',
+ 'dssc' => 'application/dssc+der',
+ 'dtb' => 'application/x-dtbook+xml',
+ 'dtd' => 'application/xml-dtd',
+ 'dts' => 'audio/vnd.dts',
+ 'dtshd' => 'audio/vnd.dts.hd',
+ 'dump' => 'application/octet-stream',
+ 'dvb' => 'video/vnd.dvb.file',
+ 'dvi' => 'application/x-dvi',
+ 'dwd' => 'application/atsc-dwd+xml',
+ 'dwf' => 'model/vnd.dwf',
+ 'dwg' => 'image/vnd.dwg',
+ 'dxf' => 'image/vnd.dxf',
+ 'dxp' => 'application/vnd.spotfire.dxp',
+ 'dxr' => 'application/x-director',
+ 'ear' => 'application/java-archive',
+ 'ecelp4800' => 'audio/vnd.nuera.ecelp4800',
+ 'ecelp7470' => 'audio/vnd.nuera.ecelp7470',
+ 'ecelp9600' => 'audio/vnd.nuera.ecelp9600',
+ 'ecma' => 'application/ecmascript',
+ 'edm' => 'application/vnd.novadigm.edm',
+ 'edx' => 'application/vnd.novadigm.edx',
+ 'efif' => 'application/vnd.picsel',
+ 'ei6' => 'application/vnd.pg.osasli',
+ 'elc' => 'application/octet-stream',
+ 'emf' => 'image/emf',
+ 'eml' => 'message/rfc822',
+ 'emma' => 'application/emma+xml',
+ 'emotionml' => 'application/emotionml+xml',
+ 'emz' => 'application/x-msmetafile',
+ 'eol' => 'audio/vnd.digital-winds',
+ 'eot' => 'application/vnd.ms-fontobject',
+ 'eps' => 'application/postscript',
+ 'epub' => 'application/epub+zip',
+ 'es3' => 'application/vnd.eszigno3+xml',
+ 'esa' => 'application/vnd.osgi.subsystem',
+ 'esf' => 'application/vnd.epson.esf',
+ 'et3' => 'application/vnd.eszigno3+xml',
+ 'etx' => 'text/x-setext',
+ 'eva' => 'application/x-eva',
+ 'evy' => 'application/x-envoy',
+ 'exe' => 'application/octet-stream',
+ 'exi' => 'application/exi',
+ 'exp' => 'application/express',
+ 'exr' => 'image/aces',
+ 'ext' => 'application/vnd.novadigm.ext',
+ 'ez' => 'application/andrew-inset',
+ 'ez2' => 'application/vnd.ezpix-album',
+ 'ez3' => 'application/vnd.ezpix-package',
+ 'f' => 'text/x-fortran',
+ 'f4v' => 'video/mp4',
+ 'f77' => 'text/x-fortran',
+ 'f90' => 'text/x-fortran',
+ 'fbs' => 'image/vnd.fastbidsheet',
+ 'fcdt' => 'application/vnd.adobe.formscentral.fcdt',
+ 'fcs' => 'application/vnd.isac.fcs',
+ 'fdf' => 'application/vnd.fdf',
+ 'fdt' => 'application/fdt+xml',
+ 'fe_launch' => 'application/vnd.denovo.fcselayout-link',
+ 'fg5' => 'application/vnd.fujitsu.oasysgp',
+ 'fgd' => 'application/x-director',
+ 'fh' => 'image/x-freehand',
+ 'fh4' => 'image/x-freehand',
+ 'fh5' => 'image/x-freehand',
+ 'fh7' => 'image/x-freehand',
+ 'fhc' => 'image/x-freehand',
+ 'fig' => 'application/x-xfig',
+ 'fits' => 'image/fits',
+ 'flac' => 'audio/x-flac',
+ 'fli' => 'video/x-fli',
+ 'flo' => 'application/vnd.micrografx.flo',
+ 'flv' => 'video/x-flv',
+ 'flw' => 'application/vnd.kde.kivio',
+ 'flx' => 'text/vnd.fmi.flexstor',
+ 'fly' => 'text/vnd.fly',
+ 'fm' => 'application/vnd.framemaker',
+ 'fnc' => 'application/vnd.frogans.fnc',
+ 'fo' => 'application/vnd.software602.filler.form+xml',
+ 'for' => 'text/x-fortran',
+ 'fpx' => 'image/vnd.fpx',
+ 'frame' => 'application/vnd.framemaker',
+ 'fsc' => 'application/vnd.fsc.weblaunch',
+ 'fst' => 'image/vnd.fst',
+ 'ftc' => 'application/vnd.fluxtime.clip',
+ 'fti' => 'application/vnd.anser-web-funds-transfer-initiation',
+ 'fvt' => 'video/vnd.fvt',
+ 'fxp' => 'application/vnd.adobe.fxp',
+ 'fxpl' => 'application/vnd.adobe.fxp',
+ 'fzs' => 'application/vnd.fuzzysheet',
+ 'g2w' => 'application/vnd.geoplan',
+ 'g3' => 'image/g3fax',
+ 'g3w' => 'application/vnd.geospace',
+ 'gac' => 'application/vnd.groove-account',
+ 'gam' => 'application/x-tads',
+ 'gbr' => 'application/rpki-ghostbusters',
+ 'gca' => 'application/x-gca-compressed',
+ 'gdl' => 'model/vnd.gdl',
+ 'gdoc' => 'application/vnd.google-apps.document',
+ 'ged' => 'text/vnd.familysearch.gedcom',
+ 'geo' => 'application/vnd.dynageo',
+ 'geojson' => 'application/geo+json',
+ 'gex' => 'application/vnd.geometry-explorer',
+ 'ggb' => 'application/vnd.geogebra.file',
+ 'ggt' => 'application/vnd.geogebra.tool',
+ 'ghf' => 'application/vnd.groove-help',
+ 'gif' => 'image/gif',
+ 'gim' => 'application/vnd.groove-identity-message',
+ 'glb' => 'model/gltf-binary',
+ 'gltf' => 'model/gltf+json',
+ 'gml' => 'application/gml+xml',
+ 'gmx' => 'application/vnd.gmx',
+ 'gnumeric' => 'application/x-gnumeric',
+ 'gpg' => 'application/gpg-keys',
+ 'gph' => 'application/vnd.flographit',
+ 'gpx' => 'application/gpx+xml',
+ 'gqf' => 'application/vnd.grafeq',
+ 'gqs' => 'application/vnd.grafeq',
+ 'gram' => 'application/srgs',
+ 'gramps' => 'application/x-gramps-xml',
+ 'gre' => 'application/vnd.geometry-explorer',
+ 'grv' => 'application/vnd.groove-injector',
+ 'grxml' => 'application/srgs+xml',
+ 'gsf' => 'application/x-font-ghostscript',
+ 'gsheet' => 'application/vnd.google-apps.spreadsheet',
+ 'gslides' => 'application/vnd.google-apps.presentation',
+ 'gtar' => 'application/x-gtar',
+ 'gtm' => 'application/vnd.groove-tool-message',
+ 'gtw' => 'model/vnd.gtw',
+ 'gv' => 'text/vnd.graphviz',
+ 'gxf' => 'application/gxf',
+ 'gxt' => 'application/vnd.geonext',
+ 'gz' => 'application/gzip',
+ 'gzip' => 'application/gzip',
+ 'h' => 'text/x-c',
+ 'h261' => 'video/h261',
+ 'h263' => 'video/h263',
+ 'h264' => 'video/h264',
+ 'hal' => 'application/vnd.hal+xml',
+ 'hbci' => 'application/vnd.hbci',
+ 'hbs' => 'text/x-handlebars-template',
+ 'hdd' => 'application/x-virtualbox-hdd',
+ 'hdf' => 'application/x-hdf',
+ 'heic' => 'image/heic',
+ 'heics' => 'image/heic-sequence',
+ 'heif' => 'image/heif',
+ 'heifs' => 'image/heif-sequence',
+ 'hej2' => 'image/hej2k',
+ 'held' => 'application/atsc-held+xml',
+ 'hh' => 'text/x-c',
+ 'hjson' => 'application/hjson',
+ 'hlp' => 'application/winhlp',
+ 'hpgl' => 'application/vnd.hp-hpgl',
+ 'hpid' => 'application/vnd.hp-hpid',
+ 'hps' => 'application/vnd.hp-hps',
+ 'hqx' => 'application/mac-binhex40',
+ 'hsj2' => 'image/hsj2',
+ 'htc' => 'text/x-component',
+ 'htke' => 'application/vnd.kenameaapp',
+ 'htm' => 'text/html',
+ 'html' => 'text/html',
+ 'hvd' => 'application/vnd.yamaha.hv-dic',
+ 'hvp' => 'application/vnd.yamaha.hv-voice',
+ 'hvs' => 'application/vnd.yamaha.hv-script',
+ 'i2g' => 'application/vnd.intergeo',
+ 'icc' => 'application/vnd.iccprofile',
+ 'ice' => 'x-conference/x-cooltalk',
+ 'icm' => 'application/vnd.iccprofile',
+ 'ico' => 'image/x-icon',
+ 'ics' => 'text/calendar',
+ 'ief' => 'image/ief',
+ 'ifb' => 'text/calendar',
+ 'ifm' => 'application/vnd.shana.informed.formdata',
+ 'iges' => 'model/iges',
+ 'igl' => 'application/vnd.igloader',
+ 'igm' => 'application/vnd.insors.igm',
+ 'igs' => 'model/iges',
+ 'igx' => 'application/vnd.micrografx.igx',
+ 'iif' => 'application/vnd.shana.informed.interchange',
+ 'img' => 'application/octet-stream',
+ 'imp' => 'application/vnd.accpac.simply.imp',
+ 'ims' => 'application/vnd.ms-ims',
+ 'in' => 'text/plain',
+ 'ini' => 'text/plain',
+ 'ink' => 'application/inkml+xml',
+ 'inkml' => 'application/inkml+xml',
+ 'install' => 'application/x-install-instructions',
+ 'iota' => 'application/vnd.astraea-software.iota',
+ 'ipfix' => 'application/ipfix',
+ 'ipk' => 'application/vnd.shana.informed.package',
+ 'irm' => 'application/vnd.ibm.rights-management',
+ 'irp' => 'application/vnd.irepository.package+xml',
+ 'iso' => 'application/x-iso9660-image',
+ 'itp' => 'application/vnd.shana.informed.formtemplate',
+ 'its' => 'application/its+xml',
+ 'ivp' => 'application/vnd.immervision-ivp',
+ 'ivu' => 'application/vnd.immervision-ivu',
+ 'jad' => 'text/vnd.sun.j2me.app-descriptor',
+ 'jade' => 'text/jade',
+ 'jam' => 'application/vnd.jam',
+ 'jar' => 'application/java-archive',
+ 'jardiff' => 'application/x-java-archive-diff',
+ 'java' => 'text/x-java-source',
+ 'jhc' => 'image/jphc',
+ 'jisp' => 'application/vnd.jisp',
+ 'jls' => 'image/jls',
+ 'jlt' => 'application/vnd.hp-jlyt',
+ 'jng' => 'image/x-jng',
+ 'jnlp' => 'application/x-java-jnlp-file',
+ 'joda' => 'application/vnd.joost.joda-archive',
+ 'jp2' => 'image/jp2',
+ 'jpe' => 'image/jpeg',
+ 'jpeg' => 'image/jpeg',
+ 'jpf' => 'image/jpx',
+ 'jpg' => 'image/jpeg',
+ 'jpg2' => 'image/jp2',
+ 'jpgm' => 'video/jpm',
+ 'jpgv' => 'video/jpeg',
+ 'jph' => 'image/jph',
+ 'jpm' => 'video/jpm',
+ 'jpx' => 'image/jpx',
+ 'js' => 'application/javascript',
+ 'json' => 'application/json',
+ 'json5' => 'application/json5',
+ 'jsonld' => 'application/ld+json',
+ 'jsonml' => 'application/jsonml+json',
+ 'jsx' => 'text/jsx',
+ 'jt' => 'model/jt',
+ 'jxr' => 'image/jxr',
+ 'jxra' => 'image/jxra',
+ 'jxrs' => 'image/jxrs',
+ 'jxs' => 'image/jxs',
+ 'jxsc' => 'image/jxsc',
+ 'jxsi' => 'image/jxsi',
+ 'jxss' => 'image/jxss',
+ 'kar' => 'audio/midi',
+ 'karbon' => 'application/vnd.kde.karbon',
+ 'kdb' => 'application/octet-stream',
+ 'kdbx' => 'application/x-keepass2',
+ 'key' => 'application/x-iwork-keynote-sffkey',
+ 'kfo' => 'application/vnd.kde.kformula',
+ 'kia' => 'application/vnd.kidspiration',
+ 'kml' => 'application/vnd.google-earth.kml+xml',
+ 'kmz' => 'application/vnd.google-earth.kmz',
+ 'kne' => 'application/vnd.kinar',
+ 'knp' => 'application/vnd.kinar',
+ 'kon' => 'application/vnd.kde.kontour',
+ 'kpr' => 'application/vnd.kde.kpresenter',
+ 'kpt' => 'application/vnd.kde.kpresenter',
+ 'kpxx' => 'application/vnd.ds-keypoint',
+ 'ksp' => 'application/vnd.kde.kspread',
+ 'ktr' => 'application/vnd.kahootz',
+ 'ktx' => 'image/ktx',
+ 'ktx2' => 'image/ktx2',
+ 'ktz' => 'application/vnd.kahootz',
+ 'kwd' => 'application/vnd.kde.kword',
+ 'kwt' => 'application/vnd.kde.kword',
+ 'lasxml' => 'application/vnd.las.las+xml',
+ 'latex' => 'application/x-latex',
+ 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop',
+ 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml',
+ 'les' => 'application/vnd.hhe.lesson-player',
+ 'less' => 'text/less',
+ 'lgr' => 'application/lgr+xml',
+ 'lha' => 'application/octet-stream',
+ 'link66' => 'application/vnd.route66.link66+xml',
+ 'list' => 'text/plain',
+ 'list3820' => 'application/vnd.ibm.modcap',
+ 'listafp' => 'application/vnd.ibm.modcap',
+ 'litcoffee' => 'text/coffeescript',
+ 'lnk' => 'application/x-ms-shortcut',
+ 'log' => 'text/plain',
+ 'lostxml' => 'application/lost+xml',
+ 'lrf' => 'application/octet-stream',
+ 'lrm' => 'application/vnd.ms-lrm',
+ 'ltf' => 'application/vnd.frogans.ltf',
+ 'lua' => 'text/x-lua',
+ 'luac' => 'application/x-lua-bytecode',
+ 'lvp' => 'audio/vnd.lucent.voice',
+ 'lwp' => 'application/vnd.lotus-wordpro',
+ 'lzh' => 'application/octet-stream',
+ 'm1v' => 'video/mpeg',
+ 'm2a' => 'audio/mpeg',
+ 'm2v' => 'video/mpeg',
+ 'm3a' => 'audio/mpeg',
+ 'm3u' => 'text/plain',
+ 'm3u8' => 'application/vnd.apple.mpegurl',
+ 'm4a' => 'audio/x-m4a',
+ 'm4p' => 'application/mp4',
+ 'm4s' => 'video/iso.segment',
+ 'm4u' => 'application/vnd.mpegurl',
+ 'm4v' => 'video/x-m4v',
+ 'm13' => 'application/x-msmediaview',
+ 'm14' => 'application/x-msmediaview',
+ 'm21' => 'application/mp21',
+ 'ma' => 'application/mathematica',
+ 'mads' => 'application/mads+xml',
+ 'maei' => 'application/mmt-aei+xml',
+ 'mag' => 'application/vnd.ecowin.chart',
+ 'maker' => 'application/vnd.framemaker',
+ 'man' => 'text/troff',
+ 'manifest' => 'text/cache-manifest',
+ 'map' => 'application/json',
+ 'mar' => 'application/octet-stream',
+ 'markdown' => 'text/markdown',
+ 'mathml' => 'application/mathml+xml',
+ 'mb' => 'application/mathematica',
+ 'mbk' => 'application/vnd.mobius.mbk',
+ 'mbox' => 'application/mbox',
+ 'mc1' => 'application/vnd.medcalcdata',
+ 'mcd' => 'application/vnd.mcd',
+ 'mcurl' => 'text/vnd.curl.mcurl',
+ 'md' => 'text/markdown',
+ 'mdb' => 'application/x-msaccess',
+ 'mdi' => 'image/vnd.ms-modi',
+ 'mdx' => 'text/mdx',
+ 'me' => 'text/troff',
+ 'mesh' => 'model/mesh',
+ 'meta4' => 'application/metalink4+xml',
+ 'metalink' => 'application/metalink+xml',
+ 'mets' => 'application/mets+xml',
+ 'mfm' => 'application/vnd.mfmp',
+ 'mft' => 'application/rpki-manifest',
+ 'mgp' => 'application/vnd.osgeo.mapguide.package',
+ 'mgz' => 'application/vnd.proteus.magazine',
+ 'mid' => 'audio/midi',
+ 'midi' => 'audio/midi',
+ 'mie' => 'application/x-mie',
+ 'mif' => 'application/vnd.mif',
+ 'mime' => 'message/rfc822',
+ 'mj2' => 'video/mj2',
+ 'mjp2' => 'video/mj2',
+ 'mjs' => 'text/javascript',
+ 'mk3d' => 'video/x-matroska',
+ 'mka' => 'audio/x-matroska',
+ 'mkd' => 'text/x-markdown',
+ 'mks' => 'video/x-matroska',
+ 'mkv' => 'video/x-matroska',
+ 'mlp' => 'application/vnd.dolby.mlp',
+ 'mmd' => 'application/vnd.chipnuts.karaoke-mmd',
+ 'mmf' => 'application/vnd.smaf',
+ 'mml' => 'text/mathml',
+ 'mmr' => 'image/vnd.fujixerox.edmics-mmr',
+ 'mng' => 'video/x-mng',
+ 'mny' => 'application/x-msmoney',
+ 'mobi' => 'application/x-mobipocket-ebook',
+ 'mods' => 'application/mods+xml',
+ 'mov' => 'video/quicktime',
+ 'movie' => 'video/x-sgi-movie',
+ 'mp2' => 'audio/mpeg',
+ 'mp2a' => 'audio/mpeg',
+ 'mp3' => 'audio/mpeg',
+ 'mp4' => 'video/mp4',
+ 'mp4a' => 'audio/mp4',
+ 'mp4s' => 'application/mp4',
+ 'mp4v' => 'video/mp4',
+ 'mp21' => 'application/mp21',
+ 'mpc' => 'application/vnd.mophun.certificate',
+ 'mpd' => 'application/dash+xml',
+ 'mpe' => 'video/mpeg',
+ 'mpeg' => 'video/mpeg',
+ 'mpf' => 'application/media-policy-dataset+xml',
+ 'mpg' => 'video/mpeg',
+ 'mpg4' => 'video/mp4',
+ 'mpga' => 'audio/mpeg',
+ 'mpkg' => 'application/vnd.apple.installer+xml',
+ 'mpm' => 'application/vnd.blueice.multipass',
+ 'mpn' => 'application/vnd.mophun.application',
+ 'mpp' => 'application/vnd.ms-project',
+ 'mpt' => 'application/vnd.ms-project',
+ 'mpy' => 'application/vnd.ibm.minipay',
+ 'mqy' => 'application/vnd.mobius.mqy',
+ 'mrc' => 'application/marc',
+ 'mrcx' => 'application/marcxml+xml',
+ 'ms' => 'text/troff',
+ 'mscml' => 'application/mediaservercontrol+xml',
+ 'mseed' => 'application/vnd.fdsn.mseed',
+ 'mseq' => 'application/vnd.mseq',
+ 'msf' => 'application/vnd.epson.msf',
+ 'msg' => 'application/vnd.ms-outlook',
+ 'msh' => 'model/mesh',
+ 'msi' => 'application/x-msdownload',
+ 'msix' => 'application/msix',
+ 'msixbundle' => 'application/msixbundle',
+ 'msl' => 'application/vnd.mobius.msl',
+ 'msm' => 'application/octet-stream',
+ 'msp' => 'application/octet-stream',
+ 'msty' => 'application/vnd.muvee.style',
+ 'mtl' => 'model/mtl',
+ 'mts' => 'model/vnd.mts',
+ 'mus' => 'application/vnd.musician',
+ 'musd' => 'application/mmt-usd+xml',
+ 'musicxml' => 'application/vnd.recordare.musicxml+xml',
+ 'mvb' => 'application/x-msmediaview',
+ 'mvt' => 'application/vnd.mapbox-vector-tile',
+ 'mwf' => 'application/vnd.mfer',
+ 'mxf' => 'application/mxf',
+ 'mxl' => 'application/vnd.recordare.musicxml',
+ 'mxmf' => 'audio/mobile-xmf',
+ 'mxml' => 'application/xv+xml',
+ 'mxs' => 'application/vnd.triscape.mxs',
+ 'mxu' => 'video/vnd.mpegurl',
+ 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install',
+ 'n3' => 'text/n3',
+ 'nb' => 'application/mathematica',
+ 'nbp' => 'application/vnd.wolfram.player',
+ 'nc' => 'application/x-netcdf',
+ 'ncx' => 'application/x-dtbncx+xml',
+ 'nfo' => 'text/x-nfo',
+ 'ngdat' => 'application/vnd.nokia.n-gage.data',
+ 'nitf' => 'application/vnd.nitf',
+ 'nlu' => 'application/vnd.neurolanguage.nlu',
+ 'nml' => 'application/vnd.enliven',
+ 'nnd' => 'application/vnd.noblenet-directory',
+ 'nns' => 'application/vnd.noblenet-sealer',
+ 'nnw' => 'application/vnd.noblenet-web',
+ 'npx' => 'image/vnd.net-fpx',
+ 'nq' => 'application/n-quads',
+ 'nsc' => 'application/x-conference',
+ 'nsf' => 'application/vnd.lotus-notes',
+ 'nt' => 'application/n-triples',
+ 'ntf' => 'application/vnd.nitf',
+ 'numbers' => 'application/x-iwork-numbers-sffnumbers',
+ 'nzb' => 'application/x-nzb',
+ 'oa2' => 'application/vnd.fujitsu.oasys2',
+ 'oa3' => 'application/vnd.fujitsu.oasys3',
+ 'oas' => 'application/vnd.fujitsu.oasys',
+ 'obd' => 'application/x-msbinder',
+ 'obgx' => 'application/vnd.openblox.game+xml',
+ 'obj' => 'model/obj',
+ 'oda' => 'application/oda',
+ 'odb' => 'application/vnd.oasis.opendocument.database',
+ 'odc' => 'application/vnd.oasis.opendocument.chart',
+ 'odf' => 'application/vnd.oasis.opendocument.formula',
+ 'odft' => 'application/vnd.oasis.opendocument.formula-template',
+ 'odg' => 'application/vnd.oasis.opendocument.graphics',
+ 'odi' => 'application/vnd.oasis.opendocument.image',
+ 'odm' => 'application/vnd.oasis.opendocument.text-master',
+ 'odp' => 'application/vnd.oasis.opendocument.presentation',
+ 'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
+ 'odt' => 'application/vnd.oasis.opendocument.text',
+ 'oga' => 'audio/ogg',
+ 'ogex' => 'model/vnd.opengex',
+ 'ogg' => 'audio/ogg',
+ 'ogv' => 'video/ogg',
+ 'ogx' => 'application/ogg',
+ 'omdoc' => 'application/omdoc+xml',
+ 'onepkg' => 'application/onenote',
+ 'onetmp' => 'application/onenote',
+ 'onetoc' => 'application/onenote',
+ 'onetoc2' => 'application/onenote',
+ 'opf' => 'application/oebps-package+xml',
+ 'opml' => 'text/x-opml',
+ 'oprc' => 'application/vnd.palm',
+ 'opus' => 'audio/ogg',
+ 'org' => 'text/x-org',
+ 'osf' => 'application/vnd.yamaha.openscoreformat',
+ 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml',
+ 'osm' => 'application/vnd.openstreetmap.data+xml',
+ 'otc' => 'application/vnd.oasis.opendocument.chart-template',
+ 'otf' => 'font/otf',
+ 'otg' => 'application/vnd.oasis.opendocument.graphics-template',
+ 'oth' => 'application/vnd.oasis.opendocument.text-web',
+ 'oti' => 'application/vnd.oasis.opendocument.image-template',
+ 'otp' => 'application/vnd.oasis.opendocument.presentation-template',
+ 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template',
+ 'ott' => 'application/vnd.oasis.opendocument.text-template',
+ 'ova' => 'application/x-virtualbox-ova',
+ 'ovf' => 'application/x-virtualbox-ovf',
+ 'owl' => 'application/rdf+xml',
+ 'oxps' => 'application/oxps',
+ 'oxt' => 'application/vnd.openofficeorg.extension',
+ 'p' => 'text/x-pascal',
+ 'p7a' => 'application/x-pkcs7-signature',
+ 'p7b' => 'application/x-pkcs7-certificates',
+ 'p7c' => 'application/pkcs7-mime',
+ 'p7m' => 'application/pkcs7-mime',
+ 'p7r' => 'application/x-pkcs7-certreqresp',
+ 'p7s' => 'application/pkcs7-signature',
+ 'p8' => 'application/pkcs8',
+ 'p10' => 'application/x-pkcs10',
+ 'p12' => 'application/x-pkcs12',
+ 'pac' => 'application/x-ns-proxy-autoconfig',
+ 'pages' => 'application/x-iwork-pages-sffpages',
+ 'pas' => 'text/x-pascal',
+ 'paw' => 'application/vnd.pawaafile',
+ 'pbd' => 'application/vnd.powerbuilder6',
+ 'pbm' => 'image/x-portable-bitmap',
+ 'pcap' => 'application/vnd.tcpdump.pcap',
+ 'pcf' => 'application/x-font-pcf',
+ 'pcl' => 'application/vnd.hp-pcl',
+ 'pclxl' => 'application/vnd.hp-pclxl',
+ 'pct' => 'image/x-pict',
+ 'pcurl' => 'application/vnd.curl.pcurl',
+ 'pcx' => 'image/x-pcx',
+ 'pdb' => 'application/x-pilot',
+ 'pde' => 'text/x-processing',
+ 'pdf' => 'application/pdf',
+ 'pem' => 'application/x-x509-user-cert',
+ 'pfa' => 'application/x-font-type1',
+ 'pfb' => 'application/x-font-type1',
+ 'pfm' => 'application/x-font-type1',
+ 'pfr' => 'application/font-tdpfr',
+ 'pfx' => 'application/x-pkcs12',
+ 'pgm' => 'image/x-portable-graymap',
+ 'pgn' => 'application/x-chess-pgn',
+ 'pgp' => 'application/pgp',
+ 'phar' => 'application/octet-stream',
+ 'php' => 'application/x-httpd-php',
+ 'php3' => 'application/x-httpd-php',
+ 'php4' => 'application/x-httpd-php',
+ 'phps' => 'application/x-httpd-php-source',
+ 'phtml' => 'application/x-httpd-php',
+ 'pic' => 'image/x-pict',
+ 'pkg' => 'application/octet-stream',
+ 'pki' => 'application/pkixcmp',
+ 'pkipath' => 'application/pkix-pkipath',
+ 'pkpass' => 'application/vnd.apple.pkpass',
+ 'pl' => 'application/x-perl',
+ 'plb' => 'application/vnd.3gpp.pic-bw-large',
+ 'plc' => 'application/vnd.mobius.plc',
+ 'plf' => 'application/vnd.pocketlearn',
+ 'pls' => 'application/pls+xml',
+ 'pm' => 'application/x-perl',
+ 'pml' => 'application/vnd.ctc-posml',
+ 'png' => 'image/png',
+ 'pnm' => 'image/x-portable-anymap',
+ 'portpkg' => 'application/vnd.macports.portpkg',
+ 'pot' => 'application/vnd.ms-powerpoint',
+ 'potm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
+ 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template',
+ 'ppa' => 'application/vnd.ms-powerpoint',
+ 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12',
+ 'ppd' => 'application/vnd.cups-ppd',
+ 'ppm' => 'image/x-portable-pixmap',
+ 'pps' => 'application/vnd.ms-powerpoint',
+ 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12',
+ 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
+ 'ppt' => 'application/powerpoint',
+ 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
+ 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
+ 'pqa' => 'application/vnd.palm',
+ 'prc' => 'model/prc',
+ 'pre' => 'application/vnd.lotus-freelance',
+ 'prf' => 'application/pics-rules',
+ 'provx' => 'application/provenance+xml',
+ 'ps' => 'application/postscript',
+ 'psb' => 'application/vnd.3gpp.pic-bw-small',
+ 'psd' => 'application/x-photoshop',
+ 'psf' => 'application/x-font-linux-psf',
+ 'pskcxml' => 'application/pskc+xml',
+ 'pti' => 'image/prs.pti',
+ 'ptid' => 'application/vnd.pvi.ptid1',
+ 'pub' => 'application/x-mspublisher',
+ 'pvb' => 'application/vnd.3gpp.pic-bw-var',
+ 'pwn' => 'application/vnd.3m.post-it-notes',
+ 'pya' => 'audio/vnd.ms-playready.media.pya',
+ 'pyo' => 'model/vnd.pytha.pyox',
+ 'pyox' => 'model/vnd.pytha.pyox',
+ 'pyv' => 'video/vnd.ms-playready.media.pyv',
+ 'qam' => 'application/vnd.epson.quickanime',
+ 'qbo' => 'application/vnd.intu.qbo',
+ 'qfx' => 'application/vnd.intu.qfx',
+ 'qps' => 'application/vnd.publishare-delta-tree',
+ 'qt' => 'video/quicktime',
+ 'qwd' => 'application/vnd.quark.quarkxpress',
+ 'qwt' => 'application/vnd.quark.quarkxpress',
+ 'qxb' => 'application/vnd.quark.quarkxpress',
+ 'qxd' => 'application/vnd.quark.quarkxpress',
+ 'qxl' => 'application/vnd.quark.quarkxpress',
+ 'qxt' => 'application/vnd.quark.quarkxpress',
+ 'ra' => 'audio/x-realaudio',
+ 'ram' => 'audio/x-pn-realaudio',
+ 'raml' => 'application/raml+yaml',
+ 'rapd' => 'application/route-apd+xml',
+ 'rar' => 'application/x-rar',
+ 'ras' => 'image/x-cmu-raster',
+ 'rcprofile' => 'application/vnd.ipunplugged.rcprofile',
+ 'rdf' => 'application/rdf+xml',
+ 'rdz' => 'application/vnd.data-vision.rdz',
+ 'relo' => 'application/p2p-overlay+xml',
+ 'rep' => 'application/vnd.businessobjects',
+ 'res' => 'application/x-dtbresource+xml',
+ 'rgb' => 'image/x-rgb',
+ 'rif' => 'application/reginfo+xml',
+ 'rip' => 'audio/vnd.rip',
+ 'ris' => 'application/x-research-info-systems',
+ 'rl' => 'application/resource-lists+xml',
+ 'rlc' => 'image/vnd.fujixerox.edmics-rlc',
+ 'rld' => 'application/resource-lists-diff+xml',
+ 'rm' => 'audio/x-pn-realaudio',
+ 'rmi' => 'audio/midi',
+ 'rmp' => 'audio/x-pn-realaudio-plugin',
+ 'rms' => 'application/vnd.jcp.javame.midlet-rms',
+ 'rmvb' => 'application/vnd.rn-realmedia-vbr',
+ 'rnc' => 'application/relax-ng-compact-syntax',
+ 'rng' => 'application/xml',
+ 'roa' => 'application/rpki-roa',
+ 'roff' => 'text/troff',
+ 'rp9' => 'application/vnd.cloanto.rp9',
+ 'rpm' => 'audio/x-pn-realaudio-plugin',
+ 'rpss' => 'application/vnd.nokia.radio-presets',
+ 'rpst' => 'application/vnd.nokia.radio-preset',
+ 'rq' => 'application/sparql-query',
+ 'rs' => 'application/rls-services+xml',
+ 'rsa' => 'application/x-pkcs7',
+ 'rsat' => 'application/atsc-rsat+xml',
+ 'rsd' => 'application/rsd+xml',
+ 'rsheet' => 'application/urc-ressheet+xml',
+ 'rss' => 'application/rss+xml',
+ 'rtf' => 'text/rtf',
+ 'rtx' => 'text/richtext',
+ 'run' => 'application/x-makeself',
+ 'rusd' => 'application/route-usd+xml',
+ 'rv' => 'video/vnd.rn-realvideo',
+ 's' => 'text/x-asm',
+ 's3m' => 'audio/s3m',
+ 'saf' => 'application/vnd.yamaha.smaf-audio',
+ 'sass' => 'text/x-sass',
+ 'sbml' => 'application/sbml+xml',
+ 'sc' => 'application/vnd.ibm.secure-container',
+ 'scd' => 'application/x-msschedule',
+ 'scm' => 'application/vnd.lotus-screencam',
+ 'scq' => 'application/scvp-cv-request',
+ 'scs' => 'application/scvp-cv-response',
+ 'scss' => 'text/x-scss',
+ 'scurl' => 'text/vnd.curl.scurl',
+ 'sda' => 'application/vnd.stardivision.draw',
+ 'sdc' => 'application/vnd.stardivision.calc',
+ 'sdd' => 'application/vnd.stardivision.impress',
+ 'sdkd' => 'application/vnd.solent.sdkm+xml',
+ 'sdkm' => 'application/vnd.solent.sdkm+xml',
+ 'sdp' => 'application/sdp',
+ 'sdw' => 'application/vnd.stardivision.writer',
+ 'sea' => 'application/octet-stream',
+ 'see' => 'application/vnd.seemail',
+ 'seed' => 'application/vnd.fdsn.seed',
+ 'sema' => 'application/vnd.sema',
+ 'semd' => 'application/vnd.semd',
+ 'semf' => 'application/vnd.semf',
+ 'senmlx' => 'application/senml+xml',
+ 'sensmlx' => 'application/sensml+xml',
+ 'ser' => 'application/java-serialized-object',
+ 'setpay' => 'application/set-payment-initiation',
+ 'setreg' => 'application/set-registration-initiation',
+ 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data',
+ 'sfs' => 'application/vnd.spotfire.sfs',
+ 'sfv' => 'text/x-sfv',
+ 'sgi' => 'image/sgi',
+ 'sgl' => 'application/vnd.stardivision.writer-global',
+ 'sgm' => 'text/sgml',
+ 'sgml' => 'text/sgml',
+ 'sh' => 'application/x-sh',
+ 'shar' => 'application/x-shar',
+ 'shex' => 'text/shex',
+ 'shf' => 'application/shf+xml',
+ 'shtml' => 'text/html',
+ 'sid' => 'image/x-mrsid-image',
+ 'sieve' => 'application/sieve',
+ 'sig' => 'application/pgp-signature',
+ 'sil' => 'audio/silk',
+ 'silo' => 'model/mesh',
+ 'sis' => 'application/vnd.symbian.install',
+ 'sisx' => 'application/vnd.symbian.install',
+ 'sit' => 'application/x-stuffit',
+ 'sitx' => 'application/x-stuffitx',
+ 'siv' => 'application/sieve',
+ 'skd' => 'application/vnd.koan',
+ 'skm' => 'application/vnd.koan',
+ 'skp' => 'application/vnd.koan',
+ 'skt' => 'application/vnd.koan',
+ 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12',
+ 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide',
+ 'slim' => 'text/slim',
+ 'slm' => 'text/slim',
+ 'sls' => 'application/route-s-tsid+xml',
+ 'slt' => 'application/vnd.epson.salt',
+ 'sm' => 'application/vnd.stepmania.stepchart',
+ 'smf' => 'application/vnd.stardivision.math',
+ 'smi' => 'application/smil',
+ 'smil' => 'application/smil',
+ 'smv' => 'video/x-smv',
+ 'smzip' => 'application/vnd.stepmania.package',
+ 'snd' => 'audio/basic',
+ 'snf' => 'application/x-font-snf',
+ 'so' => 'application/octet-stream',
+ 'spc' => 'application/x-pkcs7-certificates',
+ 'spdx' => 'text/spdx',
+ 'spf' => 'application/vnd.yamaha.smaf-phrase',
+ 'spl' => 'application/x-futuresplash',
+ 'spot' => 'text/vnd.in3d.spot',
+ 'spp' => 'application/scvp-vp-response',
+ 'spq' => 'application/scvp-vp-request',
+ 'spx' => 'audio/ogg',
+ 'sql' => 'application/x-sql',
+ 'src' => 'application/x-wais-source',
+ 'srt' => 'application/x-subrip',
+ 'sru' => 'application/sru+xml',
+ 'srx' => 'application/sparql-results+xml',
+ 'ssdl' => 'application/ssdl+xml',
+ 'sse' => 'application/vnd.kodak-descriptor',
+ 'ssf' => 'application/vnd.epson.ssf',
+ 'ssml' => 'application/ssml+xml',
+ 'sst' => 'application/octet-stream',
+ 'st' => 'application/vnd.sailingtracker.track',
+ 'stc' => 'application/vnd.sun.xml.calc.template',
+ 'std' => 'application/vnd.sun.xml.draw.template',
+ 'step' => 'application/STEP',
+ 'stf' => 'application/vnd.wt.stf',
+ 'sti' => 'application/vnd.sun.xml.impress.template',
+ 'stk' => 'application/hyperstudio',
+ 'stl' => 'model/stl',
+ 'stp' => 'application/STEP',
+ 'stpx' => 'model/step+xml',
+ 'stpxz' => 'model/step-xml+zip',
+ 'stpz' => 'model/step+zip',
+ 'str' => 'application/vnd.pg.format',
+ 'stw' => 'application/vnd.sun.xml.writer.template',
+ 'styl' => 'text/stylus',
+ 'stylus' => 'text/stylus',
+ 'sub' => 'text/vnd.dvb.subtitle',
+ 'sus' => 'application/vnd.sus-calendar',
+ 'susp' => 'application/vnd.sus-calendar',
+ 'sv4cpio' => 'application/x-sv4cpio',
+ 'sv4crc' => 'application/x-sv4crc',
+ 'svc' => 'application/vnd.dvb.service',
+ 'svd' => 'application/vnd.svd',
+ 'svg' => 'image/svg+xml',
+ 'svgz' => 'image/svg+xml',
+ 'swa' => 'application/x-director',
+ 'swf' => 'application/x-shockwave-flash',
+ 'swi' => 'application/vnd.aristanetworks.swi',
+ 'swidtag' => 'application/swid+xml',
+ 'sxc' => 'application/vnd.sun.xml.calc',
+ 'sxd' => 'application/vnd.sun.xml.draw',
+ 'sxg' => 'application/vnd.sun.xml.writer.global',
+ 'sxi' => 'application/vnd.sun.xml.impress',
+ 'sxm' => 'application/vnd.sun.xml.math',
+ 'sxw' => 'application/vnd.sun.xml.writer',
+ 't' => 'text/troff',
+ 't3' => 'application/x-t3vm-image',
+ 't38' => 'image/t38',
+ 'taglet' => 'application/vnd.mynfc',
+ 'tao' => 'application/vnd.tao.intent-module-archive',
+ 'tap' => 'image/vnd.tencent.tap',
+ 'tar' => 'application/x-tar',
+ 'tcap' => 'application/vnd.3gpp2.tcap',
+ 'tcl' => 'application/x-tcl',
+ 'td' => 'application/urc-targetdesc+xml',
+ 'teacher' => 'application/vnd.smart.teacher',
+ 'tei' => 'application/tei+xml',
+ 'teicorpus' => 'application/tei+xml',
+ 'tex' => 'application/x-tex',
+ 'texi' => 'application/x-texinfo',
+ 'texinfo' => 'application/x-texinfo',
+ 'text' => 'text/plain',
+ 'tfi' => 'application/thraud+xml',
+ 'tfm' => 'application/x-tex-tfm',
+ 'tfx' => 'image/tiff-fx',
+ 'tga' => 'image/x-tga',
+ 'tgz' => 'application/x-tar',
+ 'thmx' => 'application/vnd.ms-officetheme',
+ 'tif' => 'image/tiff',
+ 'tiff' => 'image/tiff',
+ 'tk' => 'application/x-tcl',
+ 'tmo' => 'application/vnd.tmobile-livetv',
+ 'toml' => 'application/toml',
+ 'torrent' => 'application/x-bittorrent',
+ 'tpl' => 'application/vnd.groove-tool-template',
+ 'tpt' => 'application/vnd.trid.tpt',
+ 'tr' => 'text/troff',
+ 'tra' => 'application/vnd.trueapp',
+ 'trig' => 'application/trig',
+ 'trm' => 'application/x-msterminal',
+ 'ts' => 'video/mp2t',
+ 'tsd' => 'application/timestamped-data',
+ 'tsv' => 'text/tab-separated-values',
+ 'ttc' => 'font/collection',
+ 'ttf' => 'font/ttf',
+ 'ttl' => 'text/turtle',
+ 'ttml' => 'application/ttml+xml',
+ 'twd' => 'application/vnd.simtech-mindmapper',
+ 'twds' => 'application/vnd.simtech-mindmapper',
+ 'txd' => 'application/vnd.genomatix.tuxedo',
+ 'txf' => 'application/vnd.mobius.txf',
+ 'txt' => 'text/plain',
+ 'u3d' => 'model/u3d',
+ 'u8dsn' => 'message/global-delivery-status',
+ 'u8hdr' => 'message/global-headers',
+ 'u8mdn' => 'message/global-disposition-notification',
+ 'u8msg' => 'message/global',
+ 'u32' => 'application/x-authorware-bin',
+ 'ubj' => 'application/ubjson',
+ 'udeb' => 'application/x-debian-package',
+ 'ufd' => 'application/vnd.ufdl',
+ 'ufdl' => 'application/vnd.ufdl',
+ 'ulx' => 'application/x-glulx',
+ 'umj' => 'application/vnd.umajin',
+ 'unityweb' => 'application/vnd.unity',
+ 'uo' => 'application/vnd.uoml+xml',
+ 'uoml' => 'application/vnd.uoml+xml',
+ 'uri' => 'text/uri-list',
+ 'uris' => 'text/uri-list',
+ 'urls' => 'text/uri-list',
+ 'usda' => 'model/vnd.usda',
+ 'usdz' => 'model/vnd.usdz+zip',
+ 'ustar' => 'application/x-ustar',
+ 'utz' => 'application/vnd.uiq.theme',
+ 'uu' => 'text/x-uuencode',
+ 'uva' => 'audio/vnd.dece.audio',
+ 'uvd' => 'application/vnd.dece.data',
+ 'uvf' => 'application/vnd.dece.data',
+ 'uvg' => 'image/vnd.dece.graphic',
+ 'uvh' => 'video/vnd.dece.hd',
+ 'uvi' => 'image/vnd.dece.graphic',
+ 'uvm' => 'video/vnd.dece.mobile',
+ 'uvp' => 'video/vnd.dece.pd',
+ 'uvs' => 'video/vnd.dece.sd',
+ 'uvt' => 'application/vnd.dece.ttml+xml',
+ 'uvu' => 'video/vnd.uvvu.mp4',
+ 'uvv' => 'video/vnd.dece.video',
+ 'uvva' => 'audio/vnd.dece.audio',
+ 'uvvd' => 'application/vnd.dece.data',
+ 'uvvf' => 'application/vnd.dece.data',
+ 'uvvg' => 'image/vnd.dece.graphic',
+ 'uvvh' => 'video/vnd.dece.hd',
+ 'uvvi' => 'image/vnd.dece.graphic',
+ 'uvvm' => 'video/vnd.dece.mobile',
+ 'uvvp' => 'video/vnd.dece.pd',
+ 'uvvs' => 'video/vnd.dece.sd',
+ 'uvvt' => 'application/vnd.dece.ttml+xml',
+ 'uvvu' => 'video/vnd.uvvu.mp4',
+ 'uvvv' => 'video/vnd.dece.video',
+ 'uvvx' => 'application/vnd.dece.unspecified',
+ 'uvvz' => 'application/vnd.dece.zip',
+ 'uvx' => 'application/vnd.dece.unspecified',
+ 'uvz' => 'application/vnd.dece.zip',
+ 'vbox' => 'application/x-virtualbox-vbox',
+ 'vbox-extpack' => 'application/x-virtualbox-vbox-extpack',
+ 'vcard' => 'text/vcard',
+ 'vcd' => 'application/x-cdlink',
+ 'vcf' => 'text/x-vcard',
+ 'vcg' => 'application/vnd.groove-vcard',
+ 'vcs' => 'text/x-vcalendar',
+ 'vcx' => 'application/vnd.vcx',
+ 'vdi' => 'application/x-virtualbox-vdi',
+ 'vds' => 'model/vnd.sap.vds',
+ 'vhd' => 'application/x-virtualbox-vhd',
+ 'vis' => 'application/vnd.visionary',
+ 'viv' => 'video/vnd.vivo',
+ 'vlc' => 'application/videolan',
+ 'vmdk' => 'application/x-virtualbox-vmdk',
+ 'vob' => 'video/x-ms-vob',
+ 'vor' => 'application/vnd.stardivision.writer',
+ 'vox' => 'application/x-authorware-bin',
+ 'vrml' => 'model/vrml',
+ 'vsd' => 'application/vnd.visio',
+ 'vsf' => 'application/vnd.vsf',
+ 'vss' => 'application/vnd.visio',
+ 'vst' => 'application/vnd.visio',
+ 'vsw' => 'application/vnd.visio',
+ 'vtf' => 'image/vnd.valve.source.texture',
+ 'vtt' => 'text/vtt',
+ 'vtu' => 'model/vnd.vtu',
+ 'vxml' => 'application/voicexml+xml',
+ 'w3d' => 'application/x-director',
+ 'wad' => 'application/x-doom',
+ 'wadl' => 'application/vnd.sun.wadl+xml',
+ 'war' => 'application/java-archive',
+ 'wasm' => 'application/wasm',
+ 'wav' => 'audio/x-wav',
+ 'wax' => 'audio/x-ms-wax',
+ 'wbmp' => 'image/vnd.wap.wbmp',
+ 'wbs' => 'application/vnd.criticaltools.wbs+xml',
+ 'wbxml' => 'application/wbxml',
+ 'wcm' => 'application/vnd.ms-works',
+ 'wdb' => 'application/vnd.ms-works',
+ 'wdp' => 'image/vnd.ms-photo',
+ 'weba' => 'audio/webm',
+ 'webapp' => 'application/x-web-app-manifest+json',
+ 'webm' => 'video/webm',
+ 'webmanifest' => 'application/manifest+json',
+ 'webp' => 'image/webp',
+ 'wg' => 'application/vnd.pmi.widget',
+ 'wgsl' => 'text/wgsl',
+ 'wgt' => 'application/widget',
+ 'wif' => 'application/watcherinfo+xml',
+ 'wks' => 'application/vnd.ms-works',
+ 'wm' => 'video/x-ms-wm',
+ 'wma' => 'audio/x-ms-wma',
+ 'wmd' => 'application/x-ms-wmd',
+ 'wmf' => 'image/wmf',
+ 'wml' => 'text/vnd.wap.wml',
+ 'wmlc' => 'application/wmlc',
+ 'wmls' => 'text/vnd.wap.wmlscript',
+ 'wmlsc' => 'application/vnd.wap.wmlscriptc',
+ 'wmv' => 'video/x-ms-wmv',
+ 'wmx' => 'video/x-ms-wmx',
+ 'wmz' => 'application/x-msmetafile',
+ 'woff' => 'font/woff',
+ 'woff2' => 'font/woff2',
+ 'word' => 'application/msword',
+ 'wpd' => 'application/vnd.wordperfect',
+ 'wpl' => 'application/vnd.ms-wpl',
+ 'wps' => 'application/vnd.ms-works',
+ 'wqd' => 'application/vnd.wqd',
+ 'wri' => 'application/x-mswrite',
+ 'wrl' => 'model/vrml',
+ 'wsc' => 'message/vnd.wfa.wsc',
+ 'wsdl' => 'application/wsdl+xml',
+ 'wspolicy' => 'application/wspolicy+xml',
+ 'wtb' => 'application/vnd.webturbo',
+ 'wvx' => 'video/x-ms-wvx',
+ 'x3d' => 'model/x3d+xml',
+ 'x3db' => 'model/x3d+fastinfoset',
+ 'x3dbz' => 'model/x3d+binary',
+ 'x3dv' => 'model/x3d-vrml',
+ 'x3dvz' => 'model/x3d+vrml',
+ 'x3dz' => 'model/x3d+xml',
+ 'x32' => 'application/x-authorware-bin',
+ 'x_b' => 'model/vnd.parasolid.transmit.binary',
+ 'x_t' => 'model/vnd.parasolid.transmit.text',
+ 'xaml' => 'application/xaml+xml',
+ 'xap' => 'application/x-silverlight-app',
+ 'xar' => 'application/vnd.xara',
+ 'xav' => 'application/xcap-att+xml',
+ 'xbap' => 'application/x-ms-xbap',
+ 'xbd' => 'application/vnd.fujixerox.docuworks.binder',
+ 'xbm' => 'image/x-xbitmap',
+ 'xca' => 'application/xcap-caps+xml',
+ 'xcs' => 'application/calendar+xml',
+ 'xdf' => 'application/xcap-diff+xml',
+ 'xdm' => 'application/vnd.syncml.dm+xml',
+ 'xdp' => 'application/vnd.adobe.xdp+xml',
+ 'xdssc' => 'application/dssc+xml',
+ 'xdw' => 'application/vnd.fujixerox.docuworks',
+ 'xel' => 'application/xcap-el+xml',
+ 'xenc' => 'application/xenc+xml',
+ 'xer' => 'application/patch-ops-error+xml',
+ 'xfdf' => 'application/xfdf',
+ 'xfdl' => 'application/vnd.xfdl',
+ 'xht' => 'application/xhtml+xml',
+ 'xhtm' => 'application/vnd.pwg-xhtml-print+xml',
+ 'xhtml' => 'application/xhtml+xml',
+ 'xhvml' => 'application/xv+xml',
+ 'xif' => 'image/vnd.xiff',
+ 'xl' => 'application/excel',
+ 'xla' => 'application/vnd.ms-excel',
+ 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12',
+ 'xlc' => 'application/vnd.ms-excel',
+ 'xlf' => 'application/xliff+xml',
+ 'xlm' => 'application/vnd.ms-excel',
+ 'xls' => 'application/vnd.ms-excel',
+ 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
+ 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12',
+ 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'xlt' => 'application/vnd.ms-excel',
+ 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12',
+ 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
+ 'xlw' => 'application/vnd.ms-excel',
+ 'xm' => 'audio/xm',
+ 'xml' => 'application/xml',
+ 'xns' => 'application/xcap-ns+xml',
+ 'xo' => 'application/vnd.olpc-sugar',
+ 'xop' => 'application/xop+xml',
+ 'xpi' => 'application/x-xpinstall',
+ 'xpl' => 'application/xproc+xml',
+ 'xpm' => 'image/x-xpixmap',
+ 'xpr' => 'application/vnd.is-xpr',
+ 'xps' => 'application/vnd.ms-xpsdocument',
+ 'xpw' => 'application/vnd.intercon.formnet',
+ 'xpx' => 'application/vnd.intercon.formnet',
+ 'xsd' => 'application/xml',
+ 'xsf' => 'application/prs.xsf+xml',
+ 'xsl' => 'application/xml',
+ 'xslt' => 'application/xslt+xml',
+ 'xsm' => 'application/vnd.syncml+xml',
+ 'xspf' => 'application/xspf+xml',
+ 'xul' => 'application/vnd.mozilla.xul+xml',
+ 'xvm' => 'application/xv+xml',
+ 'xvml' => 'application/xv+xml',
+ 'xwd' => 'image/x-xwindowdump',
+ 'xyz' => 'chemical/x-xyz',
+ 'xz' => 'application/x-xz',
+ 'yaml' => 'text/yaml',
+ 'yang' => 'application/yang',
+ 'yin' => 'application/yin+xml',
+ 'yml' => 'text/yaml',
+ 'ymp' => 'text/x-suse-ymp',
+ 'z' => 'application/x-compress',
+ 'z1' => 'application/x-zmachine',
+ 'z2' => 'application/x-zmachine',
+ 'z3' => 'application/x-zmachine',
+ 'z4' => 'application/x-zmachine',
+ 'z5' => 'application/x-zmachine',
+ 'z6' => 'application/x-zmachine',
+ 'z7' => 'application/x-zmachine',
+ 'z8' => 'application/x-zmachine',
+ 'zaz' => 'application/vnd.zzazz.deck+xml',
+ 'zip' => 'application/zip',
+ 'zir' => 'application/vnd.zul',
+ 'zirz' => 'application/vnd.zul',
+ 'zmm' => 'application/vnd.handheld-entertainment+xml',
+ 'zsh' => 'text/x-scriptzsh',
+ ];
+
+ /**
+ * Determines the mimetype of a file by looking at its extension.
+ *
+ * @see https://raw.githubusercontent.com/jshttp/mime-db/master/db.json
+ */
+ public static function fromFilename(string $filename): ?string
+ {
+ return self::fromExtension(pathinfo($filename, PATHINFO_EXTENSION));
+ }
+
+ /**
+ * Maps a file extensions to a mimetype.
+ *
+ * @see https://raw.githubusercontent.com/jshttp/mime-db/master/db.json
+ */
+ public static function fromExtension(string $extension): ?string
+ {
+ return self::MIME_TYPES[strtolower($extension)] ?? null;
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/MultipartStream.php b/vendor/guzzlehttp/psr7/src/MultipartStream.php
new file mode 100644
index 000000000..43d718f65
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/MultipartStream.php
@@ -0,0 +1,165 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * Stream that when read returns bytes for a streaming multipart or
+ * multipart/form-data stream.
+ */
+final class MultipartStream implements StreamInterface
+{
+ use StreamDecoratorTrait;
+
+ /** @var string */
+ private $boundary;
+
+ /** @var StreamInterface */
+ private $stream;
+
+ /**
+ * @param array $elements Array of associative arrays, each containing a
+ * required "name" key mapping to the form field,
+ * name, a required "contents" key mapping to a
+ * StreamInterface/resource/string, an optional
+ * "headers" associative array of custom headers,
+ * and an optional "filename" key mapping to a
+ * string to send as the filename in the part.
+ * @param string $boundary You can optionally provide a specific boundary
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function __construct(array $elements = [], ?string $boundary = null)
+ {
+ $this->boundary = $boundary ?: bin2hex(random_bytes(20));
+ $this->stream = $this->createStream($elements);
+ }
+
+ public function getBoundary(): string
+ {
+ return $this->boundary;
+ }
+
+ public function isWritable(): bool
+ {
+ return false;
+ }
+
+ /**
+ * Get the headers needed before transferring the content of a POST file
+ *
+ * @param string[] $headers
+ */
+ private function getHeaders(array $headers): string
+ {
+ $str = '';
+ foreach ($headers as $key => $value) {
+ $str .= "{$key}: {$value}\r\n";
+ }
+
+ return "--{$this->boundary}\r\n".trim($str)."\r\n\r\n";
+ }
+
+ /**
+ * Create the aggregate stream that will be used to upload the POST data
+ */
+ protected function createStream(array $elements = []): StreamInterface
+ {
+ $stream = new AppendStream();
+
+ foreach ($elements as $element) {
+ if (!is_array($element)) {
+ throw new \UnexpectedValueException('An array is expected');
+ }
+ $this->addElement($stream, $element);
+ }
+
+ // Add the trailing boundary with CRLF
+ $stream->addStream(Utils::streamFor("--{$this->boundary}--\r\n"));
+
+ return $stream;
+ }
+
+ private function addElement(AppendStream $stream, array $element): void
+ {
+ foreach (['contents', 'name'] as $key) {
+ if (!array_key_exists($key, $element)) {
+ throw new \InvalidArgumentException("A '{$key}' key is required");
+ }
+ }
+
+ $element['contents'] = Utils::streamFor($element['contents']);
+
+ if (empty($element['filename'])) {
+ $uri = $element['contents']->getMetadata('uri');
+ if ($uri && \is_string($uri) && \substr($uri, 0, 6) !== 'php://' && \substr($uri, 0, 7) !== 'data://') {
+ $element['filename'] = $uri;
+ }
+ }
+
+ [$body, $headers] = $this->createElement(
+ $element['name'],
+ $element['contents'],
+ $element['filename'] ?? null,
+ $element['headers'] ?? []
+ );
+
+ $stream->addStream(Utils::streamFor($this->getHeaders($headers)));
+ $stream->addStream($body);
+ $stream->addStream(Utils::streamFor("\r\n"));
+ }
+
+ /**
+ * @param string[] $headers
+ *
+ * @return array{0: StreamInterface, 1: string[]}
+ */
+ private function createElement(string $name, StreamInterface $stream, ?string $filename, array $headers): array
+ {
+ // Set a default content-disposition header if one was no provided
+ $disposition = self::getHeader($headers, 'content-disposition');
+ if (!$disposition) {
+ $headers['Content-Disposition'] = ($filename === '0' || $filename)
+ ? sprintf(
+ 'form-data; name="%s"; filename="%s"',
+ $name,
+ basename($filename)
+ )
+ : "form-data; name=\"{$name}\"";
+ }
+
+ // Set a default content-length header if one was no provided
+ $length = self::getHeader($headers, 'content-length');
+ if (!$length) {
+ if ($length = $stream->getSize()) {
+ $headers['Content-Length'] = (string) $length;
+ }
+ }
+
+ // Set a default Content-Type if one was not supplied
+ $type = self::getHeader($headers, 'content-type');
+ if (!$type && ($filename === '0' || $filename)) {
+ $headers['Content-Type'] = MimeType::fromFilename($filename) ?? 'application/octet-stream';
+ }
+
+ return [$stream, $headers];
+ }
+
+ /**
+ * @param string[] $headers
+ */
+ private static function getHeader(array $headers, string $key): ?string
+ {
+ $lowercaseHeader = strtolower($key);
+ foreach ($headers as $k => $v) {
+ if (strtolower((string) $k) === $lowercaseHeader) {
+ return $v;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/NoSeekStream.php b/vendor/guzzlehttp/psr7/src/NoSeekStream.php
new file mode 100644
index 000000000..161a224f0
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/NoSeekStream.php
@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * Stream decorator that prevents a stream from being seeked.
+ */
+final class NoSeekStream implements StreamInterface
+{
+ use StreamDecoratorTrait;
+
+ /** @var StreamInterface */
+ private $stream;
+
+ public function seek($offset, $whence = SEEK_SET): void
+ {
+ throw new \RuntimeException('Cannot seek a NoSeekStream');
+ }
+
+ public function isSeekable(): bool
+ {
+ return false;
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/PumpStream.php b/vendor/guzzlehttp/psr7/src/PumpStream.php
new file mode 100644
index 000000000..e2040709f
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/PumpStream.php
@@ -0,0 +1,179 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * Provides a read only stream that pumps data from a PHP callable.
+ *
+ * When invoking the provided callable, the PumpStream will pass the amount of
+ * data requested to read to the callable. The callable can choose to ignore
+ * this value and return fewer or more bytes than requested. Any extra data
+ * returned by the provided callable is buffered internally until drained using
+ * the read() function of the PumpStream. The provided callable MUST return
+ * false when there is no more data to read.
+ */
+final class PumpStream implements StreamInterface
+{
+ /** @var callable(int): (string|false|null)|null */
+ private $source;
+
+ /** @var int|null */
+ private $size;
+
+ /** @var int */
+ private $tellPos = 0;
+
+ /** @var array */
+ private $metadata;
+
+ /** @var BufferStream */
+ private $buffer;
+
+ /**
+ * @param callable(int): (string|false|null) $source Source of the stream data. The callable MAY
+ * accept an integer argument used to control the
+ * amount of data to return. The callable MUST
+ * return a string when called, or false|null on error
+ * or EOF.
+ * @param array{size?: int, metadata?: array} $options Stream options:
+ * - metadata: Hash of metadata to use with stream.
+ * - size: Size of the stream, if known.
+ */
+ public function __construct(callable $source, array $options = [])
+ {
+ $this->source = $source;
+ $this->size = $options['size'] ?? null;
+ $this->metadata = $options['metadata'] ?? [];
+ $this->buffer = new BufferStream();
+ }
+
+ public function __toString(): string
+ {
+ try {
+ return Utils::copyToString($this);
+ } catch (\Throwable $e) {
+ if (\PHP_VERSION_ID >= 70400) {
+ throw $e;
+ }
+ trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);
+
+ return '';
+ }
+ }
+
+ public function close(): void
+ {
+ $this->detach();
+ }
+
+ public function detach()
+ {
+ $this->tellPos = 0;
+ $this->source = null;
+
+ return null;
+ }
+
+ public function getSize(): ?int
+ {
+ return $this->size;
+ }
+
+ public function tell(): int
+ {
+ return $this->tellPos;
+ }
+
+ public function eof(): bool
+ {
+ return $this->source === null;
+ }
+
+ public function isSeekable(): bool
+ {
+ return false;
+ }
+
+ public function rewind(): void
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET): void
+ {
+ throw new \RuntimeException('Cannot seek a PumpStream');
+ }
+
+ public function isWritable(): bool
+ {
+ return false;
+ }
+
+ public function write($string): int
+ {
+ throw new \RuntimeException('Cannot write to a PumpStream');
+ }
+
+ public function isReadable(): bool
+ {
+ return true;
+ }
+
+ public function read($length): string
+ {
+ $data = $this->buffer->read($length);
+ $readLen = strlen($data);
+ $this->tellPos += $readLen;
+ $remaining = $length - $readLen;
+
+ if ($remaining) {
+ $this->pump($remaining);
+ $data .= $this->buffer->read($remaining);
+ $this->tellPos += strlen($data) - $readLen;
+ }
+
+ return $data;
+ }
+
+ public function getContents(): string
+ {
+ $result = '';
+ while (!$this->eof()) {
+ $result .= $this->read(1000000);
+ }
+
+ return $result;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getMetadata($key = null)
+ {
+ if (!$key) {
+ return $this->metadata;
+ }
+
+ return $this->metadata[$key] ?? null;
+ }
+
+ private function pump(int $length): void
+ {
+ if ($this->source !== null) {
+ do {
+ $data = ($this->source)($length);
+ if ($data === false || $data === null) {
+ $this->source = null;
+
+ return;
+ }
+ $this->buffer->write($data);
+ $length -= strlen($data);
+ } while ($length > 0);
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Query.php b/vendor/guzzlehttp/psr7/src/Query.php
new file mode 100644
index 000000000..ccf867a0b
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Query.php
@@ -0,0 +1,118 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+final class Query
+{
+ /**
+ * Parse a query string into an associative array.
+ *
+ * If multiple values are found for the same key, the value of that key
+ * value pair will become an array. This function does not parse nested
+ * PHP style arrays into an associative array (e.g., `foo[a]=1&foo[b]=2`
+ * will be parsed into `['foo[a]' => '1', 'foo[b]' => '2'])`.
+ *
+ * @param string $str Query string to parse
+ * @param int|bool $urlEncoding How the query string is encoded
+ */
+ public static function parse(string $str, $urlEncoding = true): array
+ {
+ $result = [];
+
+ if ($str === '') {
+ return $result;
+ }
+
+ if ($urlEncoding === true) {
+ $decoder = function ($value) {
+ return rawurldecode(str_replace('+', ' ', (string) $value));
+ };
+ } elseif ($urlEncoding === PHP_QUERY_RFC3986) {
+ $decoder = 'rawurldecode';
+ } elseif ($urlEncoding === PHP_QUERY_RFC1738) {
+ $decoder = 'urldecode';
+ } else {
+ $decoder = function ($str) {
+ return $str;
+ };
+ }
+
+ foreach (explode('&', $str) as $kvp) {
+ $parts = explode('=', $kvp, 2);
+ $key = $decoder($parts[0]);
+ $value = isset($parts[1]) ? $decoder($parts[1]) : null;
+ if (!array_key_exists($key, $result)) {
+ $result[$key] = $value;
+ } else {
+ if (!is_array($result[$key])) {
+ $result[$key] = [$result[$key]];
+ }
+ $result[$key][] = $value;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Build a query string from an array of key value pairs.
+ *
+ * This function can use the return value of `parse()` to build a query
+ * string. This function does not modify the provided keys when an array is
+ * encountered (like `http_build_query()` would).
+ *
+ * @param array $params Query string parameters.
+ * @param int|false $encoding Set to false to not encode,
+ * PHP_QUERY_RFC3986 to encode using
+ * RFC3986, or PHP_QUERY_RFC1738 to
+ * encode using RFC1738.
+ * @param bool $treatBoolsAsInts Set to true to encode as 0/1, and
+ * false as false/true.
+ */
+ public static function build(array $params, $encoding = PHP_QUERY_RFC3986, bool $treatBoolsAsInts = true): string
+ {
+ if (!$params) {
+ return '';
+ }
+
+ if ($encoding === false) {
+ $encoder = function (string $str): string {
+ return $str;
+ };
+ } elseif ($encoding === PHP_QUERY_RFC3986) {
+ $encoder = 'rawurlencode';
+ } elseif ($encoding === PHP_QUERY_RFC1738) {
+ $encoder = 'urlencode';
+ } else {
+ throw new \InvalidArgumentException('Invalid type');
+ }
+
+ $castBool = $treatBoolsAsInts ? static function ($v) { return (int) $v; } : static function ($v) { return $v ? 'true' : 'false'; };
+
+ $qs = '';
+ foreach ($params as $k => $v) {
+ $k = $encoder((string) $k);
+ if (!is_array($v)) {
+ $qs .= $k;
+ $v = is_bool($v) ? $castBool($v) : $v;
+ if ($v !== null) {
+ $qs .= '='.$encoder((string) $v);
+ }
+ $qs .= '&';
+ } else {
+ foreach ($v as $vv) {
+ $qs .= $k;
+ $vv = is_bool($vv) ? $castBool($vv) : $vv;
+ if ($vv !== null) {
+ $qs .= '='.$encoder((string) $vv);
+ }
+ $qs .= '&';
+ }
+ }
+ }
+
+ return $qs ? (string) substr($qs, 0, -1) : '';
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Request.php b/vendor/guzzlehttp/psr7/src/Request.php
new file mode 100644
index 000000000..faafe1ad8
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Request.php
@@ -0,0 +1,159 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use InvalidArgumentException;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\StreamInterface;
+use Psr\Http\Message\UriInterface;
+
+/**
+ * PSR-7 request implementation.
+ */
+class Request implements RequestInterface
+{
+ use MessageTrait;
+
+ /** @var string */
+ private $method;
+
+ /** @var string|null */
+ private $requestTarget;
+
+ /** @var UriInterface */
+ private $uri;
+
+ /**
+ * @param string $method HTTP method
+ * @param string|UriInterface $uri URI
+ * @param (string|string[])[] $headers Request headers
+ * @param string|resource|StreamInterface|null $body Request body
+ * @param string $version Protocol version
+ */
+ public function __construct(
+ string $method,
+ $uri,
+ array $headers = [],
+ $body = null,
+ string $version = '1.1'
+ ) {
+ $this->assertMethod($method);
+ if (!($uri instanceof UriInterface)) {
+ $uri = new Uri($uri);
+ }
+
+ $this->method = strtoupper($method);
+ $this->uri = $uri;
+ $this->setHeaders($headers);
+ $this->protocol = $version;
+
+ if (!isset($this->headerNames['host'])) {
+ $this->updateHostFromUri();
+ }
+
+ if ($body !== '' && $body !== null) {
+ $this->stream = Utils::streamFor($body);
+ }
+ }
+
+ public function getRequestTarget(): string
+ {
+ if ($this->requestTarget !== null) {
+ return $this->requestTarget;
+ }
+
+ $target = $this->uri->getPath();
+ if ($target === '') {
+ $target = '/';
+ }
+ if ($this->uri->getQuery() != '') {
+ $target .= '?'.$this->uri->getQuery();
+ }
+
+ return $target;
+ }
+
+ public function withRequestTarget($requestTarget): RequestInterface
+ {
+ if (preg_match('#\s#', $requestTarget)) {
+ throw new InvalidArgumentException(
+ 'Invalid request target provided; cannot contain whitespace'
+ );
+ }
+
+ $new = clone $this;
+ $new->requestTarget = $requestTarget;
+
+ return $new;
+ }
+
+ public function getMethod(): string
+ {
+ return $this->method;
+ }
+
+ public function withMethod($method): RequestInterface
+ {
+ $this->assertMethod($method);
+ $new = clone $this;
+ $new->method = strtoupper($method);
+
+ return $new;
+ }
+
+ public function getUri(): UriInterface
+ {
+ return $this->uri;
+ }
+
+ public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface
+ {
+ if ($uri === $this->uri) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->uri = $uri;
+
+ if (!$preserveHost || !isset($this->headerNames['host'])) {
+ $new->updateHostFromUri();
+ }
+
+ return $new;
+ }
+
+ private function updateHostFromUri(): void
+ {
+ $host = $this->uri->getHost();
+
+ if ($host == '') {
+ return;
+ }
+
+ if (($port = $this->uri->getPort()) !== null) {
+ $host .= ':'.$port;
+ }
+
+ if (isset($this->headerNames['host'])) {
+ $header = $this->headerNames['host'];
+ } else {
+ $header = 'Host';
+ $this->headerNames['host'] = 'Host';
+ }
+ // Ensure Host is the first header.
+ // See: https://datatracker.ietf.org/doc/html/rfc7230#section-5.4
+ $this->headers = [$header => [$host]] + $this->headers;
+ }
+
+ /**
+ * @param mixed $method
+ */
+ private function assertMethod($method): void
+ {
+ if (!is_string($method) || $method === '') {
+ throw new InvalidArgumentException('Method must be a non-empty string.');
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Response.php b/vendor/guzzlehttp/psr7/src/Response.php
new file mode 100644
index 000000000..34e612fda
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Response.php
@@ -0,0 +1,161 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * PSR-7 response implementation.
+ */
+class Response implements ResponseInterface
+{
+ use MessageTrait;
+
+ /** Map of standard HTTP status code/reason phrases */
+ private const PHRASES = [
+ 100 => 'Continue',
+ 101 => 'Switching Protocols',
+ 102 => 'Processing',
+ 200 => 'OK',
+ 201 => 'Created',
+ 202 => 'Accepted',
+ 203 => 'Non-Authoritative Information',
+ 204 => 'No Content',
+ 205 => 'Reset Content',
+ 206 => 'Partial Content',
+ 207 => 'Multi-status',
+ 208 => 'Already Reported',
+ 300 => 'Multiple Choices',
+ 301 => 'Moved Permanently',
+ 302 => 'Found',
+ 303 => 'See Other',
+ 304 => 'Not Modified',
+ 305 => 'Use Proxy',
+ 306 => 'Switch Proxy',
+ 307 => 'Temporary Redirect',
+ 308 => 'Permanent Redirect',
+ 400 => 'Bad Request',
+ 401 => 'Unauthorized',
+ 402 => 'Payment Required',
+ 403 => 'Forbidden',
+ 404 => 'Not Found',
+ 405 => 'Method Not Allowed',
+ 406 => 'Not Acceptable',
+ 407 => 'Proxy Authentication Required',
+ 408 => 'Request Time-out',
+ 409 => 'Conflict',
+ 410 => 'Gone',
+ 411 => 'Length Required',
+ 412 => 'Precondition Failed',
+ 413 => 'Request Entity Too Large',
+ 414 => 'Request-URI Too Large',
+ 415 => 'Unsupported Media Type',
+ 416 => 'Requested range not satisfiable',
+ 417 => 'Expectation Failed',
+ 418 => 'I\'m a teapot',
+ 422 => 'Unprocessable Entity',
+ 423 => 'Locked',
+ 424 => 'Failed Dependency',
+ 425 => 'Unordered Collection',
+ 426 => 'Upgrade Required',
+ 428 => 'Precondition Required',
+ 429 => 'Too Many Requests',
+ 431 => 'Request Header Fields Too Large',
+ 451 => 'Unavailable For Legal Reasons',
+ 500 => 'Internal Server Error',
+ 501 => 'Not Implemented',
+ 502 => 'Bad Gateway',
+ 503 => 'Service Unavailable',
+ 504 => 'Gateway Time-out',
+ 505 => 'HTTP Version not supported',
+ 506 => 'Variant Also Negotiates',
+ 507 => 'Insufficient Storage',
+ 508 => 'Loop Detected',
+ 510 => 'Not Extended',
+ 511 => 'Network Authentication Required',
+ ];
+
+ /** @var string */
+ private $reasonPhrase;
+
+ /** @var int */
+ private $statusCode;
+
+ /**
+ * @param int $status Status code
+ * @param (string|string[])[] $headers Response headers
+ * @param string|resource|StreamInterface|null $body Response body
+ * @param string $version Protocol version
+ * @param string|null $reason Reason phrase (when empty a default will be used based on the status code)
+ */
+ public function __construct(
+ int $status = 200,
+ array $headers = [],
+ $body = null,
+ string $version = '1.1',
+ ?string $reason = null
+ ) {
+ $this->assertStatusCodeRange($status);
+
+ $this->statusCode = $status;
+
+ if ($body !== '' && $body !== null) {
+ $this->stream = Utils::streamFor($body);
+ }
+
+ $this->setHeaders($headers);
+ if ($reason == '' && isset(self::PHRASES[$this->statusCode])) {
+ $this->reasonPhrase = self::PHRASES[$this->statusCode];
+ } else {
+ $this->reasonPhrase = (string) $reason;
+ }
+
+ $this->protocol = $version;
+ }
+
+ public function getStatusCode(): int
+ {
+ return $this->statusCode;
+ }
+
+ public function getReasonPhrase(): string
+ {
+ return $this->reasonPhrase;
+ }
+
+ public function withStatus($code, $reasonPhrase = ''): ResponseInterface
+ {
+ $this->assertStatusCodeIsInteger($code);
+ $code = (int) $code;
+ $this->assertStatusCodeRange($code);
+
+ $new = clone $this;
+ $new->statusCode = $code;
+ if ($reasonPhrase == '' && isset(self::PHRASES[$new->statusCode])) {
+ $reasonPhrase = self::PHRASES[$new->statusCode];
+ }
+ $new->reasonPhrase = (string) $reasonPhrase;
+
+ return $new;
+ }
+
+ /**
+ * @param mixed $statusCode
+ */
+ private function assertStatusCodeIsInteger($statusCode): void
+ {
+ if (filter_var($statusCode, FILTER_VALIDATE_INT) === false) {
+ throw new \InvalidArgumentException('Status code must be an integer value.');
+ }
+ }
+
+ private function assertStatusCodeRange(int $statusCode): void
+ {
+ if ($statusCode < 100 || $statusCode >= 600) {
+ throw new \InvalidArgumentException('Status code must be an integer value between 1xx and 5xx.');
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Rfc7230.php b/vendor/guzzlehttp/psr7/src/Rfc7230.php
new file mode 100644
index 000000000..8219dba4d
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Rfc7230.php
@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+/**
+ * @internal
+ */
+final class Rfc7230
+{
+ /**
+ * Header related regular expressions (based on amphp/http package)
+ *
+ * Note: header delimiter (\r\n) is modified to \r?\n to accept line feed only delimiters for BC reasons.
+ *
+ * @see https://github.com/amphp/http/blob/v1.0.1/src/Rfc7230.php#L12-L15
+ *
+ * @license https://github.com/amphp/http/blob/v1.0.1/LICENSE
+ */
+ public const HEADER_REGEX = "(^([^()<>@,;:\\\"/[\]?={}\x01-\x20\x7F]++):[ \t]*+((?:[ \t]*+[\x21-\x7E\x80-\xFF]++)*+)[ \t]*+\r?\n)m";
+ public const HEADER_FOLD_REGEX = "(\r?\n[ \t]++)";
+}
diff --git a/vendor/guzzlehttp/psr7/src/ServerRequest.php b/vendor/guzzlehttp/psr7/src/ServerRequest.php
new file mode 100644
index 000000000..3cc953453
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/ServerRequest.php
@@ -0,0 +1,340 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use InvalidArgumentException;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\StreamInterface;
+use Psr\Http\Message\UploadedFileInterface;
+use Psr\Http\Message\UriInterface;
+
+/**
+ * Server-side HTTP request
+ *
+ * Extends the Request definition to add methods for accessing incoming data,
+ * specifically server parameters, cookies, matched path parameters, query
+ * string arguments, body parameters, and upload file information.
+ *
+ * "Attributes" are discovered via decomposing the request (and usually
+ * specifically the URI path), and typically will be injected by the application.
+ *
+ * Requests are considered immutable; all methods that might change state are
+ * implemented such that they retain the internal state of the current
+ * message and return a new instance that contains the changed state.
+ */
+class ServerRequest extends Request implements ServerRequestInterface
+{
+ /**
+ * @var array
+ */
+ private $attributes = [];
+
+ /**
+ * @var array
+ */
+ private $cookieParams = [];
+
+ /**
+ * @var array|object|null
+ */
+ private $parsedBody;
+
+ /**
+ * @var array
+ */
+ private $queryParams = [];
+
+ /**
+ * @var array
+ */
+ private $serverParams;
+
+ /**
+ * @var array
+ */
+ private $uploadedFiles = [];
+
+ /**
+ * @param string $method HTTP method
+ * @param string|UriInterface $uri URI
+ * @param (string|string[])[] $headers Request headers
+ * @param string|resource|StreamInterface|null $body Request body
+ * @param string $version Protocol version
+ * @param array $serverParams Typically the $_SERVER superglobal
+ */
+ public function __construct(
+ string $method,
+ $uri,
+ array $headers = [],
+ $body = null,
+ string $version = '1.1',
+ array $serverParams = []
+ ) {
+ $this->serverParams = $serverParams;
+
+ parent::__construct($method, $uri, $headers, $body, $version);
+ }
+
+ /**
+ * Return an UploadedFile instance array.
+ *
+ * @param array $files An array which respect $_FILES structure
+ *
+ * @throws InvalidArgumentException for unrecognized values
+ */
+ public static function normalizeFiles(array $files): array
+ {
+ $normalized = [];
+
+ foreach ($files as $key => $value) {
+ if ($value instanceof UploadedFileInterface) {
+ $normalized[$key] = $value;
+ } elseif (is_array($value) && isset($value['tmp_name'])) {
+ $normalized[$key] = self::createUploadedFileFromSpec($value);
+ } elseif (is_array($value)) {
+ $normalized[$key] = self::normalizeFiles($value);
+ continue;
+ } else {
+ throw new InvalidArgumentException('Invalid value in files specification');
+ }
+ }
+
+ return $normalized;
+ }
+
+ /**
+ * Create and return an UploadedFile instance from a $_FILES specification.
+ *
+ * If the specification represents an array of values, this method will
+ * delegate to normalizeNestedFileSpec() and return that return value.
+ *
+ * @param array $value $_FILES struct
+ *
+ * @return UploadedFileInterface|UploadedFileInterface[]
+ */
+ private static function createUploadedFileFromSpec(array $value)
+ {
+ if (is_array($value['tmp_name'])) {
+ return self::normalizeNestedFileSpec($value);
+ }
+
+ return new UploadedFile(
+ $value['tmp_name'],
+ (int) $value['size'],
+ (int) $value['error'],
+ $value['name'],
+ $value['type']
+ );
+ }
+
+ /**
+ * Normalize an array of file specifications.
+ *
+ * Loops through all nested files and returns a normalized array of
+ * UploadedFileInterface instances.
+ *
+ * @return UploadedFileInterface[]
+ */
+ private static function normalizeNestedFileSpec(array $files = []): array
+ {
+ $normalizedFiles = [];
+
+ foreach (array_keys($files['tmp_name']) as $key) {
+ $spec = [
+ 'tmp_name' => $files['tmp_name'][$key],
+ 'size' => $files['size'][$key] ?? null,
+ 'error' => $files['error'][$key] ?? null,
+ 'name' => $files['name'][$key] ?? null,
+ 'type' => $files['type'][$key] ?? null,
+ ];
+ $normalizedFiles[$key] = self::createUploadedFileFromSpec($spec);
+ }
+
+ return $normalizedFiles;
+ }
+
+ /**
+ * Return a ServerRequest populated with superglobals:
+ * $_GET
+ * $_POST
+ * $_COOKIE
+ * $_FILES
+ * $_SERVER
+ */
+ public static function fromGlobals(): ServerRequestInterface
+ {
+ $method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
+ $headers = getallheaders();
+ $uri = self::getUriFromGlobals();
+ $body = new CachingStream(new LazyOpenStream('php://input', 'r+'));
+ $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? str_replace('HTTP/', '', $_SERVER['SERVER_PROTOCOL']) : '1.1';
+
+ $serverRequest = new ServerRequest($method, $uri, $headers, $body, $protocol, $_SERVER);
+
+ return $serverRequest
+ ->withCookieParams($_COOKIE)
+ ->withQueryParams($_GET)
+ ->withParsedBody($_POST)
+ ->withUploadedFiles(self::normalizeFiles($_FILES));
+ }
+
+ private static function extractHostAndPortFromAuthority(string $authority): array
+ {
+ $uri = 'http://'.$authority;
+ $parts = parse_url($uri);
+ if (false === $parts) {
+ return [null, null];
+ }
+
+ $host = $parts['host'] ?? null;
+ $port = $parts['port'] ?? null;
+
+ return [$host, $port];
+ }
+
+ /**
+ * Get a Uri populated with values from $_SERVER.
+ */
+ public static function getUriFromGlobals(): UriInterface
+ {
+ $uri = new Uri('');
+
+ $uri = $uri->withScheme(!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' ? 'https' : 'http');
+
+ $hasPort = false;
+ if (isset($_SERVER['HTTP_HOST'])) {
+ [$host, $port] = self::extractHostAndPortFromAuthority($_SERVER['HTTP_HOST']);
+ if ($host !== null) {
+ $uri = $uri->withHost($host);
+ }
+
+ if ($port !== null) {
+ $hasPort = true;
+ $uri = $uri->withPort($port);
+ }
+ } elseif (isset($_SERVER['SERVER_NAME'])) {
+ $uri = $uri->withHost($_SERVER['SERVER_NAME']);
+ } elseif (isset($_SERVER['SERVER_ADDR'])) {
+ $uri = $uri->withHost($_SERVER['SERVER_ADDR']);
+ }
+
+ if (!$hasPort && isset($_SERVER['SERVER_PORT'])) {
+ $uri = $uri->withPort($_SERVER['SERVER_PORT']);
+ }
+
+ $hasQuery = false;
+ if (isset($_SERVER['REQUEST_URI'])) {
+ $requestUriParts = explode('?', $_SERVER['REQUEST_URI'], 2);
+ $uri = $uri->withPath($requestUriParts[0]);
+ if (isset($requestUriParts[1])) {
+ $hasQuery = true;
+ $uri = $uri->withQuery($requestUriParts[1]);
+ }
+ }
+
+ if (!$hasQuery && isset($_SERVER['QUERY_STRING'])) {
+ $uri = $uri->withQuery($_SERVER['QUERY_STRING']);
+ }
+
+ return $uri;
+ }
+
+ public function getServerParams(): array
+ {
+ return $this->serverParams;
+ }
+
+ public function getUploadedFiles(): array
+ {
+ return $this->uploadedFiles;
+ }
+
+ public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface
+ {
+ $new = clone $this;
+ $new->uploadedFiles = $uploadedFiles;
+
+ return $new;
+ }
+
+ public function getCookieParams(): array
+ {
+ return $this->cookieParams;
+ }
+
+ public function withCookieParams(array $cookies): ServerRequestInterface
+ {
+ $new = clone $this;
+ $new->cookieParams = $cookies;
+
+ return $new;
+ }
+
+ public function getQueryParams(): array
+ {
+ return $this->queryParams;
+ }
+
+ public function withQueryParams(array $query): ServerRequestInterface
+ {
+ $new = clone $this;
+ $new->queryParams = $query;
+
+ return $new;
+ }
+
+ /**
+ * @return array|object|null
+ */
+ public function getParsedBody()
+ {
+ return $this->parsedBody;
+ }
+
+ public function withParsedBody($data): ServerRequestInterface
+ {
+ $new = clone $this;
+ $new->parsedBody = $data;
+
+ return $new;
+ }
+
+ public function getAttributes(): array
+ {
+ return $this->attributes;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getAttribute($attribute, $default = null)
+ {
+ if (false === array_key_exists($attribute, $this->attributes)) {
+ return $default;
+ }
+
+ return $this->attributes[$attribute];
+ }
+
+ public function withAttribute($attribute, $value): ServerRequestInterface
+ {
+ $new = clone $this;
+ $new->attributes[$attribute] = $value;
+
+ return $new;
+ }
+
+ public function withoutAttribute($attribute): ServerRequestInterface
+ {
+ if (false === array_key_exists($attribute, $this->attributes)) {
+ return $this;
+ }
+
+ $new = clone $this;
+ unset($new->attributes[$attribute]);
+
+ return $new;
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Stream.php b/vendor/guzzlehttp/psr7/src/Stream.php
new file mode 100644
index 000000000..0aff9b2b7
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Stream.php
@@ -0,0 +1,283 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * PHP stream implementation.
+ */
+class Stream implements StreamInterface
+{
+ /**
+ * @see https://www.php.net/manual/en/function.fopen.php
+ * @see https://www.php.net/manual/en/function.gzopen.php
+ */
+ private const READABLE_MODES = '/r|a\+|ab\+|w\+|wb\+|x\+|xb\+|c\+|cb\+/';
+ private const WRITABLE_MODES = '/a|w|r\+|rb\+|rw|x|c/';
+
+ /** @var resource */
+ private $stream;
+ /** @var int|null */
+ private $size;
+ /** @var bool */
+ private $seekable;
+ /** @var bool */
+ private $readable;
+ /** @var bool */
+ private $writable;
+ /** @var string|null */
+ private $uri;
+ /** @var mixed[] */
+ private $customMetadata;
+
+ /**
+ * This constructor accepts an associative array of options.
+ *
+ * - size: (int) If a read stream would otherwise have an indeterminate
+ * size, but the size is known due to foreknowledge, then you can
+ * provide that size, in bytes.
+ * - metadata: (array) Any additional metadata to return when the metadata
+ * of the stream is accessed.
+ *
+ * @param resource $stream Stream resource to wrap.
+ * @param array{size?: int, metadata?: array} $options Associative array of options.
+ *
+ * @throws \InvalidArgumentException if the stream is not a stream resource
+ */
+ public function __construct($stream, array $options = [])
+ {
+ if (!is_resource($stream)) {
+ throw new \InvalidArgumentException('Stream must be a resource');
+ }
+
+ if (isset($options['size'])) {
+ $this->size = $options['size'];
+ }
+
+ $this->customMetadata = $options['metadata'] ?? [];
+ $this->stream = $stream;
+ $meta = stream_get_meta_data($this->stream);
+ $this->seekable = $meta['seekable'];
+ $this->readable = (bool) preg_match(self::READABLE_MODES, $meta['mode']);
+ $this->writable = (bool) preg_match(self::WRITABLE_MODES, $meta['mode']);
+ $this->uri = $this->getMetadata('uri');
+ }
+
+ /**
+ * Closes the stream when the destructed
+ */
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ public function __toString(): string
+ {
+ try {
+ if ($this->isSeekable()) {
+ $this->seek(0);
+ }
+
+ return $this->getContents();
+ } catch (\Throwable $e) {
+ if (\PHP_VERSION_ID >= 70400) {
+ throw $e;
+ }
+ trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);
+
+ return '';
+ }
+ }
+
+ public function getContents(): string
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+
+ if (!$this->readable) {
+ throw new \RuntimeException('Cannot read from non-readable stream');
+ }
+
+ return Utils::tryGetContents($this->stream);
+ }
+
+ public function close(): void
+ {
+ if (isset($this->stream)) {
+ if (is_resource($this->stream)) {
+ fclose($this->stream);
+ }
+ $this->detach();
+ }
+ }
+
+ public function detach()
+ {
+ if (!isset($this->stream)) {
+ return null;
+ }
+
+ $result = $this->stream;
+ unset($this->stream);
+ $this->size = $this->uri = null;
+ $this->readable = $this->writable = $this->seekable = false;
+
+ return $result;
+ }
+
+ public function getSize(): ?int
+ {
+ if ($this->size !== null) {
+ return $this->size;
+ }
+
+ if (!isset($this->stream)) {
+ return null;
+ }
+
+ // Clear the stat cache if the stream has a URI
+ if ($this->uri) {
+ clearstatcache(true, $this->uri);
+ }
+
+ $stats = fstat($this->stream);
+ if (is_array($stats) && isset($stats['size'])) {
+ $this->size = $stats['size'];
+
+ return $this->size;
+ }
+
+ return null;
+ }
+
+ public function isReadable(): bool
+ {
+ return $this->readable;
+ }
+
+ public function isWritable(): bool
+ {
+ return $this->writable;
+ }
+
+ public function isSeekable(): bool
+ {
+ return $this->seekable;
+ }
+
+ public function eof(): bool
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+
+ return feof($this->stream);
+ }
+
+ public function tell(): int
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+
+ $result = ftell($this->stream);
+
+ if ($result === false) {
+ throw new \RuntimeException('Unable to determine stream position');
+ }
+
+ return $result;
+ }
+
+ public function rewind(): void
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET): void
+ {
+ $whence = (int) $whence;
+
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+ if (!$this->seekable) {
+ throw new \RuntimeException('Stream is not seekable');
+ }
+ if (fseek($this->stream, $offset, $whence) === -1) {
+ throw new \RuntimeException('Unable to seek to stream position '
+ .$offset.' with whence '.var_export($whence, true));
+ }
+ }
+
+ public function read($length): string
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+ if (!$this->readable) {
+ throw new \RuntimeException('Cannot read from non-readable stream');
+ }
+ if ($length < 0) {
+ throw new \RuntimeException('Length parameter cannot be negative');
+ }
+
+ if (0 === $length) {
+ return '';
+ }
+
+ try {
+ $string = fread($this->stream, $length);
+ } catch (\Exception $e) {
+ throw new \RuntimeException('Unable to read from stream', 0, $e);
+ }
+
+ if (false === $string) {
+ throw new \RuntimeException('Unable to read from stream');
+ }
+
+ return $string;
+ }
+
+ public function write($string): int
+ {
+ if (!isset($this->stream)) {
+ throw new \RuntimeException('Stream is detached');
+ }
+ if (!$this->writable) {
+ throw new \RuntimeException('Cannot write to a non-writable stream');
+ }
+
+ // We can't know the size after writing anything
+ $this->size = null;
+ $result = fwrite($this->stream, $string);
+
+ if ($result === false) {
+ throw new \RuntimeException('Unable to write to stream');
+ }
+
+ return $result;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getMetadata($key = null)
+ {
+ if (!isset($this->stream)) {
+ return $key ? null : [];
+ } elseif (!$key) {
+ return $this->customMetadata + stream_get_meta_data($this->stream);
+ } elseif (isset($this->customMetadata[$key])) {
+ return $this->customMetadata[$key];
+ }
+
+ $meta = stream_get_meta_data($this->stream);
+
+ return $meta[$key] ?? null;
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php b/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php
new file mode 100644
index 000000000..601c13afb
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/StreamDecoratorTrait.php
@@ -0,0 +1,156 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * Stream decorator trait
+ *
+ * @property StreamInterface $stream
+ */
+trait StreamDecoratorTrait
+{
+ /**
+ * @param StreamInterface $stream Stream to decorate
+ */
+ public function __construct(StreamInterface $stream)
+ {
+ $this->stream = $stream;
+ }
+
+ /**
+ * Magic method used to create a new stream if streams are not added in
+ * the constructor of a decorator (e.g., LazyOpenStream).
+ *
+ * @return StreamInterface
+ */
+ public function __get(string $name)
+ {
+ if ($name === 'stream') {
+ $this->stream = $this->createStream();
+
+ return $this->stream;
+ }
+
+ throw new \UnexpectedValueException("$name not found on class");
+ }
+
+ public function __toString(): string
+ {
+ try {
+ if ($this->isSeekable()) {
+ $this->seek(0);
+ }
+
+ return $this->getContents();
+ } catch (\Throwable $e) {
+ if (\PHP_VERSION_ID >= 70400) {
+ throw $e;
+ }
+ trigger_error(sprintf('%s::__toString exception: %s', self::class, (string) $e), E_USER_ERROR);
+
+ return '';
+ }
+ }
+
+ public function getContents(): string
+ {
+ return Utils::copyToString($this);
+ }
+
+ /**
+ * Allow decorators to implement custom methods
+ *
+ * @return mixed
+ */
+ public function __call(string $method, array $args)
+ {
+ /** @var callable $callable */
+ $callable = [$this->stream, $method];
+ $result = ($callable)(...$args);
+
+ // Always return the wrapped object if the result is a return $this
+ return $result === $this->stream ? $this : $result;
+ }
+
+ public function close(): void
+ {
+ $this->stream->close();
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getMetadata($key = null)
+ {
+ return $this->stream->getMetadata($key);
+ }
+
+ public function detach()
+ {
+ return $this->stream->detach();
+ }
+
+ public function getSize(): ?int
+ {
+ return $this->stream->getSize();
+ }
+
+ public function eof(): bool
+ {
+ return $this->stream->eof();
+ }
+
+ public function tell(): int
+ {
+ return $this->stream->tell();
+ }
+
+ public function isReadable(): bool
+ {
+ return $this->stream->isReadable();
+ }
+
+ public function isWritable(): bool
+ {
+ return $this->stream->isWritable();
+ }
+
+ public function isSeekable(): bool
+ {
+ return $this->stream->isSeekable();
+ }
+
+ public function rewind(): void
+ {
+ $this->seek(0);
+ }
+
+ public function seek($offset, $whence = SEEK_SET): void
+ {
+ $this->stream->seek($offset, $whence);
+ }
+
+ public function read($length): string
+ {
+ return $this->stream->read($length);
+ }
+
+ public function write($string): int
+ {
+ return $this->stream->write($string);
+ }
+
+ /**
+ * Implement in subclasses to dynamically create streams when requested.
+ *
+ * @throws \BadMethodCallException
+ */
+ protected function createStream(): StreamInterface
+ {
+ throw new \BadMethodCallException('Not implemented');
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/StreamWrapper.php b/vendor/guzzlehttp/psr7/src/StreamWrapper.php
new file mode 100644
index 000000000..77b04d747
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/StreamWrapper.php
@@ -0,0 +1,207 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\StreamInterface;
+
+/**
+ * Converts Guzzle streams into PHP stream resources.
+ *
+ * @see https://www.php.net/streamwrapper
+ */
+final class StreamWrapper
+{
+ /** @var resource */
+ public $context;
+
+ /** @var StreamInterface */
+ private $stream;
+
+ /** @var string r, r+, or w */
+ private $mode;
+
+ /**
+ * Returns a resource representing the stream.
+ *
+ * @param StreamInterface $stream The stream to get a resource for
+ *
+ * @return resource
+ *
+ * @throws \InvalidArgumentException if stream is not readable or writable
+ */
+ public static function getResource(StreamInterface $stream)
+ {
+ self::register();
+
+ if ($stream->isReadable()) {
+ $mode = $stream->isWritable() ? 'r+' : 'r';
+ } elseif ($stream->isWritable()) {
+ $mode = 'w';
+ } else {
+ throw new \InvalidArgumentException('The stream must be readable, '
+ .'writable, or both.');
+ }
+
+ return fopen('guzzle://stream', $mode, false, self::createStreamContext($stream));
+ }
+
+ /**
+ * Creates a stream context that can be used to open a stream as a php stream resource.
+ *
+ * @return resource
+ */
+ public static function createStreamContext(StreamInterface $stream)
+ {
+ return stream_context_create([
+ 'guzzle' => ['stream' => $stream],
+ ]);
+ }
+
+ /**
+ * Registers the stream wrapper if needed
+ */
+ public static function register(): void
+ {
+ if (!in_array('guzzle', stream_get_wrappers())) {
+ stream_wrapper_register('guzzle', __CLASS__);
+ }
+ }
+
+ public function stream_open(string $path, string $mode, int $options, ?string &$opened_path = null): bool
+ {
+ $options = stream_context_get_options($this->context);
+
+ if (!isset($options['guzzle']['stream'])) {
+ return false;
+ }
+
+ $this->mode = $mode;
+ $this->stream = $options['guzzle']['stream'];
+
+ return true;
+ }
+
+ public function stream_read(int $count): string
+ {
+ return $this->stream->read($count);
+ }
+
+ public function stream_write(string $data): int
+ {
+ return $this->stream->write($data);
+ }
+
+ public function stream_tell(): int
+ {
+ return $this->stream->tell();
+ }
+
+ public function stream_eof(): bool
+ {
+ return $this->stream->eof();
+ }
+
+ public function stream_seek(int $offset, int $whence): bool
+ {
+ $this->stream->seek($offset, $whence);
+
+ return true;
+ }
+
+ /**
+ * @return resource|false
+ */
+ public function stream_cast(int $cast_as)
+ {
+ $stream = clone $this->stream;
+ $resource = $stream->detach();
+
+ return $resource ?? false;
+ }
+
+ /**
+ * @return array{
+ * dev: int,
+ * ino: int,
+ * mode: int,
+ * nlink: int,
+ * uid: int,
+ * gid: int,
+ * rdev: int,
+ * size: int,
+ * atime: int,
+ * mtime: int,
+ * ctime: int,
+ * blksize: int,
+ * blocks: int
+ * }|false
+ */
+ public function stream_stat()
+ {
+ if ($this->stream->getSize() === null) {
+ return false;
+ }
+
+ static $modeMap = [
+ 'r' => 33060,
+ 'rb' => 33060,
+ 'r+' => 33206,
+ 'w' => 33188,
+ 'wb' => 33188,
+ ];
+
+ return [
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => $modeMap[$this->mode],
+ 'nlink' => 0,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => $this->stream->getSize() ?: 0,
+ 'atime' => 0,
+ 'mtime' => 0,
+ 'ctime' => 0,
+ 'blksize' => 0,
+ 'blocks' => 0,
+ ];
+ }
+
+ /**
+ * @return array{
+ * dev: int,
+ * ino: int,
+ * mode: int,
+ * nlink: int,
+ * uid: int,
+ * gid: int,
+ * rdev: int,
+ * size: int,
+ * atime: int,
+ * mtime: int,
+ * ctime: int,
+ * blksize: int,
+ * blocks: int
+ * }
+ */
+ public function url_stat(string $path, int $flags): array
+ {
+ return [
+ 'dev' => 0,
+ 'ino' => 0,
+ 'mode' => 0,
+ 'nlink' => 0,
+ 'uid' => 0,
+ 'gid' => 0,
+ 'rdev' => 0,
+ 'size' => 0,
+ 'atime' => 0,
+ 'mtime' => 0,
+ 'ctime' => 0,
+ 'blksize' => 0,
+ 'blocks' => 0,
+ ];
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/UploadedFile.php b/vendor/guzzlehttp/psr7/src/UploadedFile.php
new file mode 100644
index 000000000..d9b779f13
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/UploadedFile.php
@@ -0,0 +1,211 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use InvalidArgumentException;
+use Psr\Http\Message\StreamInterface;
+use Psr\Http\Message\UploadedFileInterface;
+use RuntimeException;
+
+class UploadedFile implements UploadedFileInterface
+{
+ private const ERROR_MAP = [
+ UPLOAD_ERR_OK => 'UPLOAD_ERR_OK',
+ UPLOAD_ERR_INI_SIZE => 'UPLOAD_ERR_INI_SIZE',
+ UPLOAD_ERR_FORM_SIZE => 'UPLOAD_ERR_FORM_SIZE',
+ UPLOAD_ERR_PARTIAL => 'UPLOAD_ERR_PARTIAL',
+ UPLOAD_ERR_NO_FILE => 'UPLOAD_ERR_NO_FILE',
+ UPLOAD_ERR_NO_TMP_DIR => 'UPLOAD_ERR_NO_TMP_DIR',
+ UPLOAD_ERR_CANT_WRITE => 'UPLOAD_ERR_CANT_WRITE',
+ UPLOAD_ERR_EXTENSION => 'UPLOAD_ERR_EXTENSION',
+ ];
+
+ /**
+ * @var string|null
+ */
+ private $clientFilename;
+
+ /**
+ * @var string|null
+ */
+ private $clientMediaType;
+
+ /**
+ * @var int
+ */
+ private $error;
+
+ /**
+ * @var string|null
+ */
+ private $file;
+
+ /**
+ * @var bool
+ */
+ private $moved = false;
+
+ /**
+ * @var int|null
+ */
+ private $size;
+
+ /**
+ * @var StreamInterface|null
+ */
+ private $stream;
+
+ /**
+ * @param StreamInterface|string|resource $streamOrFile
+ */
+ public function __construct(
+ $streamOrFile,
+ ?int $size,
+ int $errorStatus,
+ ?string $clientFilename = null,
+ ?string $clientMediaType = null
+ ) {
+ $this->setError($errorStatus);
+ $this->size = $size;
+ $this->clientFilename = $clientFilename;
+ $this->clientMediaType = $clientMediaType;
+
+ if ($this->isOk()) {
+ $this->setStreamOrFile($streamOrFile);
+ }
+ }
+
+ /**
+ * Depending on the value set file or stream variable
+ *
+ * @param StreamInterface|string|resource $streamOrFile
+ *
+ * @throws InvalidArgumentException
+ */
+ private function setStreamOrFile($streamOrFile): void
+ {
+ if (is_string($streamOrFile)) {
+ $this->file = $streamOrFile;
+ } elseif (is_resource($streamOrFile)) {
+ $this->stream = new Stream($streamOrFile);
+ } elseif ($streamOrFile instanceof StreamInterface) {
+ $this->stream = $streamOrFile;
+ } else {
+ throw new InvalidArgumentException(
+ 'Invalid stream or file provided for UploadedFile'
+ );
+ }
+ }
+
+ /**
+ * @throws InvalidArgumentException
+ */
+ private function setError(int $error): void
+ {
+ if (!isset(UploadedFile::ERROR_MAP[$error])) {
+ throw new InvalidArgumentException(
+ 'Invalid error status for UploadedFile'
+ );
+ }
+
+ $this->error = $error;
+ }
+
+ private static function isStringNotEmpty($param): bool
+ {
+ return is_string($param) && false === empty($param);
+ }
+
+ /**
+ * Return true if there is no upload error
+ */
+ private function isOk(): bool
+ {
+ return $this->error === UPLOAD_ERR_OK;
+ }
+
+ public function isMoved(): bool
+ {
+ return $this->moved;
+ }
+
+ /**
+ * @throws RuntimeException if is moved or not ok
+ */
+ private function validateActive(): void
+ {
+ if (false === $this->isOk()) {
+ throw new RuntimeException(\sprintf('Cannot retrieve stream due to upload error (%s)', self::ERROR_MAP[$this->error]));
+ }
+
+ if ($this->isMoved()) {
+ throw new RuntimeException('Cannot retrieve stream after it has already been moved');
+ }
+ }
+
+ public function getStream(): StreamInterface
+ {
+ $this->validateActive();
+
+ if ($this->stream instanceof StreamInterface) {
+ return $this->stream;
+ }
+
+ /** @var string $file */
+ $file = $this->file;
+
+ return new LazyOpenStream($file, 'r+');
+ }
+
+ public function moveTo($targetPath): void
+ {
+ $this->validateActive();
+
+ if (false === self::isStringNotEmpty($targetPath)) {
+ throw new InvalidArgumentException(
+ 'Invalid path provided for move operation; must be a non-empty string'
+ );
+ }
+
+ if ($this->file) {
+ $this->moved = PHP_SAPI === 'cli'
+ ? rename($this->file, $targetPath)
+ : move_uploaded_file($this->file, $targetPath);
+ } else {
+ Utils::copyToStream(
+ $this->getStream(),
+ new LazyOpenStream($targetPath, 'w')
+ );
+
+ $this->moved = true;
+ }
+
+ if (false === $this->moved) {
+ throw new RuntimeException(
+ sprintf('Uploaded file could not be moved to %s', $targetPath)
+ );
+ }
+ }
+
+ public function getSize(): ?int
+ {
+ return $this->size;
+ }
+
+ public function getError(): int
+ {
+ return $this->error;
+ }
+
+ public function getClientFilename(): ?string
+ {
+ return $this->clientFilename;
+ }
+
+ public function getClientMediaType(): ?string
+ {
+ return $this->clientMediaType;
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Uri.php b/vendor/guzzlehttp/psr7/src/Uri.php
new file mode 100644
index 000000000..a7cdfb003
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Uri.php
@@ -0,0 +1,743 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use GuzzleHttp\Psr7\Exception\MalformedUriException;
+use Psr\Http\Message\UriInterface;
+
+/**
+ * PSR-7 URI implementation.
+ *
+ * @author Michael Dowling
+ * @author Tobias Schultze
+ * @author Matthew Weier O'Phinney
+ */
+class Uri implements UriInterface, \JsonSerializable
+{
+ /**
+ * Absolute http and https URIs require a host per RFC 7230 Section 2.7
+ * but in generic URIs the host can be empty. So for http(s) URIs
+ * we apply this default host when no host is given yet to form a
+ * valid URI.
+ */
+ private const HTTP_DEFAULT_HOST = 'localhost';
+
+ private const DEFAULT_PORTS = [
+ 'http' => 80,
+ 'https' => 443,
+ 'ftp' => 21,
+ 'gopher' => 70,
+ 'nntp' => 119,
+ 'news' => 119,
+ 'telnet' => 23,
+ 'tn3270' => 23,
+ 'imap' => 143,
+ 'pop' => 110,
+ 'ldap' => 389,
+ ];
+
+ /**
+ * Unreserved characters for use in a regex.
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.3
+ */
+ private const CHAR_UNRESERVED = 'a-zA-Z0-9_\-\.~';
+
+ /**
+ * Sub-delims for use in a regex.
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-2.2
+ */
+ private const CHAR_SUB_DELIMS = '!\$&\'\(\)\*\+,;=';
+ private const QUERY_SEPARATORS_REPLACEMENT = ['=' => '%3D', '&' => '%26'];
+
+ /** @var string Uri scheme. */
+ private $scheme = '';
+
+ /** @var string Uri user info. */
+ private $userInfo = '';
+
+ /** @var string Uri host. */
+ private $host = '';
+
+ /** @var int|null Uri port. */
+ private $port;
+
+ /** @var string Uri path. */
+ private $path = '';
+
+ /** @var string Uri query string. */
+ private $query = '';
+
+ /** @var string Uri fragment. */
+ private $fragment = '';
+
+ /** @var string|null String representation */
+ private $composedComponents;
+
+ public function __construct(string $uri = '')
+ {
+ if ($uri !== '') {
+ $parts = self::parse($uri);
+ if ($parts === false) {
+ throw new MalformedUriException("Unable to parse URI: $uri");
+ }
+ $this->applyParts($parts);
+ }
+ }
+
+ /**
+ * UTF-8 aware \parse_url() replacement.
+ *
+ * The internal function produces broken output for non ASCII domain names
+ * (IDN) when used with locales other than "C".
+ *
+ * On the other hand, cURL understands IDN correctly only when UTF-8 locale
+ * is configured ("C.UTF-8", "en_US.UTF-8", etc.).
+ *
+ * @see https://bugs.php.net/bug.php?id=52923
+ * @see https://www.php.net/manual/en/function.parse-url.php#114817
+ * @see https://curl.haxx.se/libcurl/c/CURLOPT_URL.html#ENCODING
+ *
+ * @return array|false
+ */
+ private static function parse(string $url)
+ {
+ // If IPv6
+ $prefix = '';
+ if (preg_match('%^(.*://\[[0-9:a-fA-F]+\])(.*?)$%', $url, $matches)) {
+ /** @var array{0:string, 1:string, 2:string} $matches */
+ $prefix = $matches[1];
+ $url = $matches[2];
+ }
+
+ /** @var string */
+ $encodedUrl = preg_replace_callback(
+ '%[^:/@?&=#]+%usD',
+ static function ($matches) {
+ return urlencode($matches[0]);
+ },
+ $url
+ );
+
+ $result = parse_url($prefix.$encodedUrl);
+
+ if ($result === false) {
+ return false;
+ }
+
+ return array_map('urldecode', $result);
+ }
+
+ public function __toString(): string
+ {
+ if ($this->composedComponents === null) {
+ $this->composedComponents = self::composeComponents(
+ $this->scheme,
+ $this->getAuthority(),
+ $this->path,
+ $this->query,
+ $this->fragment
+ );
+ }
+
+ return $this->composedComponents;
+ }
+
+ /**
+ * Composes a URI reference string from its various components.
+ *
+ * Usually this method does not need to be called manually but instead is used indirectly via
+ * `Psr\Http\Message\UriInterface::__toString`.
+ *
+ * PSR-7 UriInterface treats an empty component the same as a missing component as
+ * getQuery(), getFragment() etc. always return a string. This explains the slight
+ * difference to RFC 3986 Section 5.3.
+ *
+ * Another adjustment is that the authority separator is added even when the authority is missing/empty
+ * for the "file" scheme. This is because PHP stream functions like `file_get_contents` only work with
+ * `file:///myfile` but not with `file:/myfile` although they are equivalent according to RFC 3986. But
+ * `file:///` is the more common syntax for the file scheme anyway (Chrome for example redirects to
+ * that format).
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.3
+ */
+ public static function composeComponents(?string $scheme, ?string $authority, string $path, ?string $query, ?string $fragment): string
+ {
+ $uri = '';
+
+ // weak type checks to also accept null until we can add scalar type hints
+ if ($scheme != '') {
+ $uri .= $scheme.':';
+ }
+
+ if ($authority != '' || $scheme === 'file') {
+ $uri .= '//'.$authority;
+ }
+
+ if ($authority != '' && $path != '' && $path[0] != '/') {
+ $path = '/'.$path;
+ }
+
+ $uri .= $path;
+
+ if ($query != '') {
+ $uri .= '?'.$query;
+ }
+
+ if ($fragment != '') {
+ $uri .= '#'.$fragment;
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Whether the URI has the default port of the current scheme.
+ *
+ * `Psr\Http\Message\UriInterface::getPort` may return null or the standard port. This method can be used
+ * independently of the implementation.
+ */
+ public static function isDefaultPort(UriInterface $uri): bool
+ {
+ return $uri->getPort() === null
+ || (isset(self::DEFAULT_PORTS[$uri->getScheme()]) && $uri->getPort() === self::DEFAULT_PORTS[$uri->getScheme()]);
+ }
+
+ /**
+ * Whether the URI is absolute, i.e. it has a scheme.
+ *
+ * An instance of UriInterface can either be an absolute URI or a relative reference. This method returns true
+ * if it is the former. An absolute URI has a scheme. A relative reference is used to express a URI relative
+ * to another URI, the base URI. Relative references can be divided into several forms:
+ * - network-path references, e.g. '//example.com/path'
+ * - absolute-path references, e.g. '/path'
+ * - relative-path references, e.g. 'subpath'
+ *
+ * @see Uri::isNetworkPathReference
+ * @see Uri::isAbsolutePathReference
+ * @see Uri::isRelativePathReference
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4
+ */
+ public static function isAbsolute(UriInterface $uri): bool
+ {
+ return $uri->getScheme() !== '';
+ }
+
+ /**
+ * Whether the URI is a network-path reference.
+ *
+ * A relative reference that begins with two slash characters is termed an network-path reference.
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
+ */
+ public static function isNetworkPathReference(UriInterface $uri): bool
+ {
+ return $uri->getScheme() === '' && $uri->getAuthority() !== '';
+ }
+
+ /**
+ * Whether the URI is a absolute-path reference.
+ *
+ * A relative reference that begins with a single slash character is termed an absolute-path reference.
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
+ */
+ public static function isAbsolutePathReference(UriInterface $uri): bool
+ {
+ return $uri->getScheme() === ''
+ && $uri->getAuthority() === ''
+ && isset($uri->getPath()[0])
+ && $uri->getPath()[0] === '/';
+ }
+
+ /**
+ * Whether the URI is a relative-path reference.
+ *
+ * A relative reference that does not begin with a slash character is termed a relative-path reference.
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.2
+ */
+ public static function isRelativePathReference(UriInterface $uri): bool
+ {
+ return $uri->getScheme() === ''
+ && $uri->getAuthority() === ''
+ && (!isset($uri->getPath()[0]) || $uri->getPath()[0] !== '/');
+ }
+
+ /**
+ * Whether the URI is a same-document reference.
+ *
+ * A same-document reference refers to a URI that is, aside from its fragment
+ * component, identical to the base URI. When no base URI is given, only an empty
+ * URI reference (apart from its fragment) is considered a same-document reference.
+ *
+ * @param UriInterface $uri The URI to check
+ * @param UriInterface|null $base An optional base URI to compare against
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-4.4
+ */
+ public static function isSameDocumentReference(UriInterface $uri, ?UriInterface $base = null): bool
+ {
+ if ($base !== null) {
+ $uri = UriResolver::resolve($base, $uri);
+
+ return ($uri->getScheme() === $base->getScheme())
+ && ($uri->getAuthority() === $base->getAuthority())
+ && ($uri->getPath() === $base->getPath())
+ && ($uri->getQuery() === $base->getQuery());
+ }
+
+ return $uri->getScheme() === '' && $uri->getAuthority() === '' && $uri->getPath() === '' && $uri->getQuery() === '';
+ }
+
+ /**
+ * Creates a new URI with a specific query string value removed.
+ *
+ * Any existing query string values that exactly match the provided key are
+ * removed.
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param string $key Query string key to remove.
+ */
+ public static function withoutQueryValue(UriInterface $uri, string $key): UriInterface
+ {
+ $result = self::getFilteredQueryString($uri, [$key]);
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Creates a new URI with a specific query string value.
+ *
+ * Any existing query string values that exactly match the provided key are
+ * removed and replaced with the given key value pair.
+ *
+ * A value of null will set the query string key without a value, e.g. "key"
+ * instead of "key=value".
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param string $key Key to set.
+ * @param string|null $value Value to set
+ */
+ public static function withQueryValue(UriInterface $uri, string $key, ?string $value): UriInterface
+ {
+ $result = self::getFilteredQueryString($uri, [$key]);
+
+ $result[] = self::generateQueryString($key, $value);
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Creates a new URI with multiple specific query string values.
+ *
+ * It has the same behavior as withQueryValue() but for an associative array of key => value.
+ *
+ * @param UriInterface $uri URI to use as a base.
+ * @param (string|null)[] $keyValueArray Associative array of key and values
+ */
+ public static function withQueryValues(UriInterface $uri, array $keyValueArray): UriInterface
+ {
+ $result = self::getFilteredQueryString($uri, array_keys($keyValueArray));
+
+ foreach ($keyValueArray as $key => $value) {
+ $result[] = self::generateQueryString((string) $key, $value !== null ? (string) $value : null);
+ }
+
+ return $uri->withQuery(implode('&', $result));
+ }
+
+ /**
+ * Creates a URI from a hash of `parse_url` components.
+ *
+ * @see https://www.php.net/manual/en/function.parse-url.php
+ *
+ * @throws MalformedUriException If the components do not form a valid URI.
+ */
+ public static function fromParts(array $parts): UriInterface
+ {
+ $uri = new self();
+ $uri->applyParts($parts);
+ $uri->validateState();
+
+ return $uri;
+ }
+
+ public function getScheme(): string
+ {
+ return $this->scheme;
+ }
+
+ public function getAuthority(): string
+ {
+ $authority = $this->host;
+ if ($this->userInfo !== '') {
+ $authority = $this->userInfo.'@'.$authority;
+ }
+
+ if ($this->port !== null) {
+ $authority .= ':'.$this->port;
+ }
+
+ return $authority;
+ }
+
+ public function getUserInfo(): string
+ {
+ return $this->userInfo;
+ }
+
+ public function getHost(): string
+ {
+ return $this->host;
+ }
+
+ public function getPort(): ?int
+ {
+ return $this->port;
+ }
+
+ public function getPath(): string
+ {
+ return $this->path;
+ }
+
+ public function getQuery(): string
+ {
+ return $this->query;
+ }
+
+ public function getFragment(): string
+ {
+ return $this->fragment;
+ }
+
+ public function withScheme($scheme): UriInterface
+ {
+ $scheme = $this->filterScheme($scheme);
+
+ if ($this->scheme === $scheme) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->scheme = $scheme;
+ $new->composedComponents = null;
+ $new->removeDefaultPort();
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withUserInfo($user, $password = null): UriInterface
+ {
+ $info = $this->filterUserInfoComponent($user);
+ if ($password !== null) {
+ $info .= ':'.$this->filterUserInfoComponent($password);
+ }
+
+ if ($this->userInfo === $info) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->userInfo = $info;
+ $new->composedComponents = null;
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withHost($host): UriInterface
+ {
+ $host = $this->filterHost($host);
+
+ if ($this->host === $host) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->host = $host;
+ $new->composedComponents = null;
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withPort($port): UriInterface
+ {
+ $port = $this->filterPort($port);
+
+ if ($this->port === $port) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->port = $port;
+ $new->composedComponents = null;
+ $new->removeDefaultPort();
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withPath($path): UriInterface
+ {
+ $path = $this->filterPath($path);
+
+ if ($this->path === $path) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->path = $path;
+ $new->composedComponents = null;
+ $new->validateState();
+
+ return $new;
+ }
+
+ public function withQuery($query): UriInterface
+ {
+ $query = $this->filterQueryAndFragment($query);
+
+ if ($this->query === $query) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->query = $query;
+ $new->composedComponents = null;
+
+ return $new;
+ }
+
+ public function withFragment($fragment): UriInterface
+ {
+ $fragment = $this->filterQueryAndFragment($fragment);
+
+ if ($this->fragment === $fragment) {
+ return $this;
+ }
+
+ $new = clone $this;
+ $new->fragment = $fragment;
+ $new->composedComponents = null;
+
+ return $new;
+ }
+
+ public function jsonSerialize(): string
+ {
+ return $this->__toString();
+ }
+
+ /**
+ * Apply parse_url parts to a URI.
+ *
+ * @param array $parts Array of parse_url parts to apply.
+ */
+ private function applyParts(array $parts): void
+ {
+ $this->scheme = isset($parts['scheme'])
+ ? $this->filterScheme($parts['scheme'])
+ : '';
+ $this->userInfo = isset($parts['user'])
+ ? $this->filterUserInfoComponent($parts['user'])
+ : '';
+ $this->host = isset($parts['host'])
+ ? $this->filterHost($parts['host'])
+ : '';
+ $this->port = isset($parts['port'])
+ ? $this->filterPort($parts['port'])
+ : null;
+ $this->path = isset($parts['path'])
+ ? $this->filterPath($parts['path'])
+ : '';
+ $this->query = isset($parts['query'])
+ ? $this->filterQueryAndFragment($parts['query'])
+ : '';
+ $this->fragment = isset($parts['fragment'])
+ ? $this->filterQueryAndFragment($parts['fragment'])
+ : '';
+ if (isset($parts['pass'])) {
+ $this->userInfo .= ':'.$this->filterUserInfoComponent($parts['pass']);
+ }
+
+ $this->removeDefaultPort();
+ }
+
+ /**
+ * @param mixed $scheme
+ *
+ * @throws \InvalidArgumentException If the scheme is invalid.
+ */
+ private function filterScheme($scheme): string
+ {
+ if (!is_string($scheme)) {
+ throw new \InvalidArgumentException('Scheme must be a string');
+ }
+
+ return \strtr($scheme, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
+ }
+
+ /**
+ * @param mixed $component
+ *
+ * @throws \InvalidArgumentException If the user info is invalid.
+ */
+ private function filterUserInfoComponent($component): string
+ {
+ if (!is_string($component)) {
+ throw new \InvalidArgumentException('User info must be a string');
+ }
+
+ return preg_replace_callback(
+ '/(?:[^%'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.']+|%(?![A-Fa-f0-9]{2}))/',
+ [$this, 'rawurlencodeMatchZero'],
+ $component
+ );
+ }
+
+ /**
+ * @param mixed $host
+ *
+ * @throws \InvalidArgumentException If the host is invalid.
+ */
+ private function filterHost($host): string
+ {
+ if (!is_string($host)) {
+ throw new \InvalidArgumentException('Host must be a string');
+ }
+
+ return \strtr($host, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
+ }
+
+ /**
+ * @param mixed $port
+ *
+ * @throws \InvalidArgumentException If the port is invalid.
+ */
+ private function filterPort($port): ?int
+ {
+ if ($port === null) {
+ return null;
+ }
+
+ $port = (int) $port;
+ if (0 > $port || 0xFFFF < $port) {
+ throw new \InvalidArgumentException(
+ sprintf('Invalid port: %d. Must be between 0 and 65535', $port)
+ );
+ }
+
+ return $port;
+ }
+
+ /**
+ * @param (string|int)[] $keys
+ *
+ * @return string[]
+ */
+ private static function getFilteredQueryString(UriInterface $uri, array $keys): array
+ {
+ $current = $uri->getQuery();
+
+ if ($current === '') {
+ return [];
+ }
+
+ $decodedKeys = array_map(function ($k): string {
+ return rawurldecode((string) $k);
+ }, $keys);
+
+ return array_filter(explode('&', $current), function ($part) use ($decodedKeys) {
+ return !in_array(rawurldecode(explode('=', $part)[0]), $decodedKeys, true);
+ });
+ }
+
+ private static function generateQueryString(string $key, ?string $value): string
+ {
+ // Query string separators ("=", "&") within the key or value need to be encoded
+ // (while preventing double-encoding) before setting the query string. All other
+ // chars that need percent-encoding will be encoded by withQuery().
+ $queryString = strtr($key, self::QUERY_SEPARATORS_REPLACEMENT);
+
+ if ($value !== null) {
+ $queryString .= '='.strtr($value, self::QUERY_SEPARATORS_REPLACEMENT);
+ }
+
+ return $queryString;
+ }
+
+ private function removeDefaultPort(): void
+ {
+ if ($this->port !== null && self::isDefaultPort($this)) {
+ $this->port = null;
+ }
+ }
+
+ /**
+ * Filters the path of a URI
+ *
+ * @param mixed $path
+ *
+ * @throws \InvalidArgumentException If the path is invalid.
+ */
+ private function filterPath($path): string
+ {
+ if (!is_string($path)) {
+ throw new \InvalidArgumentException('Path must be a string');
+ }
+
+ return preg_replace_callback(
+ '/(?:[^'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.'%:@\/]++|%(?![A-Fa-f0-9]{2}))/',
+ [$this, 'rawurlencodeMatchZero'],
+ $path
+ );
+ }
+
+ /**
+ * Filters the query string or fragment of a URI.
+ *
+ * @param mixed $str
+ *
+ * @throws \InvalidArgumentException If the query or fragment is invalid.
+ */
+ private function filterQueryAndFragment($str): string
+ {
+ if (!is_string($str)) {
+ throw new \InvalidArgumentException('Query and fragment must be a string');
+ }
+
+ return preg_replace_callback(
+ '/(?:[^'.self::CHAR_UNRESERVED.self::CHAR_SUB_DELIMS.'%:@\/\?]++|%(?![A-Fa-f0-9]{2}))/',
+ [$this, 'rawurlencodeMatchZero'],
+ $str
+ );
+ }
+
+ private function rawurlencodeMatchZero(array $match): string
+ {
+ return rawurlencode($match[0]);
+ }
+
+ private function validateState(): void
+ {
+ if ($this->host === '' && ($this->scheme === 'http' || $this->scheme === 'https')) {
+ $this->host = self::HTTP_DEFAULT_HOST;
+ }
+
+ if ($this->getAuthority() === '') {
+ if (0 === strpos($this->path, '//')) {
+ throw new MalformedUriException('The path of a URI without an authority must not start with two slashes "//"');
+ }
+ if ($this->scheme === '' && false !== strpos(explode('/', $this->path, 2)[0], ':')) {
+ throw new MalformedUriException('A relative URI must not have a path beginning with a segment containing a colon');
+ }
+ }
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/UriComparator.php b/vendor/guzzlehttp/psr7/src/UriComparator.php
new file mode 100644
index 000000000..70c582aa0
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/UriComparator.php
@@ -0,0 +1,52 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\UriInterface;
+
+/**
+ * Provides methods to determine if a modified URL should be considered cross-origin.
+ *
+ * @author Graham Campbell
+ */
+final class UriComparator
+{
+ /**
+ * Determines if a modified URL should be considered cross-origin with
+ * respect to an original URL.
+ */
+ public static function isCrossOrigin(UriInterface $original, UriInterface $modified): bool
+ {
+ if (\strcasecmp($original->getHost(), $modified->getHost()) !== 0) {
+ return true;
+ }
+
+ if ($original->getScheme() !== $modified->getScheme()) {
+ return true;
+ }
+
+ if (self::computePort($original) !== self::computePort($modified)) {
+ return true;
+ }
+
+ return false;
+ }
+
+ private static function computePort(UriInterface $uri): int
+ {
+ $port = $uri->getPort();
+
+ if (null !== $port) {
+ return $port;
+ }
+
+ return 'https' === $uri->getScheme() ? 443 : 80;
+ }
+
+ private function __construct()
+ {
+ // cannot be instantiated
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/UriNormalizer.php b/vendor/guzzlehttp/psr7/src/UriNormalizer.php
new file mode 100644
index 000000000..e17455737
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/UriNormalizer.php
@@ -0,0 +1,220 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\UriInterface;
+
+/**
+ * Provides methods to normalize and compare URIs.
+ *
+ * @author Tobias Schultze
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-6
+ */
+final class UriNormalizer
+{
+ /**
+ * Default normalizations which only include the ones that preserve semantics.
+ */
+ public const PRESERVING_NORMALIZATIONS =
+ self::CAPITALIZE_PERCENT_ENCODING |
+ self::DECODE_UNRESERVED_CHARACTERS |
+ self::CONVERT_EMPTY_PATH |
+ self::REMOVE_DEFAULT_HOST |
+ self::REMOVE_DEFAULT_PORT |
+ self::REMOVE_DOT_SEGMENTS;
+
+ /**
+ * All letters within a percent-encoding triplet (e.g., "%3A") are case-insensitive, and should be capitalized.
+ *
+ * Example: http://example.org/a%c2%b1b → http://example.org/a%C2%B1b
+ */
+ public const CAPITALIZE_PERCENT_ENCODING = 1;
+
+ /**
+ * Decodes percent-encoded octets of unreserved characters.
+ *
+ * For consistency, percent-encoded octets in the ranges of ALPHA (%41–%5A and %61–%7A), DIGIT (%30–%39),
+ * hyphen (%2D), period (%2E), underscore (%5F), or tilde (%7E) should not be created by URI producers and,
+ * when found in a URI, should be decoded to their corresponding unreserved characters by URI normalizers.
+ *
+ * Example: http://example.org/%7Eusern%61me/ → http://example.org/~username/
+ */
+ public const DECODE_UNRESERVED_CHARACTERS = 2;
+
+ /**
+ * Converts the empty path to "/" for http and https URIs.
+ *
+ * Example: http://example.org → http://example.org/
+ */
+ public const CONVERT_EMPTY_PATH = 4;
+
+ /**
+ * Removes the default host of the given URI scheme from the URI.
+ *
+ * Only the "file" scheme defines the default host "localhost".
+ * All of `file:/myfile`, `file:///myfile`, and `file://localhost/myfile`
+ * are equivalent according to RFC 3986. The first format is not accepted
+ * by PHPs stream functions and thus already normalized implicitly to the
+ * second format in the Uri class. See `GuzzleHttp\Psr7\Uri::composeComponents`.
+ *
+ * Example: file://localhost/myfile → file:///myfile
+ */
+ public const REMOVE_DEFAULT_HOST = 8;
+
+ /**
+ * Removes the default port of the given URI scheme from the URI.
+ *
+ * Example: http://example.org:80/ → http://example.org/
+ */
+ public const REMOVE_DEFAULT_PORT = 16;
+
+ /**
+ * Removes unnecessary dot-segments.
+ *
+ * Dot-segments in relative-path references are not removed as it would
+ * change the semantics of the URI reference.
+ *
+ * Example: http://example.org/../a/b/../c/./d.html → http://example.org/a/c/d.html
+ */
+ public const REMOVE_DOT_SEGMENTS = 32;
+
+ /**
+ * Paths which include two or more adjacent slashes are converted to one.
+ *
+ * Webservers usually ignore duplicate slashes and treat those URIs equivalent.
+ * But in theory those URIs do not need to be equivalent. So this normalization
+ * may change the semantics. Encoded slashes (%2F) are not removed.
+ *
+ * Example: http://example.org//foo///bar.html → http://example.org/foo/bar.html
+ */
+ public const REMOVE_DUPLICATE_SLASHES = 64;
+
+ /**
+ * Sort query parameters with their values in alphabetical order.
+ *
+ * However, the order of parameters in a URI may be significant (this is not defined by the standard).
+ * So this normalization is not safe and may change the semantics of the URI.
+ *
+ * Example: ?lang=en&article=fred → ?article=fred&lang=en
+ *
+ * Note: The sorting is neither locale nor Unicode aware (the URI query does not get decoded at all) as the
+ * purpose is to be able to compare URIs in a reproducible way, not to have the params sorted perfectly.
+ */
+ public const SORT_QUERY_PARAMETERS = 128;
+
+ /**
+ * Returns a normalized URI.
+ *
+ * The scheme and host component are already normalized to lowercase per PSR-7 UriInterface.
+ * This methods adds additional normalizations that can be configured with the $flags parameter.
+ *
+ * PSR-7 UriInterface cannot distinguish between an empty component and a missing component as
+ * getQuery(), getFragment() etc. always return a string. This means the URIs "/?#" and "/" are
+ * treated equivalent which is not necessarily true according to RFC 3986. But that difference
+ * is highly uncommon in reality. So this potential normalization is implied in PSR-7 as well.
+ *
+ * @param UriInterface $uri The URI to normalize
+ * @param int $flags A bitmask of normalizations to apply, see constants
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-6.2
+ */
+ public static function normalize(UriInterface $uri, int $flags = self::PRESERVING_NORMALIZATIONS): UriInterface
+ {
+ if ($flags & self::CAPITALIZE_PERCENT_ENCODING) {
+ $uri = self::capitalizePercentEncoding($uri);
+ }
+
+ if ($flags & self::DECODE_UNRESERVED_CHARACTERS) {
+ $uri = self::decodeUnreservedCharacters($uri);
+ }
+
+ if ($flags & self::CONVERT_EMPTY_PATH && $uri->getPath() === ''
+ && ($uri->getScheme() === 'http' || $uri->getScheme() === 'https')
+ ) {
+ $uri = $uri->withPath('/');
+ }
+
+ if ($flags & self::REMOVE_DEFAULT_HOST && $uri->getScheme() === 'file' && $uri->getHost() === 'localhost') {
+ $uri = $uri->withHost('');
+ }
+
+ if ($flags & self::REMOVE_DEFAULT_PORT && $uri->getPort() !== null && Uri::isDefaultPort($uri)) {
+ $uri = $uri->withPort(null);
+ }
+
+ if ($flags & self::REMOVE_DOT_SEGMENTS && !Uri::isRelativePathReference($uri)) {
+ $uri = $uri->withPath(UriResolver::removeDotSegments($uri->getPath()));
+ }
+
+ if ($flags & self::REMOVE_DUPLICATE_SLASHES) {
+ $uri = $uri->withPath(preg_replace('#//++#', '/', $uri->getPath()));
+ }
+
+ if ($flags & self::SORT_QUERY_PARAMETERS && $uri->getQuery() !== '') {
+ $queryKeyValues = explode('&', $uri->getQuery());
+ sort($queryKeyValues);
+ $uri = $uri->withQuery(implode('&', $queryKeyValues));
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Whether two URIs can be considered equivalent.
+ *
+ * Both URIs are normalized automatically before comparison with the given $normalizations bitmask. The method also
+ * accepts relative URI references and returns true when they are equivalent. This of course assumes they will be
+ * resolved against the same base URI. If this is not the case, determination of equivalence or difference of
+ * relative references does not mean anything.
+ *
+ * @param UriInterface $uri1 An URI to compare
+ * @param UriInterface $uri2 An URI to compare
+ * @param int $normalizations A bitmask of normalizations to apply, see constants
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-6.1
+ */
+ public static function isEquivalent(UriInterface $uri1, UriInterface $uri2, int $normalizations = self::PRESERVING_NORMALIZATIONS): bool
+ {
+ return (string) self::normalize($uri1, $normalizations) === (string) self::normalize($uri2, $normalizations);
+ }
+
+ private static function capitalizePercentEncoding(UriInterface $uri): UriInterface
+ {
+ $regex = '/(?:%[A-Fa-f0-9]{2})++/';
+
+ $callback = function (array $match): string {
+ return strtoupper($match[0]);
+ };
+
+ return
+ $uri->withPath(
+ preg_replace_callback($regex, $callback, $uri->getPath())
+ )->withQuery(
+ preg_replace_callback($regex, $callback, $uri->getQuery())
+ );
+ }
+
+ private static function decodeUnreservedCharacters(UriInterface $uri): UriInterface
+ {
+ $regex = '/%(?:2D|2E|5F|7E|3[0-9]|[46][1-9A-F]|[57][0-9A])/i';
+
+ $callback = function (array $match): string {
+ return rawurldecode($match[0]);
+ };
+
+ return
+ $uri->withPath(
+ preg_replace_callback($regex, $callback, $uri->getPath())
+ )->withQuery(
+ preg_replace_callback($regex, $callback, $uri->getQuery())
+ );
+ }
+
+ private function __construct()
+ {
+ // cannot be instantiated
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/UriResolver.php b/vendor/guzzlehttp/psr7/src/UriResolver.php
new file mode 100644
index 000000000..3737be1e5
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/UriResolver.php
@@ -0,0 +1,211 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\UriInterface;
+
+/**
+ * Resolves a URI reference in the context of a base URI and the opposite way.
+ *
+ * @author Tobias Schultze
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-5
+ */
+final class UriResolver
+{
+ /**
+ * Removes dot segments from a path and returns the new path.
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.2.4
+ */
+ public static function removeDotSegments(string $path): string
+ {
+ if ($path === '' || $path === '/') {
+ return $path;
+ }
+
+ $results = [];
+ $segments = explode('/', $path);
+ foreach ($segments as $segment) {
+ if ($segment === '..') {
+ array_pop($results);
+ } elseif ($segment !== '.') {
+ $results[] = $segment;
+ }
+ }
+
+ $newPath = implode('/', $results);
+
+ if ($path[0] === '/' && (!isset($newPath[0]) || $newPath[0] !== '/')) {
+ // Re-add the leading slash if necessary for cases like "/.."
+ $newPath = '/'.$newPath;
+ } elseif ($newPath !== '' && ($segment === '.' || $segment === '..')) {
+ // Add the trailing slash if necessary
+ // If newPath is not empty, then $segment must be set and is the last segment from the foreach
+ $newPath .= '/';
+ }
+
+ return $newPath;
+ }
+
+ /**
+ * Converts the relative URI into a new URI that is resolved against the base URI.
+ *
+ * @see https://datatracker.ietf.org/doc/html/rfc3986#section-5.2
+ */
+ public static function resolve(UriInterface $base, UriInterface $rel): UriInterface
+ {
+ if ((string) $rel === '') {
+ // we can simply return the same base URI instance for this same-document reference
+ return $base;
+ }
+
+ if ($rel->getScheme() != '') {
+ return $rel->withPath(self::removeDotSegments($rel->getPath()));
+ }
+
+ if ($rel->getAuthority() != '') {
+ $targetAuthority = $rel->getAuthority();
+ $targetPath = self::removeDotSegments($rel->getPath());
+ $targetQuery = $rel->getQuery();
+ } else {
+ $targetAuthority = $base->getAuthority();
+ if ($rel->getPath() === '') {
+ $targetPath = $base->getPath();
+ $targetQuery = $rel->getQuery() != '' ? $rel->getQuery() : $base->getQuery();
+ } else {
+ if ($rel->getPath()[0] === '/') {
+ $targetPath = $rel->getPath();
+ } else {
+ if ($targetAuthority != '' && $base->getPath() === '') {
+ $targetPath = '/'.$rel->getPath();
+ } else {
+ $lastSlashPos = strrpos($base->getPath(), '/');
+ if ($lastSlashPos === false) {
+ $targetPath = $rel->getPath();
+ } else {
+ $targetPath = substr($base->getPath(), 0, $lastSlashPos + 1).$rel->getPath();
+ }
+ }
+ }
+ $targetPath = self::removeDotSegments($targetPath);
+ $targetQuery = $rel->getQuery();
+ }
+ }
+
+ return new Uri(Uri::composeComponents(
+ $base->getScheme(),
+ $targetAuthority,
+ $targetPath,
+ $targetQuery,
+ $rel->getFragment()
+ ));
+ }
+
+ /**
+ * Returns the target URI as a relative reference from the base URI.
+ *
+ * This method is the counterpart to resolve():
+ *
+ * (string) $target === (string) UriResolver::resolve($base, UriResolver::relativize($base, $target))
+ *
+ * One use-case is to use the current request URI as base URI and then generate relative links in your documents
+ * to reduce the document size or offer self-contained downloadable document archives.
+ *
+ * $base = new Uri('http://example.com/a/b/');
+ * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/c')); // prints 'c'.
+ * echo UriResolver::relativize($base, new Uri('http://example.com/a/x/y')); // prints '../x/y'.
+ * echo UriResolver::relativize($base, new Uri('http://example.com/a/b/?q')); // prints '?q'.
+ * echo UriResolver::relativize($base, new Uri('http://example.org/a/b/')); // prints '//example.org/a/b/'.
+ *
+ * This method also accepts a target that is already relative and will try to relativize it further. Only a
+ * relative-path reference will be returned as-is.
+ *
+ * echo UriResolver::relativize($base, new Uri('/a/b/c')); // prints 'c' as well
+ */
+ public static function relativize(UriInterface $base, UriInterface $target): UriInterface
+ {
+ if ($target->getScheme() !== ''
+ && ($base->getScheme() !== $target->getScheme() || $target->getAuthority() === '' && $base->getAuthority() !== '')
+ ) {
+ return $target;
+ }
+
+ if (Uri::isRelativePathReference($target)) {
+ // As the target is already highly relative we return it as-is. It would be possible to resolve
+ // the target with `$target = self::resolve($base, $target);` and then try make it more relative
+ // by removing a duplicate query. But let's not do that automatically.
+ return $target;
+ }
+
+ if ($target->getAuthority() !== '' && $base->getAuthority() !== $target->getAuthority()) {
+ return $target->withScheme('');
+ }
+
+ // We must remove the path before removing the authority because if the path starts with two slashes, the URI
+ // would turn invalid. And we also cannot set a relative path before removing the authority, as that is also
+ // invalid.
+ $emptyPathUri = $target->withScheme('')->withPath('')->withUserInfo('')->withPort(null)->withHost('');
+
+ if ($base->getPath() !== $target->getPath()) {
+ return $emptyPathUri->withPath(self::getRelativePath($base, $target));
+ }
+
+ if ($base->getQuery() === $target->getQuery()) {
+ // Only the target fragment is left. And it must be returned even if base and target fragment are the same.
+ return $emptyPathUri->withQuery('');
+ }
+
+ // If the base URI has a query but the target has none, we cannot return an empty path reference as it would
+ // inherit the base query component when resolving.
+ if ($target->getQuery() === '') {
+ $segments = explode('/', $target->getPath());
+ /** @var string $lastSegment */
+ $lastSegment = end($segments);
+
+ return $emptyPathUri->withPath($lastSegment === '' ? './' : $lastSegment);
+ }
+
+ return $emptyPathUri;
+ }
+
+ private static function getRelativePath(UriInterface $base, UriInterface $target): string
+ {
+ $sourceSegments = explode('/', $base->getPath());
+ $targetSegments = explode('/', $target->getPath());
+ array_pop($sourceSegments);
+ $targetLastSegment = array_pop($targetSegments);
+ foreach ($sourceSegments as $i => $segment) {
+ if (isset($targetSegments[$i]) && $segment === $targetSegments[$i]) {
+ unset($sourceSegments[$i], $targetSegments[$i]);
+ } else {
+ break;
+ }
+ }
+ $targetSegments[] = $targetLastSegment;
+ $relativePath = str_repeat('../', count($sourceSegments)).implode('/', $targetSegments);
+
+ // A reference to am empty last segment or an empty first sub-segment must be prefixed with "./".
+ // This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
+ // as the first segment of a relative-path reference, as it would be mistaken for a scheme name.
+ if ('' === $relativePath || false !== strpos(explode('/', $relativePath, 2)[0], ':')) {
+ $relativePath = "./$relativePath";
+ } elseif ('/' === $relativePath[0]) {
+ if ($base->getAuthority() != '' && $base->getPath() === '') {
+ // In this case an extra slash is added by resolve() automatically. So we must not add one here.
+ $relativePath = ".$relativePath";
+ } else {
+ $relativePath = "./$relativePath";
+ }
+ }
+
+ return $relativePath;
+ }
+
+ private function __construct()
+ {
+ // cannot be instantiated
+ }
+}
diff --git a/vendor/guzzlehttp/psr7/src/Utils.php b/vendor/guzzlehttp/psr7/src/Utils.php
new file mode 100644
index 000000000..7682d2cdc
--- /dev/null
+++ b/vendor/guzzlehttp/psr7/src/Utils.php
@@ -0,0 +1,477 @@
+<?php
+
+declare(strict_types=1);
+
+namespace GuzzleHttp\Psr7;
+
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Message\StreamInterface;
+use Psr\Http\Message\UriInterface;
+
+final class Utils
+{
+ /**
+ * Remove the items given by the keys, case insensitively from the data.
+ *
+ * @param (string|int)[] $keys
+ */
+ public static function caselessRemove(array $keys, array $data): array
+ {
+ $result = [];
+
+ foreach ($keys as &$key) {
+ $key = strtolower((string) $key);
+ }
+
+ foreach ($data as $k => $v) {
+ if (!in_array(strtolower((string) $k), $keys)) {
+ $result[$k] = $v;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Copy the contents of a stream into another stream until the given number
+ * of bytes have been read.
+ *
+ * @param StreamInterface $source Stream to read from
+ * @param StreamInterface $dest Stream to write to
+ * @param int $maxLen Maximum number of bytes to read. Pass -1
+ * to read the entire stream.
+ *
+ * @throws \RuntimeException on error.
+ */
+ public static function copyToStream(StreamInterface $source, StreamInterface $dest, int $maxLen = -1): void
+ {
+ $bufferSize = 8192;
+
+ if ($maxLen === -1) {
+ while (!$source->eof()) {
+ if (!$dest->write($source->read($bufferSize))) {
+ break;
+ }
+ }
+ } else {
+ $remaining = $maxLen;
+ while ($remaining > 0 && !$source->eof()) {
+ $buf = $source->read(min($bufferSize, $remaining));
+ $len = strlen($buf);
+ if (!$len) {
+ break;
+ }
+ $remaining -= $len;
+ $dest->write($buf);
+ }
+ }
+ }
+
+ /**
+ * Copy the contents of a stream into a string until the given number of
+ * bytes have been read.
+ *
+ * @param StreamInterface $stream Stream to read
+ * @param int $maxLen Maximum number of bytes to read. Pass -1
+ * to read the entire stream.
+ *
+ * @throws \RuntimeException on error.
+ */
+ public static function copyToString(StreamInterface $stream, int $maxLen = -1): string
+ {
+ $buffer = '';
+
+ if ($maxLen === -1) {
+ while (!$stream->eof()) {
+ $buf = $stream->read(1048576);
+ if ($buf === '') {
+ break;
+ }
+ $buffer .= $buf;
+ }
+
+ return $buffer;
+ }
+
+ $len = 0;
+ while (!$stream->eof() && $len < $maxLen) {
+ $buf = $stream->read($maxLen - $len);
+ if ($buf === '') {
+ break;
+ }
+ $buffer .= $buf;
+ $len = strlen($buffer);
+ }
+
+ return $buffer;
+ }
+
+ /**
+ * Calculate a hash of a stream.
+ *
+ * This method reads the entire stream to calculate a rolling hash, based
+ * on PHP's `hash_init` functions.
+ *
+ * @param StreamInterface $stream Stream to calculate the hash for
+ * @param string $algo Hash algorithm (e.g. md5, crc32, etc)
+ * @param bool $rawOutput Whether or not to use raw output
+ *
+ * @throws \RuntimeException on error.
+ */
+ public static function hash(StreamInterface $stream, string $algo, bool $rawOutput = false): string
+ {
+ $pos = $stream->tell();
+
+ if ($pos > 0) {
+ $stream->rewind();
+ }
+
+ $ctx = hash_init($algo);
+ while (!$stream->eof()) {
+ hash_update($ctx, $stream->read(1048576));
+ }
+
+ $out = hash_final($ctx, $rawOutput);
+ $stream->seek($pos);
+
+ return $out;
+ }
+
+ /**
+ * Clone and modify a request with the given changes.
+ *
+ * This method is useful for reducing the number of clones needed to mutate
+ * a message.
+ *
+ * The changes can be one of:
+ * - method: (string) Changes the HTTP method.
+ * - set_headers: (array) Sets the given headers.
+ * - remove_headers: (array) Remove the given headers.
+ * - body: (mixed) Sets the given body.
+ * - uri: (UriInterface) Set the URI.
+ * - query: (string) Set the query string value of the URI.
+ * - version: (string) Set the protocol version.
+ *
+ * @param RequestInterface $request Request to clone and modify.
+ * @param array $changes Changes to apply.
+ */
+ public static function modifyRequest(RequestInterface $request, array $changes): RequestInterface
+ {
+ if (!$changes) {
+ return $request;
+ }
+
+ $headers = $request->getHeaders();
+
+ if (!isset($changes['uri'])) {
+ $uri = $request->getUri();
+ } else {
+ // Remove the host header if one is on the URI
+ if ($host = $changes['uri']->getHost()) {
+ $changes['set_headers']['Host'] = $host;
+
+ if ($port = $changes['uri']->getPort()) {
+ $standardPorts = ['http' => 80, 'https' => 443];
+ $scheme = $changes['uri']->getScheme();
+ if (isset($standardPorts[$scheme]) && $port != $standardPorts[$scheme]) {
+ $changes['set_headers']['Host'] .= ':'.$port;
+ }
+ }
+ }
+ $uri = $changes['uri'];
+ }
+
+ if (!empty($changes['remove_headers'])) {
+ $headers = self::caselessRemove($changes['remove_headers'], $headers);
+ }
+
+ if (!empty($changes['set_headers'])) {
+ $headers = self::caselessRemove(array_keys($changes['set_headers']), $headers);
+ $headers = $changes['set_headers'] + $headers;
+ }
+
+ if (isset($changes['query'])) {
+ $uri = $uri->withQuery($changes['query']);
+ }
+
+ if ($request instanceof ServerRequestInterface) {
+ $new = (new ServerRequest(
+ $changes['method'] ?? $request->getMethod(),
+ $uri,
+ $headers,
+ $changes['body'] ?? $request->getBody(),
+ $changes['version'] ?? $request->getProtocolVersion(),
+ $request->getServerParams()
+ ))
+ ->withParsedBody($request->getParsedBody())
+ ->withQueryParams($request->getQueryParams())
+ ->withCookieParams($request->getCookieParams())
+ ->withUploadedFiles($request->getUploadedFiles());
+
+ foreach ($request->getAttributes() as $key => $value) {
+ $new = $new->withAttribute($key, $value);
+ }
+
+ return $new;
+ }
+
+ return new Request(
+ $changes['method'] ?? $request->getMethod(),
+ $uri,
+ $headers,
+ $changes['body'] ?? $request->getBody(),
+ $changes['version'] ?? $request->getProtocolVersion()
+ );
+ }
+
+ /**
+ * Read a line from the stream up to the maximum allowed buffer length.
+ *
+ * @param StreamInterface $stream Stream to read from
+ * @param int|null $maxLength Maximum buffer length
+ */
+ public static function readLine(StreamInterface $stream, ?int $maxLength = null): string
+ {
+ $buffer = '';
+ $size = 0;
+
+ while (!$stream->eof()) {
+ if ('' === ($byte = $stream->read(1))) {
+ return $buffer;
+ }
+ $buffer .= $byte;
+ // Break when a new line is found or the max length - 1 is reached
+ if ($byte === "\n" || ++$size === $maxLength - 1) {
+ break;
+ }
+ }
+
+ return $buffer;
+ }
+
+ /**
+ * Redact the password in the user info part of a URI.
+ */
+ public static function redactUserInfo(UriInterface $uri): UriInterface
+ {
+ $userInfo = $uri->getUserInfo();
+
+ if (false !== ($pos = \strpos($userInfo, ':'))) {
+ return $uri->withUserInfo(\substr($userInfo, 0, $pos), '***');
+ }
+
+ return $uri;
+ }
+
+ /**
+ * Create a new stream based on the input type.
+ *
+ * Options is an associative array that can contain the following keys:
+ * - metadata: Array of custom metadata.
+ * - size: Size of the stream.
+ *
+ * This method accepts the following `$resource` types:
+ * - `Psr\Http\Message\StreamInterface`: Returns the value as-is.
+ * - `string`: Creates a stream object that uses the given string as the contents.
+ * - `resource`: Creates a stream object that wraps the given PHP stream resource.
+ * - `Iterator`: If the provided value implements `Iterator`, then a read-only
+ * stream object will be created that wraps the given iterable. Each time the
+ * stream is read from, data from the iterator will fill a buffer and will be
+ * continuously called until the buffer is equal to the requested read size.
+ * Subsequent read calls will first read from the buffer and then call `next`
+ * on the underlying iterator until it is exhausted.
+ * - `object` with `__toString()`: If the object has the `__toString()` method,
+ * the object will be cast to a string and then a stream will be returned that
+ * uses the string value.
+ * - `NULL`: When `null` is passed, an empty stream object is returned.
+ * - `callable` When a callable is passed, a read-only stream object will be
+ * created that invokes the given callable. The callable is invoked with the
+ * number of suggested bytes to read. The callable can return any number of
+ * bytes, but MUST return `false` when there is no more data to return. The
+ * stream object that wraps the callable will invoke the callable until the
+ * number of requested bytes are available. Any additional bytes will be
+ * buffered and used in subsequent reads.
+ *
+ * @param resource|string|int|float|bool|StreamInterface|callable|\Iterator|null $resource Entity body data
+ * @param array{size?: int, metadata?: array} $options Additional options
+ *
+ * @throws \InvalidArgumentException if the $resource arg is not valid.
+ */
+ public static function streamFor($resource = '', array $options = []): StreamInterface
+ {
+ if (is_scalar($resource)) {
+ $stream = self::tryFopen('php://temp', 'r+');
+ if ($resource !== '') {
+ fwrite($stream, (string) $resource);
+ fseek($stream, 0);
+ }
+
+ return new Stream($stream, $options);
+ }
+
+ switch (gettype($resource)) {
+ case 'resource':
+ /*
+ * The 'php://input' is a special stream with quirks and inconsistencies.
+ * We avoid using that stream by reading it into php://temp
+ */
+
+ /** @var resource $resource */
+ if ((\stream_get_meta_data($resource)['uri'] ?? '') === 'php://input') {
+ $stream = self::tryFopen('php://temp', 'w+');
+ stream_copy_to_stream($resource, $stream);
+ fseek($stream, 0);
+ $resource = $stream;
+ }
+
+ return new Stream($resource, $options);
+ case 'object':
+ /** @var object $resource */
+ if ($resource instanceof StreamInterface) {
+ return $resource;
+ } elseif ($resource instanceof \Iterator) {
+ return new PumpStream(function () use ($resource) {
+ if (!$resource->valid()) {
+ return false;
+ }
+ $result = $resource->current();
+ $resource->next();
+
+ return $result;
+ }, $options);
+ } elseif (method_exists($resource, '__toString')) {
+ return self::streamFor((string) $resource, $options);
+ }
+ break;
+ case 'NULL':
+ return new Stream(self::tryFopen('php://temp', 'r+'), $options);
+ }
+
+ if (is_callable($resource)) {
+ return new PumpStream($resource, $options);
+ }
+
+ throw new \InvalidArgumentException('Invalid resource type: '.gettype($resource));
+ }
+
+ /**
+ * Safely opens a PHP stream resource using a filename.
+ *
+ * When fopen fails, PHP normally raises a warning. This function adds an
+ * error handler that checks for errors and throws an exception instead.
+ *
+ * @param string $filename File to open
+ * @param string $mode Mode used to open the file
+ *
+ * @return resource
+ *
+ * @throws \RuntimeException if the file cannot be opened
+ */
+ public static function tryFopen(string $filename, string $mode)
+ {
+ $ex = null;
+ set_error_handler(static function (int $errno, string $errstr) use ($filename, $mode, &$ex): bool {
+ $ex = new \RuntimeException(sprintf(
+ 'Unable to open "%s" using mode "%s": %s',
+ $filename,
+ $mode,
+ $errstr
+ ));
+
+ return true;
+ });
+
+ try {
+ /** @var resource $handle */
+ $handle = fopen($filename, $mode);
+ } catch (\Throwable $e) {
+ $ex = new \RuntimeException(sprintf(
+ 'Unable to open "%s" using mode "%s": %s',
+ $filename,
+ $mode,
+ $e->getMessage()
+ ), 0, $e);
+ }
+
+ restore_error_handler();
+
+ if ($ex) {
+ /** @var $ex \RuntimeException */
+ throw $ex;
+ }
+
+ return $handle;
+ }
+
+ /**
+ * Safely gets the contents of a given stream.
+ *
+ * When stream_get_contents fails, PHP normally raises a warning. This
+ * function adds an error handler that checks for errors and throws an
+ * exception instead.
+ *
+ * @param resource $stream
+ *
+ * @throws \RuntimeException if the stream cannot be read
+ */
+ public static function tryGetContents($stream): string
+ {
+ $ex = null;
+ set_error_handler(static function (int $errno, string $errstr) use (&$ex): bool {
+ $ex = new \RuntimeException(sprintf(
+ 'Unable to read stream contents: %s',
+ $errstr
+ ));
+
+ return true;
+ });
+
+ try {
+ /** @var string|false $contents */
+ $contents = stream_get_contents($stream);
+
+ if ($contents === false) {
+ $ex = new \RuntimeException('Unable to read stream contents');
+ }
+ } catch (\Throwable $e) {
+ $ex = new \RuntimeException(sprintf(
+ 'Unable to read stream contents: %s',
+ $e->getMessage()
+ ), 0, $e);
+ }
+
+ restore_error_handler();
+
+ if ($ex) {
+ /** @var $ex \RuntimeException */
+ throw $ex;
+ }
+
+ return $contents;
+ }
+
+ /**
+ * Returns a UriInterface for the given value.
+ *
+ * This function accepts a string or UriInterface and returns a
+ * UriInterface for the given value. If the value is already a
+ * UriInterface, it is returned as-is.
+ *
+ * @param string|UriInterface $uri
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function uriFor($uri): UriInterface
+ {
+ if ($uri instanceof UriInterface) {
+ return $uri;
+ }
+
+ if (is_string($uri)) {
+ return new Uri($uri);
+ }
+
+ throw new \InvalidArgumentException('URI must be a string or UriInterface');
+ }
+}
diff --git a/vendor/macgirvin/http-message-signer/LICENSE b/vendor/macgirvin/http-message-signer/LICENSE
new file mode 100644
index 000000000..36f87d3af
--- /dev/null
+++ b/vendor/macgirvin/http-message-signer/LICENSE
@@ -0,0 +1,30 @@
+BSD 3-Clause License
+
+Copyright (c) 2025, Quantificant LLC <waitman@quantificant.com>
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names
+ of its contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/vendor/macgirvin/http-message-signer/README.md b/vendor/macgirvin/http-message-signer/README.md
new file mode 100644
index 000000000..cd1aab1aa
--- /dev/null
+++ b/vendor/macgirvin/http-message-signer/README.md
@@ -0,0 +1,130 @@
+# HTTP Message Signer (RFC 9421)
+
+
+A PHP 8.1+ library for signing and verifying HTTP messages (requests or responses) per [RFC 9421](https://www.rfc-editor.org/rfc/rfc9421).
+
+This is a fork of quantificant/http-message-signer
+
+Supports:
+- RSA-SHA256
+- Ed25519
+- HMAC-SHA256
+- PSR-7 requests (e.g., Guzzle)
+- Optionally (recommended) calculate and verify body digest (content-digest header)
+
+Requirements:
+- bakame/http-structured-fields
+- psr/http-message
+
+## Note
+
+This is Alpha version please report issues. Thanks. Tested on PHP 8.4, should run fine on 8.1+
+
+2025-05-28: Partially reversed the constructor change.
+
+
+## Installation
+
+```bash
+composer require macgirvin/http-message-signer
+```
+
+
+## Notes
+
+An instance of a PSR-7 MessageInterface is passed to the sign and verify functions. This can be a RequestInterface or a ResponseInterface. Typically, this will be a RequestInterface. If your web framework does not supply a pre-populated PSR7-compatible request interface, you can quickly generate one using
+
+```
+use GuzzleHttp\Psr7\ServerRequest;
+
+$request = ServerRequest::fromGlobals();
+```
+
+This would typically be used to verify a message.
+
+To sign a message, install the composer package guzzlehttp/psr7 and create an instance of `Request`.
+
+## Usage
+
+```php
+use HttpSignature\HttpMessageSigner;
+use HttpSignature\UnProcessableSignatureException;
+use GuzzleHttp\Psr7\Request;
+
+$request = new Request(
+ 'GET',
+ 'https://api.example.com/resource?bat&baz=3',
+ [
+ 'Host' => 'api.example.com',
+ 'Date' => gmdate('D, d M Y H:i:s T'),
+ ...additional headers
+ ]
+);
+
+$signer = (new HttpMessageSigner())
+ ->setPrivateKey($privateKey) // only needed for signing
+ ->setPublicKey($publicKey) // only needed for verifying
+ ->setKeyId('https://example.com/dave#rsaKey') // required
+ ->setAlgorithm('rsa-sha256') // required
+ ->setCreated(time()) // recommended
+ ->setExpires(time() + 300) // optional, contentious
+ ->setNonce('xJJ9;ro.3*kidney`') // optional one-time token
+ ->setTag('fediverse') // optional app profile name
+ ->setSignatureId('sig1') // optional, default is sig1
+
+try {
+ $request = $signer->signRequest('("@method" "@path" "host" "date")', $request);
+}
+catch (UnProcessableSignatureException $exception) {
+ $whatHappened = $exception->getMessage();
+}
+try {
+ $isValid = $signer->verifyRequest($request);
+} catch (UnProcessableSignatureException $exception) {
+ $isValid = false;
+ $whatHappened = $exception->getMessage();
+}
+```
+
+See full examples in `/tests`.
+
+## Structured Fields
+
+RFC9421 makes heavy use of HTTP Structured Fields (RFC8941/RFC9651). The syntax is very precise and unforgiving.
+
+The signRequest() method takes a structured InnerList of components to sign. These may be headers or derived fields.
+The string will look something like the following (where `...` represents additional components):
+
+```
+'("header1" "header2" "@method" ...)'
+```
+
+and may include modifier parameters. These are represented as
+
+```
+'("@query-param";name="foo" "header2";sf "header3" ...)'
+```
+
+Field names beginning with '@' are components derived from the HTTP request but may not be represented in the headers. Please review RFC9421 for precise definitions.
+
+Using the 'sf' parameter on a component will treat a signature component as a Structured Field when normalising the string.
+
+However, parsing Structured Fields by adding the 'sf' parameter is likely to fail unless you know what `type` it is. A built-in table contains the type definition for a number of known stuctured header types. This list is probably incomplete. A method `addStructuredFieldTypes()` is available to add the type information so it can be successfully parsed. This takes an array with key of the lowercase header name and a value; which is one of 'list', 'innerlist', 'parameters, 'dictionary', 'item'. If the header name is in the list and the 'sf' modifier is used, the header will be parsed as the Structured Field type indicated.
+
+If a Structured Field is declared as type 'dictionary'; it is suitable for use with the RFC9421 `key` parameter. Using this parameter will fail if the Structured Field type is unknown or has not been registered.
+
+The signRequest() and verifyRequest() methods both use an instance of MessageInterface. In nearly all cases, this will be the RequestInterface. However, when signing responses, the default will be the ResponseInterface, and if components are required from the RequestInterface, the :req parameter must be added to the field definition.
+
+To sign or verify an HTTP Response, use a ResponseInterface as the provided `$interface`, and provide the RequestInterface in `$originalRequest`. This is optional but will allow the `req` modifier to work correctly when signing Responses.
+
+## Known issues
+Currently not implemented is the special handling of the `cookie` and `set-cookie` headers when using the `sf` modifier. For further information please see https://httpwg.org/http-extensions/draft-ietf-httpbis-retrofit.html and https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis-20 (or later). It is planned to implement this once RFC6265bis is finalised as a new RFC.
+
+Also not currently implemented are some of the many signature algorithms; as we're currently focused primarily on rsa-sha256 and ed25519.
+
+Pull requests welcome.
+
+
+## License
+
+BSD 3-Clause
diff --git a/vendor/macgirvin/http-message-signer/TESTING b/vendor/macgirvin/http-message-signer/TESTING
new file mode 100644
index 000000000..a04c516a2
--- /dev/null
+++ b/vendor/macgirvin/http-message-signer/TESTING
@@ -0,0 +1,5 @@
+Run Tests:
+
+$ composer require --dev phpunit/phpunit
+$ vendor/bin/phpunit
+
diff --git a/vendor/macgirvin/http-message-signer/composer.json b/vendor/macgirvin/http-message-signer/composer.json
new file mode 100644
index 000000000..dfb735938
--- /dev/null
+++ b/vendor/macgirvin/http-message-signer/composer.json
@@ -0,0 +1,29 @@
+{
+ "name": "macgirvin/http-message-signer",
+ "description": "RFC 9421 HTTP Message Signer and Verifier for PSR-7 requests",
+ "type": "library",
+ "require": {
+ "php": "^8.1",
+ "psr/http-message": "^2.0",
+ "guzzlehttp/psr7": "^2.0",
+ "bakame/http-structured-fields": "^2.0",
+ "ext-openssl": "*",
+ "phpseclib/phpseclib": "~3.0",
+ "phpseclib/phpseclib2_compat": "^1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^10.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "HttpSignature\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "HttpSignature\\Tests\\": "tests/"
+ }
+ },
+ "license": "BSD-3-Clause",
+ "minimum-stability": "stable"
+}
diff --git a/vendor/macgirvin/http-message-signer/phpunit.xml b/vendor/macgirvin/http-message-signer/phpunit.xml
new file mode 100644
index 000000000..4149ce1a9
--- /dev/null
+++ b/vendor/macgirvin/http-message-signer/phpunit.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/12.1/phpunit.xsd"
+ bootstrap="vendor/autoload.php"
+ displayDetailsOnTestsThatTriggerWarnings="true"
+ colors="true">
+
+ <testsuites>
+ <testsuite name="HttpSignature Test Suite">
+ <directory>tests</directory>
+ </testsuite>
+ </testsuites>
+
+ <source>
+ <include>
+ <directory>src</directory>
+ </include>
+ </source>
+
+</phpunit>
diff --git a/vendor/macgirvin/http-message-signer/src/HttpMessageSigner.php b/vendor/macgirvin/http-message-signer/src/HttpMessageSigner.php
new file mode 100644
index 000000000..31c481f22
--- /dev/null
+++ b/vendor/macgirvin/http-message-signer/src/HttpMessageSigner.php
@@ -0,0 +1,876 @@
+<?php
+
+namespace HttpSignature;
+
+use Bakame\Http\StructuredFields\Dictionary;
+use Bakame\Http\StructuredFields\InnerList;
+use Bakame\Http\StructuredFields\Item;
+use Bakame\Http\StructuredFields\OuterList;
+use Bakame\Http\StructuredFields\Parameters;
+use Psr\Http\Message\MessageInterface;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use phpseclib\Crypt\RSA;
+
+class HttpMessageSigner
+{
+ private string $keyId;
+ private string $privateKey;
+ private string $publicKey;
+ private string $algorithm;
+ private string $signatureId = 'sig1';
+ private string $created = '';
+ private string $expires = '';
+ private string $nonce = '';
+ private string $tag = '';
+
+ private array $structuredFieldTypes = [];
+
+ private $originalRequest;
+
+
+ public function __construct()
+ {
+ $this->setStructuredFieldTypes((new StructuredFieldTypes())->getFields());
+ return $this;
+ }
+
+ public function getHeaders($interface): array
+ {
+ $headers = [];
+ foreach ($interface->getHeaders() as $name => $values) {
+ $headers[strtolower($name)] = implode(', ', $values);
+ }
+ return $headers;
+ }
+
+
+ /* PSR-7 interface to signing function */
+ /**
+ * @param string $coveredFields
+ * @param MessageInterface $interface
+ * @param RequestInterface|null $originalRequest
+ * @return MessageInterface
+ * @throws UnProcessableSignatureException
+ */
+
+ public function signRequest(string $coveredFields, MessageInterface $interface, RequestInterface $originalRequest = null): MessageInterface
+ {
+ $headers = $this->getHeaders($interface);
+ if ($originalRequest) {
+ $this->setOriginalRequest($originalRequest);
+ }
+
+ $signedHeaders = $this->sign(
+ $headers,
+ $coveredFields,
+ $interface
+ );
+
+ foreach (['signature-input', 'signature'] as $header) {
+ $interface = $interface->withHeader($header, $signedHeaders[$header]);
+ }
+ return $interface;
+ }
+
+ /* PSR-7 verify interface and also check body digest if included */
+ /**
+ * @param MessageInterface $interface
+ * @param RequestInterface|null $originalRequest
+ * @return bool
+ *
+ * @throws UnProcessableSignatureException
+ */
+
+ public function verifyRequest(MessageInterface $interface, RequestInterface $originalRequest = null): bool
+ {
+ $headers = [];
+ if ($originalRequest) {
+ $this->setOriginalRequest($originalRequest);
+ }
+ foreach ($interface->getHeaders() as $name => $values) {
+ $headers[strtolower($name)] = implode(', ', $values);
+ }
+
+ /* check the body digest if it's present */
+
+ if (isset($headers['content-digest'])) {
+ $body = (string)$interface->getBody();
+ if (!$this->isBodyDigestValid($body, $headers['content-digest'])) {
+ return false;
+ }
+ }
+
+ return $this->verify($headers, $interface);
+ }
+
+ /**
+ * check body digest
+ *
+ * From https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Digest
+ * The algorithm used to create a digest of the message content. Only two registered digest algorithms are
+ * considered secure: sha-512 and sha-256. The insecure (legacy) registered digest algorithms
+ * are: md5, sha (SHA-1), unixsum, unixcksum, adler (ADLER32) and crc32c.
+ */
+
+ private function isBodyDigestValid(string $body, string $headerValue): bool
+ {
+ if (!preg_match('/sha-(.*?)=:(.*?):/', $headerValue, $matches)) {
+ return false;
+ }
+ if (!in_array($matches[1], ['256', '512'], true)) {
+ return false;
+ }
+
+ $algorithm = 'sha' . $matches[1];
+
+ $expectedDigest = base64_decode($matches[2]);
+ $actualDigest = hash($algorithm, $body, true);
+
+ return hash_equals($expectedDigest, $actualDigest);
+ }
+
+ /**
+ * @param array $headers
+ * @param string $coveredFields
+ *
+ * Can be used as a development function to peek into the internals of the exact things
+ * that were signed and/or test the internal results of data normalisation.
+ * This matches the sign() function except that it just returns the serialised string
+ * and does not sign it.
+ */
+ public function calculateSignatureBase(array $headers, string $coveredFields, $interface)
+ {
+ $signatureComponents = [];
+ $processedComponents = [];
+ $dict = $this->parseStructuredDict($coveredFields);
+
+ if ($dict->isNotEmpty()) {
+ $coveredStructuredFields = $dict->__toString();
+ $indices = $dict->indices();
+ foreach ($indices as $index) {
+ $member = $dict->getByIndex($index);
+ if (!$member) {
+ throw new UnProcessableSignatureException('Index ' . $index . ' not found');
+ }
+ if (in_array($member, $processedComponents, true)) {
+ throw new UnProcessableSignatureException('Duplicate member found');
+ }
+ $processedComponents[] = $member;
+ $signatureComponents[] = $this->canonicalizeComponent($member, $headers, $interface);
+ }
+ }
+
+ $signatureInput = $coveredStructuredFields . ';keyid="'
+ . $this->keyId . '";alg="' . $this->algorithm . '"';
+
+ if ($this->created) {
+ $signatureInput .= ';created=' . $this->created;
+ }
+ if ($this->expires) {
+ $signatureInput .= ';expires=' . $this->expires;
+ }
+ if ($this->nonce) {
+ $signatureInput .= ';nonce="' . $this->nonce . '"';
+ }
+ if ($this->tag) {
+ $signatureInput .= ';tag="' . $this->tag . '"';
+ }
+
+
+ /**
+ * Always include @signature-params in the result.
+ */
+ $signatureComponents[] = '"@signature-params": ' . $signatureInput;
+
+ $signatureBase = implode("\n", $signatureComponents);
+ return $signatureBase;
+
+ }
+
+ public function sign(array $headers, string $coveredFields, MessageInterface $interface): array
+ {
+ $signatureComponents = [];
+ $processedComponents = [];
+
+ $dict = $this->parseStructuredDict($coveredFields);
+
+ if ($dict->isNotEmpty()) {
+ $coveredStructuredFields = $dict->__toString();
+ $indices = $dict->indices();
+ foreach ($indices as $index) {
+ $member = $dict->getByIndex($index);
+ if (!$member) {
+ throw new UnProcessableSignatureException('Index ' . $index . ' not found');
+ }
+ if (in_array($member, $processedComponents, true)) {
+ throw new UnProcessableSignatureException('Duplicate member found');
+ }
+ $processedComponents[] = $member;
+ $signatureComponents[] = $this->canonicalizeComponent($member, $headers, $interface);
+ }
+ }
+
+ $signatureInput = $coveredStructuredFields . ';keyid="'
+ . $this->keyId . '";alg="' . $this->algorithm . '"'
+ . (($this->created) ? ';created=' . $this->created : '')
+ . (($this->expires) ? ';expires=' . $this->expires : '')
+ . (($this->nonce) ? ';nonce="' . $this->nonce . '"' : '')
+ . (($this->tag) ? ';tag="' . $this->tag . '"' : '');
+
+ /**
+ * Always include @signature-params in the result.
+ */
+ $signatureComponents[] = '"@signature-params": ' . $signatureInput;
+
+ $signatureBase = implode("\n", $signatureComponents);
+ $signature = $this->createSignature($signatureBase);
+
+ $headers['signature-input'] = "$this->signatureId=$signatureInput";
+ $headers['signature'] = "$this->signatureId=:$signature:";
+
+ return $headers;
+ }
+
+ public function verify(array $headers, $interface): bool
+ {
+ if (!isset($headers['signature-input'], $headers['signature'])) {
+ return false;
+ }
+ $headers[] = 'signature-params';
+
+ $sigInputDict = $this->parseStructuredDict($headers['signature-input']);
+
+ $signatureComponents = [];
+
+ if ($sigInputDict->isNotEmpty()) {
+ $indices = $sigInputDict->indices();
+ foreach ($indices as $index) {
+ [$dictName, $members] = $sigInputDict->getByIndex($index);
+ if ($members instanceof InnerList) {
+ $innerIndices = $members->indices();
+ foreach ($innerIndices as $innerIndex) {
+ $member = $members->getByIndex($innerIndex);
+ $signatureComponents[$dictName][] = $this->canonicalizeComponent($member, $headers, $interface);
+ }
+ $parameters = $this->extractParameters($members);
+ if ($parameters) {
+ foreach ($parameters as $key => $value) {
+ if (!in_array($key, ['created', 'expires', 'nonce', 'alg', 'keyid', 'tag'])) {
+ return false;
+ }
+ }
+ }
+ if (isset($parameters['expires'])) {
+ $expires = (int) $parameters['expires'];
+ if ($expires < time()) {
+ return false;
+ }
+ if (isset($parameters['created'])) {
+ $created = (int) $parameters['created'];
+ if ($created >= $expires) {
+ return false;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ $sigDict = $this->parseStructuredDict($headers['signature']);
+
+ foreach ($signatureComponents as $dictName => $dictComponents) {
+ $namedSignatureComponents = $signatureComponents[$dictName];
+ $signatureParamsStr = $sigInputDict[$dictName]->toHttpValue();
+ $namedSignatureComponents[] = '"@signature-params": ' . $signatureParamsStr;
+ $signatureBase = implode("\n", $namedSignatureComponents);
+ if (!isset($sigDict[$dictName])) {
+ return false;
+ }
+
+ $decodedSig = base64_decode(trim($sigDict[$dictName]->__toString(), ':'));
+ return $this->verifySignature($signatureBase, $decodedSig, $parameters['alg'] ?? $this->algorithm);
+ }
+ return false;
+ }
+
+ protected function extractParameters($field): array
+ {
+ $parameters = [];
+ $fieldParams = $field->parameters();
+
+ if ($fieldParams->isNotEmpty()) {
+ $indices = $fieldParams->indices();
+ foreach ($indices as $index) {
+ [$name, $item] = $fieldParams->getByIndex($index);
+ $parameters[$name] = $item->value();
+ }
+ }
+ return $parameters;
+ }
+
+ private function canonicalizeComponent($field, array $headers, MessageInterface $interface): string
+ {
+ $fieldName = $field->value();
+ $parameters = $this->extractParameters($field);
+ if (isset($parameters['bs']) && isset($parameters['sf'])) {
+ throw new UnProcessableSignatureException('Cannot use both bs and sf');
+ }
+
+ $whichRequest = $interface;
+ if (isset($parameters['req'])) {
+ if ($interface instanceof ResponseInterface) {
+ $whichRequest = $this->getOriginalRequest();
+ }
+ else {
+ throw new UnProcessableSignatureException('missing request for req parameter');
+ }
+ }
+ $whichHeaders = $headers;
+
+ if (isset($parameters['tr'])) {
+ $whichHeaders = $whichRequest->getTrailers();
+ }
+
+ [$name, $value] = $this->getFieldValue($fieldName, $whichRequest, $whichHeaders, $parameters);
+
+ if (isset($parameters['bs'])) {
+ $result = $name . ';bs: ';
+ $values = $whichRequest->getHeader($fieldName);
+ if (!$values) {
+ return '';
+ }
+ if (!is_array($values)) {
+ $values = [$values];
+ }
+ foreach ($values as $value) {
+ $value = trim($value);
+ $result .= ':' . base64_encode($value) . ':' . ', ';
+ }
+ return $values ? rtrim($result, ', ') : $result;
+ }
+
+ if (isset($parameters['sf'])) {
+ $value = $this->applyStructuredField($name, $value);
+ return $name . ';sf: ' . $value;
+ }
+ if (isset($parameters['key'])) {
+ $childName = $parameters['key'];
+ $value = $this->applySingleKeyValue($name, $childName, $value);
+ return $name . ';key="' . $childName . '": ' . $value;
+ }
+ return $name . ': ' . $value;
+ }
+
+ private function getFieldValue($fieldName, MessageInterface $interface, $headers, $parameters ): array
+ {
+ // The $interface has no single method to extract this, so build it from
+ // the avilable components.
+ $targetUri = $interface->getUri()->getScheme() . '://' . $interface->getUri()->getAuthority()
+ . $interface->getUri()->getPath() . $interface->getUri()->getQuery();
+
+ $value = match ($fieldName) {
+ '@signature-params' => ['', ''],
+ '@method' => ['"@method"', strtoupper($interface->getMethod())],
+ '@authority' => ['"@authority"', $interface->getUri()->getAuthority()],
+ '@scheme' => ['"@scheme"', strtolower($interface->getUri()->getScheme())],
+ '@target-uri' => ['"@target-uri"', $targetUri],
+ '@request-target' => ['"@request-target"', $interface->getRequestTarget()],
+ '@path' => ['"@path"', $interface->getUri()->getPath()],
+ '@query' => ['"@query"', $interface->getUri()->getQuery()],
+ '@query-param' => $this->getQueryParam($interface, $parameters) ?? ['', ''],
+ '@status' => ['"@status"', '"@status": ' . $interface->getStatusCode()],
+ default => ['"' . $fieldName . '"', trim($headers[$fieldName] ?? '')],
+ };
+ return $value;
+ }
+
+ /**
+ * @param string $query
+ * @return array|null
+ *
+ * Should not use PHP's parse_str function here, as it has some issues with
+ * spaces and dots in parameter names. These are unlikely to occur, but the
+ * following function treats them as opaque strings rather than as variable
+ * names.
+ */
+ private function parseQueryString(string $query): array|null
+ {
+ $result = [];
+
+ if (!isset($query)) {
+ return null;
+ }
+
+ $queryParams = explode('&', $query);
+ foreach ($queryParams as $param) {
+ // The '=' character is not required and indicates a boolean true value if unset.
+ $element = explode('=', $param, 2);
+ $result[urldecode($element[0])] = isset($element[1]) ? urldecode($element[1]) : '';
+ }
+ return $result;
+ }
+
+ /**
+ * @param $parameters
+ * @param $name
+ * @return string|null
+ *
+ * Find one query parameter by name (which must supplied as parameters in the (structured) covered field list).
+ */
+ private function getQueryParam($whichRequest, array $parameters): array
+ {
+ $queryString = $whichRequest->getUri()->getQuery();
+ if ($queryString) {
+ $queryParams = $this->parseQueryString($queryString);
+ $fieldName = $parameters['name'];
+ if ($fieldName) {
+ return ['"_' . $fieldName . '_"', $queryParams[$fieldName] ? '"' . $queryParams[$fieldName] . '"' : ''];
+ }
+ }
+ throw new UnProcessableSignatureException('Query string named parameter not set');
+ }
+
+ private function applyStructuredField(string $name, string $fieldValue): string
+ {
+ $type = $this->structuredFieldTypes[trim($name, '"')];
+ switch ($type) {
+ case 'list':
+ $field = OuterList::fromHttpValue($fieldValue);
+ break;
+ case 'innerlist':
+ $field = InnerList::fromHttpValue($fieldValue);
+ break;
+ case 'parameters':
+ $field = Parameters::fromHttpValue($fieldValue);
+ break;
+ case 'dictionary':
+ $field = Dictionary::fromHttpValue($fieldValue);
+ break;
+ case 'item':
+ $field = Item::fromHttpValue($fieldValue);
+ break;
+ case 'url':
+ return '"' . $fieldValue . '"';
+ case 'date':
+ return '@' . strtotime($fieldValue);
+ case 'etag':
+ $result = '';
+ $list = explode(',', $fieldValue);
+ foreach ($list as $item) {
+ if (str_starts_with(trim($item), 'W/')) {
+ $result .= substr(trim($item), 2) . '; w' . ', ';
+ } else {
+ $result .= trim($item) . ', ';
+ }
+ }
+ return rtrim($result, ', ');
+ case 'cookie':
+ // @TODO
+ default:
+ break;
+ }
+ if (!$field) {
+ throw new UnProcessableSignatureException('Unknown or unregistered structured field type');
+ }
+ return $field->toHttpValue();
+ }
+
+ private function applySingleKeyValue(string $name, string $key, string $fieldValue): string
+ {
+ $type = $this->structuredFieldTypes[trim($name, '"')];
+ if (empty($type) || $type === 'dictionary') {
+ $dictionary = Dictionary::fromHttpValue($fieldValue);
+ if ($dictionary->isNotEmpty() && isset($dictionary[$key])) {
+ return $dictionary[$key]->toHttpValue();
+ }
+ }
+ return '';
+ }
+
+ private function createSignature(string $data): string
+ {
+ return match ($this->algorithm) {
+ 'rsa-v1_5-sha256' => $this->rsaSign($data),
+ 'rsa-v1_5-sha512' => $this->rsa512Sign($data),
+ 'rsa-sha256' => $this->rsaSign($data),
+ 'rsa-pss-sha512' => $this->pssSign($data),
+ 'ed25519' => $this->ed25519Sign($data),
+ 'hmac-sha256' => base64_encode(hash_hmac('sha256', $data, $this->privateKey, true)),
+ default => throw new UnProcessableSignatureException("Unsupported algorithm: $this->algorithm")
+ };
+ }
+
+ private function verifySignature(string $data, string $signature, string $alg): bool
+ {
+ return match ($alg) {
+ 'rsa-v1_5-sha256' => openssl_verify($data, $signature, $this->publicKey,
+ OPENSSL_ALGO_SHA256) === 1,
+ 'rsa-v1_5-sha512' => openssl_verify($data, $signature, $this->publicKey,
+ OPENSSL_ALGO_SHA512) === 1,
+ 'rsa-sha256' => openssl_verify($data, $signature, $this->publicKey,
+ OPENSSL_ALGO_SHA256) === 1,
+ 'rsa-pss-sha512' => $this->pssVerify($data, $signature),
+ 'ed25519' => openssl_verify($data, $signature, $this->publicKey, "Ed25519") === 1,
+ 'hmac-sha256' => hash_equals(
+ base64_encode(hash_hmac('sha256', $data, $this->privateKey, true)),
+ base64_encode($signature)
+ ),
+ default => false
+ };
+ }
+
+ /* sign with rsa or ed25519 */
+
+ private function rsaSign(string $data): string
+ {
+ if (!openssl_sign($data, $signature, $this->privateKey, OPENSSL_ALGO_SHA256)) {
+ throw new UnProcessableSignatureException("RSA signing failed");
+ }
+ return base64_encode($signature);
+ }
+ private function rsa512Sign(string $data): string
+ {
+ if (!openssl_sign($data, $signature, $this->privateKey, OPENSSL_ALGO_SHA512)) {
+ throw new UnProcessableSignatureException("RSA signing failed");
+ }
+ return base64_encode($signature);
+ }
+
+ private function pssSign(string $data): string
+ {
+ $rsa = new RSA();
+ if ($rsa->loadKey($this->privateKey) !== true) {
+ throw new UnprocessableSignatureException("PSS loadkey failure");
+ };
+ $rsa->setHash('sha512');
+ $rsa->setMGFHash('sha512');
+ $rsa->setSignatureMode(RSA::SIGNATURE_PSS);
+ try {
+ $signatureBytes = $rsa->sign($data);
+ } catch (\Exception $exception) {
+ throw new UnprocessableSignatureException($exception->getMessage());
+ }
+ return base64_encode($signatureBytes);
+ }
+
+ private function ed25519Sign(string $data): string
+ {
+ if (!openssl_sign($data, $signature, $this->privateKey, "Ed25519")) {
+ throw new UnProcessableSignatureException("Ed25519 signing failed");
+ }
+ return base64_encode($signature);
+ }
+
+ private function pssVerify(string $data, $signature): bool
+ {
+ $rsa = new RSA();
+ if (!$rsa->loadKey($this->publicKey)) {
+ throw new UnprocessableSignatureException("PSS loadkey failure");
+ };
+ $rsa->setHash('sha512');
+ $rsa->setMGFHash('sha512');
+ $rsa->setSignatureMode(RSA::SIGNATURE_PSS);
+ try {
+ $verified = $rsa->verify($data, $signature);
+ } catch (\Exception $exception) {
+ $verified = false;
+ }
+ return $verified;
+ }
+
+ /* parse a structed dict */
+
+ private function parseStructuredDict(string $headerValue)
+ {
+ if (str_starts_with(trim($headerValue), '(')) {
+ return InnerList::fromHttpValue($headerValue);
+ }
+ else {
+ return Dictionary::fromHttpValue($headerValue);
+ }
+ }
+
+ /* Recommended to calculate the digest of the body and add it to
+ covered headers and sign, but not required. Convenience function
+ to calculate the digest.
+
+ ex:
+
+ $digest = $signer->createContentDigestHeader($body);
+ $request = $request->withHeader('Content-Digest', $digest);
+ */
+
+ /**
+ * @param string $body
+ * @param $algorithm ('sha256' || 'sha512')
+ * @return string
+ *
+ * From https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Digest
+ * The algorithm used to create a digest of the message content. Only two registered digest algorithms are
+ * considered secure: sha-512 and sha-256. The insecure (legacy) registered digest algorithms
+ * are: md5, sha (SHA-1), unixsum, unixcksum, adler (ADLER32) and crc32c.
+ */
+
+ public function createContentDigestHeader(string $body, $algorithm = 'sha256'): string
+ {
+ $supportedAlgorithms = ['sha256' => 'sha-256', 'sha512' => 'sha-512'];
+ foreach ($supportedAlgorithms as $alg => $value) {
+ if ($alg === $algorithm) {
+ $algorithmHeaderString = $value;
+ break;
+ }
+ }
+ if (!isset($algorithmHeaderString)) {
+ throw new UnProcessableSignatureException("Unsupported digest algorithm: $algorithm");
+ }
+ $digest = hash($algorithm, $body, true);
+ /**
+ * Output as structured field.
+ */
+ return $algorithmHeaderString . '=:' . base64_encode($digest) . ':';
+ }
+
+ /* Convenience function, probably want to use a robust PSR7 solution instead */
+
+ public static function parseHttpMessage(string $raw): array
+ {
+ [$headerPart, $body] = explode("\r\n\r\n", $raw, 2);
+ $lines = explode("\r\n", $headerPart);
+ $requestLine = array_shift($lines);
+ [$method, $path] = explode(' ', $requestLine);
+
+ $headers = [];
+ foreach ($lines as $line) {
+ [$name, $value] = explode(':', $line, 2);
+ $headers[strtolower(trim($name))] = trim($value);
+ }
+
+ return [
+ 'method' => $method,
+ 'path' => $path,
+ 'headers' => $headers,
+ 'body' => $body
+ ];
+ }
+
+ /**
+ * @return array
+ */
+ public function getStructuredFieldTypes(): array
+ {
+ return $this->structuredFieldTypes;
+ }
+
+ /**
+ * @param array $structuredFieldTypes
+ * @return HttpMessageSigner
+ */
+ public function setStructuredFieldTypes(array $structuredFieldTypes): HttpMessageSigner
+ {
+ $this->structuredFieldTypes = $structuredFieldTypes;
+ return $this;
+ }
+
+ /**
+ * $structuredFieldType consists of a key named after a specific header field, and a value
+ * which is one of 'list', 'innerlist', 'parameters, 'dictionary', 'item'.
+ *
+ * Example:
+ * ['example-dict' => 'dictionary']
+ *
+ * The 'sf' flag will not be honoured unless the structured type of the header is registered/known.
+ *
+ * @param array $structuredFieldType
+ * @return $this
+ */
+
+ public function addStructuredFieldType(array $structuredFieldType): HttpMessageSigner
+ {
+ foreach ($structuredFieldType as $key => $value) {
+ $this->structuredFieldTypes[$key] = $value;
+ }
+ return $this;
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getOriginalRequest()
+ {
+ return $this->originalRequest;
+ }
+
+ /**
+ * @param mixed $originalRequest
+ * @return HttpMessageSigner
+ */
+ public function setOriginalRequest($originalRequest)
+ {
+ $this->originalRequest = $originalRequest;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getKeyId(): string
+ {
+ return $this->keyId;
+ }
+
+ /**
+ * @param string $keyId
+ * @return HttpMessageSigner
+ */
+ public function setKeyId(string $keyId): HttpMessageSigner
+ {
+ $this->keyId = $keyId;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPrivateKey(): string
+ {
+ return $this->privateKey;
+ }
+
+ /**
+ * @param string $privateKey
+ * @return HttpMessageSigner
+ */
+ public function setPrivateKey(string $privateKey): HttpMessageSigner
+ {
+ $this->privateKey = $privateKey;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPublicKey(): string
+ {
+ return $this->publicKey;
+ }
+
+ /**
+ * @param string $publicKey
+ * @return HttpMessageSigner
+ */
+ public function setPublicKey(string $publicKey): HttpMessageSigner
+ {
+ $this->publicKey = $publicKey;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getAlgorithm(): string
+ {
+ return $this->algorithm;
+ }
+
+ /**
+ * @param string $algorithm
+ * @return HttpMessageSigner
+ */
+ public function setAlgorithm(string $algorithm): HttpMessageSigner
+ {
+ $this->algorithm = $algorithm;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getSignatureId(): string
+ {
+ return $this->signatureId;
+ }
+
+ /**
+ * @param string $signatureId
+ * @return HttpMessageSigner
+ */
+ public function setSignatureId(string $signatureId): HttpMessageSigner
+ {
+ $this->signatureId = $signatureId;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getCreated(): string
+ {
+ return $this->created;
+ }
+
+ /**
+ * @param string $created
+ * @return HttpMessageSigner
+ */
+ public function setCreated(string $created): HttpMessageSigner
+ {
+ $this->created = $created;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getExpires(): string
+ {
+ return $this->expires;
+ }
+
+ /**
+ * @param string $expires
+ * @return HttpMessageSigner
+ */
+ public function setExpires(string $expires): HttpMessageSigner
+ {
+ $this->expires = $expires;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getNonce(): string
+ {
+ return $this->nonce;
+ }
+
+ /**
+ * @param string $nonce
+ * @return HttpMessageSigner
+ */
+ public function setNonce(string $nonce): HttpMessageSigner
+ {
+ $this->nonce = $nonce;
+ return $this;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTag(): string
+ {
+ return $this->tag;
+ }
+
+ /**
+ * @param string $tag
+ * @return HttpMessageSigner
+ */
+ public function setTag(string $tag): HttpMessageSigner
+ {
+ $this->tag = $tag;
+ return $this;
+ }
+}
+
diff --git a/vendor/macgirvin/http-message-signer/src/StructuredFieldTypes.php b/vendor/macgirvin/http-message-signer/src/StructuredFieldTypes.php
new file mode 100644
index 000000000..df70717f8
--- /dev/null
+++ b/vendor/macgirvin/http-message-signer/src/StructuredFieldTypes.php
@@ -0,0 +1,91 @@
+<?php
+namespace HttpSignature;
+
+
+class StructuredFieldTypes
+{
+ public function __construct()
+ {
+ return $this;
+ }
+
+ public function getFields(): array
+ {
+ $returnValue = [];
+ $fields = explode("\n", $this->fieldlist);
+ foreach ($fields as $entry) {
+ $exploded = explode(" ", $entry);
+ $returnValue[$exploded[0]] = trim($exploded[1]);
+ }
+ return $returnValue;
+ }
+
+ public $fieldlist =
+'accept list
+accept-encoding list
+accept-language list
+accept-patch list
+accept-post list
+accept-ranges list
+access-control-allow-credentials item
+access-control-allow-headers list
+access-control-allow-methods list
+access-control-allow-origin item
+access-control-expose-headers list
+access-control-max-age item
+access-control-request-headers list
+access-control-request-method item
+age item
+allow list
+alpn list
+alt-svc dictionary
+alt-used item
+cache-control dictionary
+cdn-loop list
+clear-site-data list
+connection list
+content-encoding list
+content-language list
+content-length list
+content-location url
+content-type item
+cookie cookie
+cross-origin-resource-policy item
+date date
+dnt item
+etag etag
+expect dictionary
+expect-ct dictionary
+expires date
+host item
+if-match etag
+if-modified-since date
+if-none-match etag
+if-unmodified-since date
+keep-alive dictionary
+last-modified date
+location url
+max-forwards item
+origin item
+pragma dictionary
+prefer dictionary
+preference-applied dictionary
+referer url
+retry-after item
+sec-websocket-extensions list
+sec-websocket-protocol list
+sec-websocket-version item
+server-timing list
+set-cookie cookie
+surrogate-control dictionary
+te list
+timing-allow-origin list
+trailer list
+transfer-encoding list
+upgrade-insecure-requests item
+vary list
+x-content-type-options item
+x-frame-options item
+x-xss-protection list';
+
+} \ No newline at end of file
diff --git a/vendor/macgirvin/http-message-signer/src/UnProcessableSignatureException.php b/vendor/macgirvin/http-message-signer/src/UnProcessableSignatureException.php
new file mode 100644
index 000000000..b3dacd8eb
--- /dev/null
+++ b/vendor/macgirvin/http-message-signer/src/UnProcessableSignatureException.php
@@ -0,0 +1,8 @@
+<?php
+
+namespace HttpSignature;
+
+class UnProcessableSignatureException extends \Exception
+{
+
+} \ No newline at end of file
diff --git a/vendor/paragonie/random_compat/LICENSE b/vendor/paragonie/random_compat/LICENSE
new file mode 100644
index 000000000..45c7017df
--- /dev/null
+++ b/vendor/paragonie/random_compat/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Paragon Initiative Enterprises
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/vendor/paragonie/random_compat/build-phar.sh b/vendor/paragonie/random_compat/build-phar.sh
new file mode 100755
index 000000000..b4a5ba31c
--- /dev/null
+++ b/vendor/paragonie/random_compat/build-phar.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+basedir=$( dirname $( readlink -f ${BASH_SOURCE[0]} ) )
+
+php -dphar.readonly=0 "$basedir/other/build_phar.php" $* \ No newline at end of file
diff --git a/vendor/paragonie/random_compat/composer.json b/vendor/paragonie/random_compat/composer.json
new file mode 100644
index 000000000..f2b9c4e51
--- /dev/null
+++ b/vendor/paragonie/random_compat/composer.json
@@ -0,0 +1,34 @@
+{
+ "name": "paragonie/random_compat",
+ "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
+ "keywords": [
+ "csprng",
+ "random",
+ "polyfill",
+ "pseudorandom"
+ ],
+ "license": "MIT",
+ "type": "library",
+ "authors": [
+ {
+ "name": "Paragon Initiative Enterprises",
+ "email": "security@paragonie.com",
+ "homepage": "https://paragonie.com"
+ }
+ ],
+ "support": {
+ "issues": "https://github.com/paragonie/random_compat/issues",
+ "email": "info@paragonie.com",
+ "source": "https://github.com/paragonie/random_compat"
+ },
+ "require": {
+ "php": ">= 7"
+ },
+ "require-dev": {
+ "vimeo/psalm": "^1",
+ "phpunit/phpunit": "4.*|5.*"
+ },
+ "suggest": {
+ "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
+ }
+}
diff --git a/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey b/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey
new file mode 100644
index 000000000..eb50ebfcd
--- /dev/null
+++ b/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey
@@ -0,0 +1,5 @@
+-----BEGIN PUBLIC KEY-----
+MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEEd+wCqJDrx5B4OldM0dQE0ZMX+lx1ZWm
+pui0SUqD4G29L3NGsz9UhJ/0HjBdbnkhIK5xviT0X5vtjacF6ajgcCArbTB+ds+p
++h7Q084NuSuIpNb6YPfoUFgC/CL9kAoc
+-----END PUBLIC KEY-----
diff --git a/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc b/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc
new file mode 100644
index 000000000..6a1d7f300
--- /dev/null
+++ b/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc
@@ -0,0 +1,11 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v2.0.22 (MingW32)
+
+iQEcBAABAgAGBQJWtW1hAAoJEGuXocKCZATaJf0H+wbZGgskK1dcRTsuVJl9IWip
+QwGw/qIKI280SD6/ckoUMxKDCJiFuPR14zmqnS36k7N5UNPnpdTJTS8T11jttSpg
+1LCmgpbEIpgaTah+cELDqFCav99fS+bEiAL5lWDAHBTE/XPjGVCqeehyPYref4IW
+NDBIEsvnHPHPLsn6X5jq4+Yj5oUixgxaMPiR+bcO4Sh+RzOVB6i2D0upWfRXBFXA
+NNnsg9/zjvoC7ZW73y9uSH+dPJTt/Vgfeiv52/v41XliyzbUyLalf02GNPY+9goV
+JHG1ulEEBJOCiUD9cE1PUIJwHA/HqyhHIvV350YoEFiHl8iSwm7SiZu5kPjaq74=
+=B6+8
+-----END PGP SIGNATURE-----
diff --git a/vendor/paragonie/random_compat/lib/random.php b/vendor/paragonie/random_compat/lib/random.php
new file mode 100644
index 000000000..c7731a56f
--- /dev/null
+++ b/vendor/paragonie/random_compat/lib/random.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * Random_* Compatibility Library
+ * for using the new PHP 7 random_* API in PHP 5 projects
+ *
+ * @version 2.99.99
+ * @released 2018-06-06
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 - 2018 Paragon Initiative Enterprises
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+// NOP
diff --git a/vendor/paragonie/random_compat/other/build_phar.php b/vendor/paragonie/random_compat/other/build_phar.php
new file mode 100644
index 000000000..70ef4b2ed
--- /dev/null
+++ b/vendor/paragonie/random_compat/other/build_phar.php
@@ -0,0 +1,57 @@
+<?php
+$dist = dirname(__DIR__).'/dist';
+if (!is_dir($dist)) {
+ mkdir($dist, 0755);
+}
+if (file_exists($dist.'/random_compat.phar')) {
+ unlink($dist.'/random_compat.phar');
+}
+$phar = new Phar(
+ $dist.'/random_compat.phar',
+ FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::KEY_AS_FILENAME,
+ 'random_compat.phar'
+);
+rename(
+ dirname(__DIR__).'/lib/random.php',
+ dirname(__DIR__).'/lib/index.php'
+);
+$phar->buildFromDirectory(dirname(__DIR__).'/lib');
+rename(
+ dirname(__DIR__).'/lib/index.php',
+ dirname(__DIR__).'/lib/random.php'
+);
+
+/**
+ * If we pass an (optional) path to a private key as a second argument, we will
+ * sign the Phar with OpenSSL.
+ *
+ * If you leave this out, it will produce an unsigned .phar!
+ */
+if ($argc > 1) {
+ if (!@is_readable($argv[1])) {
+ echo 'Could not read the private key file:', $argv[1], "\n";
+ exit(255);
+ }
+ $pkeyFile = file_get_contents($argv[1]);
+
+ $private = openssl_get_privatekey($pkeyFile);
+ if ($private !== false) {
+ $pkey = '';
+ openssl_pkey_export($private, $pkey);
+ $phar->setSignatureAlgorithm(Phar::OPENSSL, $pkey);
+
+ /**
+ * Save the corresponding public key to the file
+ */
+ if (!@is_readable($dist.'/random_compat.phar.pubkey')) {
+ $details = openssl_pkey_get_details($private);
+ file_put_contents(
+ $dist.'/random_compat.phar.pubkey',
+ $details['key']
+ );
+ }
+ } else {
+ echo 'An error occurred reading the private key from OpenSSL.', "\n";
+ exit(255);
+ }
+}
diff --git a/vendor/paragonie/random_compat/psalm-autoload.php b/vendor/paragonie/random_compat/psalm-autoload.php
new file mode 100644
index 000000000..d71d1b818
--- /dev/null
+++ b/vendor/paragonie/random_compat/psalm-autoload.php
@@ -0,0 +1,9 @@
+<?php
+
+require_once 'lib/byte_safe_strings.php';
+require_once 'lib/cast_to_int.php';
+require_once 'lib/error_polyfill.php';
+require_once 'other/ide_stubs/libsodium.php';
+require_once 'lib/random.php';
+
+$int = random_int(0, 65536);
diff --git a/vendor/paragonie/random_compat/psalm.xml b/vendor/paragonie/random_compat/psalm.xml
new file mode 100644
index 000000000..596d99dd6
--- /dev/null
+++ b/vendor/paragonie/random_compat/psalm.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+<psalm
+ autoloader="psalm-autoload.php"
+ stopOnFirstError="false"
+ useDocblockTypes="true"
+>
+ <projectFiles>
+ <directory name="lib" />
+ </projectFiles>
+ <issueHandlers>
+ <RedundantConditionGivenDocblockType errorLevel="info" />
+ <UnresolvableInclude errorLevel="info" />
+ <DuplicateClass errorLevel="info" />
+ <InvalidOperand errorLevel="info" />
+ <UndefinedConstant errorLevel="info" />
+ <MissingReturnType errorLevel="info" />
+ <InvalidReturnType errorLevel="info" />
+ </issueHandlers>
+</psalm>
diff --git a/vendor/phpseclib/phpseclib/README.md b/vendor/phpseclib/phpseclib/README.md
index 1bdee151d..37cbcb9d5 100644
--- a/vendor/phpseclib/phpseclib/README.md
+++ b/vendor/phpseclib/phpseclib/README.md
@@ -1,6 +1,6 @@
# phpseclib - PHP Secure Communications Library
-[![Build Status](https://travis-ci.com/phpseclib/phpseclib.svg?branch=2.0)](https://travis-ci.com/github/phpseclib/phpseclib)
+[![CI Status](https://github.com/phpseclib/phpseclib/actions/workflows/ci.yml/badge.svg?branch=3.0&event=push "CI Status")](https://github.com/phpseclib/phpseclib)
## Supporting phpseclib
@@ -19,7 +19,7 @@ SSH-2, SFTP, X.509, an arbitrary-precision integer arithmetic library, Ed25519 /
## Documentation
* [Documentation / Manual](https://phpseclib.com/)
-* [API Documentation](https://api.phpseclib.com/2.0/) (generated by Doctum)
+* [API Documentation](https://api.phpseclib.com/3.0/) (generated by Doctum)
## Branches
@@ -79,22 +79,19 @@ Special Thanks to our $50+ sponsors!:
2. Ensure you have Composer installed (see [Composer Download Instructions](https://getcomposer.org/download/))
3. Install Development Dependencies
-
- ``` sh
+ ```sh
composer install
```
4. Create a Feature Branch
-5. (Recommended) Run the Test Suite
-
- ``` sh
- vendor/bin/phpunit
- ```
-6. (Recommended) Check whether your code conforms to our Coding Standards by running
-
- ``` sh
- vendor/bin/phing -f build/build.xml sniff
- ```
-
-7. Send us a Pull Request
+5. Run continuous integration checks:
+ ```sh
+ composer global require php:^8.1 squizlabs/php_codesniffer friendsofphp/php-cs-fixer vimeo/psalm
+ phpcs --standard=build/php_codesniffer.xml
+ php-cs-fixer fix --config=build/php-cs-fixer.php --diff --dry-run --using-cache=no
+ psalm --config=build/psalm.xml --no-cache --long-progress --report-show-info=false --output-format=text
+ vendor/bin/phpunit --verbose --configuration tests/phpunit.xml
+ ```
+
+6. Send us a Pull Request
diff --git a/vendor/phpseclib/phpseclib/appveyor.yml b/vendor/phpseclib/phpseclib/appveyor.yml
deleted file mode 100644
index 210a90347..000000000
--- a/vendor/phpseclib/phpseclib/appveyor.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-build: false
-shallow_clone: false
-platform:
- - x86
- - x64
-clone_folder: C:\projects\phpseclib
-
-install:
- - cinst -y OpenSSL.Light
- - SET PATH=C:\Program Files\OpenSSL;%PATH%
- - sc config wuauserv start= auto
- - net start wuauserv
- - cinst -y php --version 5.6.30
- - cd c:\tools\php56
- - copy php.ini-production php.ini
- - echo date.timezone="UTC" >> php.ini
- - echo extension_dir=ext >> php.ini
- - echo extension=php_openssl.dll >> php.ini
- - echo extension=php_gmp.dll >> php.ini
- - cd C:\projects\phpseclib
- - SET PATH=C:\tools\php56;%PATH%
- - php.exe -r "readfile('http://getcomposer.org/installer');" | php.exe
- - php.exe composer.phar install --prefer-source --no-interaction
-
-test_script:
- - cd C:\projects\phpseclib
- - vendor\bin\phpunit.bat tests/Windows32Test.php \ No newline at end of file
diff --git a/vendor/phpseclib/phpseclib/composer.json b/vendor/phpseclib/phpseclib/composer.json
index 3fbffa67c..d996700e2 100644
--- a/vendor/phpseclib/phpseclib/composer.json
+++ b/vendor/phpseclib/phpseclib/composer.json
@@ -51,26 +51,34 @@
}
],
"require": {
- "php": ">=5.3.3"
+ "php": ">=5.6.1",
+ "paragonie/constant_time_encoding": "^1|^2|^3",
+ "paragonie/random_compat": "^1.4|^2.0|^9.99.99"
},
"require-dev": {
- "phing/phing": "~2.7",
- "phpunit/phpunit": "^4.8.35|^5.7|^6.0|^9.4",
- "squizlabs/php_codesniffer": "~2.0"
+ "phpunit/phpunit": "*"
},
"suggest": {
"ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
"ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations.",
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
"ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
- "ext-xml": "Install the XML extension to load XML formatted public keys."
+ "ext-dom": "Install the DOM extension to load XML formatted public keys."
},
"autoload": {
"files": [
"phpseclib/bootstrap.php"
],
"psr-4": {
- "phpseclib\\": "phpseclib/"
+ "phpseclib3\\": "phpseclib/"
}
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "phpseclib3\\Tests\\": "tests/"
+ }
+ },
+ "config": {
+ "sort-packages": true
}
}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Common/Functions/Strings.php b/vendor/phpseclib/phpseclib/phpseclib/Common/Functions/Strings.php
new file mode 100644
index 000000000..ad8f63b65
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Common/Functions/Strings.php
@@ -0,0 +1,507 @@
+<?php
+
+/**
+ * Common String Functions
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Common\Functions;
+
+use ParagonIE\ConstantTime\Base64;
+use ParagonIE\ConstantTime\Base64UrlSafe;
+use ParagonIE\ConstantTime\Hex;
+use phpseclib3\Math\BigInteger;
+use phpseclib3\Math\Common\FiniteField;
+
+/**
+ * Common String Functions
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Strings
+{
+ /**
+ * String Shift
+ *
+ * Inspired by array_shift
+ *
+ * @param string $string
+ * @param int $index
+ * @return string
+ */
+ public static function shift(&$string, $index = 1)
+ {
+ $substr = substr($string, 0, $index);
+ $string = substr($string, $index);
+ return $substr;
+ }
+
+ /**
+ * String Pop
+ *
+ * Inspired by array_pop
+ *
+ * @param string $string
+ * @param int $index
+ * @return string
+ */
+ public static function pop(&$string, $index = 1)
+ {
+ $substr = substr($string, -$index);
+ $string = substr($string, 0, -$index);
+ return $substr;
+ }
+
+ /**
+ * Parse SSH2-style string
+ *
+ * Returns either an array or a boolean if $data is malformed.
+ *
+ * Valid characters for $format are as follows:
+ *
+ * C = byte
+ * b = boolean (true/false)
+ * N = uint32
+ * Q = uint64
+ * s = string
+ * i = mpint
+ * L = name-list
+ *
+ * uint64 is not supported.
+ *
+ * @param string $format
+ * @param string $data
+ * @return mixed
+ */
+ public static function unpackSSH2($format, &$data)
+ {
+ $format = self::formatPack($format);
+ $result = [];
+ for ($i = 0; $i < strlen($format); $i++) {
+ switch ($format[$i]) {
+ case 'C':
+ case 'b':
+ if (!strlen($data)) {
+ throw new \LengthException('At least one byte needs to be present for successful C / b decodes');
+ }
+ break;
+ case 'N':
+ case 'i':
+ case 's':
+ case 'L':
+ if (strlen($data) < 4) {
+ throw new \LengthException('At least four byte needs to be present for successful N / i / s / L decodes');
+ }
+ break;
+ case 'Q':
+ if (strlen($data) < 8) {
+ throw new \LengthException('At least eight byte needs to be present for successful N / i / s / L decodes');
+ }
+ break;
+
+ default:
+ throw new \InvalidArgumentException('$format contains an invalid character');
+ }
+ switch ($format[$i]) {
+ case 'C':
+ $result[] = ord(self::shift($data));
+ continue 2;
+ case 'b':
+ $result[] = ord(self::shift($data)) != 0;
+ continue 2;
+ case 'N':
+ list(, $temp) = unpack('N', self::shift($data, 4));
+ $result[] = $temp;
+ continue 2;
+ case 'Q':
+ // pack() added support for Q in PHP 5.6.3 and PHP 5.6 is phpseclib 3's minimum version
+ // so in theory we could support this BUT, "64-bit format codes are not available for
+ // 32-bit versions" and phpseclib works on 32-bit installs. on 32-bit installs
+ // 64-bit floats can be used to get larger numbers then 32-bit signed ints would allow
+ // for. sure, you're not gonna get the full precision of 64-bit numbers but just because
+ // you need > 32-bit precision doesn't mean you need the full 64-bit precision
+ $unpacked = unpack('Nupper/Nlower', self::shift($data, 8));
+ $upper = $unpacked['upper'];
+ $lower = $unpacked['lower'];
+ $temp = $upper ? 4294967296 * $upper : 0;
+ $temp += $lower < 0 ? ($lower & 0x7FFFFFFFF) + 0x80000000 : $lower;
+ // $temp = hexdec(bin2hex(self::shift($data, 8)));
+ $result[] = $temp;
+ continue 2;
+ }
+ list(, $length) = unpack('N', self::shift($data, 4));
+ if (strlen($data) < $length) {
+ throw new \LengthException("$length bytes needed; " . strlen($data) . ' bytes available');
+ }
+ $temp = self::shift($data, $length);
+ switch ($format[$i]) {
+ case 'i':
+ $result[] = new BigInteger($temp, -256);
+ break;
+ case 's':
+ $result[] = $temp;
+ break;
+ case 'L':
+ $result[] = explode(',', $temp);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Create SSH2-style string
+ *
+ * @param string $format
+ * @param string|int|float|array|bool ...$elements
+ * @return string
+ */
+ public static function packSSH2($format, ...$elements)
+ {
+ $format = self::formatPack($format);
+ if (strlen($format) != count($elements)) {
+ throw new \InvalidArgumentException('There must be as many arguments as there are characters in the $format string');
+ }
+ $result = '';
+ for ($i = 0; $i < strlen($format); $i++) {
+ $element = $elements[$i];
+ switch ($format[$i]) {
+ case 'C':
+ if (!is_int($element)) {
+ throw new \InvalidArgumentException('Bytes must be represented as an integer between 0 and 255, inclusive.');
+ }
+ $result .= pack('C', $element);
+ break;
+ case 'b':
+ if (!is_bool($element)) {
+ throw new \InvalidArgumentException('A boolean parameter was expected.');
+ }
+ $result .= $element ? "\1" : "\0";
+ break;
+ case 'Q':
+ if (!is_int($element) && !is_float($element)) {
+ throw new \InvalidArgumentException('An integer was expected.');
+ }
+ // 4294967296 == 1 << 32
+ $result .= pack('NN', $element / 4294967296, $element);
+ break;
+ case 'N':
+ if (is_float($element)) {
+ $element = (int) $element;
+ }
+ if (!is_int($element)) {
+ throw new \InvalidArgumentException('An integer was expected.');
+ }
+ $result .= pack('N', $element);
+ break;
+ case 's':
+ if (!self::is_stringable($element)) {
+ throw new \InvalidArgumentException('A string was expected.');
+ }
+ $result .= pack('Na*', strlen($element), $element);
+ break;
+ case 'i':
+ if (!$element instanceof BigInteger && !$element instanceof FiniteField\Integer) {
+ throw new \InvalidArgumentException('A phpseclib3\Math\BigInteger or phpseclib3\Math\Common\FiniteField\Integer object was expected.');
+ }
+ $element = $element->toBytes(true);
+ $result .= pack('Na*', strlen($element), $element);
+ break;
+ case 'L':
+ if (!is_array($element)) {
+ throw new \InvalidArgumentException('An array was expected.');
+ }
+ $element = implode(',', $element);
+ $result .= pack('Na*', strlen($element), $element);
+ break;
+ default:
+ throw new \InvalidArgumentException('$format contains an invalid character');
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Expand a pack string
+ *
+ * Converts C5 to CCCCC, for example.
+ *
+ * @param string $format
+ * @return string
+ */
+ private static function formatPack($format)
+ {
+ $parts = preg_split('#(\d+)#', $format, -1, PREG_SPLIT_DELIM_CAPTURE);
+ $format = '';
+ for ($i = 1; $i < count($parts); $i += 2) {
+ $format .= substr($parts[$i - 1], 0, -1) . str_repeat(substr($parts[$i - 1], -1), $parts[$i]);
+ }
+ $format .= $parts[$i - 1];
+
+ return $format;
+ }
+
+ /**
+ * Convert binary data into bits
+ *
+ * bin2hex / hex2bin refer to base-256 encoded data as binary, whilst
+ * decbin / bindec refer to base-2 encoded data as binary. For the purposes
+ * of this function, bin refers to base-256 encoded data whilst bits refers
+ * to base-2 encoded data
+ *
+ * @param string $x
+ * @return string
+ */
+ public static function bits2bin($x)
+ {
+ /*
+ // the pure-PHP approach is faster than the GMP approach
+ if (function_exists('gmp_export')) {
+ return strlen($x) ? gmp_export(gmp_init($x, 2)) : gmp_init(0);
+ }
+ */
+
+ if (preg_match('#[^01]#', $x)) {
+ throw new \RuntimeException('The only valid characters are 0 and 1');
+ }
+
+ if (!defined('PHP_INT_MIN')) {
+ define('PHP_INT_MIN', ~PHP_INT_MAX);
+ }
+
+ $length = strlen($x);
+ if (!$length) {
+ return '';
+ }
+ $block_size = PHP_INT_SIZE << 3;
+ $pad = $block_size - ($length % $block_size);
+ if ($pad != $block_size) {
+ $x = str_repeat('0', $pad) . $x;
+ }
+
+ $parts = str_split($x, $block_size);
+ $str = '';
+ foreach ($parts as $part) {
+ $xor = $part[0] == '1' ? PHP_INT_MIN : 0;
+ $part[0] = '0';
+ $str .= pack(
+ PHP_INT_SIZE == 4 ? 'N' : 'J',
+ $xor ^ eval('return 0b' . $part . ';')
+ );
+ }
+ return ltrim($str, "\0");
+ }
+
+ /**
+ * Convert bits to binary data
+ *
+ * @param string $x
+ * @return string
+ */
+ public static function bin2bits($x, $trim = true)
+ {
+ /*
+ // the pure-PHP approach is slower than the GMP approach BUT
+ // i want to the pure-PHP version to be easily unit tested as well
+ if (function_exists('gmp_import')) {
+ return gmp_strval(gmp_import($x), 2);
+ }
+ */
+
+ $len = strlen($x);
+ $mod = $len % PHP_INT_SIZE;
+ if ($mod) {
+ $x = str_pad($x, $len + PHP_INT_SIZE - $mod, "\0", STR_PAD_LEFT);
+ }
+
+ $bits = '';
+ if (PHP_INT_SIZE == 4) {
+ $digits = unpack('N*', $x);
+ foreach ($digits as $digit) {
+ $bits .= sprintf('%032b', $digit);
+ }
+ } else {
+ $digits = unpack('J*', $x);
+ foreach ($digits as $digit) {
+ $bits .= sprintf('%064b', $digit);
+ }
+ }
+
+ return $trim ? ltrim($bits, '0') : $bits;
+ }
+
+ /**
+ * Switch Endianness Bit Order
+ *
+ * @param string $x
+ * @return string
+ */
+ public static function switchEndianness($x)
+ {
+ $r = '';
+ for ($i = strlen($x) - 1; $i >= 0; $i--) {
+ $b = ord($x[$i]);
+ if (PHP_INT_SIZE === 8) {
+ // 3 operations
+ // from http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64BitsDiv
+ $r .= chr((($b * 0x0202020202) & 0x010884422010) % 1023);
+ } else {
+ // 7 operations
+ // from http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith32Bits
+ $p1 = ($b * 0x0802) & 0x22110;
+ $p2 = ($b * 0x8020) & 0x88440;
+ $r .= chr(
+ (($p1 | $p2) * 0x10101) >> 16
+ );
+ }
+ }
+ return $r;
+ }
+
+ /**
+ * Increment the current string
+ *
+ * @param string $var
+ * @return string
+ */
+ public static function increment_str(&$var)
+ {
+ if (function_exists('sodium_increment')) {
+ $var = strrev($var);
+ sodium_increment($var);
+ $var = strrev($var);
+ return $var;
+ }
+
+ for ($i = 4; $i <= strlen($var); $i += 4) {
+ $temp = substr($var, -$i, 4);
+ switch ($temp) {
+ case "\xFF\xFF\xFF\xFF":
+ $var = substr_replace($var, "\x00\x00\x00\x00", -$i, 4);
+ break;
+ case "\x7F\xFF\xFF\xFF":
+ $var = substr_replace($var, "\x80\x00\x00\x00", -$i, 4);
+ return $var;
+ default:
+ $temp = unpack('Nnum', $temp);
+ $var = substr_replace($var, pack('N', $temp['num'] + 1), -$i, 4);
+ return $var;
+ }
+ }
+
+ $remainder = strlen($var) % 4;
+
+ if ($remainder == 0) {
+ return $var;
+ }
+
+ $temp = unpack('Nnum', str_pad(substr($var, 0, $remainder), 4, "\0", STR_PAD_LEFT));
+ $temp = substr(pack('N', $temp['num'] + 1), -$remainder);
+ $var = substr_replace($var, $temp, 0, $remainder);
+
+ return $var;
+ }
+
+ /**
+ * Find whether the type of a variable is string (or could be converted to one)
+ *
+ * @param mixed $var
+ * @return bool
+ * @psalm-assert-if-true string|\Stringable $var
+ */
+ public static function is_stringable($var)
+ {
+ return is_string($var) || (is_object($var) && method_exists($var, '__toString'));
+ }
+
+ /**
+ * Constant Time Base64-decoding
+ *
+ * ParagoneIE\ConstantTime doesn't use libsodium if it's available so we'll do so
+ * ourselves. see https://github.com/paragonie/constant_time_encoding/issues/39
+ *
+ * @param string $data
+ * @return string
+ */
+ public static function base64_decode($data)
+ {
+ return function_exists('sodium_base642bin') ?
+ sodium_base642bin($data, SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING, '=') :
+ Base64::decode($data);
+ }
+
+ /**
+ * Constant Time Base64-decoding (URL safe)
+ *
+ * @param string $data
+ * @return string
+ */
+ public static function base64url_decode($data)
+ {
+ // return self::base64_decode(str_replace(['-', '_'], ['+', '/'], $data));
+
+ return function_exists('sodium_base642bin') ?
+ sodium_base642bin($data, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING, '=') :
+ Base64UrlSafe::decode($data);
+ }
+
+ /**
+ * Constant Time Base64-encoding
+ *
+ * @param string $data
+ * @return string
+ */
+ public static function base64_encode($data)
+ {
+ return function_exists('sodium_bin2base64') ?
+ sodium_bin2base64($data, SODIUM_BASE64_VARIANT_ORIGINAL) :
+ Base64::encode($data);
+ }
+
+ /**
+ * Constant Time Base64-encoding (URL safe)
+ *
+ * @param string $data
+ * @return string
+ */
+ public static function base64url_encode($data)
+ {
+ // return str_replace(['+', '/'], ['-', '_'], self::base64_encode($data));
+
+ return function_exists('sodium_bin2base64') ?
+ sodium_bin2base64($data, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING) :
+ Base64UrlSafe::encode($data);
+ }
+
+ /**
+ * Constant Time Hex Decoder
+ *
+ * @param string $data
+ * @return string
+ */
+ public static function hex2bin($data)
+ {
+ return function_exists('sodium_hex2bin') ?
+ sodium_hex2bin($data) :
+ Hex::decode($data);
+ }
+
+ /**
+ * Constant Time Hex Encoder
+ *
+ * @param string $data
+ * @return string
+ */
+ public static function bin2hex($data)
+ {
+ return function_exists('sodium_bin2hex') ?
+ sodium_bin2hex($data) :
+ Hex::encode($data);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/AES.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/AES.php
index 9903db105..403871627 100644
--- a/vendor/phpseclib/phpseclib/phpseclib/Crypt/AES.php
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/AES.php
@@ -16,7 +16,7 @@
* it'll be null-padded to 192-bits and 192 bits will be the key length until {@link self::setKey() setKey()}
* is called, again, at which point, it'll be recalculated.
*
- * Since \phpseclib\Crypt\AES extends \phpseclib\Crypt\Rijndael, some functions are available to be called that, in the context of AES, don't
+ * Since \phpseclib3\Crypt\AES extends \phpseclib3\Crypt\Rijndael, some functions are available to be called that, in the context of AES, don't
* make a whole lot of sense. {@link self::setBlockLength() setBlockLength()}, for instance. Calling that function,
* however possible, won't do anything (AES has a fixed block length whereas Rijndael has a variable one).
*
@@ -25,7 +25,7 @@
* <?php
* include 'vendor/autoload.php';
*
- * $aes = new \phpseclib\Crypt\AES();
+ * $aes = new \phpseclib3\Crypt\AES('ctr');
*
* $aes->setKey('abcdefghijklmnop');
*
@@ -39,58 +39,78 @@
* ?>
* </code>
*
- * @category Crypt
- * @package AES
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2008 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
-namespace phpseclib\Crypt;
+namespace phpseclib3\Crypt;
/**
* Pure-PHP implementation of AES.
*
- * @package AES
* @author Jim Wigginton <terrafrost@php.net>
- * @access public
*/
class AES extends Rijndael
{
/**
* Dummy function
*
- * Since \phpseclib\Crypt\AES extends \phpseclib\Crypt\Rijndael, this function is, technically, available, but it doesn't do anything.
+ * Since \phpseclib3\Crypt\AES extends \phpseclib3\Crypt\Rijndael, this function is, technically, available, but it doesn't do anything.
*
- * @see \phpseclib\Crypt\Rijndael::setBlockLength()
- * @access public
+ * @see \phpseclib3\Crypt\Rijndael::setBlockLength()
* @param int $length
+ * @throws \BadMethodCallException anytime it's called
*/
- function setBlockLength($length)
+ public function setBlockLength($length)
{
- return;
+ throw new \BadMethodCallException('The block length cannot be set for AES.');
}
/**
* Sets the key length
*
- * Valid key lengths are 128, 192, and 256. If the length is less than 128, it will be rounded up to
- * 128. If the length is greater than 128 and invalid, it will be rounded down to the closest valid amount.
+ * Valid key lengths are 128, 192, and 256. Set the link to bool(false) to disable a fixed key length
*
- * @see \phpseclib\Crypt\Rijndael:setKeyLength()
- * @access public
+ * @see \phpseclib3\Crypt\Rijndael:setKeyLength()
* @param int $length
+ * @throws \LengthException if the key length isn't supported
*/
- function setKeyLength($length)
+ public function setKeyLength($length)
{
+ switch ($length) {
+ case 128:
+ case 192:
+ case 256:
+ break;
+ default:
+ throw new \LengthException('Key of size ' . $length . ' not supported by this algorithm. Only keys of sizes 128, 192 or 256 supported');
+ }
parent::setKeyLength($length);
- switch ($this->key_length) {
- case 20:
- $this->key_length = 24;
+ }
+
+ /**
+ * Sets the key.
+ *
+ * Rijndael supports five different key lengths, AES only supports three.
+ *
+ * @see \phpseclib3\Crypt\Rijndael:setKey()
+ * @see setKeyLength()
+ * @param string $key
+ * @throws \LengthException if the key length isn't supported
+ */
+ public function setKey($key)
+ {
+ switch (strlen($key)) {
+ case 16:
+ case 24:
+ case 32:
break;
- case 28:
- $this->key_length = 32;
+ default:
+ throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes 16, 24 or 32 supported');
}
+
+ parent::setKey($key);
}
}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Base.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Base.php
deleted file mode 100644
index 2d4225a3f..000000000
--- a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Base.php
+++ /dev/null
@@ -1,2908 +0,0 @@
-<?php
-
-/**
- * Base Class for all \phpseclib\Crypt\* cipher classes
- *
- * PHP version 5
- *
- * Internally for phpseclib developers:
- * If you plan to add a new cipher class, please note following rules:
- *
- * - The new \phpseclib\Crypt\* cipher class should extend \phpseclib\Crypt\Base
- *
- * - Following methods are then required to be overridden/overloaded:
- *
- * - _encryptBlock()
- *
- * - _decryptBlock()
- *
- * - _setupKey()
- *
- * - All other methods are optional to be overridden/overloaded
- *
- * - Look at the source code of the current ciphers how they extend \phpseclib\Crypt\Base
- * and take one of them as a start up for the new cipher class.
- *
- * - Please read all the other comments/notes/hints here also for each class var/method
- *
- * @category Crypt
- * @package Base
- * @author Jim Wigginton <terrafrost@php.net>
- * @author Hans-Juergen Petrich <petrich@tronic-media.com>
- * @copyright 2007 Jim Wigginton
- * @license http://www.opensource.org/licenses/mit-license.html MIT License
- * @link http://phpseclib.sourceforge.net
- */
-
-namespace phpseclib\Crypt;
-
-/**
- * Base Class for all \phpseclib\Crypt\* cipher classes
- *
- * @package Base
- * @author Jim Wigginton <terrafrost@php.net>
- * @author Hans-Juergen Petrich <petrich@tronic-media.com>
- */
-abstract class Base
-{
- /**#@+
- * @access public
- * @see \phpseclib\Crypt\Base::encrypt()
- * @see \phpseclib\Crypt\Base::decrypt()
- */
- /**
- * Encrypt / decrypt using the Counter mode.
- *
- * Set to -1 since that's what Crypt/Random.php uses to index the CTR mode.
- *
- * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Counter_.28CTR.29
- */
- const MODE_CTR = -1;
- /**
- * Encrypt / decrypt using the Electronic Code Book mode.
- *
- * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29
- */
- const MODE_ECB = 1;
- /**
- * Encrypt / decrypt using the Code Book Chaining mode.
- *
- * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29
- */
- const MODE_CBC = 2;
- /**
- * Encrypt / decrypt using the Cipher Feedback mode.
- *
- * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29
- */
- const MODE_CFB = 3;
- /**
- * Encrypt / decrypt using the Cipher Feedback mode (8bit)
- */
- const MODE_CFB8 = 6;
- /**
- * Encrypt / decrypt using the Output Feedback mode (8bit)
- */
- const MODE_OFB8 = 7;
- /**
- * Encrypt / decrypt using the Output Feedback mode.
- *
- * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Output_feedback_.28OFB.29
- */
- const MODE_OFB = 4;
- /**
- * Encrypt / decrypt using streaming mode.
- */
- const MODE_STREAM = 5;
- /**#@-*/
-
- /**
- * Whirlpool available flag
- *
- * @see \phpseclib\Crypt\Base::_hashInlineCryptFunction()
- * @var bool
- * @access private
- */
- static $WHIRLPOOL_AVAILABLE;
-
- /**#@+
- * @access private
- * @see \phpseclib\Crypt\Base::__construct()
- */
- /**
- * Base value for the internal implementation $engine switch
- */
- const ENGINE_INTERNAL = 1;
- /**
- * Base value for the mcrypt implementation $engine switch
- */
- const ENGINE_MCRYPT = 2;
- /**
- * Base value for the mcrypt implementation $engine switch
- */
- const ENGINE_OPENSSL = 3;
- /**#@-*/
-
- /**
- * The Encryption Mode
- *
- * @see self::__construct()
- * @var int
- * @access private
- */
- var $mode;
-
- /**
- * The Block Length of the block cipher
- *
- * @var int
- * @access private
- */
- var $block_size = 16;
-
- /**
- * The Key
- *
- * @see self::setKey()
- * @var string
- * @access private
- */
- var $key = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
-
- /**
- * The Initialization Vector
- *
- * @see self::setIV()
- * @var string
- * @access private
- */
- var $iv = '';
-
- /**
- * A "sliding" Initialization Vector
- *
- * @see self::enableContinuousBuffer()
- * @see self::_clearBuffers()
- * @var string
- * @access private
- */
- var $encryptIV;
-
- /**
- * A "sliding" Initialization Vector
- *
- * @see self::enableContinuousBuffer()
- * @see self::_clearBuffers()
- * @var string
- * @access private
- */
- var $decryptIV;
-
- /**
- * Continuous Buffer status
- *
- * @see self::enableContinuousBuffer()
- * @var bool
- * @access private
- */
- var $continuousBuffer = false;
-
- /**
- * Encryption buffer for CTR, OFB and CFB modes
- *
- * @see self::encrypt()
- * @see self::_clearBuffers()
- * @var array
- * @access private
- */
- var $enbuffer;
-
- /**
- * Decryption buffer for CTR, OFB and CFB modes
- *
- * @see self::decrypt()
- * @see self::_clearBuffers()
- * @var array
- * @access private
- */
- var $debuffer;
-
- /**
- * mcrypt resource for encryption
- *
- * The mcrypt resource can be recreated every time something needs to be created or it can be created just once.
- * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode.
- *
- * @see self::encrypt()
- * @var resource
- * @access private
- */
- var $enmcrypt;
-
- /**
- * mcrypt resource for decryption
- *
- * The mcrypt resource can be recreated every time something needs to be created or it can be created just once.
- * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode.
- *
- * @see self::decrypt()
- * @var resource
- * @access private
- */
- var $demcrypt;
-
- /**
- * Does the enmcrypt resource need to be (re)initialized?
- *
- * @see \phpseclib\Crypt\Twofish::setKey()
- * @see \phpseclib\Crypt\Twofish::setIV()
- * @var bool
- * @access private
- */
- var $enchanged = true;
-
- /**
- * Does the demcrypt resource need to be (re)initialized?
- *
- * @see \phpseclib\Crypt\Twofish::setKey()
- * @see \phpseclib\Crypt\Twofish::setIV()
- * @var bool
- * @access private
- */
- var $dechanged = true;
-
- /**
- * mcrypt resource for CFB mode
- *
- * mcrypt's CFB mode, in (and only in) buffered context,
- * is broken, so phpseclib implements the CFB mode by it self,
- * even when the mcrypt php extension is available.
- *
- * In order to do the CFB-mode work (fast) phpseclib
- * use a separate ECB-mode mcrypt resource.
- *
- * @link http://phpseclib.sourceforge.net/cfb-demo.phps
- * @see self::encrypt()
- * @see self::decrypt()
- * @see self::_setupMcrypt()
- * @var resource
- * @access private
- */
- var $ecb;
-
- /**
- * Optimizing value while CFB-encrypting
- *
- * Only relevant if $continuousBuffer enabled
- * and $engine == self::ENGINE_MCRYPT
- *
- * It's faster to re-init $enmcrypt if
- * $buffer bytes > $cfb_init_len than
- * using the $ecb resource furthermore.
- *
- * This value depends of the chosen cipher
- * and the time it would be needed for it's
- * initialization [by mcrypt_generic_init()]
- * which, typically, depends on the complexity
- * on its internaly Key-expanding algorithm.
- *
- * @see self::encrypt()
- * @var int
- * @access private
- */
- var $cfb_init_len = 600;
-
- /**
- * Does internal cipher state need to be (re)initialized?
- *
- * @see self::setKey()
- * @see self::setIV()
- * @see self::disableContinuousBuffer()
- * @var bool
- * @access private
- */
- var $changed = true;
-
- /**
- * Padding status
- *
- * @see self::enablePadding()
- * @var bool
- * @access private
- */
- var $padding = true;
-
- /**
- * Is the mode one that is paddable?
- *
- * @see self::__construct()
- * @var bool
- * @access private
- */
- var $paddable = false;
-
- /**
- * Holds which crypt engine internaly should be use,
- * which will be determined automatically on __construct()
- *
- * Currently available $engines are:
- * - self::ENGINE_OPENSSL (very fast, php-extension: openssl, extension_loaded('openssl') required)
- * - self::ENGINE_MCRYPT (fast, php-extension: mcrypt, extension_loaded('mcrypt') required)
- * - self::ENGINE_INTERNAL (slower, pure php-engine, no php-extension required)
- *
- * @see self::_setEngine()
- * @see self::encrypt()
- * @see self::decrypt()
- * @var int
- * @access private
- */
- var $engine;
-
- /**
- * Holds the preferred crypt engine
- *
- * @see self::_setEngine()
- * @see self::setPreferredEngine()
- * @var int
- * @access private
- */
- var $preferredEngine;
-
- /**
- * The mcrypt specific name of the cipher
- *
- * Only used if $engine == self::ENGINE_MCRYPT
- *
- * @link http://www.php.net/mcrypt_module_open
- * @link http://www.php.net/mcrypt_list_algorithms
- * @see self::_setupMcrypt()
- * @var string
- * @access private
- */
- var $cipher_name_mcrypt;
-
- /**
- * The openssl specific name of the cipher
- *
- * Only used if $engine == self::ENGINE_OPENSSL
- *
- * @link http://www.php.net/openssl-get-cipher-methods
- * @var string
- * @access private
- */
- var $cipher_name_openssl;
-
- /**
- * The openssl specific name of the cipher in ECB mode
- *
- * If OpenSSL does not support the mode we're trying to use (CTR)
- * it can still be emulated with ECB mode.
- *
- * @link http://www.php.net/openssl-get-cipher-methods
- * @var string
- * @access private
- */
- var $cipher_name_openssl_ecb;
-
- /**
- * The default salt used by setPassword()
- *
- * @see self::setPassword()
- * @var string
- * @access private
- */
- var $password_default_salt = 'phpseclib/salt';
-
- /**
- * The name of the performance-optimized callback function
- *
- * Used by encrypt() / decrypt()
- * only if $engine == self::ENGINE_INTERNAL
- *
- * @see self::encrypt()
- * @see self::decrypt()
- * @see self::_setupInlineCrypt()
- * @see self::$use_inline_crypt
- * @var Callback
- * @access private
- */
- var $inline_crypt;
-
- /**
- * Holds whether performance-optimized $inline_crypt() can/should be used.
- *
- * @see self::encrypt()
- * @see self::decrypt()
- * @see self::inline_crypt
- * @var mixed
- * @access private
- */
- var $use_inline_crypt = true;
-
- /**
- * If OpenSSL can be used in ECB but not in CTR we can emulate CTR
- *
- * @see self::_openssl_ctr_process()
- * @var bool
- * @access private
- */
- var $openssl_emulate_ctr = false;
-
- /**
- * Determines what options are passed to openssl_encrypt/decrypt
- *
- * @see self::isValidEngine()
- * @var mixed
- * @access private
- */
- var $openssl_options;
-
- /**
- * Has the key length explicitly been set or should it be derived from the key, itself?
- *
- * @see self::setKeyLength()
- * @var bool
- * @access private
- */
- var $explicit_key_length = false;
-
- /**
- * Don't truncate / null pad key
- *
- * @see self::_clearBuffers()
- * @var bool
- * @access private
- */
- var $skip_key_adjustment = false;
-
- /**
- * Default Constructor.
- *
- * Determines whether or not the mcrypt extension should be used.
- *
- * $mode could be:
- *
- * - self::MODE_ECB
- *
- * - self::MODE_CBC
- *
- * - self::MODE_CTR
- *
- * - self::MODE_CFB
- *
- * - self::MODE_OFB
- *
- * If not explicitly set, self::MODE_CBC will be used.
- *
- * @param int $mode
- * @access public
- */
- function __construct($mode = self::MODE_CBC)
- {
- // $mode dependent settings
- switch ($mode) {
- case self::MODE_ECB:
- $this->paddable = true;
- $this->mode = self::MODE_ECB;
- break;
- case self::MODE_CTR:
- case self::MODE_CFB:
- case self::MODE_CFB8:
- case self::MODE_OFB8:
- case self::MODE_OFB:
- case self::MODE_STREAM:
- $this->mode = $mode;
- break;
- case self::MODE_CBC:
- default:
- $this->paddable = true;
- $this->mode = self::MODE_CBC;
- }
-
- $this->_setEngine();
-
- // Determining whether inline crypting can be used by the cipher
- if ($this->use_inline_crypt !== false) {
- $this->use_inline_crypt = version_compare(PHP_VERSION, '5.3.0') >= 0 || function_exists('create_function');
- }
-
- if (!defined('PHP_INT_SIZE')) {
- define('PHP_INT_SIZE', 4);
- }
-
- if (!defined('CRYPT_BASE_USE_REG_INTVAL')) {
- switch (true) {
- // PHP_OS & "\xDF\xDF\xDF" == strtoupper(substr(PHP_OS, 0, 3)), but a lot faster
- case (PHP_OS & "\xDF\xDF\xDF") === 'WIN':
- case !function_exists('php_uname'):
- case !is_string(php_uname('m')):
- case (php_uname('m') & "\xDF\xDF\xDF") != 'ARM':
- case PHP_INT_SIZE == 8:
- define('CRYPT_BASE_USE_REG_INTVAL', true);
- break;
- case (php_uname('m') & "\xDF\xDF\xDF") == 'ARM':
- switch (true) {
- /* PHP 7.0.0 introduced a bug that affected 32-bit ARM processors:
-
- https://github.com/php/php-src/commit/716da71446ebbd40fa6cf2cea8a4b70f504cc3cd
-
- altho the changelogs make no mention of it, this bug was fixed with this commit:
-
- https://github.com/php/php-src/commit/c1729272b17a1fe893d1a54e423d3b71470f3ee8
-
- affected versions of PHP are: 7.0.x, 7.1.0 - 7.1.23 and 7.2.0 - 7.2.11 */
- case PHP_VERSION_ID >= 70000 && PHP_VERSION_ID <= 70123:
- case PHP_VERSION_ID >= 70200 && PHP_VERSION_ID <= 70211:
- define('CRYPT_BASE_USE_REG_INTVAL', false);
- break;
- default:
- define('CRYPT_BASE_USE_REG_INTVAL', true);
- }
- }
- }
- }
-
- /**
- * Sets the initialization vector. (optional)
- *
- * SetIV is not required when self::MODE_ECB (or ie for AES: \phpseclib\Crypt\AES::MODE_ECB) is being used. If not explicitly set, it'll be assumed
- * to be all zero's.
- *
- * @access public
- * @param string $iv
- * @internal Can be overwritten by a sub class, but does not have to be
- */
- function setIV($iv)
- {
- if ($this->mode == self::MODE_ECB) {
- return;
- }
-
- $this->iv = $iv;
- $this->changed = true;
- }
-
- /**
- * Sets the key length.
- *
- * Keys with explicitly set lengths need to be treated accordingly
- *
- * @access public
- * @param int $length
- */
- function setKeyLength($length)
- {
- $this->explicit_key_length = true;
- $this->changed = true;
- $this->_setEngine();
- }
-
- /**
- * Returns the current key length in bits
- *
- * @access public
- * @return int
- */
- function getKeyLength()
- {
- return $this->key_length << 3;
- }
-
- /**
- * Returns the current block length in bits
- *
- * @access public
- * @return int
- */
- function getBlockLength()
- {
- return $this->block_size << 3;
- }
-
- /**
- * Sets the key.
- *
- * The min/max length(s) of the key depends on the cipher which is used.
- * If the key not fits the length(s) of the cipher it will paded with null bytes
- * up to the closest valid key length. If the key is more than max length,
- * we trim the excess bits.
- *
- * If the key is not explicitly set, it'll be assumed to be all null bytes.
- *
- * @access public
- * @param string $key
- */
- function setKey($key)
- {
- if (!$this->explicit_key_length) {
- $this->setKeyLength(strlen($key) << 3);
- $this->explicit_key_length = false;
- }
-
- $this->key = $key;
- $this->changed = true;
- $this->_setEngine();
- }
-
- /**
- * Sets the password.
- *
- * Depending on what $method is set to, setPassword()'s (optional) parameters are as follows:
- * {@link http://en.wikipedia.org/wiki/PBKDF2 pbkdf2} or pbkdf1:
- * $hash, $salt, $count, $dkLen
- *
- * Where $hash (default = sha1) currently supports the following hashes: see: Crypt/Hash.php
- * {@link https://en.wikipedia.org/wiki/Bcrypt bcypt}:
- * $salt, $rounds, $keylen
- *
- * This is a modified version of bcrypt used by OpenSSH.
- *
- * @see Crypt/Hash.php
- * @param string $password
- * @param string $method
- * @return bool
- * @access public
- * @internal Could, but not must, extend by the child Crypt_* class
- */
- function setPassword($password, $method = 'pbkdf2')
- {
- $key = '';
-
- switch ($method) {
- case 'bcrypt':
- $func_args = func_get_args();
-
- if (!isset($func_args[2])) {
- return false;
- }
-
- $salt = $func_args[2];
-
- $rounds = isset($func_args[3]) ? $func_args[3] : 16;
- $keylen = isset($func_args[4]) ? $func_args[4] : $this->key_length;
-
- $bf = new Blowfish();
- $key = $bf->bcrypt_pbkdf($password, $salt, $keylen + $this->block_size, $rounds);
- if (!$key) {
- return false;
- }
-
- $this->setKey(substr($key, 0, $keylen));
- $this->setIV(substr($key, $keylen));
-
- return true;
- default: // 'pbkdf2' or 'pbkdf1'
- $func_args = func_get_args();
-
- // Hash function
- $hash = isset($func_args[2]) ? $func_args[2] : 'sha1';
-
- // WPA and WPA2 use the SSID as the salt
- $salt = isset($func_args[3]) ? $func_args[3] : $this->password_default_salt;
-
- // RFC2898#section-4.2 uses 1,000 iterations by default
- // WPA and WPA2 use 4,096.
- $count = isset($func_args[4]) ? $func_args[4] : 1000;
-
- // Keylength
- if (isset($func_args[5])) {
- $dkLen = $func_args[5];
- } else {
- $dkLen = $method == 'pbkdf1' ? 2 * $this->key_length : $this->key_length;
- }
-
- switch (true) {
- case $method == 'pbkdf1':
- $hashObj = new Hash();
- $hashObj->setHash($hash);
- if ($dkLen > $hashObj->getLength()) {
- user_error('Derived key too long');
- return false;
- }
- $t = $password . $salt;
- for ($i = 0; $i < $count; ++$i) {
- $t = $hashObj->hash($t);
- }
- $key = substr($t, 0, $dkLen);
-
- $this->setKey(substr($key, 0, $dkLen >> 1));
- $this->setIV(substr($key, $dkLen >> 1));
-
- return true;
- // Determining if php[>=5.5.0]'s hash_pbkdf2() function avail- and useable
- case !function_exists('hash_pbkdf2'):
- case !function_exists('hash_algos'):
- case !in_array($hash, hash_algos()):
- $i = 1;
- $hmac = new Hash();
- $hmac->setHash($hash);
- $hmac->setKey($password);
- while (strlen($key) < $dkLen) {
- $f = $u = $hmac->hash($salt . pack('N', $i++));
- for ($j = 2; $j <= $count; ++$j) {
- $u = $hmac->hash($u);
- $f^= $u;
- }
- $key.= $f;
- }
- $key = substr($key, 0, $dkLen);
- break;
- default:
- $key = hash_pbkdf2($hash, $password, $salt, $count, $dkLen, true);
- }
- }
-
- $this->setKey($key);
-
- return true;
- }
-
- /**
- * Encrypts a message.
- *
- * $plaintext will be padded with additional bytes such that it's length is a multiple of the block size. Other cipher
- * implementations may or may not pad in the same manner. Other common approaches to padding and the reasons why it's
- * necessary are discussed in the following
- * URL:
- *
- * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html}
- *
- * An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does.
- * strlen($plaintext) will still need to be a multiple of the block size, however, arbitrary values can be added to make it that
- * length.
- *
- * @see self::decrypt()
- * @access public
- * @param string $plaintext
- * @return string $ciphertext
- * @internal Could, but not must, extend by the child Crypt_* class
- */
- function encrypt($plaintext)
- {
- if ($this->paddable) {
- $plaintext = $this->_pad($plaintext);
- }
-
- if ($this->engine === self::ENGINE_OPENSSL) {
- if ($this->changed) {
- $this->_clearBuffers();
- $this->changed = false;
- }
- switch ($this->mode) {
- case self::MODE_STREAM:
- return openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, $this->openssl_options);
- case self::MODE_ECB:
- $result = @openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, $this->openssl_options);
- return !defined('OPENSSL_RAW_DATA') ? substr($result, 0, -$this->block_size) : $result;
- case self::MODE_CBC:
- $result = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, $this->openssl_options, $this->encryptIV);
- if (!defined('OPENSSL_RAW_DATA')) {
- $result = substr($result, 0, -$this->block_size);
- }
- if ($this->continuousBuffer) {
- $this->encryptIV = substr($result, -$this->block_size);
- }
- return $result;
- case self::MODE_CTR:
- return $this->_openssl_ctr_process($plaintext, $this->encryptIV, $this->enbuffer);
- case self::MODE_CFB:
- // cfb loosely routines inspired by openssl's:
- // {@link http://cvs.openssl.org/fileview?f=openssl/crypto/modes/cfb128.c&v=1.3.2.2.2.1}
- $ciphertext = '';
- if ($this->continuousBuffer) {
- $iv = &$this->encryptIV;
- $pos = &$this->enbuffer['pos'];
- } else {
- $iv = $this->encryptIV;
- $pos = 0;
- }
- $len = strlen($plaintext);
- $i = 0;
- if ($pos) {
- $orig_pos = $pos;
- $max = $this->block_size - $pos;
- if ($len >= $max) {
- $i = $max;
- $len-= $max;
- $pos = 0;
- } else {
- $i = $len;
- $pos+= $len;
- $len = 0;
- }
- // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize
- $ciphertext = substr($iv, $orig_pos) ^ $plaintext;
- $iv = substr_replace($iv, $ciphertext, $orig_pos, $i);
- $plaintext = substr($plaintext, $i);
- }
-
- $overflow = $len % $this->block_size;
-
- if ($overflow) {
- $ciphertext.= openssl_encrypt(substr($plaintext, 0, -$overflow) . str_repeat("\0", $this->block_size), $this->cipher_name_openssl, $this->key, $this->openssl_options, $iv);
- $iv = $this->_string_pop($ciphertext, $this->block_size);
-
- $size = $len - $overflow;
- $block = $iv ^ substr($plaintext, -$overflow);
- $iv = substr_replace($iv, $block, 0, $overflow);
- $ciphertext.= $block;
- $pos = $overflow;
- } elseif ($len) {
- $ciphertext = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, $this->openssl_options, $iv);
- $iv = substr($ciphertext, -$this->block_size);
- }
-
- return $ciphertext;
- case self::MODE_CFB8:
- $ciphertext = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, $this->openssl_options, $this->encryptIV);
- if ($this->continuousBuffer) {
- if (($len = strlen($ciphertext)) >= $this->block_size) {
- $this->encryptIV = substr($ciphertext, -$this->block_size);
- } else {
- $this->encryptIV = substr($this->encryptIV, $len - $this->block_size) . substr($ciphertext, -$len);
- }
- }
- return $ciphertext;
- case self::MODE_OFB8:
- // OpenSSL has built in support for cfb8 but not ofb8
- $ciphertext = '';
- $len = strlen($plaintext);
- $iv = $this->encryptIV;
-
- for ($i = 0; $i < $len; ++$i) {
- $xor = openssl_encrypt($iv, $this->cipher_name_openssl_ecb, $this->key, $this->openssl_options, $this->decryptIV);
- $ciphertext.= $plaintext[$i] ^ $xor;
- $iv = substr($iv, 1) . $xor[0];
- }
-
- if ($this->continuousBuffer) {
- $this->encryptIV = $iv;
- }
- break;
- case self::MODE_OFB:
- return $this->_openssl_ofb_process($plaintext, $this->encryptIV, $this->enbuffer);
- }
- }
-
- if ($this->engine === self::ENGINE_MCRYPT) {
- set_error_handler(array($this, 'do_nothing'));
-
- if ($this->changed) {
- $this->_setupMcrypt();
- $this->changed = false;
- }
- if ($this->enchanged) {
- mcrypt_generic_init($this->enmcrypt, $this->key, $this->encryptIV);
- $this->enchanged = false;
- }
-
- // re: {@link http://phpseclib.sourceforge.net/cfb-demo.phps}
- // using mcrypt's default handing of CFB the above would output two different things. using phpseclib's
- // rewritten CFB implementation the above outputs the same thing twice.
- if ($this->mode == self::MODE_CFB && $this->continuousBuffer) {
- $block_size = $this->block_size;
- $iv = &$this->encryptIV;
- $pos = &$this->enbuffer['pos'];
- $len = strlen($plaintext);
- $ciphertext = '';
- $i = 0;
- if ($pos) {
- $orig_pos = $pos;
- $max = $block_size - $pos;
- if ($len >= $max) {
- $i = $max;
- $len-= $max;
- $pos = 0;
- } else {
- $i = $len;
- $pos+= $len;
- $len = 0;
- }
- $ciphertext = substr($iv, $orig_pos) ^ $plaintext;
- $iv = substr_replace($iv, $ciphertext, $orig_pos, $i);
- $this->enbuffer['enmcrypt_init'] = true;
- }
- if ($len >= $block_size) {
- if ($this->enbuffer['enmcrypt_init'] === false || $len > $this->cfb_init_len) {
- if ($this->enbuffer['enmcrypt_init'] === true) {
- mcrypt_generic_init($this->enmcrypt, $this->key, $iv);
- $this->enbuffer['enmcrypt_init'] = false;
- }
- $ciphertext.= mcrypt_generic($this->enmcrypt, substr($plaintext, $i, $len - $len % $block_size));
- $iv = substr($ciphertext, -$block_size);
- $len%= $block_size;
- } else {
- while ($len >= $block_size) {
- $iv = mcrypt_generic($this->ecb, $iv) ^ substr($plaintext, $i, $block_size);
- $ciphertext.= $iv;
- $len-= $block_size;
- $i+= $block_size;
- }
- }
- }
-
- if ($len) {
- $iv = mcrypt_generic($this->ecb, $iv);
- $block = $iv ^ substr($plaintext, -$len);
- $iv = substr_replace($iv, $block, 0, $len);
- $ciphertext.= $block;
- $pos = $len;
- }
-
- restore_error_handler();
-
- return $ciphertext;
- }
-
- $ciphertext = mcrypt_generic($this->enmcrypt, $plaintext);
-
- if (!$this->continuousBuffer) {
- mcrypt_generic_init($this->enmcrypt, $this->key, $this->encryptIV);
- }
-
- restore_error_handler();
-
- return $ciphertext;
- }
-
- if ($this->changed) {
- $this->_setup();
- $this->changed = false;
- }
- if ($this->use_inline_crypt) {
- $inline = $this->inline_crypt;
- return $inline('encrypt', $this, $plaintext);
- }
-
- $buffer = &$this->enbuffer;
- $block_size = $this->block_size;
- $ciphertext = '';
- switch ($this->mode) {
- case self::MODE_ECB:
- for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
- $ciphertext.= $this->_encryptBlock(substr($plaintext, $i, $block_size));
- }
- break;
- case self::MODE_CBC:
- $xor = $this->encryptIV;
- for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
- $block = substr($plaintext, $i, $block_size);
- $block = $this->_encryptBlock($block ^ $xor);
- $xor = $block;
- $ciphertext.= $block;
- }
- if ($this->continuousBuffer) {
- $this->encryptIV = $xor;
- }
- break;
- case self::MODE_CTR:
- $xor = $this->encryptIV;
- if (strlen($buffer['ciphertext'])) {
- for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
- $block = substr($plaintext, $i, $block_size);
- if (strlen($block) > strlen($buffer['ciphertext'])) {
- $buffer['ciphertext'].= $this->_encryptBlock($xor);
- $this->_increment_str($xor);
- }
- $key = $this->_string_shift($buffer['ciphertext'], $block_size);
- $ciphertext.= $block ^ $key;
- }
- } else {
- for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
- $block = substr($plaintext, $i, $block_size);
- $key = $this->_encryptBlock($xor);
- $this->_increment_str($xor);
- $ciphertext.= $block ^ $key;
- }
- }
- if ($this->continuousBuffer) {
- $this->encryptIV = $xor;
- if ($start = strlen($plaintext) % $block_size) {
- $buffer['ciphertext'] = substr($key, $start) . $buffer['ciphertext'];
- }
- }
- break;
- case self::MODE_CFB:
- // cfb loosely routines inspired by openssl's:
- // {@link http://cvs.openssl.org/fileview?f=openssl/crypto/modes/cfb128.c&v=1.3.2.2.2.1}
- if ($this->continuousBuffer) {
- $iv = &$this->encryptIV;
- $pos = &$buffer['pos'];
- } else {
- $iv = $this->encryptIV;
- $pos = 0;
- }
- $len = strlen($plaintext);
- $i = 0;
- if ($pos) {
- $orig_pos = $pos;
- $max = $block_size - $pos;
- if ($len >= $max) {
- $i = $max;
- $len-= $max;
- $pos = 0;
- } else {
- $i = $len;
- $pos+= $len;
- $len = 0;
- }
- // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize
- $ciphertext = substr($iv, $orig_pos) ^ $plaintext;
- $iv = substr_replace($iv, $ciphertext, $orig_pos, $i);
- }
- while ($len >= $block_size) {
- $iv = $this->_encryptBlock($iv) ^ substr($plaintext, $i, $block_size);
- $ciphertext.= $iv;
- $len-= $block_size;
- $i+= $block_size;
- }
- if ($len) {
- $iv = $this->_encryptBlock($iv);
- $block = $iv ^ substr($plaintext, $i);
- $iv = substr_replace($iv, $block, 0, $len);
- $ciphertext.= $block;
- $pos = $len;
- }
- break;
- case self::MODE_CFB8:
- // compared to regular CFB, which encrypts a block at a time,
- // here, we're encrypting a byte at a time
- $ciphertext = '';
- $len = strlen($plaintext);
- $iv = $this->encryptIV;
-
- for ($i = 0; $i < $len; ++$i) {
- $ciphertext.= ($c = $plaintext[$i] ^ $this->_encryptBlock($iv));
- $iv = substr($iv, 1) . $c;
- }
-
- if ($this->continuousBuffer) {
- if ($len >= $block_size) {
- $this->encryptIV = substr($ciphertext, -$block_size);
- } else {
- $this->encryptIV = substr($this->encryptIV, $len - $block_size) . substr($ciphertext, -$len);
- }
- }
- break;
- case self::MODE_OFB8:
- $ciphertext = '';
- $len = strlen($plaintext);
- $iv = $this->encryptIV;
-
- for ($i = 0; $i < $len; ++$i) {
- $xor = $this->_encryptBlock($iv);
- $ciphertext.= $plaintext[$i] ^ $xor;
- $iv = substr($iv, 1) . $xor[0];
- }
-
- if ($this->continuousBuffer) {
- $this->encryptIV = $iv;
- }
- break;
- case self::MODE_OFB:
- $xor = $this->encryptIV;
- if (strlen($buffer['xor'])) {
- for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
- $block = substr($plaintext, $i, $block_size);
- if (strlen($block) > strlen($buffer['xor'])) {
- $xor = $this->_encryptBlock($xor);
- $buffer['xor'].= $xor;
- }
- $key = $this->_string_shift($buffer['xor'], $block_size);
- $ciphertext.= $block ^ $key;
- }
- } else {
- for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
- $xor = $this->_encryptBlock($xor);
- $ciphertext.= substr($plaintext, $i, $block_size) ^ $xor;
- }
- $key = $xor;
- }
- if ($this->continuousBuffer) {
- $this->encryptIV = $xor;
- if ($start = strlen($plaintext) % $block_size) {
- $buffer['xor'] = substr($key, $start) . $buffer['xor'];
- }
- }
- break;
- case self::MODE_STREAM:
- $ciphertext = $this->_encryptBlock($plaintext);
- break;
- }
-
- return $ciphertext;
- }
-
- /**
- * Decrypts a message.
- *
- * If strlen($ciphertext) is not a multiple of the block size, null bytes will be added to the end of the string until
- * it is.
- *
- * @see self::encrypt()
- * @access public
- * @param string $ciphertext
- * @return string $plaintext
- * @internal Could, but not must, extend by the child Crypt_* class
- */
- function decrypt($ciphertext)
- {
- if ($this->paddable) {
- // we pad with chr(0) since that's what mcrypt_generic does. to quote from {@link http://www.php.net/function.mcrypt-generic}:
- // "The data is padded with "\0" to make sure the length of the data is n * blocksize."
- $ciphertext = str_pad($ciphertext, strlen($ciphertext) + ($this->block_size - strlen($ciphertext) % $this->block_size) % $this->block_size, chr(0));
- }
-
- if ($this->engine === self::ENGINE_OPENSSL) {
- if ($this->changed) {
- $this->_clearBuffers();
- $this->changed = false;
- }
- switch ($this->mode) {
- case self::MODE_STREAM:
- $plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, $this->openssl_options);
- break;
- case self::MODE_ECB:
- if (!defined('OPENSSL_RAW_DATA')) {
- $ciphertext.= @openssl_encrypt('', $this->cipher_name_openssl_ecb, $this->key, true);
- }
- $plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, $this->openssl_options);
- break;
- case self::MODE_CBC:
- if (!defined('OPENSSL_RAW_DATA')) {
- $padding = str_repeat(chr($this->block_size), $this->block_size) ^ substr($ciphertext, -$this->block_size);
- $ciphertext.= substr(@openssl_encrypt($padding, $this->cipher_name_openssl_ecb, $this->key, true), 0, $this->block_size);
- $offset = 2 * $this->block_size;
- } else {
- $offset = $this->block_size;
- }
- $plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, $this->openssl_options, $this->decryptIV);
- if ($this->continuousBuffer) {
- $this->decryptIV = substr($ciphertext, -$offset, $this->block_size);
- }
- break;
- case self::MODE_CTR:
- $plaintext = $this->_openssl_ctr_process($ciphertext, $this->decryptIV, $this->debuffer);
- break;
- case self::MODE_CFB:
- // cfb loosely routines inspired by openssl's:
- // {@link http://cvs.openssl.org/fileview?f=openssl/crypto/modes/cfb128.c&v=1.3.2.2.2.1}
- $plaintext = '';
- if ($this->continuousBuffer) {
- $iv = &$this->decryptIV;
- $pos = &$this->debuffer['pos'];
- } else {
- $iv = $this->decryptIV;
- $pos = 0;
- }
- $len = strlen($ciphertext);
- $i = 0;
- if ($pos) {
- $orig_pos = $pos;
- $max = $this->block_size - $pos;
- if ($len >= $max) {
- $i = $max;
- $len-= $max;
- $pos = 0;
- } else {
- $i = $len;
- $pos+= $len;
- $len = 0;
- }
- // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $this->blocksize
- $plaintext = substr($iv, $orig_pos) ^ $ciphertext;
- $iv = substr_replace($iv, substr($ciphertext, 0, $i), $orig_pos, $i);
- $ciphertext = substr($ciphertext, $i);
- }
- $overflow = $len % $this->block_size;
- if ($overflow) {
- $plaintext.= openssl_decrypt(substr($ciphertext, 0, -$overflow), $this->cipher_name_openssl, $this->key, $this->openssl_options, $iv);
- if ($len - $overflow) {
- $iv = substr($ciphertext, -$overflow - $this->block_size, -$overflow);
- }
- $iv = openssl_encrypt(str_repeat("\0", $this->block_size), $this->cipher_name_openssl, $this->key, $this->openssl_options, $iv);
- $plaintext.= $iv ^ substr($ciphertext, -$overflow);
- $iv = substr_replace($iv, substr($ciphertext, -$overflow), 0, $overflow);
- $pos = $overflow;
- } elseif ($len) {
- $plaintext.= openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, $this->openssl_options, $iv);
- $iv = substr($ciphertext, -$this->block_size);
- }
- break;
- case self::MODE_CFB8:
- $plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, $this->openssl_options, $this->decryptIV);
- if ($this->continuousBuffer) {
- if (($len = strlen($ciphertext)) >= $this->block_size) {
- $this->decryptIV = substr($ciphertext, -$this->block_size);
- } else {
- $this->decryptIV = substr($this->decryptIV, $len - $this->block_size) . substr($ciphertext, -$len);
- }
- }
- break;
- case self::MODE_OFB8:
- $plaintext = '';
- $len = strlen($ciphertext);
- $iv = $this->decryptIV;
-
- for ($i = 0; $i < $len; ++$i) {
- $xor = openssl_encrypt($iv, $this->cipher_name_openssl_ecb, $this->key, $this->openssl_options, $this->decryptIV);
- $plaintext.= $ciphertext[$i] ^ $xor;
- $iv = substr($iv, 1) . $xor[0];
- }
-
- if ($this->continuousBuffer) {
- $this->decryptIV = $iv;
- }
- break;
- case self::MODE_OFB:
- $plaintext = $this->_openssl_ofb_process($ciphertext, $this->decryptIV, $this->debuffer);
- }
-
- return $this->paddable ? $this->_unpad($plaintext) : $plaintext;
- }
-
- if ($this->engine === self::ENGINE_MCRYPT) {
- set_error_handler(array($this, 'do_nothing'));
- $block_size = $this->block_size;
- if ($this->changed) {
- $this->_setupMcrypt();
- $this->changed = false;
- }
- if ($this->dechanged) {
- mcrypt_generic_init($this->demcrypt, $this->key, $this->decryptIV);
- $this->dechanged = false;
- }
-
- if ($this->mode == self::MODE_CFB && $this->continuousBuffer) {
- $iv = &$this->decryptIV;
- $pos = &$this->debuffer['pos'];
- $len = strlen($ciphertext);
- $plaintext = '';
- $i = 0;
- if ($pos) {
- $orig_pos = $pos;
- $max = $block_size - $pos;
- if ($len >= $max) {
- $i = $max;
- $len-= $max;
- $pos = 0;
- } else {
- $i = $len;
- $pos+= $len;
- $len = 0;
- }
- // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize
- $plaintext = substr($iv, $orig_pos) ^ $ciphertext;
- $iv = substr_replace($iv, substr($ciphertext, 0, $i), $orig_pos, $i);
- }
- if ($len >= $block_size) {
- $cb = substr($ciphertext, $i, $len - $len % $block_size);
- $plaintext.= mcrypt_generic($this->ecb, $iv . $cb) ^ $cb;
- $iv = substr($cb, -$block_size);
- $len%= $block_size;
- }
- if ($len) {
- $iv = mcrypt_generic($this->ecb, $iv);
- $plaintext.= $iv ^ substr($ciphertext, -$len);
- $iv = substr_replace($iv, substr($ciphertext, -$len), 0, $len);
- $pos = $len;
- }
-
- restore_error_handler();
-
- return $plaintext;
- }
-
- $plaintext = mdecrypt_generic($this->demcrypt, $ciphertext);
-
- if (!$this->continuousBuffer) {
- mcrypt_generic_init($this->demcrypt, $this->key, $this->decryptIV);
- }
-
- restore_error_handler();
-
- return $this->paddable ? $this->_unpad($plaintext) : $plaintext;
- }
-
- if ($this->changed) {
- $this->_setup();
- $this->changed = false;
- }
- if ($this->use_inline_crypt) {
- $inline = $this->inline_crypt;
- return $inline('decrypt', $this, $ciphertext);
- }
-
- $block_size = $this->block_size;
-
- $buffer = &$this->debuffer;
- $plaintext = '';
- switch ($this->mode) {
- case self::MODE_ECB:
- for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
- $plaintext.= $this->_decryptBlock(substr($ciphertext, $i, $block_size));
- }
- break;
- case self::MODE_CBC:
- $xor = $this->decryptIV;
- for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
- $block = substr($ciphertext, $i, $block_size);
- $plaintext.= $this->_decryptBlock($block) ^ $xor;
- $xor = $block;
- }
- if ($this->continuousBuffer) {
- $this->decryptIV = $xor;
- }
- break;
- case self::MODE_CTR:
- $xor = $this->decryptIV;
- if (strlen($buffer['ciphertext'])) {
- for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
- $block = substr($ciphertext, $i, $block_size);
- if (strlen($block) > strlen($buffer['ciphertext'])) {
- $buffer['ciphertext'].= $this->_encryptBlock($xor);
- $this->_increment_str($xor);
- }
- $key = $this->_string_shift($buffer['ciphertext'], $block_size);
- $plaintext.= $block ^ $key;
- }
- } else {
- for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
- $block = substr($ciphertext, $i, $block_size);
- $key = $this->_encryptBlock($xor);
- $this->_increment_str($xor);
- $plaintext.= $block ^ $key;
- }
- }
- if ($this->continuousBuffer) {
- $this->decryptIV = $xor;
- if ($start = strlen($ciphertext) % $block_size) {
- $buffer['ciphertext'] = substr($key, $start) . $buffer['ciphertext'];
- }
- }
- break;
- case self::MODE_CFB:
- if ($this->continuousBuffer) {
- $iv = &$this->decryptIV;
- $pos = &$buffer['pos'];
- } else {
- $iv = $this->decryptIV;
- $pos = 0;
- }
- $len = strlen($ciphertext);
- $i = 0;
- if ($pos) {
- $orig_pos = $pos;
- $max = $block_size - $pos;
- if ($len >= $max) {
- $i = $max;
- $len-= $max;
- $pos = 0;
- } else {
- $i = $len;
- $pos+= $len;
- $len = 0;
- }
- // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize
- $plaintext = substr($iv, $orig_pos) ^ $ciphertext;
- $iv = substr_replace($iv, substr($ciphertext, 0, $i), $orig_pos, $i);
- }
- while ($len >= $block_size) {
- $iv = $this->_encryptBlock($iv);
- $cb = substr($ciphertext, $i, $block_size);
- $plaintext.= $iv ^ $cb;
- $iv = $cb;
- $len-= $block_size;
- $i+= $block_size;
- }
- if ($len) {
- $iv = $this->_encryptBlock($iv);
- $plaintext.= $iv ^ substr($ciphertext, $i);
- $iv = substr_replace($iv, substr($ciphertext, $i), 0, $len);
- $pos = $len;
- }
- break;
- case self::MODE_CFB8:
- $plaintext = '';
- $len = strlen($ciphertext);
- $iv = $this->decryptIV;
-
- for ($i = 0; $i < $len; ++$i) {
- $plaintext.= $ciphertext[$i] ^ $this->_encryptBlock($iv);
- $iv = substr($iv, 1) . $ciphertext[$i];
- }
-
- if ($this->continuousBuffer) {
- if ($len >= $block_size) {
- $this->decryptIV = substr($ciphertext, -$block_size);
- } else {
- $this->decryptIV = substr($this->decryptIV, $len - $block_size) . substr($ciphertext, -$len);
- }
- }
- break;
- case self::MODE_OFB8:
- $plaintext = '';
- $len = strlen($ciphertext);
- $iv = $this->decryptIV;
-
- for ($i = 0; $i < $len; ++$i) {
- $xor = $this->_encryptBlock($iv);
- $plaintext.= $ciphertext[$i] ^ $xor;
- $iv = substr($iv, 1) . $xor[0];
- }
-
- if ($this->continuousBuffer) {
- $this->decryptIV = $iv;
- }
- break;
- case self::MODE_OFB:
- $xor = $this->decryptIV;
- if (strlen($buffer['xor'])) {
- for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
- $block = substr($ciphertext, $i, $block_size);
- if (strlen($block) > strlen($buffer['xor'])) {
- $xor = $this->_encryptBlock($xor);
- $buffer['xor'].= $xor;
- }
- $key = $this->_string_shift($buffer['xor'], $block_size);
- $plaintext.= $block ^ $key;
- }
- } else {
- for ($i = 0; $i < strlen($ciphertext); $i+=$block_size) {
- $xor = $this->_encryptBlock($xor);
- $plaintext.= substr($ciphertext, $i, $block_size) ^ $xor;
- }
- $key = $xor;
- }
- if ($this->continuousBuffer) {
- $this->decryptIV = $xor;
- if ($start = strlen($ciphertext) % $block_size) {
- $buffer['xor'] = substr($key, $start) . $buffer['xor'];
- }
- }
- break;
- case self::MODE_STREAM:
- $plaintext = $this->_decryptBlock($ciphertext);
- break;
- }
- return $this->paddable ? $this->_unpad($plaintext) : $plaintext;
- }
-
- /**
- * OpenSSL CTR Processor
- *
- * PHP's OpenSSL bindings do not operate in continuous mode so we'll wrap around it. Since the keystream
- * for CTR is the same for both encrypting and decrypting this function is re-used by both Base::encrypt()
- * and Base::decrypt(). Also, OpenSSL doesn't implement CTR for all of it's symmetric ciphers so this
- * function will emulate CTR with ECB when necessary.
- *
- * @see self::encrypt()
- * @see self::decrypt()
- * @param string $plaintext
- * @param string $encryptIV
- * @param array $buffer
- * @return string
- * @access private
- */
- function _openssl_ctr_process($plaintext, &$encryptIV, &$buffer)
- {
- $ciphertext = '';
-
- $block_size = $this->block_size;
- $key = $this->key;
-
- if ($this->openssl_emulate_ctr) {
- $xor = $encryptIV;
- if (strlen($buffer['ciphertext'])) {
- for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
- $block = substr($plaintext, $i, $block_size);
- if (strlen($block) > strlen($buffer['ciphertext'])) {
- $result = @openssl_encrypt($xor, $this->cipher_name_openssl_ecb, $key, $this->openssl_options);
- $result = !defined('OPENSSL_RAW_DATA') ? substr($result, 0, -$this->block_size) : $result;
- $buffer['ciphertext'].= $result;
- }
- $this->_increment_str($xor);
- $otp = $this->_string_shift($buffer['ciphertext'], $block_size);
- $ciphertext.= $block ^ $otp;
- }
- } else {
- for ($i = 0; $i < strlen($plaintext); $i+=$block_size) {
- $block = substr($plaintext, $i, $block_size);
- $otp = @openssl_encrypt($xor, $this->cipher_name_openssl_ecb, $key, $this->openssl_options);
- $otp = !defined('OPENSSL_RAW_DATA') ? substr($otp, 0, -$this->block_size) : $otp;
- $this->_increment_str($xor);
- $ciphertext.= $block ^ $otp;
- }
- }
- if ($this->continuousBuffer) {
- $encryptIV = $xor;
- if ($start = strlen($plaintext) % $block_size) {
- $buffer['ciphertext'] = substr($key, $start) . $buffer['ciphertext'];
- }
- }
-
- return $ciphertext;
- }
-
- if (strlen($buffer['ciphertext'])) {
- $ciphertext = $plaintext ^ $this->_string_shift($buffer['ciphertext'], strlen($plaintext));
- $plaintext = substr($plaintext, strlen($ciphertext));
-
- if (!strlen($plaintext)) {
- return $ciphertext;
- }
- }
-
- $overflow = strlen($plaintext) % $block_size;
- if ($overflow) {
- $plaintext2 = $this->_string_pop($plaintext, $overflow); // ie. trim $plaintext to a multiple of $block_size and put rest of $plaintext in $plaintext2
- $encrypted = openssl_encrypt($plaintext . str_repeat("\0", $block_size), $this->cipher_name_openssl, $key, $this->openssl_options, $encryptIV);
- $temp = $this->_string_pop($encrypted, $block_size);
- $ciphertext.= $encrypted . ($plaintext2 ^ $temp);
- if ($this->continuousBuffer) {
- $buffer['ciphertext'] = substr($temp, $overflow);
- $encryptIV = $temp;
- }
- } elseif (!strlen($buffer['ciphertext'])) {
- $ciphertext.= openssl_encrypt($plaintext . str_repeat("\0", $block_size), $this->cipher_name_openssl, $key, $this->openssl_options, $encryptIV);
- $temp = $this->_string_pop($ciphertext, $block_size);
- if ($this->continuousBuffer) {
- $encryptIV = $temp;
- }
- }
- if ($this->continuousBuffer) {
- if (!defined('OPENSSL_RAW_DATA')) {
- $encryptIV.= @openssl_encrypt('', $this->cipher_name_openssl_ecb, $key, $this->openssl_options);
- }
- $encryptIV = openssl_decrypt($encryptIV, $this->cipher_name_openssl_ecb, $key, $this->openssl_options);
- if ($overflow) {
- $this->_increment_str($encryptIV);
- }
- }
-
- return $ciphertext;
- }
-
- /**
- * OpenSSL OFB Processor
- *
- * PHP's OpenSSL bindings do not operate in continuous mode so we'll wrap around it. Since the keystream
- * for OFB is the same for both encrypting and decrypting this function is re-used by both Base::encrypt()
- * and Base::decrypt().
- *
- * @see self::encrypt()
- * @see self::decrypt()
- * @param string $plaintext
- * @param string $encryptIV
- * @param array $buffer
- * @return string
- * @access private
- */
- function _openssl_ofb_process($plaintext, &$encryptIV, &$buffer)
- {
- if (strlen($buffer['xor'])) {
- $ciphertext = $plaintext ^ $buffer['xor'];
- $buffer['xor'] = substr($buffer['xor'], strlen($ciphertext));
- $plaintext = substr($plaintext, strlen($ciphertext));
- } else {
- $ciphertext = '';
- }
-
- $block_size = $this->block_size;
-
- $len = strlen($plaintext);
- $key = $this->key;
- $overflow = $len % $block_size;
-
- if (strlen($plaintext)) {
- if ($overflow) {
- $ciphertext.= openssl_encrypt(substr($plaintext, 0, -$overflow) . str_repeat("\0", $block_size), $this->cipher_name_openssl, $key, $this->openssl_options, $encryptIV);
- $xor = $this->_string_pop($ciphertext, $block_size);
- if ($this->continuousBuffer) {
- $encryptIV = $xor;
- }
- $ciphertext.= $this->_string_shift($xor, $overflow) ^ substr($plaintext, -$overflow);
- if ($this->continuousBuffer) {
- $buffer['xor'] = $xor;
- }
- } else {
- $ciphertext = openssl_encrypt($plaintext, $this->cipher_name_openssl, $key, $this->openssl_options, $encryptIV);
- if ($this->continuousBuffer) {
- $encryptIV = substr($ciphertext, -$block_size) ^ substr($plaintext, -$block_size);
- }
- }
- }
-
- return $ciphertext;
- }
-
- /**
- * phpseclib <-> OpenSSL Mode Mapper
- *
- * May need to be overwritten by classes extending this one in some cases
- *
- * @return int
- * @access private
- */
- function _openssl_translate_mode()
- {
- switch ($this->mode) {
- case self::MODE_ECB:
- return 'ecb';
- case self::MODE_CBC:
- return 'cbc';
- case self::MODE_CTR:
- return 'ctr';
- case self::MODE_CFB:
- return 'cfb';
- case self::MODE_CFB8:
- return 'cfb8';
- case self::MODE_OFB:
- return 'ofb';
- }
- }
-
- /**
- * Pad "packets".
- *
- * Block ciphers working by encrypting between their specified [$this->]block_size at a time
- * If you ever need to encrypt or decrypt something that isn't of the proper length, it becomes necessary to
- * pad the input so that it is of the proper length.
- *
- * Padding is enabled by default. Sometimes, however, it is undesirable to pad strings. Such is the case in SSH,
- * where "packets" are padded with random bytes before being encrypted. Unpad these packets and you risk stripping
- * away characters that shouldn't be stripped away. (SSH knows how many bytes are added because the length is
- * transmitted separately)
- *
- * @see self::disablePadding()
- * @access public
- */
- function enablePadding()
- {
- $this->padding = true;
- }
-
- /**
- * Do not pad packets.
- *
- * @see self::enablePadding()
- * @access public
- */
- function disablePadding()
- {
- $this->padding = false;
- }
-
- /**
- * Treat consecutive "packets" as if they are a continuous buffer.
- *
- * Say you have a 32-byte plaintext $plaintext. Using the default behavior, the two following code snippets
- * will yield different outputs:
- *
- * <code>
- * echo $rijndael->encrypt(substr($plaintext, 0, 16));
- * echo $rijndael->encrypt(substr($plaintext, 16, 16));
- * </code>
- * <code>
- * echo $rijndael->encrypt($plaintext);
- * </code>
- *
- * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates
- * another, as demonstrated with the following:
- *
- * <code>
- * $rijndael->encrypt(substr($plaintext, 0, 16));
- * echo $rijndael->decrypt($rijndael->encrypt(substr($plaintext, 16, 16)));
- * </code>
- * <code>
- * echo $rijndael->decrypt($rijndael->encrypt(substr($plaintext, 16, 16)));
- * </code>
- *
- * With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different
- * outputs. The reason is due to the fact that the initialization vector's change after every encryption /
- * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant.
- *
- * Put another way, when the continuous buffer is enabled, the state of the \phpseclib\Crypt\*() object changes after each
- * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that
- * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them),
- * however, they are also less intuitive and more likely to cause you problems.
- *
- * @see self::disableContinuousBuffer()
- * @access public
- * @internal Could, but not must, extend by the child Crypt_* class
- */
- function enableContinuousBuffer()
- {
- if ($this->mode == self::MODE_ECB) {
- return;
- }
-
- $this->continuousBuffer = true;
-
- $this->_setEngine();
- }
-
- /**
- * Treat consecutive packets as if they are a discontinuous buffer.
- *
- * The default behavior.
- *
- * @see self::enableContinuousBuffer()
- * @access public
- * @internal Could, but not must, extend by the child Crypt_* class
- */
- function disableContinuousBuffer()
- {
- if ($this->mode == self::MODE_ECB) {
- return;
- }
- if (!$this->continuousBuffer) {
- return;
- }
-
- $this->continuousBuffer = false;
- $this->changed = true;
-
- $this->_setEngine();
- }
-
- /**
- * Test for engine validity
- *
- * @see self::__construct()
- * @param int $engine
- * @access public
- * @return bool
- */
- function isValidEngine($engine)
- {
- switch ($engine) {
- case self::ENGINE_OPENSSL:
- if ($this->mode == self::MODE_STREAM && $this->continuousBuffer) {
- return false;
- }
- $this->openssl_emulate_ctr = false;
- $result = $this->cipher_name_openssl &&
- extension_loaded('openssl') &&
- // PHP 5.3.0 - 5.3.2 did not let you set IV's
- version_compare(PHP_VERSION, '5.3.3', '>=');
- if (!$result) {
- return false;
- }
-
- // prior to PHP 5.4.0 OPENSSL_RAW_DATA and OPENSSL_ZERO_PADDING were not defined. instead of expecting an integer
- // $options openssl_encrypt expected a boolean $raw_data.
- if (!defined('OPENSSL_RAW_DATA')) {
- $this->openssl_options = true;
- } else {
- $this->openssl_options = OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING;
- }
-
- $methods = openssl_get_cipher_methods();
- if (in_array($this->cipher_name_openssl, $methods)) {
- return true;
- }
- // not all of openssl's symmetric cipher's support ctr. for those
- // that don't we'll emulate it
- switch ($this->mode) {
- case self::MODE_CTR:
- if (in_array($this->cipher_name_openssl_ecb, $methods)) {
- $this->openssl_emulate_ctr = true;
- return true;
- }
- }
- return false;
- case self::ENGINE_MCRYPT:
- set_error_handler(array($this, 'do_nothing'));
- $result = $this->cipher_name_mcrypt &&
- extension_loaded('mcrypt') &&
- in_array($this->cipher_name_mcrypt, mcrypt_list_algorithms());
- restore_error_handler();
- return $result;
- case self::ENGINE_INTERNAL:
- return true;
- }
-
- return false;
- }
-
- /**
- * Sets the preferred crypt engine
- *
- * Currently, $engine could be:
- *
- * - \phpseclib\Crypt\Base::ENGINE_OPENSSL [very fast]
- *
- * - \phpseclib\Crypt\Base::ENGINE_MCRYPT [fast]
- *
- * - \phpseclib\Crypt\Base::ENGINE_INTERNAL [slow]
- *
- * If the preferred crypt engine is not available the fastest available one will be used
- *
- * @see self::__construct()
- * @param int $engine
- * @access public
- */
- function setPreferredEngine($engine)
- {
- switch ($engine) {
- //case self::ENGINE_OPENSSL;
- case self::ENGINE_MCRYPT:
- case self::ENGINE_INTERNAL:
- $this->preferredEngine = $engine;
- break;
- default:
- $this->preferredEngine = self::ENGINE_OPENSSL;
- }
-
- $this->_setEngine();
- }
-
- /**
- * Returns the engine currently being utilized
- *
- * @see self::_setEngine()
- * @access public
- */
- function getEngine()
- {
- return $this->engine;
- }
-
- /**
- * Sets the engine as appropriate
- *
- * @see self::__construct()
- * @access private
- */
- function _setEngine()
- {
- $this->engine = null;
-
- $candidateEngines = array(
- $this->preferredEngine,
- self::ENGINE_OPENSSL,
- self::ENGINE_MCRYPT
- );
- foreach ($candidateEngines as $engine) {
- if ($this->isValidEngine($engine)) {
- $this->engine = $engine;
- break;
- }
- }
- if (!$this->engine) {
- $this->engine = self::ENGINE_INTERNAL;
- }
-
- if ($this->engine != self::ENGINE_MCRYPT && $this->enmcrypt) {
- set_error_handler(array($this, 'do_nothing'));
- // Closing the current mcrypt resource(s). _mcryptSetup() will, if needed,
- // (re)open them with the module named in $this->cipher_name_mcrypt
- mcrypt_module_close($this->enmcrypt);
- mcrypt_module_close($this->demcrypt);
- $this->enmcrypt = null;
- $this->demcrypt = null;
-
- if ($this->ecb) {
- mcrypt_module_close($this->ecb);
- $this->ecb = null;
- }
- restore_error_handler();
- }
-
- $this->changed = true;
- }
-
- /**
- * Encrypts a block
- *
- * Note: Must be extended by the child \phpseclib\Crypt\* class
- *
- * @access private
- * @param string $in
- * @return string
- */
- abstract function _encryptBlock($in);
-
- /**
- * Decrypts a block
- *
- * Note: Must be extended by the child \phpseclib\Crypt\* class
- *
- * @access private
- * @param string $in
- * @return string
- */
- abstract function _decryptBlock($in);
-
- /**
- * Setup the key (expansion)
- *
- * Only used if $engine == self::ENGINE_INTERNAL
- *
- * Note: Must extend by the child \phpseclib\Crypt\* class
- *
- * @see self::_setup()
- * @access private
- */
- abstract function _setupKey();
-
- /**
- * Setup the self::ENGINE_INTERNAL $engine
- *
- * (re)init, if necessary, the internal cipher $engine and flush all $buffers
- * Used (only) if $engine == self::ENGINE_INTERNAL
- *
- * _setup() will be called each time if $changed === true
- * typically this happens when using one or more of following public methods:
- *
- * - setKey()
- *
- * - setIV()
- *
- * - disableContinuousBuffer()
- *
- * - First run of encrypt() / decrypt() with no init-settings
- *
- * @see self::setKey()
- * @see self::setIV()
- * @see self::disableContinuousBuffer()
- * @access private
- * @internal _setup() is always called before en/decryption.
- * @internal Could, but not must, extend by the child Crypt_* class
- */
- function _setup()
- {
- $this->_clearBuffers();
- $this->_setupKey();
-
- if ($this->use_inline_crypt) {
- $this->_setupInlineCrypt();
- }
- }
-
- /**
- * Setup the self::ENGINE_MCRYPT $engine
- *
- * (re)init, if necessary, the (ext)mcrypt resources and flush all $buffers
- * Used (only) if $engine = self::ENGINE_MCRYPT
- *
- * _setupMcrypt() will be called each time if $changed === true
- * typically this happens when using one or more of following public methods:
- *
- * - setKey()
- *
- * - setIV()
- *
- * - disableContinuousBuffer()
- *
- * - First run of encrypt() / decrypt()
- *
- * @see self::setKey()
- * @see self::setIV()
- * @see self::disableContinuousBuffer()
- * @access private
- * @internal Could, but not must, extend by the child Crypt_* class
- */
- function _setupMcrypt()
- {
- $this->_clearBuffers();
- $this->enchanged = $this->dechanged = true;
-
- if (!isset($this->enmcrypt)) {
- static $mcrypt_modes = array(
- self::MODE_CTR => 'ctr',
- self::MODE_ECB => MCRYPT_MODE_ECB,
- self::MODE_CBC => MCRYPT_MODE_CBC,
- self::MODE_CFB => 'ncfb',
- self::MODE_CFB8 => MCRYPT_MODE_CFB,
- self::MODE_OFB => MCRYPT_MODE_NOFB,
- self::MODE_OFB8 => MCRYPT_MODE_OFB,
- self::MODE_STREAM => MCRYPT_MODE_STREAM,
- );
-
- $this->demcrypt = mcrypt_module_open($this->cipher_name_mcrypt, '', $mcrypt_modes[$this->mode], '');
- $this->enmcrypt = mcrypt_module_open($this->cipher_name_mcrypt, '', $mcrypt_modes[$this->mode], '');
-
- // we need the $ecb mcrypt resource (only) in MODE_CFB with enableContinuousBuffer()
- // to workaround mcrypt's broken ncfb implementation in buffered mode
- // see: {@link http://phpseclib.sourceforge.net/cfb-demo.phps}
- if ($this->mode == self::MODE_CFB) {
- $this->ecb = mcrypt_module_open($this->cipher_name_mcrypt, '', MCRYPT_MODE_ECB, '');
- }
- } // else should mcrypt_generic_deinit be called?
-
- if ($this->mode == self::MODE_CFB) {
- mcrypt_generic_init($this->ecb, $this->key, str_repeat("\0", $this->block_size));
- }
- }
-
- /**
- * Pads a string
- *
- * Pads a string using the RSA PKCS padding standards so that its length is a multiple of the blocksize.
- * $this->block_size - (strlen($text) % $this->block_size) bytes are added, each of which is equal to
- * chr($this->block_size - (strlen($text) % $this->block_size)
- *
- * If padding is disabled and $text is not a multiple of the blocksize, the string will be padded regardless
- * and padding will, hence forth, be enabled.
- *
- * @see self::_unpad()
- * @param string $text
- * @access private
- * @return string
- */
- function _pad($text)
- {
- $length = strlen($text);
-
- if (!$this->padding) {
- if ($length % $this->block_size == 0) {
- return $text;
- } else {
- user_error("The plaintext's length ($length) is not a multiple of the block size ({$this->block_size})");
- $this->padding = true;
- }
- }
-
- $pad = $this->block_size - ($length % $this->block_size);
-
- return str_pad($text, $length + $pad, chr($pad));
- }
-
- /**
- * Unpads a string.
- *
- * If padding is enabled and the reported padding length is invalid the encryption key will be assumed to be wrong
- * and false will be returned.
- *
- * @see self::_pad()
- * @param string $text
- * @access private
- * @return string
- */
- function _unpad($text)
- {
- if (!$this->padding) {
- return $text;
- }
-
- $length = ord($text[strlen($text) - 1]);
-
- if (!$length || $length > $this->block_size) {
- return false;
- }
-
- return substr($text, 0, -$length);
- }
-
- /**
- * Clears internal buffers
- *
- * Clearing/resetting the internal buffers is done everytime
- * after disableContinuousBuffer() or on cipher $engine (re)init
- * ie after setKey() or setIV()
- *
- * @access public
- * @internal Could, but not must, extend by the child Crypt_* class
- */
- function _clearBuffers()
- {
- $this->enbuffer = $this->debuffer = array('ciphertext' => '', 'xor' => '', 'pos' => 0, 'enmcrypt_init' => true);
-
- // mcrypt's handling of invalid's $iv:
- // $this->encryptIV = $this->decryptIV = strlen($this->iv) == $this->block_size ? $this->iv : str_repeat("\0", $this->block_size);
- $this->encryptIV = $this->decryptIV = str_pad(substr($this->iv, 0, $this->block_size), $this->block_size, "\0");
-
- if (!$this->skip_key_adjustment) {
- $this->key = str_pad(substr($this->key, 0, $this->key_length), $this->key_length, "\0");
- }
- }
-
- /**
- * String Shift
- *
- * Inspired by array_shift
- *
- * @param string $string
- * @param int $index
- * @access private
- * @return string
- */
- function _string_shift(&$string, $index = 1)
- {
- $substr = substr($string, 0, $index);
- $string = substr($string, $index);
- return $substr;
- }
-
- /**
- * String Pop
- *
- * Inspired by array_pop
- *
- * @param string $string
- * @param int $index
- * @access private
- * @return string
- */
- function _string_pop(&$string, $index = 1)
- {
- $substr = substr($string, -$index);
- $string = substr($string, 0, -$index);
- return $substr;
- }
-
- /**
- * Increment the current string
- *
- * @see self::decrypt()
- * @see self::encrypt()
- * @param string $var
- * @access private
- */
- function _increment_str(&$var)
- {
- if (function_exists('sodium_increment')) {
- $var = strrev($var);
- sodium_increment($var);
- $var = strrev($var);
- return;
- }
-
- for ($i = 4; $i <= strlen($var); $i+= 4) {
- $temp = substr($var, -$i, 4);
- switch ($temp) {
- case "\xFF\xFF\xFF\xFF":
- $var = substr_replace($var, "\x00\x00\x00\x00", -$i, 4);
- break;
- case "\x7F\xFF\xFF\xFF":
- $var = substr_replace($var, "\x80\x00\x00\x00", -$i, 4);
- return;
- default:
- $temp = unpack('Nnum', $temp);
- $var = substr_replace($var, pack('N', $temp['num'] + 1), -$i, 4);
- return;
- }
- }
-
- $remainder = strlen($var) % 4;
-
- if ($remainder == 0) {
- return;
- }
-
- $temp = unpack('Nnum', str_pad(substr($var, 0, $remainder), 4, "\0", STR_PAD_LEFT));
- $temp = substr(pack('N', $temp['num'] + 1), -$remainder);
- $var = substr_replace($var, $temp, 0, $remainder);
- }
-
- /**
- * Setup the performance-optimized function for de/encrypt()
- *
- * Stores the created (or existing) callback function-name
- * in $this->inline_crypt
- *
- * Internally for phpseclib developers:
- *
- * _setupInlineCrypt() would be called only if:
- *
- * - $engine == self::ENGINE_INTERNAL and
- *
- * - $use_inline_crypt === true
- *
- * - each time on _setup(), after(!) _setupKey()
- *
- *
- * This ensures that _setupInlineCrypt() has always a
- * full ready2go initializated internal cipher $engine state
- * where, for example, the keys allready expanded,
- * keys/block_size calculated and such.
- *
- * It is, each time if called, the responsibility of _setupInlineCrypt():
- *
- * - to set $this->inline_crypt to a valid and fully working callback function
- * as a (faster) replacement for encrypt() / decrypt()
- *
- * - NOT to create unlimited callback functions (for memory reasons!)
- * no matter how often _setupInlineCrypt() would be called. At some
- * point of amount they must be generic re-useable.
- *
- * - the code of _setupInlineCrypt() it self,
- * and the generated callback code,
- * must be, in following order:
- * - 100% safe
- * - 100% compatible to encrypt()/decrypt()
- * - using only php5+ features/lang-constructs/php-extensions if
- * compatibility (down to php4) or fallback is provided
- * - readable/maintainable/understandable/commented and... not-cryptic-styled-code :-)
- * - >= 10% faster than encrypt()/decrypt() [which is, by the way,
- * the reason for the existence of _setupInlineCrypt() :-)]
- * - memory-nice
- * - short (as good as possible)
- *
- * Note: - _setupInlineCrypt() is using _createInlineCryptFunction() to create the full callback function code.
- * - In case of using inline crypting, _setupInlineCrypt() must extend by the child \phpseclib\Crypt\* class.
- * - The following variable names are reserved:
- * - $_* (all variable names prefixed with an underscore)
- * - $self (object reference to it self. Do not use $this, but $self instead)
- * - $in (the content of $in has to en/decrypt by the generated code)
- * - The callback function should not use the 'return' statement, but en/decrypt'ing the content of $in only
- *
- *
- * @see self::_setup()
- * @see self::_createInlineCryptFunction()
- * @see self::encrypt()
- * @see self::decrypt()
- * @access private
- * @internal If a Crypt_* class providing inline crypting it must extend _setupInlineCrypt()
- */
- function _setupInlineCrypt()
- {
- // If, for any reason, an extending \phpseclib\Crypt\Base() \phpseclib\Crypt\* class
- // not using inline crypting then it must be ensured that: $this->use_inline_crypt = false
- // ie in the class var declaration of $use_inline_crypt in general for the \phpseclib\Crypt\* class,
- // in the constructor at object instance-time
- // or, if it's runtime-specific, at runtime
-
- $this->use_inline_crypt = false;
- }
-
- /**
- * Creates the performance-optimized function for en/decrypt()
- *
- * Internally for phpseclib developers:
- *
- * _createInlineCryptFunction():
- *
- * - merge the $cipher_code [setup'ed by _setupInlineCrypt()]
- * with the current [$this->]mode of operation code
- *
- * - create the $inline function, which called by encrypt() / decrypt()
- * as its replacement to speed up the en/decryption operations.
- *
- * - return the name of the created $inline callback function
- *
- * - used to speed up en/decryption
- *
- *
- *
- * The main reason why can speed up things [up to 50%] this way are:
- *
- * - using variables more effective then regular.
- * (ie no use of expensive arrays but integers $k_0, $k_1 ...
- * or even, for example, the pure $key[] values hardcoded)
- *
- * - avoiding 1000's of function calls of ie _encryptBlock()
- * but inlining the crypt operations.
- * in the mode of operation for() loop.
- *
- * - full loop unroll the (sometimes key-dependent) rounds
- * avoiding this way ++$i counters and runtime-if's etc...
- *
- * The basic code architectur of the generated $inline en/decrypt()
- * lambda function, in pseudo php, is:
- *
- * <code>
- * +----------------------------------------------------------------------------------------------+
- * | callback $inline = create_function: |
- * | lambda_function_0001_crypt_ECB($action, $text) |
- * | { |
- * | INSERT PHP CODE OF: |
- * | $cipher_code['init_crypt']; // general init code. |
- * | // ie: $sbox'es declarations used for |
- * | // encrypt and decrypt'ing. |
- * | |
- * | switch ($action) { |
- * | case 'encrypt': |
- * | INSERT PHP CODE OF: |
- * | $cipher_code['init_encrypt']; // encrypt sepcific init code. |
- * | ie: specified $key or $box |
- * | declarations for encrypt'ing. |
- * | |
- * | foreach ($ciphertext) { |
- * | $in = $block_size of $ciphertext; |
- * | |
- * | INSERT PHP CODE OF: |
- * | $cipher_code['encrypt_block']; // encrypt's (string) $in, which is always: |
- * | // strlen($in) == $this->block_size |
- * | // here comes the cipher algorithm in action |
- * | // for encryption. |
- * | // $cipher_code['encrypt_block'] has to |
- * | // encrypt the content of the $in variable |
- * | |
- * | $plaintext .= $in; |
- * | } |
- * | return $plaintext; |
- * | |
- * | case 'decrypt': |
- * | INSERT PHP CODE OF: |
- * | $cipher_code['init_decrypt']; // decrypt sepcific init code |
- * | ie: specified $key or $box |
- * | declarations for decrypt'ing. |
- * | foreach ($plaintext) { |
- * | $in = $block_size of $plaintext; |
- * | |
- * | INSERT PHP CODE OF: |
- * | $cipher_code['decrypt_block']; // decrypt's (string) $in, which is always |
- * | // strlen($in) == $this->block_size |
- * | // here comes the cipher algorithm in action |
- * | // for decryption. |
- * | // $cipher_code['decrypt_block'] has to |
- * | // decrypt the content of the $in variable |
- * | $ciphertext .= $in; |
- * | } |
- * | return $ciphertext; |
- * | } |
- * | } |
- * +----------------------------------------------------------------------------------------------+
- * </code>
- *
- * See also the \phpseclib\Crypt\*::_setupInlineCrypt()'s for
- * productive inline $cipher_code's how they works.
- *
- * Structure of:
- * <code>
- * $cipher_code = array(
- * 'init_crypt' => (string) '', // optional
- * 'init_encrypt' => (string) '', // optional
- * 'init_decrypt' => (string) '', // optional
- * 'encrypt_block' => (string) '', // required
- * 'decrypt_block' => (string) '' // required
- * );
- * </code>
- *
- * @see self::_setupInlineCrypt()
- * @see self::encrypt()
- * @see self::decrypt()
- * @param array $cipher_code
- * @access private
- * @return string (the name of the created callback function)
- */
- function _createInlineCryptFunction($cipher_code)
- {
- $block_size = $this->block_size;
-
- // optional
- $init_crypt = isset($cipher_code['init_crypt']) ? $cipher_code['init_crypt'] : '';
- $init_encrypt = isset($cipher_code['init_encrypt']) ? $cipher_code['init_encrypt'] : '';
- $init_decrypt = isset($cipher_code['init_decrypt']) ? $cipher_code['init_decrypt'] : '';
- // required
- $encrypt_block = $cipher_code['encrypt_block'];
- $decrypt_block = $cipher_code['decrypt_block'];
-
- // Generating mode of operation inline code,
- // merged with the $cipher_code algorithm
- // for encrypt- and decryption.
- switch ($this->mode) {
- case self::MODE_ECB:
- $encrypt = $init_encrypt . '
- $_ciphertext = "";
- $_plaintext_len = strlen($_text);
-
- for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') {
- $in = substr($_text, $_i, '.$block_size.');
- '.$encrypt_block.'
- $_ciphertext.= $in;
- }
-
- return $_ciphertext;
- ';
-
- $decrypt = $init_decrypt . '
- $_plaintext = "";
- $_text = str_pad($_text, strlen($_text) + ('.$block_size.' - strlen($_text) % '.$block_size.') % '.$block_size.', chr(0));
- $_ciphertext_len = strlen($_text);
-
- for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') {
- $in = substr($_text, $_i, '.$block_size.');
- '.$decrypt_block.'
- $_plaintext.= $in;
- }
-
- return $self->_unpad($_plaintext);
- ';
- break;
- case self::MODE_CTR:
- $encrypt = $init_encrypt . '
- $_ciphertext = "";
- $_plaintext_len = strlen($_text);
- $_xor = $self->encryptIV;
- $_buffer = &$self->enbuffer;
- if (strlen($_buffer["ciphertext"])) {
- for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') {
- $_block = substr($_text, $_i, '.$block_size.');
- if (strlen($_block) > strlen($_buffer["ciphertext"])) {
- $in = $_xor;
- '.$encrypt_block.'
- $self->_increment_str($_xor);
- $_buffer["ciphertext"].= $in;
- }
- $_key = $self->_string_shift($_buffer["ciphertext"], '.$block_size.');
- $_ciphertext.= $_block ^ $_key;
- }
- } else {
- for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') {
- $_block = substr($_text, $_i, '.$block_size.');
- $in = $_xor;
- '.$encrypt_block.'
- $self->_increment_str($_xor);
- $_key = $in;
- $_ciphertext.= $_block ^ $_key;
- }
- }
- if ($self->continuousBuffer) {
- $self->encryptIV = $_xor;
- if ($_start = $_plaintext_len % '.$block_size.') {
- $_buffer["ciphertext"] = substr($_key, $_start) . $_buffer["ciphertext"];
- }
- }
-
- return $_ciphertext;
- ';
-
- $decrypt = $init_encrypt . '
- $_plaintext = "";
- $_ciphertext_len = strlen($_text);
- $_xor = $self->decryptIV;
- $_buffer = &$self->debuffer;
-
- if (strlen($_buffer["ciphertext"])) {
- for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') {
- $_block = substr($_text, $_i, '.$block_size.');
- if (strlen($_block) > strlen($_buffer["ciphertext"])) {
- $in = $_xor;
- '.$encrypt_block.'
- $self->_increment_str($_xor);
- $_buffer["ciphertext"].= $in;
- }
- $_key = $self->_string_shift($_buffer["ciphertext"], '.$block_size.');
- $_plaintext.= $_block ^ $_key;
- }
- } else {
- for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') {
- $_block = substr($_text, $_i, '.$block_size.');
- $in = $_xor;
- '.$encrypt_block.'
- $self->_increment_str($_xor);
- $_key = $in;
- $_plaintext.= $_block ^ $_key;
- }
- }
- if ($self->continuousBuffer) {
- $self->decryptIV = $_xor;
- if ($_start = $_ciphertext_len % '.$block_size.') {
- $_buffer["ciphertext"] = substr($_key, $_start) . $_buffer["ciphertext"];
- }
- }
-
- return $_plaintext;
- ';
- break;
- case self::MODE_CFB:
- $encrypt = $init_encrypt . '
- $_ciphertext = "";
- $_buffer = &$self->enbuffer;
-
- if ($self->continuousBuffer) {
- $_iv = &$self->encryptIV;
- $_pos = &$_buffer["pos"];
- } else {
- $_iv = $self->encryptIV;
- $_pos = 0;
- }
- $_len = strlen($_text);
- $_i = 0;
- if ($_pos) {
- $_orig_pos = $_pos;
- $_max = '.$block_size.' - $_pos;
- if ($_len >= $_max) {
- $_i = $_max;
- $_len-= $_max;
- $_pos = 0;
- } else {
- $_i = $_len;
- $_pos+= $_len;
- $_len = 0;
- }
- $_ciphertext = substr($_iv, $_orig_pos) ^ $_text;
- $_iv = substr_replace($_iv, $_ciphertext, $_orig_pos, $_i);
- }
- while ($_len >= '.$block_size.') {
- $in = $_iv;
- '.$encrypt_block.';
- $_iv = $in ^ substr($_text, $_i, '.$block_size.');
- $_ciphertext.= $_iv;
- $_len-= '.$block_size.';
- $_i+= '.$block_size.';
- }
- if ($_len) {
- $in = $_iv;
- '.$encrypt_block.'
- $_iv = $in;
- $_block = $_iv ^ substr($_text, $_i);
- $_iv = substr_replace($_iv, $_block, 0, $_len);
- $_ciphertext.= $_block;
- $_pos = $_len;
- }
- return $_ciphertext;
- ';
-
- $decrypt = $init_encrypt . '
- $_plaintext = "";
- $_buffer = &$self->debuffer;
-
- if ($self->continuousBuffer) {
- $_iv = &$self->decryptIV;
- $_pos = &$_buffer["pos"];
- } else {
- $_iv = $self->decryptIV;
- $_pos = 0;
- }
- $_len = strlen($_text);
- $_i = 0;
- if ($_pos) {
- $_orig_pos = $_pos;
- $_max = '.$block_size.' - $_pos;
- if ($_len >= $_max) {
- $_i = $_max;
- $_len-= $_max;
- $_pos = 0;
- } else {
- $_i = $_len;
- $_pos+= $_len;
- $_len = 0;
- }
- $_plaintext = substr($_iv, $_orig_pos) ^ $_text;
- $_iv = substr_replace($_iv, substr($_text, 0, $_i), $_orig_pos, $_i);
- }
- while ($_len >= '.$block_size.') {
- $in = $_iv;
- '.$encrypt_block.'
- $_iv = $in;
- $cb = substr($_text, $_i, '.$block_size.');
- $_plaintext.= $_iv ^ $cb;
- $_iv = $cb;
- $_len-= '.$block_size.';
- $_i+= '.$block_size.';
- }
- if ($_len) {
- $in = $_iv;
- '.$encrypt_block.'
- $_iv = $in;
- $_plaintext.= $_iv ^ substr($_text, $_i);
- $_iv = substr_replace($_iv, substr($_text, $_i), 0, $_len);
- $_pos = $_len;
- }
-
- return $_plaintext;
- ';
- break;
- case self::MODE_CFB8:
- $encrypt = $init_encrypt . '
- $_ciphertext = "";
- $_len = strlen($_text);
- $_iv = $self->encryptIV;
-
- for ($_i = 0; $_i < $_len; ++$_i) {
- $in = $_iv;
- '.$encrypt_block.'
- $_ciphertext.= ($_c = $_text[$_i] ^ $in);
- $_iv = substr($_iv, 1) . $_c;
- }
-
- if ($self->continuousBuffer) {
- if ($_len >= '.$block_size.') {
- $self->encryptIV = substr($_ciphertext, -'.$block_size.');
- } else {
- $self->encryptIV = substr($self->encryptIV, $_len - '.$block_size.') . substr($_ciphertext, -$_len);
- }
- }
-
- return $_ciphertext;
- ';
- $decrypt = $init_encrypt . '
- $_plaintext = "";
- $_len = strlen($_text);
- $_iv = $self->decryptIV;
-
- for ($_i = 0; $_i < $_len; ++$_i) {
- $in = $_iv;
- '.$encrypt_block.'
- $_plaintext.= $_text[$_i] ^ $in;
- $_iv = substr($_iv, 1) . $_text[$_i];
- }
-
- if ($self->continuousBuffer) {
- if ($_len >= '.$block_size.') {
- $self->decryptIV = substr($_text, -'.$block_size.');
- } else {
- $self->decryptIV = substr($self->decryptIV, $_len - '.$block_size.') . substr($_text, -$_len);
- }
- }
-
- return $_plaintext;
- ';
- break;
- case self::MODE_OFB8:
- $encrypt = $init_encrypt . '
- $_ciphertext = "";
- $_len = strlen($_text);
- $_iv = $self->encryptIV;
-
- for ($_i = 0; $_i < $_len; ++$_i) {
- $in = $_iv;
- '.$encrypt_block.'
- $_ciphertext.= $_text[$_i] ^ $in;
- $_iv = substr($_iv, 1) . $in[0];
- }
-
- if ($self->continuousBuffer) {
- $self->encryptIV = $_iv;
- }
-
- return $_ciphertext;
- ';
- $decrypt = $init_encrypt . '
- $_plaintext = "";
- $_len = strlen($_text);
- $_iv = $self->decryptIV;
-
- for ($_i = 0; $_i < $_len; ++$_i) {
- $in = $_iv;
- '.$encrypt_block.'
- $_plaintext.= $_text[$_i] ^ $in;
- $_iv = substr($_iv, 1) . $in[0];
- }
-
- if ($self->continuousBuffer) {
- $self->decryptIV = $_iv;
- }
-
- return $_plaintext;
- ';
- break;
- case self::MODE_OFB:
- $encrypt = $init_encrypt . '
- $_ciphertext = "";
- $_plaintext_len = strlen($_text);
- $_xor = $self->encryptIV;
- $_buffer = &$self->enbuffer;
-
- if (strlen($_buffer["xor"])) {
- for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') {
- $_block = substr($_text, $_i, '.$block_size.');
- if (strlen($_block) > strlen($_buffer["xor"])) {
- $in = $_xor;
- '.$encrypt_block.'
- $_xor = $in;
- $_buffer["xor"].= $_xor;
- }
- $_key = $self->_string_shift($_buffer["xor"], '.$block_size.');
- $_ciphertext.= $_block ^ $_key;
- }
- } else {
- for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') {
- $in = $_xor;
- '.$encrypt_block.'
- $_xor = $in;
- $_ciphertext.= substr($_text, $_i, '.$block_size.') ^ $_xor;
- }
- $_key = $_xor;
- }
- if ($self->continuousBuffer) {
- $self->encryptIV = $_xor;
- if ($_start = $_plaintext_len % '.$block_size.') {
- $_buffer["xor"] = substr($_key, $_start) . $_buffer["xor"];
- }
- }
- return $_ciphertext;
- ';
-
- $decrypt = $init_encrypt . '
- $_plaintext = "";
- $_ciphertext_len = strlen($_text);
- $_xor = $self->decryptIV;
- $_buffer = &$self->debuffer;
-
- if (strlen($_buffer["xor"])) {
- for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') {
- $_block = substr($_text, $_i, '.$block_size.');
- if (strlen($_block) > strlen($_buffer["xor"])) {
- $in = $_xor;
- '.$encrypt_block.'
- $_xor = $in;
- $_buffer["xor"].= $_xor;
- }
- $_key = $self->_string_shift($_buffer["xor"], '.$block_size.');
- $_plaintext.= $_block ^ $_key;
- }
- } else {
- for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') {
- $in = $_xor;
- '.$encrypt_block.'
- $_xor = $in;
- $_plaintext.= substr($_text, $_i, '.$block_size.') ^ $_xor;
- }
- $_key = $_xor;
- }
- if ($self->continuousBuffer) {
- $self->decryptIV = $_xor;
- if ($_start = $_ciphertext_len % '.$block_size.') {
- $_buffer["xor"] = substr($_key, $_start) . $_buffer["xor"];
- }
- }
- return $_plaintext;
- ';
- break;
- case self::MODE_STREAM:
- $encrypt = $init_encrypt . '
- $_ciphertext = "";
- '.$encrypt_block.'
- return $_ciphertext;
- ';
- $decrypt = $init_decrypt . '
- $_plaintext = "";
- '.$decrypt_block.'
- return $_plaintext;
- ';
- break;
- // case self::MODE_CBC:
- default:
- $encrypt = $init_encrypt . '
- $_ciphertext = "";
- $_plaintext_len = strlen($_text);
-
- $in = $self->encryptIV;
-
- for ($_i = 0; $_i < $_plaintext_len; $_i+= '.$block_size.') {
- $in = substr($_text, $_i, '.$block_size.') ^ $in;
- '.$encrypt_block.'
- $_ciphertext.= $in;
- }
-
- if ($self->continuousBuffer) {
- $self->encryptIV = $in;
- }
-
- return $_ciphertext;
- ';
-
- $decrypt = $init_decrypt . '
- $_plaintext = "";
- $_text = str_pad($_text, strlen($_text) + ('.$block_size.' - strlen($_text) % '.$block_size.') % '.$block_size.', chr(0));
- $_ciphertext_len = strlen($_text);
-
- $_iv = $self->decryptIV;
-
- for ($_i = 0; $_i < $_ciphertext_len; $_i+= '.$block_size.') {
- $in = $_block = substr($_text, $_i, '.$block_size.');
- '.$decrypt_block.'
- $_plaintext.= $in ^ $_iv;
- $_iv = $_block;
- }
-
- if ($self->continuousBuffer) {
- $self->decryptIV = $_iv;
- }
-
- return $self->_unpad($_plaintext);
- ';
- break;
- }
-
- // Create the $inline function and return its name as string. Ready to run!
- eval('$func = function ($_action, &$self, $_text) { ' . $init_crypt . 'if ($_action == "encrypt") { ' . $encrypt . ' } else { ' . $decrypt . ' } };');
- return $func;
- }
-
- /**
- * Holds the lambda_functions table (classwide)
- *
- * Each name of the lambda function, created from
- * _setupInlineCrypt() && _createInlineCryptFunction()
- * is stored, classwide (!), here for reusing.
- *
- * The string-based index of $function is a classwide
- * unique value representing, at least, the $mode of
- * operation (or more... depends of the optimizing level)
- * for which $mode the lambda function was created.
- *
- * @access private
- * @return array &$functions
- */
- function &_getLambdaFunctions()
- {
- static $functions = array();
- return $functions;
- }
-
- /**
- * Generates a digest from $bytes
- *
- * @see self::_setupInlineCrypt()
- * @access private
- * @param string $bytes
- * @return string
- */
- function _hashInlineCryptFunction($bytes)
- {
- if (!isset(self::$WHIRLPOOL_AVAILABLE)) {
- self::$WHIRLPOOL_AVAILABLE = extension_loaded('hash') && in_array('whirlpool', hash_algos());
- }
-
- $result = '';
- $hash = $bytes;
-
- switch (true) {
- case self::$WHIRLPOOL_AVAILABLE:
- foreach (str_split($bytes, 64) as $t) {
- $hash = hash('whirlpool', $hash, true);
- $result .= $t ^ $hash;
- }
- return $result . hash('whirlpool', $hash, true);
- default:
- $len = strlen($bytes);
- for ($i = 0; $i < $len; $i+=20) {
- $t = substr($bytes, $i, 20);
- $hash = pack('H*', sha1($hash));
- $result .= $t ^ $hash;
- }
- return $result . pack('H*', sha1($hash));
- }
- }
-
- /**
- * Convert float to int
- *
- * On ARM CPUs converting floats to ints doesn't always work
- *
- * @access private
- * @param string $x
- * @return int
- */
- function safe_intval($x)
- {
- if (is_int($x)) {
- return $x;
- }
- return (fmod($x, 0x80000000) & 0x7FFFFFFF) |
- ((fmod(floor($x / 0x80000000), 2) & 1) << 31);
- }
-
- /**
- * eval()'able string for in-line float to int
- *
- * @access private
- * @return string
- */
- function safe_intval_inline()
- {
- if (CRYPT_BASE_USE_REG_INTVAL) {
- return PHP_INT_SIZE == 4 ? 'intval(%s)' : '%s';
- }
-
- $safeint = '(is_int($temp = %s) ? $temp : (fmod($temp, 0x80000000) & 0x7FFFFFFF) | ';
- return $safeint . '((fmod(floor($temp / 0x80000000), 2) & 1) << 31))';
- }
-
- /**
- * Dummy error handler to suppress mcrypt errors
- *
- * @access private
- */
- function do_nothing()
- {
- }
-
- /**
- * Is the continuous buffer enabled?
- *
- * @access public
- * @return boolean
- */
- function continuousBufferEnabled()
- {
- return $this->continuousBuffer;
- }
-}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Blowfish.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Blowfish.php
index 346c064b8..998cf8bb3 100644
--- a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Blowfish.php
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Blowfish.php
@@ -59,7 +59,7 @@
*
* This explains 3 of the 4 _encryptBlock() implementations. the last _encryptBlock()
* implementation can best be understood by doing Ctrl + F and searching for where
- * CRYPT_BASE_USE_REG_INTVAL is defined.
+ * self::$use_reg_intval is defined.
*
* # phpseclib's three different _setupKey() implementations
*
@@ -97,7 +97,7 @@
* <?php
* include 'vendor/autoload.php';
*
- * $blowfish = new \phpseclib\Crypt\Blowfish();
+ * $blowfish = new \phpseclib3\Crypt\Blowfish('ctr');
*
* $blowfish->setKey('12345678901234567890123456789012');
*
@@ -107,8 +107,6 @@
* ?>
* </code>
*
- * @category Crypt
- * @package Blowfish
* @author Jim Wigginton <terrafrost@php.net>
* @author Hans-Juergen Petrich <petrich@tronic-media.com>
* @copyright 2007 Jim Wigginton
@@ -116,63 +114,50 @@
* @link http://phpseclib.sourceforge.net
*/
-namespace phpseclib\Crypt;
+namespace phpseclib3\Crypt;
+
+use phpseclib3\Crypt\Common\BlockCipher;
/**
* Pure-PHP implementation of Blowfish.
*
- * @package Blowfish
* @author Jim Wigginton <terrafrost@php.net>
* @author Hans-Juergen Petrich <petrich@tronic-media.com>
- * @access public
*/
-class Blowfish extends Base
+class Blowfish extends BlockCipher
{
/**
* Block Length of the cipher
*
- * @see \phpseclib\Crypt\Base::block_size
+ * @see Common\SymmetricKey::block_size
* @var int
- * @access private
*/
- var $block_size = 8;
+ protected $block_size = 8;
/**
* The mcrypt specific name of the cipher
*
- * @see \phpseclib\Crypt\Base::cipher_name_mcrypt
+ * @see Common\SymmetricKey::cipher_name_mcrypt
* @var string
- * @access private
*/
- var $cipher_name_mcrypt = 'blowfish';
+ protected $cipher_name_mcrypt = 'blowfish';
/**
* Optimizing value while CFB-encrypting
*
- * @see \phpseclib\Crypt\Base::cfb_init_len
+ * @see Common\SymmetricKey::cfb_init_len
* @var int
- * @access private
- */
- var $cfb_init_len = 500;
-
- /**
- * SHA512 Object
- *
- * @see self::bcrypt_pbkdf
- * @var object
- * @access private
*/
- var $sha512;
+ protected $cfb_init_len = 500;
/**
- * The fixed subkeys boxes ($sbox0 - $sbox3) with 256 entries each
+ * The fixed subkeys boxes
*
- * S-Box 0
+ * S-Box
*
- * @access private
* @var array
*/
- var $sbox0 = array(
+ private static $sbox = [
0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
@@ -204,16 +189,8 @@ class Blowfish extends Base
0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
- 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a
- );
+ 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a,
- /**
- * S-Box 1
- *
- * @access private
- * @var array
- */
- var $sbox1 = array(
0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
@@ -245,16 +222,8 @@ class Blowfish extends Base
0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
- 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7
- );
+ 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7,
- /**
- * S-Box 2
- *
- * @access private
- * @var array
- */
- var $sbox2 = array(
0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
@@ -286,16 +255,8 @@ class Blowfish extends Base
0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
- 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0
- );
+ 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0,
- /**
- * S-Box 3
- *
- * @access private
- * @var array
- */
- var $sbox3 = array(
0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
@@ -328,19 +289,18 @@ class Blowfish extends Base
0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6
- );
+ ];
/**
* P-Array consists of 18 32-bit subkeys
*
* @var array
- * @access private
*/
- var $parray = array(
+ private static $parray = [
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0,
0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b
- );
+ ];
/**
* The BCTX-working Array
@@ -348,64 +308,41 @@ class Blowfish extends Base
* Holds the expanded key [p] and the key-depended s-boxes [sb]
*
* @var array
- * @access private
*/
- var $bctx;
+ private $bctx;
/**
* Holds the last used key
*
* @var array
- * @access private
*/
- var $kl;
+ private $kl;
/**
* The Key Length (in bytes)
- *
- * @see \phpseclib\Crypt\Base::setKeyLength()
- * @var int
- * @access private
- * @internal The max value is 256 / 8 = 32, the min value is 128 / 8 = 16. Exists in conjunction with $Nk
+ * {@internal The max value is 256 / 8 = 32, the min value is 128 / 8 = 16. Exists in conjunction with $Nk
* because the encryption / decryption / key schedule creation requires this number and not $key_length. We could
* derive this from $key_length or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu
- * of that, we'll just precompute it once.
+ * of that, we'll just precompute it once.}
+ *
+ * @see Common\SymmetricKey::setKeyLength()
+ * @var int
*/
- var $key_length = 16;
+ protected $key_length = 16;
/**
* Default Constructor.
*
- * Determines whether or not the mcrypt extension should be used.
- *
- * $mode could be:
- *
- * - CRYPT_MODE_ECB
- *
- * - CRYPT_MODE_CBC
- *
- * - CRYPT_MODE_CTR
- *
- * - CRYPT_MODE_CFB
- *
- * - CRYPT_MODE_OFB
- *
- * (or the alias constants of the chosen cipher, for example for AES: CRYPT_AES_MODE_ECB or CRYPT_AES_MODE_CBC ...)
- *
- * If not explicitly set, CRYPT_MODE_CBC will be used.
- *
- * @param int $mode
- * @access public
+ * @param string $mode
+ * @throws \InvalidArgumentException if an invalid / unsupported mode is provided
*/
- function __construct($mode = self::MODE_CBC)
+ public function __construct($mode)
{
parent::__construct($mode);
- $this->sbox0 = array_map('intval', $this->sbox0);
- $this->sbox1 = array_map('intval', $this->sbox1);
- $this->sbox2 = array_map('intval', $this->sbox2);
- $this->sbox3 = array_map('intval', $this->sbox3);
- $this->parray = array_map('intval', $this->parray);
+ if ($this->mode == self::MODE_STREAM) {
+ throw new \InvalidArgumentException('Block ciphers cannot be ran in stream mode');
+ }
}
/**
@@ -413,78 +350,65 @@ class Blowfish extends Base
*
* Key lengths can be between 32 and 448 bits.
*
- * @access public
* @param int $length
*/
- function setKeyLength($length)
+ public function setKeyLength($length)
{
- if ($length < 32) {
- $this->key_length = 4;
- } elseif ($length > 448) {
- $this->key_length = 56;
- } else {
- $this->key_length = $length >> 3;
+ if ($length < 32 || $length > 448) {
+ throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys of sizes between 32 and 448 bits are supported');
}
+ $this->key_length = $length >> 3;
+
parent::setKeyLength($length);
}
/**
* Test for engine validity
*
- * This is mainly just a wrapper to set things up for \phpseclib\Crypt\Base::isValidEngine()
+ * This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine()
*
- * @see \phpseclib\Crypt\Base::isValidEngine()
+ * @see Common\SymmetricKey::isValidEngine()
* @param int $engine
- * @access public
* @return bool
*/
- function isValidEngine($engine)
+ protected function isValidEngineHelper($engine)
{
if ($engine == self::ENGINE_OPENSSL) {
+ if ($this->key_length < 16) {
+ return false;
+ }
// quoting https://www.openssl.org/news/openssl-3.0-notes.html, OpenSSL 3.0.1
// "Moved all variations of the EVP ciphers CAST5, BF, IDEA, SEED, RC2, RC4, RC5, and DES to the legacy provider"
// in theory openssl_get_cipher_methods() should catch this but, on GitHub Actions, at least, it does not
if (defined('OPENSSL_VERSION_TEXT') && version_compare(preg_replace('#OpenSSL (\d+\.\d+\.\d+) .*#', '$1', OPENSSL_VERSION_TEXT), '3.0.1', '>=')) {
return false;
}
- if (version_compare(PHP_VERSION, '5.3.7') < 0 && $this->key_length != 16) {
- return false;
- }
- if ($this->key_length < 16) {
- return false;
- }
$this->cipher_name_openssl_ecb = 'bf-ecb';
- $this->cipher_name_openssl = 'bf-' . $this->_openssl_translate_mode();
+ $this->cipher_name_openssl = 'bf-' . $this->openssl_translate_mode();
}
- return parent::isValidEngine($engine);
+ return parent::isValidEngineHelper($engine);
}
/**
* Setup the key (expansion)
*
- * @see \phpseclib\Crypt\Base::_setupKey()
- * @access private
+ * @see Common\SymmetricKey::_setupKey()
*/
- function _setupKey()
+ protected function setupKey()
{
if (isset($this->kl['key']) && $this->key === $this->kl['key']) {
// already expanded
return;
}
- $this->kl = array('key' => $this->key);
+ $this->kl = ['key' => $this->key];
/* key-expanding p[] and S-Box building sb[] */
- $this->bctx = array(
- 'p' => array(),
- 'sb' => array(
- $this->sbox0,
- $this->sbox1,
- $this->sbox2,
- $this->sbox3
- )
- );
+ $this->bctx = [
+ 'p' => [],
+ 'sb' => self::$sbox
+ ];
// unpack binary string in unsigned chars
$key = array_values(unpack('C*', $this->key));
@@ -498,27 +422,40 @@ class Blowfish extends Base
$j = 0;
}
}
- $this->bctx['p'][] = $this->parray[$i] ^ intval($data);
+ $this->bctx['p'][] = self::$parray[$i] ^ intval($data);
}
// encrypt the zero-string, replace P1 and P2 with the encrypted data,
// encrypt P3 and P4 with the new P1 and P2, do it with all P-array and subkeys
$data = "\0\0\0\0\0\0\0\0";
for ($i = 0; $i < 18; $i += 2) {
- list($l, $r) = array_values(unpack('N*', $data = $this->_encryptBlock($data)));
+ list($l, $r) = array_values(unpack('N*', $data = $this->encryptBlock($data)));
$this->bctx['p'][$i ] = $l;
$this->bctx['p'][$i + 1] = $r;
}
- for ($i = 0; $i < 4; ++$i) {
+ for ($i = 0; $i < 0x400; $i += 0x100) {
for ($j = 0; $j < 256; $j += 2) {
- list($l, $r) = array_values(unpack('N*', $data = $this->_encryptBlock($data)));
- $this->bctx['sb'][$i][$j ] = $l;
- $this->bctx['sb'][$i][$j + 1] = $r;
+ list($l, $r) = array_values(unpack('N*', $data = $this->encryptBlock($data)));
+ $this->bctx['sb'][$i | $j] = $l;
+ $this->bctx['sb'][$i | ($j + 1)] = $r;
}
}
}
/**
+ * Initialize Static Variables
+ */
+ protected static function initialize_static_variables()
+ {
+ if (is_float(self::$sbox[0x200])) {
+ self::$sbox = array_map('intval', self::$sbox);
+ self::$parray = array_map('intval', self::$parray);
+ }
+
+ parent::initialize_static_variables();
+ }
+
+ /**
* bcrypt
*
* @param string $sha2pass
@@ -526,35 +463,28 @@ class Blowfish extends Base
* @access private
* @return string
*/
- function _bcrypt_hash($sha2pass, $sha2salt)
+ private static function bcrypt_hash($sha2pass, $sha2salt)
{
- $p = $this->parray;
- $sbox0 = $this->sbox0;
- $sbox1 = $this->sbox1;
- $sbox2 = $this->sbox2;
- $sbox3 = $this->sbox3;
+ $p = self::$parray;
+ $sbox = self::$sbox;
$cdata = array_values(unpack('N*', 'OxychromaticBlowfishSwatDynamite'));
$sha2pass = array_values(unpack('N*', $sha2pass));
$sha2salt = array_values(unpack('N*', $sha2salt));
- $this->_expandstate($sha2salt, $sha2pass, $sbox0, $sbox1, $sbox2, $sbox3, $p);
+ self::expandstate($sha2salt, $sha2pass, $sbox, $p);
for ($i = 0; $i < 64; $i++) {
- $this->_expand0state($sha2salt, $sbox0, $sbox1, $sbox2, $sbox3, $p);
- $this->_expand0state($sha2pass, $sbox0, $sbox1, $sbox2, $sbox3, $p);
+ self::expand0state($sha2salt, $sbox, $p);
+ self::expand0state($sha2pass, $sbox, $p);
}
for ($i = 0; $i < 64; $i++) {
- for ($j = 0; $j < 8; $j+= 2) { // count($cdata) == 8
- list($cdata[$j], $cdata[$j + 1]) = $this->_encryptBlockHelperFast($cdata[$j], $cdata[$j + 1], $sbox0, $sbox1, $sbox2, $sbox3, $p);
+ for ($j = 0; $j < 8; $j += 2) { // count($cdata) == 8
+ list($cdata[$j], $cdata[$j + 1]) = self::encryptBlockHelperFast($cdata[$j], $cdata[$j + 1], $sbox, $p);
}
}
- $output = '';
- for ($i = 0; $i < count($cdata); $i++) {
- $output.= pack('L*', $cdata[$i]);
- }
- return $output;
+ return pack('V*', ...$cdata);
}
/**
@@ -565,37 +495,34 @@ class Blowfish extends Base
* @param int $keylen
* @param int $rounds
* @access public
- * @return false|string
+ * @return string
*/
- function bcrypt_pbkdf($pass, $salt, $keylen, $rounds)
+ public static function bcrypt_pbkdf($pass, $salt, $keylen, $rounds)
{
- if (PHP_INT_SIZE == 4) {
- user_error('bcrypt is far too slow to be practical on 32-bit versions of PHP');
- return false;
- }
+ self::initialize_static_variables();
- if (!isset($this->sha512)) {
- $this->sha512 = new Hash('sha512');
+ if (PHP_INT_SIZE == 4) {
+ throw new \RuntimeException('bcrypt is far too slow to be practical on 32-bit versions of PHP');
}
- $sha2pass = $this->sha512->hash($pass);
- $results = array();
+ $sha2pass = hash('sha512', $pass, true);
+ $results = [];
$count = 1;
while (32 * count($results) < $keylen) {
$countsalt = $salt . pack('N', $count++);
- $sha2salt = $this->sha512->hash($countsalt);
- $out = $tmpout = $this->_bcrypt_hash($sha2pass, $sha2salt);
+ $sha2salt = hash('sha512', $countsalt, true);
+ $out = $tmpout = self::bcrypt_hash($sha2pass, $sha2salt);
for ($i = 1; $i < $rounds; $i++) {
- $sha2salt = $this->sha512->hash($tmpout);
- $tmpout = $this->_bcrypt_hash($sha2pass, $sha2salt);
- $out^= $tmpout;
+ $sha2salt = hash('sha512', $tmpout, true);
+ $tmpout = self::bcrypt_hash($sha2pass, $sha2salt);
+ $out ^= $tmpout;
}
$results[] = $out;
}
$output = '';
for ($i = 0; $i < 32; $i++) {
foreach ($results as $result) {
- $output.= $result[$i];
+ $output .= $result[$i];
}
}
return substr($output, 0, $keylen);
@@ -606,20 +533,17 @@ class Blowfish extends Base
*
* @access private
* @param int[] $key
- * @param int[] $sbox0
- * @param int[] $sbox1
- * @param int[] $sbox2
- * @param int[] $sbox3
+ * @param int[] $sbox
* @param int[] $p
* @see self::_bcrypt_hash()
*/
- function _expand0state($key, &$sbox0, &$sbox1, &$sbox2, &$sbox3, &$p)
+ private static function expand0state(array $key, array &$sbox, array &$p)
{
// expand0state is basically the same thing as this:
- //return $this->_expandstate(array_fill(0, 16, 0), $key);
+ //return self::expandstate(array_fill(0, 16, 0), $key);
// but this separate function eliminates a bunch of XORs and array lookups
- $p = array(
+ $p = [
$p[0] ^ $key[0],
$p[1] ^ $key[1],
$p[2] ^ $key[2],
@@ -638,38 +562,23 @@ class Blowfish extends Base
$p[15] ^ $key[15],
$p[16] ^ $key[0],
$p[17] ^ $key[1]
- );
+ ];
// @codingStandardsIgnoreStart
- list( $p[0], $p[1]) = $this->_encryptBlockHelperFast( 0, 0, $sbox0, $sbox1, $sbox2, $sbox3, $p);
- list( $p[2], $p[3]) = $this->_encryptBlockHelperFast($p[ 0], $p[ 1], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- list( $p[4], $p[5]) = $this->_encryptBlockHelperFast($p[ 2], $p[ 3], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- list( $p[6], $p[7]) = $this->_encryptBlockHelperFast($p[ 4], $p[ 5], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- list( $p[8], $p[9]) = $this->_encryptBlockHelperFast($p[ 6], $p[ 7], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- list($p[10], $p[11]) = $this->_encryptBlockHelperFast($p[ 8], $p[ 9], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- list($p[12], $p[13]) = $this->_encryptBlockHelperFast($p[10], $p[11], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- list($p[14], $p[15]) = $this->_encryptBlockHelperFast($p[12], $p[13], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- list($p[16], $p[17]) = $this->_encryptBlockHelperFast($p[14], $p[15], $sbox0, $sbox1, $sbox2, $sbox3, $p);
+ list( $p[0], $p[1]) = self::encryptBlockHelperFast( 0, 0, $sbox, $p);
+ list( $p[2], $p[3]) = self::encryptBlockHelperFast($p[ 0], $p[ 1], $sbox, $p);
+ list( $p[4], $p[5]) = self::encryptBlockHelperFast($p[ 2], $p[ 3], $sbox, $p);
+ list( $p[6], $p[7]) = self::encryptBlockHelperFast($p[ 4], $p[ 5], $sbox, $p);
+ list( $p[8], $p[9]) = self::encryptBlockHelperFast($p[ 6], $p[ 7], $sbox, $p);
+ list($p[10], $p[11]) = self::encryptBlockHelperFast($p[ 8], $p[ 9], $sbox, $p);
+ list($p[12], $p[13]) = self::encryptBlockHelperFast($p[10], $p[11], $sbox, $p);
+ list($p[14], $p[15]) = self::encryptBlockHelperFast($p[12], $p[13], $sbox, $p);
+ list($p[16], $p[17]) = self::encryptBlockHelperFast($p[14], $p[15], $sbox, $p);
// @codingStandardsIgnoreEnd
- list($sbox0[0], $sbox0[1]) = $this->_encryptBlockHelperFast($p[16], $p[17], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- for ($i = 2; $i < 256; $i+= 2) {
- list($sbox0[$i], $sbox0[$i + 1]) = $this->_encryptBlockHelperFast($sbox0[$i - 2], $sbox0[$i - 1], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- }
-
- list($sbox1[0], $sbox1[1]) = $this->_encryptBlockHelperFast($sbox0[254], $sbox0[255], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- for ($i = 2; $i < 256; $i+= 2) {
- list($sbox1[$i], $sbox1[$i + 1]) = $this->_encryptBlockHelperFast($sbox1[$i - 2], $sbox1[$i - 1], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- }
-
- list($sbox2[0], $sbox2[1]) = $this->_encryptBlockHelperFast($sbox1[254], $sbox1[255], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- for ($i = 2; $i < 256; $i+= 2) {
- list($sbox2[$i], $sbox2[$i + 1]) = $this->_encryptBlockHelperFast($sbox2[$i - 2], $sbox2[$i - 1], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- }
-
- list($sbox3[0], $sbox3[1]) = $this->_encryptBlockHelperFast($sbox2[254], $sbox2[255], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- for ($i = 2; $i < 256; $i+= 2) {
- list($sbox3[$i], $sbox3[$i + 1]) = $this->_encryptBlockHelperFast($sbox3[$i - 2], $sbox3[$i - 1], $sbox0, $sbox1, $sbox2, $sbox3, $p);
+ list($sbox[0], $sbox[1]) = self::encryptBlockHelperFast($p[16], $p[17], $sbox, $p);
+ for ($i = 2; $i < 1024; $i += 2) {
+ list($sbox[$i], $sbox[$i + 1]) = self::encryptBlockHelperFast($sbox[$i - 2], $sbox[$i - 1], $sbox, $p);
}
}
@@ -679,16 +588,13 @@ class Blowfish extends Base
* @access private
* @param int[] $data
* @param int[] $key
- * @param int[] $sbox0
- * @param int[] $sbox1
- * @param int[] $sbox2
- * @param int[] $sbox3
+ * @param int[] $sbox
* @param int[] $p
* @see self::_bcrypt_hash()
*/
- function _expandstate($data, $key, &$sbox0, &$sbox1, &$sbox2, &$sbox3, &$p)
+ private static function expandstate(array $data, array $key, array &$sbox, array &$p)
{
- $p = array(
+ $p = [
$p[0] ^ $key[0],
$p[1] ^ $key[1],
$p[2] ^ $key[2],
@@ -707,64 +613,45 @@ class Blowfish extends Base
$p[15] ^ $key[15],
$p[16] ^ $key[0],
$p[17] ^ $key[1]
- );
+ ];
// @codingStandardsIgnoreStart
- list( $p[0], $p[1]) = $this->_encryptBlockHelperFast($data[ 0] , $data[ 1] , $sbox0, $sbox1, $sbox2, $sbox3, $p);
- list( $p[2], $p[3]) = $this->_encryptBlockHelperFast($data[ 2] ^ $p[ 0], $data[ 3] ^ $p[ 1], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- list( $p[4], $p[5]) = $this->_encryptBlockHelperFast($data[ 4] ^ $p[ 2], $data[ 5] ^ $p[ 3], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- list( $p[6], $p[7]) = $this->_encryptBlockHelperFast($data[ 6] ^ $p[ 4], $data[ 7] ^ $p[ 5], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- list( $p[8], $p[9]) = $this->_encryptBlockHelperFast($data[ 8] ^ $p[ 6], $data[ 9] ^ $p[ 7], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- list($p[10], $p[11]) = $this->_encryptBlockHelperFast($data[10] ^ $p[ 8], $data[11] ^ $p[ 9], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- list($p[12], $p[13]) = $this->_encryptBlockHelperFast($data[12] ^ $p[10], $data[13] ^ $p[11], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- list($p[14], $p[15]) = $this->_encryptBlockHelperFast($data[14] ^ $p[12], $data[15] ^ $p[13], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- list($p[16], $p[17]) = $this->_encryptBlockHelperFast($data[ 0] ^ $p[14], $data[ 1] ^ $p[15], $sbox0, $sbox1, $sbox2, $sbox3, $p);
+ list( $p[0], $p[1]) = self::encryptBlockHelperFast($data[ 0] , $data[ 1] , $sbox, $p);
+ list( $p[2], $p[3]) = self::encryptBlockHelperFast($data[ 2] ^ $p[ 0], $data[ 3] ^ $p[ 1], $sbox, $p);
+ list( $p[4], $p[5]) = self::encryptBlockHelperFast($data[ 4] ^ $p[ 2], $data[ 5] ^ $p[ 3], $sbox, $p);
+ list( $p[6], $p[7]) = self::encryptBlockHelperFast($data[ 6] ^ $p[ 4], $data[ 7] ^ $p[ 5], $sbox, $p);
+ list( $p[8], $p[9]) = self::encryptBlockHelperFast($data[ 8] ^ $p[ 6], $data[ 9] ^ $p[ 7], $sbox, $p);
+ list($p[10], $p[11]) = self::encryptBlockHelperFast($data[10] ^ $p[ 8], $data[11] ^ $p[ 9], $sbox, $p);
+ list($p[12], $p[13]) = self::encryptBlockHelperFast($data[12] ^ $p[10], $data[13] ^ $p[11], $sbox, $p);
+ list($p[14], $p[15]) = self::encryptBlockHelperFast($data[14] ^ $p[12], $data[15] ^ $p[13], $sbox, $p);
+ list($p[16], $p[17]) = self::encryptBlockHelperFast($data[ 0] ^ $p[14], $data[ 1] ^ $p[15], $sbox, $p);
// @codingStandardsIgnoreEnd
- list($sbox0[0], $sbox0[1]) = $this->_encryptBlockHelperFast($data[2] ^ $p[16], $data[3] ^ $p[17], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- for ($i = 2, $j = 4; $i < 256; $i+= 2, $j = ($j + 2) % 16) { // instead of 16 maybe count($data) would be better?
- list($sbox0[$i], $sbox0[$i + 1]) = $this->_encryptBlockHelperFast($data[$j] ^ $sbox0[$i - 2], $data[$j + 1] ^ $sbox0[$i - 1], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- }
-
- list($sbox1[0], $sbox1[1]) = $this->_encryptBlockHelperFast($data[2] ^ $sbox0[254], $data[3] ^ $sbox0[255], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- for ($i = 2, $j = 4; $i < 256; $i+= 2, $j = ($j + 2) % 16) {
- list($sbox1[$i], $sbox1[$i + 1]) = $this->_encryptBlockHelperFast($data[$j] ^ $sbox1[$i - 2], $data[$j + 1] ^ $sbox1[$i - 1], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- }
-
- list($sbox2[0], $sbox2[1]) = $this->_encryptBlockHelperFast($data[2] ^ $sbox1[254], $data[3] ^ $sbox1[255], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- for ($i = 2, $j = 4; $i < 256; $i+= 2, $j = ($j + 2) % 16) {
- list($sbox2[$i], $sbox2[$i + 1]) = $this->_encryptBlockHelperFast($data[$j] ^ $sbox2[$i - 2], $data[$j + 1] ^ $sbox2[$i - 1], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- }
-
- list($sbox3[0], $sbox3[1]) = $this->_encryptBlockHelperFast($data[2] ^ $sbox2[254], $data[3] ^ $sbox2[255], $sbox0, $sbox1, $sbox2, $sbox3, $p);
- for ($i = 2, $j = 4; $i < 256; $i+= 2, $j = ($j + 2) % 16) {
- list($sbox3[$i], $sbox3[$i + 1]) = $this->_encryptBlockHelperFast($data[$j] ^ $sbox3[$i - 2], $data[$j + 1] ^ $sbox3[$i - 1], $sbox0, $sbox1, $sbox2, $sbox3, $p);
+ list($sbox[0], $sbox[1]) = self::encryptBlockHelperFast($data[2] ^ $p[16], $data[3] ^ $p[17], $sbox, $p);
+ for ($i = 2, $j = 4; $i < 1024; $i += 2, $j = ($j + 2) % 16) { // instead of 16 maybe count($data) would be better?
+ list($sbox[$i], $sbox[$i + 1]) = self::encryptBlockHelperFast($data[$j] ^ $sbox[$i - 2], $data[$j + 1] ^ $sbox[$i - 1], $sbox, $p);
}
}
/**
* Encrypts a block
*
- * @access private
* @param string $in
* @return string
*/
- function _encryptBlock($in)
+ protected function encryptBlock($in)
{
- $p = $this->bctx["p"];
- // extract($this->bctx["sb"], EXTR_PREFIX_ALL, "sb"); // slower
- $sb_0 = $this->bctx["sb"][0];
- $sb_1 = $this->bctx["sb"][1];
- $sb_2 = $this->bctx["sb"][2];
- $sb_3 = $this->bctx["sb"][3];
-
- $in = unpack("N*", $in);
+ $p = $this->bctx['p'];
+ // extract($this->bctx['sb'], EXTR_PREFIX_ALL, 'sb'); // slower
+ $sb = $this->bctx['sb'];
+
+ $in = unpack('N*', $in);
$l = $in[1];
$r = $in[2];
- list($r, $l) = PHP_INT_SIZE === 8 ?
- $this->_encryptBlockHelperFast($l, $r, $sb_0, $sb_1, $sb_2, $sb_3, $p) :
- $this->_encryptBlockHelperSlow($l, $r, $sb_0, $sb_1, $sb_2, $sb_3, $p);
+ list($r, $l) = PHP_INT_SIZE == 4 ?
+ self::encryptBlockHelperSlow($l, $r, $sb, $p) :
+ self::encryptBlockHelperFast($l, $r, $sb, $p);
return pack("N*", $r, $l);
}
@@ -775,34 +662,31 @@ class Blowfish extends Base
* @access private
* @param int $x0
* @param int $x1
- * @param int[] $sbox0
- * @param int[] $sbox1
- * @param int[] $sbox2
- * @param int[] $sbox3
+ * @param int[] $sbox
* @param int[] $p
* @return int[]
*/
- function _encryptBlockHelperFast($x0, $x1, $sbox0, $sbox1, $sbox2, $sbox3, $p)
+ private static function encryptBlockHelperFast($x0, $x1, array $sbox, array $p)
{
$x0 ^= $p[0];
- $x1 ^= ((($sbox0[($x0 & 0xFF000000) >> 24] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[1];
- $x0 ^= ((($sbox0[($x1 & 0xFF000000) >> 24] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[2];
- $x1 ^= ((($sbox0[($x0 & 0xFF000000) >> 24] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[3];
- $x0 ^= ((($sbox0[($x1 & 0xFF000000) >> 24] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[4];
- $x1 ^= ((($sbox0[($x0 & 0xFF000000) >> 24] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[5];
- $x0 ^= ((($sbox0[($x1 & 0xFF000000) >> 24] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[6];
- $x1 ^= ((($sbox0[($x0 & 0xFF000000) >> 24] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[7];
- $x0 ^= ((($sbox0[($x1 & 0xFF000000) >> 24] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[8];
- $x1 ^= ((($sbox0[($x0 & 0xFF000000) >> 24] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[9];
- $x0 ^= ((($sbox0[($x1 & 0xFF000000) >> 24] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[10];
- $x1 ^= ((($sbox0[($x0 & 0xFF000000) >> 24] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[11];
- $x0 ^= ((($sbox0[($x1 & 0xFF000000) >> 24] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[12];
- $x1 ^= ((($sbox0[($x0 & 0xFF000000) >> 24] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[13];
- $x0 ^= ((($sbox0[($x1 & 0xFF000000) >> 24] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[14];
- $x1 ^= ((($sbox0[($x0 & 0xFF000000) >> 24] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[15];
- $x0 ^= ((($sbox0[($x1 & 0xFF000000) >> 24] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[16];
-
- return array($x1 & 0xFFFFFFFF ^ $p[17], $x0 & 0xFFFFFFFF);
+ $x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[1];
+ $x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[2];
+ $x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[3];
+ $x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[4];
+ $x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[5];
+ $x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[6];
+ $x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[7];
+ $x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[8];
+ $x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[9];
+ $x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[10];
+ $x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[11];
+ $x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[12];
+ $x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[13];
+ $x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[14];
+ $x1 ^= ((($sbox[($x0 & 0xFF000000) >> 24] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[15];
+ $x0 ^= ((($sbox[($x1 & 0xFF000000) >> 24] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[16];
+
+ return [$x1 & 0xFFFFFFFF ^ $p[17], $x0 & 0xFFFFFFFF];
}
/**
@@ -811,183 +695,141 @@ class Blowfish extends Base
* @access private
* @param int $x0
* @param int $x1
- * @param int[] $sbox0
- * @param int[] $sbox1
- * @param int[] $sbox2
- * @param int[] $sbox3
+ * @param int[] $sbox
* @param int[] $p
* @return int[]
*/
- function _encryptBlockHelperSlow($x0, $x1, $sbox0, $sbox1, $sbox2, $sbox3, $p)
+ private static function encryptBlockHelperSlow($x0, $x1, array $sbox, array $p)
{
// -16777216 == intval(0xFF000000) on 32-bit PHP installs
- $x0^= $p[0];
- $x1^= $this->safe_intval(($this->safe_intval($sbox0[(($x0 & -16777216) >> 24) & 0xFF] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[1];
- $x0^= $this->safe_intval(($this->safe_intval($sbox0[(($x1 & -16777216) >> 24) & 0xFF] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[2];
- $x1^= $this->safe_intval(($this->safe_intval($sbox0[(($x0 & -16777216) >> 24) & 0xFF] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[3];
- $x0^= $this->safe_intval(($this->safe_intval($sbox0[(($x1 & -16777216) >> 24) & 0xFF] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[4];
- $x1^= $this->safe_intval(($this->safe_intval($sbox0[(($x0 & -16777216) >> 24) & 0xFF] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[5];
- $x0^= $this->safe_intval(($this->safe_intval($sbox0[(($x1 & -16777216) >> 24) & 0xFF] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[6];
- $x1^= $this->safe_intval(($this->safe_intval($sbox0[(($x0 & -16777216) >> 24) & 0xFF] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[7];
- $x0^= $this->safe_intval(($this->safe_intval($sbox0[(($x1 & -16777216) >> 24) & 0xFF] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[8];
- $x1^= $this->safe_intval(($this->safe_intval($sbox0[(($x0 & -16777216) >> 24) & 0xFF] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[9];
- $x0^= $this->safe_intval(($this->safe_intval($sbox0[(($x1 & -16777216) >> 24) & 0xFF] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[10];
- $x1^= $this->safe_intval(($this->safe_intval($sbox0[(($x0 & -16777216) >> 24) & 0xFF] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[11];
- $x0^= $this->safe_intval(($this->safe_intval($sbox0[(($x1 & -16777216) >> 24) & 0xFF] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[12];
- $x1^= $this->safe_intval(($this->safe_intval($sbox0[(($x0 & -16777216) >> 24) & 0xFF] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[13];
- $x0^= $this->safe_intval(($this->safe_intval($sbox0[(($x1 & -16777216) >> 24) & 0xFF] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[14];
- $x1^= $this->safe_intval(($this->safe_intval($sbox0[(($x0 & -16777216) >> 24) & 0xFF] + $sbox1[($x0 & 0xFF0000) >> 16]) ^ $sbox2[($x0 & 0xFF00) >> 8]) + $sbox3[$x0 & 0xFF]) ^ $p[15];
- $x0^= $this->safe_intval(($this->safe_intval($sbox0[(($x1 & -16777216) >> 24) & 0xFF] + $sbox1[($x1 & 0xFF0000) >> 16]) ^ $sbox2[($x1 & 0xFF00) >> 8]) + $sbox3[$x1 & 0xFF]) ^ $p[16];
-
- return array($x1 ^ $p[17], $x0);
+ $x0 ^= $p[0];
+ $x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[1];
+ $x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[2];
+ $x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[3];
+ $x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[4];
+ $x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[5];
+ $x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[6];
+ $x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[7];
+ $x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[8];
+ $x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[9];
+ $x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[10];
+ $x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[11];
+ $x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[12];
+ $x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[13];
+ $x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[14];
+ $x1 ^= self::safe_intval((self::safe_intval($sbox[(($x0 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x0 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x0 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x0 & 0xFF)]) ^ $p[15];
+ $x0 ^= self::safe_intval((self::safe_intval($sbox[(($x1 & -16777216) >> 24) & 0xFF] + $sbox[0x100 | (($x1 & 0xFF0000) >> 16)]) ^ $sbox[0x200 | (($x1 & 0xFF00) >> 8)]) + $sbox[0x300 | ($x1 & 0xFF)]) ^ $p[16];
+
+ return [$x1 ^ $p[17], $x0];
}
/**
* Decrypts a block
*
- * @access private
* @param string $in
* @return string
*/
- function _decryptBlock($in)
+ protected function decryptBlock($in)
{
- $p = $this->bctx["p"];
- $sb_0 = $this->bctx["sb"][0];
- $sb_1 = $this->bctx["sb"][1];
- $sb_2 = $this->bctx["sb"][2];
- $sb_3 = $this->bctx["sb"][3];
+ $p = $this->bctx['p'];
+ $sb = $this->bctx['sb'];
- $in = unpack("N*", $in);
+ $in = unpack('N*', $in);
$l = $in[1];
$r = $in[2];
- for ($i = 17; $i > 2; $i-= 2) {
- $l^= $p[$i];
- $r^= $this->safe_intval(($this->safe_intval($sb_0[$l >> 24 & 0xff] + $sb_1[$l >> 16 & 0xff]) ^
- $sb_2[$l >> 8 & 0xff]) +
- $sb_3[$l & 0xff]);
+ for ($i = 17; $i > 2; $i -= 2) {
+ $l ^= $p[$i];
+ $r ^= self::safe_intval((self::safe_intval($sb[$l >> 24 & 0xff] + $sb[0x100 + ($l >> 16 & 0xff)]) ^
+ $sb[0x200 + ($l >> 8 & 0xff)]) +
+ $sb[0x300 + ($l & 0xff)]);
- $r^= $p[$i - 1];
- $l^= $this->safe_intval(($this->safe_intval($sb_0[$r >> 24 & 0xff] + $sb_1[$r >> 16 & 0xff]) ^
- $sb_2[$r >> 8 & 0xff]) +
- $sb_3[$r & 0xff]);
+ $r ^= $p[$i - 1];
+ $l ^= self::safe_intval((self::safe_intval($sb[$r >> 24 & 0xff] + $sb[0x100 + ($r >> 16 & 0xff)]) ^
+ $sb[0x200 + ($r >> 8 & 0xff)]) +
+ $sb[0x300 + ($r & 0xff)]);
}
- return pack("N*", $r ^ $p[0], $l ^ $p[1]);
+ return pack('N*', $r ^ $p[0], $l ^ $p[1]);
}
/**
* Setup the performance-optimized function for de/encrypt()
*
- * @see \phpseclib\Crypt\Base::_setupInlineCrypt()
- * @access private
+ * @see Common\SymmetricKey::_setupInlineCrypt()
*/
- function _setupInlineCrypt()
+ protected function setupInlineCrypt()
{
- $lambda_functions =& self::_getLambdaFunctions();
-
- // We create max. 10 hi-optimized code for memory reason. Means: For each $key one ultra fast inline-crypt function.
- // (Currently, for Blowfish, one generated $lambda_function cost on php5.5@32bit ~100kb unfreeable mem and ~180kb on php5.5@64bit)
- // After that, we'll still create very fast optimized code but not the hi-ultimative code, for each $mode one.
- $gen_hi_opt_code = (bool)(count($lambda_functions) < 10);
-
- // Generation of a unique hash for our generated code
- $code_hash = "Crypt_Blowfish, {$this->mode}";
- if ($gen_hi_opt_code) {
- $code_hash = str_pad($code_hash, 32) . $this->_hashInlineCryptFunction($this->key);
- }
-
- $safeint = $this->safe_intval_inline();
-
- if (!isset($lambda_functions[$code_hash])) {
- switch (true) {
- case $gen_hi_opt_code:
- $p = $this->bctx['p'];
- $init_crypt = '
- static $sb_0, $sb_1, $sb_2, $sb_3;
- if (!$sb_0) {
- $sb_0 = $self->bctx["sb"][0];
- $sb_1 = $self->bctx["sb"][1];
- $sb_2 = $self->bctx["sb"][2];
- $sb_3 = $self->bctx["sb"][3];
- }
- ';
- break;
- default:
- $p = array();
- for ($i = 0; $i < 18; ++$i) {
- $p[] = '$p_' . $i;
- }
- $init_crypt = '
- list($sb_0, $sb_1, $sb_2, $sb_3) = $self->bctx["sb"];
- list(' . implode(',', $p) . ') = $self->bctx["p"];
-
- ';
+ $p = $this->bctx['p'];
+ $init_crypt = '
+ static $sb;
+ if (!$sb) {
+ $sb = $this->bctx["sb"];
}
-
- // Generating encrypt code:
- $encrypt_block = '
- $in = unpack("N*", $in);
- $l = $in[1];
- $r = $in[2];
- ';
- for ($i = 0; $i < 16; $i+= 2) {
- $encrypt_block.= '
- $l^= ' . $p[$i] . ';
- $r^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb_0[$l >> 24 & 0xff] + $sb_1[$l >> 16 & 0xff]') . ' ^
- $sb_2[$l >> 8 & 0xff]) +
- $sb_3[$l & 0xff]') . ';
-
- $r^= ' . $p[$i + 1] . ';
- $l^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb_0[$r >> 24 & 0xff] + $sb_1[$r >> 16 & 0xff]') . ' ^
- $sb_2[$r >> 8 & 0xff]) +
- $sb_3[$r & 0xff]') . ';
- ';
- }
- $encrypt_block.= '
- $in = pack("N*",
- $r ^ ' . $p[17] . ',
- $l ^ ' . $p[16] . '
- );
+ ';
+
+ $safeint = self::safe_intval_inline();
+
+ // Generating encrypt code:
+ $encrypt_block = '
+ $in = unpack("N*", $in);
+ $l = $in[1];
+ $r = $in[2];
+ ';
+ for ($i = 0; $i < 16; $i += 2) {
+ $encrypt_block .= '
+ $l^= ' . $p[$i] . ';
+ $r^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb[$l >> 24 & 0xff] + $sb[0x100 + ($l >> 16 & 0xff)]') . ' ^
+ $sb[0x200 + ($l >> 8 & 0xff)]) +
+ $sb[0x300 + ($l & 0xff)]') . ';
+
+ $r^= ' . $p[$i + 1] . ';
+ $l^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb[$r >> 24 & 0xff] + $sb[0x100 + ($r >> 16 & 0xff)]') . ' ^
+ $sb[0x200 + ($r >> 8 & 0xff)]) +
+ $sb[0x300 + ($r & 0xff)]') . ';
';
-
- // Generating decrypt code:
- $decrypt_block = '
- $in = unpack("N*", $in);
- $l = $in[1];
- $r = $in[2];
- ';
-
- for ($i = 17; $i > 2; $i-= 2) {
- $decrypt_block.= '
- $l^= ' . $p[$i] . ';
- $r^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb_0[$l >> 24 & 0xff] + $sb_1[$l >> 16 & 0xff]') . ' ^
- $sb_2[$l >> 8 & 0xff]) +
- $sb_3[$l & 0xff]') . ';
-
- $r^= ' . $p[$i - 1] . ';
- $l^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb_0[$r >> 24 & 0xff] + $sb_1[$r >> 16 & 0xff]') . ' ^
- $sb_2[$r >> 8 & 0xff]) +
- $sb_3[$r & 0xff]') . ';
- ';
- }
-
- $decrypt_block.= '
- $in = pack("N*",
- $r ^ ' . $p[0] . ',
- $l ^ ' . $p[1] . '
- );
+ }
+ $encrypt_block .= '
+ $in = pack("N*",
+ $r ^ ' . $p[17] . ',
+ $l ^ ' . $p[16] . '
+ );
+ ';
+ // Generating decrypt code:
+ $decrypt_block = '
+ $in = unpack("N*", $in);
+ $l = $in[1];
+ $r = $in[2];
+ ';
+
+ for ($i = 17; $i > 2; $i -= 2) {
+ $decrypt_block .= '
+ $l^= ' . $p[$i] . ';
+ $r^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb[$l >> 24 & 0xff] + $sb[0x100 + ($l >> 16 & 0xff)]') . ' ^
+ $sb[0x200 + ($l >> 8 & 0xff)]) +
+ $sb[0x300 + ($l & 0xff)]') . ';
+
+ $r^= ' . $p[$i - 1] . ';
+ $l^= ' . sprintf($safeint, '(' . sprintf($safeint, '$sb[$r >> 24 & 0xff] + $sb[0x100 + ($r >> 16 & 0xff)]') . ' ^
+ $sb[0x200 + ($r >> 8 & 0xff)]) +
+ $sb[0x300 + ($r & 0xff)]') . ';
';
+ }
- $lambda_functions[$code_hash] = $this->_createInlineCryptFunction(
- array(
- 'init_crypt' => $init_crypt,
- 'init_encrypt' => '',
- 'init_decrypt' => '',
- 'encrypt_block' => $encrypt_block,
- 'decrypt_block' => $decrypt_block
- )
+ $decrypt_block .= '
+ $in = pack("N*",
+ $r ^ ' . $p[0] . ',
+ $l ^ ' . $p[1] . '
);
- }
- $this->inline_crypt = $lambda_functions[$code_hash];
+ ';
+
+ $this->inline_crypt = $this->createInlineCryptFunction(
+ [
+ 'init_crypt' => $init_crypt,
+ 'init_encrypt' => '',
+ 'init_decrypt' => '',
+ 'encrypt_block' => $encrypt_block,
+ 'decrypt_block' => $decrypt_block
+ ]
+ );
}
}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/ChaCha20.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/ChaCha20.php
new file mode 100644
index 000000000..b2691b5dd
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/ChaCha20.php
@@ -0,0 +1,799 @@
+<?php
+
+/**
+ * Pure-PHP implementation of ChaCha20.
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2019 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt;
+
+use phpseclib3\Exception\BadDecryptionException;
+use phpseclib3\Exception\InsufficientSetupException;
+
+/**
+ * Pure-PHP implementation of ChaCha20.
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class ChaCha20 extends Salsa20
+{
+ /**
+ * The OpenSSL specific name of the cipher
+ *
+ * @var string
+ */
+ protected $cipher_name_openssl = 'chacha20';
+
+ /**
+ * Test for engine validity
+ *
+ * This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine()
+ *
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
+ * @param int $engine
+ * @return bool
+ */
+ protected function isValidEngineHelper($engine)
+ {
+ switch ($engine) {
+ case self::ENGINE_LIBSODIUM:
+ // PHP 7.2.0 (30 Nov 2017) added support for libsodium
+
+ // we could probably make it so that if $this->counter == 0 then the first block would be done with either OpenSSL
+ // or PHP and then subsequent blocks would then be done with libsodium but idk - it's not a high priority atm
+
+ // we could also make it so that if $this->counter == 0 and $this->continuousBuffer then do the first string
+ // with libsodium and subsequent strings with openssl or pure-PHP but again not a high priority
+ return function_exists('sodium_crypto_aead_chacha20poly1305_ietf_encrypt') &&
+ $this->key_length == 32 &&
+ (($this->usePoly1305 && !isset($this->poly1305Key) && $this->counter == 0) || $this->counter == 1) &&
+ !$this->continuousBuffer;
+ case self::ENGINE_OPENSSL:
+ // OpenSSL 1.1.0 (released 25 Aug 2016) added support for chacha20.
+ // PHP didn't support OpenSSL 1.1.0 until 7.0.19 (11 May 2017)
+
+ // if you attempt to provide openssl with a 128 bit key (as opposed to a 256 bit key) openssl will null
+ // pad the key to 256 bits and still use the expansion constant for 256-bit keys. the fact that
+ // openssl treats the IV as both the counter and nonce, however, let's us use openssl in continuous mode
+ // whereas libsodium does not
+ if ($this->key_length != 32) {
+ return false;
+ }
+ }
+
+ return parent::isValidEngineHelper($engine);
+ }
+
+ /**
+ * Encrypts a message.
+ *
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
+ * @see self::crypt()
+ * @param string $plaintext
+ * @return string $ciphertext
+ */
+ public function encrypt($plaintext)
+ {
+ $this->setup();
+
+ if ($this->engine == self::ENGINE_LIBSODIUM) {
+ return $this->encrypt_with_libsodium($plaintext);
+ }
+
+ return parent::encrypt($plaintext);
+ }
+
+ /**
+ * Decrypts a message.
+ *
+ * $this->decrypt($this->encrypt($plaintext)) == $this->encrypt($this->encrypt($plaintext)).
+ * At least if the continuous buffer is disabled.
+ *
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
+ * @see self::crypt()
+ * @param string $ciphertext
+ * @return string $plaintext
+ */
+ public function decrypt($ciphertext)
+ {
+ $this->setup();
+
+ if ($this->engine == self::ENGINE_LIBSODIUM) {
+ return $this->decrypt_with_libsodium($ciphertext);
+ }
+
+ return parent::decrypt($ciphertext);
+ }
+
+ /**
+ * Encrypts a message with libsodium
+ *
+ * @see self::encrypt()
+ * @param string $plaintext
+ * @return string $text
+ */
+ private function encrypt_with_libsodium($plaintext)
+ {
+ $params = [$plaintext, $this->aad, $this->nonce, $this->key];
+ $ciphertext = strlen($this->nonce) == 8 ?
+ sodium_crypto_aead_chacha20poly1305_encrypt(...$params) :
+ sodium_crypto_aead_chacha20poly1305_ietf_encrypt(...$params);
+ if (!$this->usePoly1305) {
+ return substr($ciphertext, 0, strlen($plaintext));
+ }
+
+ $newciphertext = substr($ciphertext, 0, strlen($plaintext));
+
+ $this->newtag = $this->usingGeneratedPoly1305Key && strlen($this->nonce) == 12 ?
+ substr($ciphertext, strlen($plaintext)) :
+ $this->poly1305($newciphertext);
+
+ return $newciphertext;
+ }
+
+ /**
+ * Decrypts a message with libsodium
+ *
+ * @see self::decrypt()
+ * @param string $ciphertext
+ * @return string $text
+ */
+ private function decrypt_with_libsodium($ciphertext)
+ {
+ $params = [$ciphertext, $this->aad, $this->nonce, $this->key];
+
+ if (isset($this->poly1305Key)) {
+ if ($this->oldtag === false) {
+ throw new InsufficientSetupException('Authentication Tag has not been set');
+ }
+ if ($this->usingGeneratedPoly1305Key && strlen($this->nonce) == 12) {
+ $plaintext = sodium_crypto_aead_chacha20poly1305_ietf_decrypt(...$params);
+ $this->oldtag = false;
+ if ($plaintext === false) {
+ throw new BadDecryptionException('Derived authentication tag and supplied authentication tag do not match');
+ }
+ return $plaintext;
+ }
+ $newtag = $this->poly1305($ciphertext);
+ if ($this->oldtag != substr($newtag, 0, strlen($this->oldtag))) {
+ $this->oldtag = false;
+ throw new BadDecryptionException('Derived authentication tag and supplied authentication tag do not match');
+ }
+ $this->oldtag = false;
+ }
+
+ $plaintext = strlen($this->nonce) == 8 ?
+ sodium_crypto_aead_chacha20poly1305_encrypt(...$params) :
+ sodium_crypto_aead_chacha20poly1305_ietf_encrypt(...$params);
+
+ return substr($plaintext, 0, strlen($ciphertext));
+ }
+
+ /**
+ * Sets the nonce.
+ *
+ * @param string $nonce
+ */
+ public function setNonce($nonce)
+ {
+ if (!is_string($nonce)) {
+ throw new \UnexpectedValueException('The nonce should be a string');
+ }
+
+ /*
+ from https://tools.ietf.org/html/rfc7539#page-7
+
+ "Note also that the original ChaCha had a 64-bit nonce and 64-bit
+ block count. We have modified this here to be more consistent with
+ recommendations in Section 3.2 of [RFC5116]."
+ */
+ switch (strlen($nonce)) {
+ case 8: // 64 bits
+ case 12: // 96 bits
+ break;
+ default:
+ throw new \LengthException('Nonce of size ' . strlen($nonce) . ' not supported by this algorithm. Only 64-bit nonces or 96-bit nonces are supported');
+ }
+
+ $this->nonce = $nonce;
+ $this->changed = true;
+ $this->setEngine();
+ }
+
+ /**
+ * Setup the self::ENGINE_INTERNAL $engine
+ *
+ * (re)init, if necessary, the internal cipher $engine
+ *
+ * _setup() will be called each time if $changed === true
+ * typically this happens when using one or more of following public methods:
+ *
+ * - setKey()
+ *
+ * - setNonce()
+ *
+ * - First run of encrypt() / decrypt() with no init-settings
+ *
+ * @see self::setKey()
+ * @see self::setNonce()
+ * @see self::disableContinuousBuffer()
+ */
+ protected function setup()
+ {
+ if (!$this->changed) {
+ return;
+ }
+
+ $this->enbuffer = $this->debuffer = ['ciphertext' => '', 'counter' => $this->counter];
+
+ $this->changed = $this->nonIVChanged = false;
+
+ if ($this->nonce === false) {
+ throw new InsufficientSetupException('No nonce has been defined');
+ }
+
+ if ($this->key === false) {
+ throw new InsufficientSetupException('No key has been defined');
+ }
+
+ if ($this->usePoly1305 && !isset($this->poly1305Key)) {
+ $this->usingGeneratedPoly1305Key = true;
+ if ($this->engine == self::ENGINE_LIBSODIUM) {
+ return;
+ }
+ $this->createPoly1305Key();
+ }
+
+ $key = $this->key;
+ if (strlen($key) == 16) {
+ $constant = 'expand 16-byte k';
+ $key .= $key;
+ } else {
+ $constant = 'expand 32-byte k';
+ }
+
+ $this->p1 = $constant . $key;
+ $this->p2 = $this->nonce;
+ if (strlen($this->nonce) == 8) {
+ $this->p2 = "\0\0\0\0" . $this->p2;
+ }
+ }
+
+ /**
+ * The quarterround function
+ *
+ * @param int $a
+ * @param int $b
+ * @param int $c
+ * @param int $d
+ */
+ protected static function quarterRound(&$a, &$b, &$c, &$d)
+ {
+ // in https://datatracker.ietf.org/doc/html/rfc7539#section-2.1 the addition,
+ // xor'ing and rotation are all on the same line so i'm keeping it on the same
+ // line here as well
+ // @codingStandardsIgnoreStart
+ $a+= $b; $d = self::leftRotate(intval($d) ^ intval($a), 16);
+ $c+= $d; $b = self::leftRotate(intval($b) ^ intval($c), 12);
+ $a+= $b; $d = self::leftRotate(intval($d) ^ intval($a), 8);
+ $c+= $d; $b = self::leftRotate(intval($b) ^ intval($c), 7);
+ // @codingStandardsIgnoreEnd
+ }
+
+ /**
+ * The doubleround function
+ *
+ * @param int $x0 (by reference)
+ * @param int $x1 (by reference)
+ * @param int $x2 (by reference)
+ * @param int $x3 (by reference)
+ * @param int $x4 (by reference)
+ * @param int $x5 (by reference)
+ * @param int $x6 (by reference)
+ * @param int $x7 (by reference)
+ * @param int $x8 (by reference)
+ * @param int $x9 (by reference)
+ * @param int $x10 (by reference)
+ * @param int $x11 (by reference)
+ * @param int $x12 (by reference)
+ * @param int $x13 (by reference)
+ * @param int $x14 (by reference)
+ * @param int $x15 (by reference)
+ */
+ protected static function doubleRound(&$x0, &$x1, &$x2, &$x3, &$x4, &$x5, &$x6, &$x7, &$x8, &$x9, &$x10, &$x11, &$x12, &$x13, &$x14, &$x15)
+ {
+ // columnRound
+ static::quarterRound($x0, $x4, $x8, $x12);
+ static::quarterRound($x1, $x5, $x9, $x13);
+ static::quarterRound($x2, $x6, $x10, $x14);
+ static::quarterRound($x3, $x7, $x11, $x15);
+ // rowRound
+ static::quarterRound($x0, $x5, $x10, $x15);
+ static::quarterRound($x1, $x6, $x11, $x12);
+ static::quarterRound($x2, $x7, $x8, $x13);
+ static::quarterRound($x3, $x4, $x9, $x14);
+ }
+
+ /**
+ * The Salsa20 hash function function
+ *
+ * On my laptop this loop unrolled / function dereferenced version of parent::salsa20 encrypts 1mb of text in
+ * 0.65s vs the 0.85s that it takes with the parent method.
+ *
+ * If we were free to assume that the host OS would always be 64-bits then the if condition in leftRotate could
+ * be eliminated and we could knock this done to 0.60s.
+ *
+ * For comparison purposes, RC4 takes 0.16s and AES in CTR mode with the Eval engine takes 0.48s.
+ * AES in CTR mode with the PHP engine takes 1.19s. Salsa20 / ChaCha20 do not benefit as much from the Eval
+ * approach due to the fact that there are a lot less variables to de-reference, fewer loops to unroll, etc
+ *
+ * @param string $x
+ */
+ protected static function salsa20($x)
+ {
+ list(, $x0, $x1, $x2, $x3, $x4, $x5, $x6, $x7, $x8, $x9, $x10, $x11, $x12, $x13, $x14, $x15) = unpack('V*', $x);
+ $z0 = $x0;
+ $z1 = $x1;
+ $z2 = $x2;
+ $z3 = $x3;
+ $z4 = $x4;
+ $z5 = $x5;
+ $z6 = $x6;
+ $z7 = $x7;
+ $z8 = $x8;
+ $z9 = $x9;
+ $z10 = $x10;
+ $z11 = $x11;
+ $z12 = $x12;
+ $z13 = $x13;
+ $z14 = $x14;
+ $z15 = $x15;
+
+ // @codingStandardsIgnoreStart
+ // columnRound
+ $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
+ $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
+ $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
+ $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
+
+ $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
+ $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
+ $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
+ $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
+
+ $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
+ $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
+ $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
+ $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
+
+ $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
+ $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
+ $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
+ $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
+
+ // rowRound
+ $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
+ $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
+ $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
+ $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
+
+ $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
+ $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
+ $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
+ $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
+
+ $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
+ $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
+ $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
+ $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
+
+ $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
+ $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
+ $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
+ $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
+
+ // columnRound
+ $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
+ $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
+ $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
+ $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
+
+ $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
+ $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
+ $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
+ $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
+
+ $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
+ $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
+ $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
+ $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
+
+ $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
+ $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
+ $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
+ $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
+
+ // rowRound
+ $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
+ $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
+ $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
+ $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
+
+ $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
+ $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
+ $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
+ $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
+
+ $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
+ $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
+ $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
+ $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
+
+ $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
+ $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
+ $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
+ $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
+
+ // columnRound
+ $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
+ $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
+ $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
+ $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
+
+ $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
+ $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
+ $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
+ $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
+
+ $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
+ $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
+ $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
+ $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
+
+ $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
+ $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
+ $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
+ $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
+
+ // rowRound
+ $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
+ $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
+ $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
+ $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
+
+ $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
+ $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
+ $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
+ $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
+
+ $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
+ $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
+ $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
+ $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
+
+ $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
+ $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
+ $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
+ $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
+
+ // columnRound
+ $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
+ $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
+ $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
+ $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
+
+ $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
+ $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
+ $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
+ $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
+
+ $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
+ $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
+ $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
+ $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
+
+ $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
+ $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
+ $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
+ $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
+
+ // rowRound
+ $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
+ $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
+ $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
+ $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
+
+ $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
+ $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
+ $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
+ $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
+
+ $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
+ $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
+ $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
+ $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
+
+ $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
+ $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
+ $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
+ $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
+
+ // columnRound
+ $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
+ $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
+ $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
+ $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
+
+ $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
+ $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
+ $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
+ $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
+
+ $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
+ $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
+ $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
+ $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
+
+ $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
+ $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
+ $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
+ $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
+
+ // rowRound
+ $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
+ $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
+ $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
+ $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
+
+ $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
+ $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
+ $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
+ $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
+
+ $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
+ $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
+ $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
+ $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
+
+ $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
+ $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
+ $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
+ $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
+
+ // columnRound
+ $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
+ $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
+ $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
+ $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
+
+ $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
+ $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
+ $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
+ $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
+
+ $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
+ $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
+ $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
+ $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
+
+ $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
+ $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
+ $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
+ $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
+
+ // rowRound
+ $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
+ $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
+ $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
+ $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
+
+ $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
+ $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
+ $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
+ $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
+
+ $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
+ $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
+ $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
+ $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
+
+ $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
+ $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
+ $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
+ $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
+
+ // columnRound
+ $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
+ $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
+ $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
+ $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
+
+ $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
+ $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
+ $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
+ $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
+
+ $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
+ $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
+ $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
+ $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
+
+ $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
+ $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
+ $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
+ $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
+
+ // rowRound
+ $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
+ $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
+ $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
+ $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
+
+ $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
+ $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
+ $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
+ $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
+
+ $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
+ $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
+ $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
+ $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
+
+ $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
+ $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
+ $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
+ $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
+
+ // columnRound
+ $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
+ $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
+ $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
+ $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
+
+ $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
+ $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
+ $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
+ $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
+
+ $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
+ $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
+ $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
+ $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
+
+ $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
+ $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
+ $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
+ $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
+
+ // rowRound
+ $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
+ $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
+ $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
+ $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
+
+ $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
+ $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
+ $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
+ $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
+
+ $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
+ $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
+ $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
+ $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
+
+ $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
+ $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
+ $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
+ $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
+
+ // columnRound
+ $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
+ $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
+ $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
+ $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
+
+ $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
+ $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
+ $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
+ $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
+
+ $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
+ $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
+ $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
+ $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
+
+ $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
+ $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
+ $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
+ $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
+
+ // rowRound
+ $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
+ $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
+ $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
+ $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
+
+ $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
+ $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
+ $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
+ $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
+
+ $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
+ $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
+ $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
+ $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
+
+ $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
+ $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
+ $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
+ $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
+
+ // columnRound
+ $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 16);
+ $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 12);
+ $x0+= $x4; $x12 = self::leftRotate(intval($x12) ^ intval($x0), 8);
+ $x8+= $x12; $x4 = self::leftRotate(intval($x4) ^ intval($x8), 7);
+
+ $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 16);
+ $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 12);
+ $x1+= $x5; $x13 = self::leftRotate(intval($x13) ^ intval($x1), 8);
+ $x9+= $x13; $x5 = self::leftRotate(intval($x5) ^ intval($x9), 7);
+
+ $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 16);
+ $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 12);
+ $x2+= $x6; $x14 = self::leftRotate(intval($x14) ^ intval($x2), 8);
+ $x10+= $x14; $x6 = self::leftRotate(intval($x6) ^ intval($x10), 7);
+
+ $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 16);
+ $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 12);
+ $x3+= $x7; $x15 = self::leftRotate(intval($x15) ^ intval($x3), 8);
+ $x11+= $x15; $x7 = self::leftRotate(intval($x7) ^ intval($x11), 7);
+
+ // rowRound
+ $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 16);
+ $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 12);
+ $x0+= $x5; $x15 = self::leftRotate(intval($x15) ^ intval($x0), 8);
+ $x10+= $x15; $x5 = self::leftRotate(intval($x5) ^ intval($x10), 7);
+
+ $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 16);
+ $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 12);
+ $x1+= $x6; $x12 = self::leftRotate(intval($x12) ^ intval($x1), 8);
+ $x11+= $x12; $x6 = self::leftRotate(intval($x6) ^ intval($x11), 7);
+
+ $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 16);
+ $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 12);
+ $x2+= $x7; $x13 = self::leftRotate(intval($x13) ^ intval($x2), 8);
+ $x8+= $x13; $x7 = self::leftRotate(intval($x7) ^ intval($x8), 7);
+
+ $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 16);
+ $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 12);
+ $x3+= $x4; $x14 = self::leftRotate(intval($x14) ^ intval($x3), 8);
+ $x9+= $x14; $x4 = self::leftRotate(intval($x4) ^ intval($x9), 7);
+ // @codingStandardsIgnoreEnd
+
+ $x0 += $z0;
+ $x1 += $z1;
+ $x2 += $z2;
+ $x3 += $z3;
+ $x4 += $z4;
+ $x5 += $z5;
+ $x6 += $z6;
+ $x7 += $z7;
+ $x8 += $z8;
+ $x9 += $z9;
+ $x10 += $z10;
+ $x11 += $z11;
+ $x12 += $z12;
+ $x13 += $z13;
+ $x14 += $z14;
+ $x15 += $z15;
+
+ return pack('V*', $x0, $x1, $x2, $x3, $x4, $x5, $x6, $x7, $x8, $x9, $x10, $x11, $x12, $x13, $x14, $x15);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/AsymmetricKey.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/AsymmetricKey.php
new file mode 100644
index 000000000..a380e43d7
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/AsymmetricKey.php
@@ -0,0 +1,581 @@
+<?php
+
+/**
+ * Base Class for all asymmetric key ciphers
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\Common;
+
+use phpseclib3\Crypt\DSA;
+use phpseclib3\Crypt\Hash;
+use phpseclib3\Crypt\RSA;
+use phpseclib3\Exception\NoKeyLoadedException;
+use phpseclib3\Exception\UnsupportedFormatException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Base Class for all asymmetric cipher classes
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class AsymmetricKey
+{
+ /**
+ * Precomputed Zero
+ *
+ * @var BigInteger
+ */
+ protected static $zero;
+
+ /**
+ * Precomputed One
+ *
+ * @var BigInteger
+ */
+ protected static $one;
+
+ /**
+ * Format of the loaded key
+ *
+ * @var string
+ */
+ protected $format;
+
+ /**
+ * Hash function
+ *
+ * @var Hash
+ */
+ protected $hash;
+
+ /**
+ * HMAC function
+ *
+ * @var Hash
+ */
+ private $hmac;
+
+ /**
+ * Supported plugins (lower case)
+ *
+ * @see self::initialize_static_variables()
+ * @var array
+ */
+ private static $plugins = [];
+
+ /**
+ * Invisible plugins
+ *
+ * @see self::initialize_static_variables()
+ * @var array
+ */
+ private static $invisiblePlugins = [];
+
+ /**
+ * Available Engines
+ *
+ * @var boolean[]
+ */
+ protected static $engines = [];
+
+ /**
+ * Key Comment
+ *
+ * @var null|string
+ */
+ private $comment;
+
+ /**
+ * @param string $type
+ * @return array|string
+ */
+ abstract public function toString($type, array $options = []);
+
+ /**
+ * The constructor
+ */
+ protected function __construct()
+ {
+ self::initialize_static_variables();
+
+ $this->hash = new Hash('sha256');
+ $this->hmac = new Hash('sha256');
+ }
+
+ /**
+ * Initialize static variables
+ */
+ protected static function initialize_static_variables()
+ {
+ if (!isset(self::$zero)) {
+ self::$zero = new BigInteger(0);
+ self::$one = new BigInteger(1);
+ }
+
+ self::loadPlugins('Keys');
+ if (static::ALGORITHM != 'RSA' && static::ALGORITHM != 'DH') {
+ self::loadPlugins('Signature');
+ }
+ }
+
+ /**
+ * Load the key
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return PublicKey|PrivateKey
+ */
+ public static function load($key, $password = false)
+ {
+ self::initialize_static_variables();
+
+ $class = new \ReflectionClass(static::class);
+ if ($class->isFinal()) {
+ throw new \RuntimeException('load() should not be called from final classes (' . static::class . ')');
+ }
+
+ $components = false;
+ foreach (self::$plugins[static::ALGORITHM]['Keys'] as $format) {
+ if (isset(self::$invisiblePlugins[static::ALGORITHM]) && in_array($format, self::$invisiblePlugins[static::ALGORITHM])) {
+ continue;
+ }
+ try {
+ $components = $format::load($key, $password);
+ } catch (\Exception $e) {
+ $components = false;
+ }
+ if ($components !== false) {
+ break;
+ }
+ }
+
+ if ($components === false) {
+ throw new NoKeyLoadedException('Unable to read key');
+ }
+
+ $components['format'] = $format;
+ $components['secret'] = isset($components['secret']) ? $components['secret'] : '';
+ $comment = isset($components['comment']) ? $components['comment'] : null;
+ $new = static::onLoad($components);
+ $new->format = $format;
+ $new->comment = $comment;
+ return $new instanceof PrivateKey ?
+ $new->withPassword($password) :
+ $new;
+ }
+
+ /**
+ * Loads a private key
+ *
+ * @return PrivateKey
+ * @param string|array $key
+ * @param string $password optional
+ */
+ public static function loadPrivateKey($key, $password = '')
+ {
+ $key = self::load($key, $password);
+ if (!$key instanceof PrivateKey) {
+ throw new NoKeyLoadedException('The key that was loaded was not a private key');
+ }
+ return $key;
+ }
+
+ /**
+ * Loads a public key
+ *
+ * @return PublicKey
+ * @param string|array $key
+ */
+ public static function loadPublicKey($key)
+ {
+ $key = self::load($key);
+ if (!$key instanceof PublicKey) {
+ throw new NoKeyLoadedException('The key that was loaded was not a public key');
+ }
+ return $key;
+ }
+
+ /**
+ * Loads parameters
+ *
+ * @return AsymmetricKey
+ * @param string|array $key
+ */
+ public static function loadParameters($key)
+ {
+ $key = self::load($key);
+ if (!$key instanceof PrivateKey && !$key instanceof PublicKey) {
+ throw new NoKeyLoadedException('The key that was loaded was not a parameter');
+ }
+ return $key;
+ }
+
+ /**
+ * Load the key, assuming a specific format
+ *
+ * @param string $type
+ * @param string $key
+ * @param string $password optional
+ * @return static
+ */
+ public static function loadFormat($type, $key, $password = false)
+ {
+ self::initialize_static_variables();
+
+ $components = false;
+ $format = strtolower($type);
+ if (isset(self::$plugins[static::ALGORITHM]['Keys'][$format])) {
+ $format = self::$plugins[static::ALGORITHM]['Keys'][$format];
+ $components = $format::load($key, $password);
+ }
+
+ if ($components === false) {
+ throw new NoKeyLoadedException('Unable to read key');
+ }
+
+ $components['format'] = $format;
+ $components['secret'] = isset($components['secret']) ? $components['secret'] : '';
+
+ $new = static::onLoad($components);
+ $new->format = $format;
+ return $new instanceof PrivateKey ?
+ $new->withPassword($password) :
+ $new;
+ }
+
+ /**
+ * Loads a private key
+ *
+ * @return PrivateKey
+ * @param string $type
+ * @param string $key
+ * @param string $password optional
+ */
+ public static function loadPrivateKeyFormat($type, $key, $password = false)
+ {
+ $key = self::loadFormat($type, $key, $password);
+ if (!$key instanceof PrivateKey) {
+ throw new NoKeyLoadedException('The key that was loaded was not a private key');
+ }
+ return $key;
+ }
+
+ /**
+ * Loads a public key
+ *
+ * @return PublicKey
+ * @param string $type
+ * @param string $key
+ */
+ public static function loadPublicKeyFormat($type, $key)
+ {
+ $key = self::loadFormat($type, $key);
+ if (!$key instanceof PublicKey) {
+ throw new NoKeyLoadedException('The key that was loaded was not a public key');
+ }
+ return $key;
+ }
+
+ /**
+ * Loads parameters
+ *
+ * @return AsymmetricKey
+ * @param string $type
+ * @param string|array $key
+ */
+ public static function loadParametersFormat($type, $key)
+ {
+ $key = self::loadFormat($type, $key);
+ if (!$key instanceof PrivateKey && !$key instanceof PublicKey) {
+ throw new NoKeyLoadedException('The key that was loaded was not a parameter');
+ }
+ return $key;
+ }
+
+ /**
+ * Validate Plugin
+ *
+ * @param string $format
+ * @param string $type
+ * @param string $method optional
+ * @return mixed
+ */
+ protected static function validatePlugin($format, $type, $method = null)
+ {
+ $type = strtolower($type);
+ if (!isset(self::$plugins[static::ALGORITHM][$format][$type])) {
+ throw new UnsupportedFormatException("$type is not a supported format");
+ }
+ $type = self::$plugins[static::ALGORITHM][$format][$type];
+ if (isset($method) && !method_exists($type, $method)) {
+ throw new UnsupportedFormatException("$type does not implement $method");
+ }
+
+ return $type;
+ }
+
+ /**
+ * Load Plugins
+ *
+ * @param string $format
+ */
+ private static function loadPlugins($format)
+ {
+ if (!isset(self::$plugins[static::ALGORITHM][$format])) {
+ self::$plugins[static::ALGORITHM][$format] = [];
+ foreach (new \DirectoryIterator(__DIR__ . '/../' . static::ALGORITHM . '/Formats/' . $format . '/') as $file) {
+ if ($file->getExtension() != 'php') {
+ continue;
+ }
+ $name = $file->getBasename('.php');
+ if ($name[0] == '.') {
+ continue;
+ }
+ $type = 'phpseclib3\Crypt\\' . static::ALGORITHM . '\\Formats\\' . $format . '\\' . $name;
+ $reflect = new \ReflectionClass($type);
+ if ($reflect->isTrait()) {
+ continue;
+ }
+ self::$plugins[static::ALGORITHM][$format][strtolower($name)] = $type;
+ if ($reflect->hasConstant('IS_INVISIBLE')) {
+ self::$invisiblePlugins[static::ALGORITHM][] = $type;
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns a list of supported formats.
+ *
+ * @return array
+ */
+ public static function getSupportedKeyFormats()
+ {
+ self::initialize_static_variables();
+
+ return self::$plugins[static::ALGORITHM]['Keys'];
+ }
+
+ /**
+ * Add a fileformat plugin
+ *
+ * The plugin needs to either already be loaded or be auto-loadable.
+ * Loading a plugin whose shortname overwrite an existing shortname will overwrite the old plugin.
+ *
+ * @see self::load()
+ * @param string $fullname
+ * @return bool
+ */
+ public static function addFileFormat($fullname)
+ {
+ self::initialize_static_variables();
+
+ if (class_exists($fullname)) {
+ $meta = new \ReflectionClass($fullname);
+ $shortname = $meta->getShortName();
+ self::$plugins[static::ALGORITHM]['Keys'][strtolower($shortname)] = $fullname;
+ if ($meta->hasConstant('IS_INVISIBLE')) {
+ self::$invisiblePlugins[static::ALGORITHM][] = strtolower($shortname);
+ }
+ }
+ }
+
+ /**
+ * Returns the format of the loaded key.
+ *
+ * If the key that was loaded wasn't in a valid or if the key was auto-generated
+ * with RSA::createKey() then this will throw an exception.
+ *
+ * @see self::load()
+ * @return mixed
+ */
+ public function getLoadedFormat()
+ {
+ if (empty($this->format)) {
+ throw new NoKeyLoadedException('This key was created with createKey - it was not loaded with load. Therefore there is no "loaded format"');
+ }
+
+ $meta = new \ReflectionClass($this->format);
+ return $meta->getShortName();
+ }
+
+ /**
+ * Returns the key's comment
+ *
+ * Not all key formats support comments. If you want to set a comment use toString()
+ *
+ * @return null|string
+ */
+ public function getComment()
+ {
+ return $this->comment;
+ }
+
+ /**
+ * Tests engine validity
+ *
+ */
+ public static function useBestEngine()
+ {
+ static::$engines = [
+ 'PHP' => true,
+ 'OpenSSL' => extension_loaded('openssl'),
+ // this test can be satisfied by either of the following:
+ // http://php.net/manual/en/book.sodium.php
+ // https://github.com/paragonie/sodium_compat
+ 'libsodium' => function_exists('sodium_crypto_sign_keypair')
+ ];
+
+ return static::$engines;
+ }
+
+ /**
+ * Flag to use internal engine only (useful for unit testing)
+ *
+ */
+ public static function useInternalEngine()
+ {
+ static::$engines = [
+ 'PHP' => true,
+ 'OpenSSL' => false,
+ 'libsodium' => false
+ ];
+ }
+
+ /**
+ * __toString() magic method
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toString('PKCS8');
+ }
+
+ /**
+ * Determines which hashing function should be used
+ *
+ * @param string $hash
+ */
+ public function withHash($hash)
+ {
+ $new = clone $this;
+
+ $new->hash = new Hash($hash);
+ $new->hmac = new Hash($hash);
+
+ return $new;
+ }
+
+ /**
+ * Returns the hash algorithm currently being used
+ *
+ */
+ public function getHash()
+ {
+ return clone $this->hash;
+ }
+
+ /**
+ * Compute the pseudorandom k for signature generation,
+ * using the process specified for deterministic DSA.
+ *
+ * @param string $h1
+ * @return string
+ */
+ protected function computek($h1)
+ {
+ $v = str_repeat("\1", strlen($h1));
+
+ $k = str_repeat("\0", strlen($h1));
+
+ $x = $this->int2octets($this->x);
+ $h1 = $this->bits2octets($h1);
+
+ $this->hmac->setKey($k);
+ $k = $this->hmac->hash($v . "\0" . $x . $h1);
+ $this->hmac->setKey($k);
+ $v = $this->hmac->hash($v);
+ $k = $this->hmac->hash($v . "\1" . $x . $h1);
+ $this->hmac->setKey($k);
+ $v = $this->hmac->hash($v);
+
+ $qlen = $this->q->getLengthInBytes();
+
+ while (true) {
+ $t = '';
+ while (strlen($t) < $qlen) {
+ $v = $this->hmac->hash($v);
+ $t = $t . $v;
+ }
+ $k = $this->bits2int($t);
+
+ if (!$k->equals(self::$zero) && $k->compare($this->q) < 0) {
+ break;
+ }
+ $k = $this->hmac->hash($v . "\0");
+ $this->hmac->setKey($k);
+ $v = $this->hmac->hash($v);
+ }
+
+ return $k;
+ }
+
+ /**
+ * Integer to Octet String
+ *
+ * @param BigInteger $v
+ * @return string
+ */
+ private function int2octets($v)
+ {
+ $out = $v->toBytes();
+ $rolen = $this->q->getLengthInBytes();
+ if (strlen($out) < $rolen) {
+ return str_pad($out, $rolen, "\0", STR_PAD_LEFT);
+ } elseif (strlen($out) > $rolen) {
+ return substr($out, -$rolen);
+ } else {
+ return $out;
+ }
+ }
+
+ /**
+ * Bit String to Integer
+ *
+ * @param string $in
+ * @return BigInteger
+ */
+ protected function bits2int($in)
+ {
+ $v = new BigInteger($in, 256);
+ $vlen = strlen($in) << 3;
+ $qlen = $this->q->getLength();
+ if ($vlen > $qlen) {
+ return $v->bitwise_rightShift($vlen - $qlen);
+ }
+ return $v;
+ }
+
+ /**
+ * Bit String to Octet String
+ *
+ * @param string $in
+ * @return string
+ */
+ private function bits2octets($in)
+ {
+ $z1 = $this->bits2int($in);
+ $z2 = $z1->subtract($this->q);
+ return $z2->compare(self::$zero) < 0 ?
+ $this->int2octets($z1) :
+ $this->int2octets($z2);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/BlockCipher.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/BlockCipher.php
new file mode 100644
index 000000000..b2642be11
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/BlockCipher.php
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * Base Class for all block ciphers
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @author Hans-Juergen Petrich <petrich@tronic-media.com>
+ * @copyright 2007 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\Common;
+
+/**
+ * Base Class for all block cipher classes
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class BlockCipher extends SymmetricKey
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/JWK.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/JWK.php
new file mode 100644
index 000000000..98b8dacc9
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/JWK.php
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * JSON Web Key (RFC7517) Handler
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\Common\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+
+/**
+ * JSON Web Key Formatted Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class JWK
+{
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ if (!Strings::is_stringable($key)) {
+ throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
+ }
+
+ $key = preg_replace('#\s#', '', $key); // remove whitespace
+
+ if (PHP_VERSION_ID >= 73000) {
+ $key = json_decode($key, null, 512, JSON_THROW_ON_ERROR);
+ } else {
+ $key = json_decode($key);
+ if (!$key) {
+ throw new \RuntimeException('Unable to decode JSON');
+ }
+ }
+
+ if (isset($key->kty)) {
+ return $key;
+ }
+
+ if (!is_object($key)) {
+ throw new \RuntimeException('invalid JWK: not an object');
+ }
+
+ if (!isset($key->keys)) {
+ throw new \RuntimeException('invalid JWK: object has no property "keys"');
+ }
+
+ if (count($key->keys) != 1) {
+ throw new \RuntimeException('Although the JWK key format supports multiple keys phpseclib does not');
+ }
+
+ return $key->keys[0];
+ }
+
+ /**
+ * Wrap a key appropriately
+ *
+ * @return string
+ */
+ protected static function wrapKey(array $key, array $options)
+ {
+ return json_encode(['keys' => [$key + $options]]);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/OpenSSH.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/OpenSSH.php
new file mode 100644
index 000000000..ab7f05045
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/OpenSSH.php
@@ -0,0 +1,224 @@
+<?php
+
+/**
+ * OpenSSH Key Handler
+ *
+ * PHP version 5
+ *
+ * Place in $HOME/.ssh/authorized_keys
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\Common\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\AES;
+use phpseclib3\Crypt\Random;
+use phpseclib3\Exception\BadDecryptionException;
+
+/**
+ * OpenSSH Formatted RSA Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class OpenSSH
+{
+ /**
+ * Default comment
+ *
+ * @var string
+ */
+ protected static $comment = 'phpseclib-generated-key';
+
+ /**
+ * Binary key flag
+ *
+ * @var bool
+ */
+ protected static $binary = false;
+
+ /**
+ * Sets the default comment
+ *
+ * @param string $comment
+ */
+ public static function setComment($comment)
+ {
+ self::$comment = str_replace(["\r", "\n"], '', $comment);
+ }
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * $type can be either ssh-dss or ssh-rsa
+ *
+ * @param string $key
+ * @param string $password
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ if (!Strings::is_stringable($key)) {
+ throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
+ }
+
+ // key format is described here:
+ // https://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.key?annotate=HEAD
+
+ if (strpos($key, 'BEGIN OPENSSH PRIVATE KEY') !== false) {
+ $key = preg_replace('#(?:^-.*?-[\r\n]*$)|\s#ms', '', $key);
+ $key = Strings::base64_decode($key);
+ $magic = Strings::shift($key, 15);
+ if ($magic != "openssh-key-v1\0") {
+ throw new \RuntimeException('Expected openssh-key-v1');
+ }
+ list($ciphername, $kdfname, $kdfoptions, $numKeys) = Strings::unpackSSH2('sssN', $key);
+ if ($numKeys != 1) {
+ // if we wanted to support multiple keys we could update PublicKeyLoader to preview what the # of keys
+ // would be; it'd then call Common\Keys\OpenSSH.php::load() and get the paddedKey. it'd then pass
+ // that to the appropriate key loading parser $numKey times or something
+ throw new \RuntimeException('Although the OpenSSH private key format supports multiple keys phpseclib does not');
+ }
+ switch ($ciphername) {
+ case 'none':
+ break;
+ case 'aes256-ctr':
+ if ($kdfname != 'bcrypt') {
+ throw new \RuntimeException('Only the bcrypt kdf is supported (' . $kdfname . ' encountered)');
+ }
+ list($salt, $rounds) = Strings::unpackSSH2('sN', $kdfoptions);
+ $crypto = new AES('ctr');
+ //$crypto->setKeyLength(256);
+ //$crypto->disablePadding();
+ $crypto->setPassword($password, 'bcrypt', $salt, $rounds, 32);
+ break;
+ default:
+ throw new \RuntimeException('The only supported ciphers are: none, aes256-ctr (' . $ciphername . ' is being used)');
+ }
+
+ list($publicKey, $paddedKey) = Strings::unpackSSH2('ss', $key);
+ list($type) = Strings::unpackSSH2('s', $publicKey);
+ if (isset($crypto)) {
+ $paddedKey = $crypto->decrypt($paddedKey);
+ }
+ list($checkint1, $checkint2) = Strings::unpackSSH2('NN', $paddedKey);
+ // any leftover bytes in $paddedKey are for padding? but they should be sequential bytes. eg. 1, 2, 3, etc.
+ if ($checkint1 != $checkint2) {
+ if (isset($crypto)) {
+ throw new BadDecryptionException('Unable to decrypt key - please verify the password you are using');
+ }
+ throw new \RuntimeException("The two checkints do not match ($checkint1 vs. $checkint2)");
+ }
+ self::checkType($type);
+
+ return compact('type', 'publicKey', 'paddedKey');
+ }
+
+ $parts = explode(' ', $key, 3);
+
+ if (!isset($parts[1])) {
+ $key = base64_decode($parts[0]);
+ $comment = false;
+ } else {
+ $asciiType = $parts[0];
+ self::checkType($parts[0]);
+ $key = base64_decode($parts[1]);
+ $comment = isset($parts[2]) ? $parts[2] : false;
+ }
+ if ($key === false) {
+ throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
+ }
+
+ list($type) = Strings::unpackSSH2('s', $key);
+ self::checkType($type);
+ if (isset($asciiType) && $asciiType != $type) {
+ throw new \RuntimeException('Two different types of keys are claimed: ' . $asciiType . ' and ' . $type);
+ }
+ if (strlen($key) <= 4) {
+ throw new \UnexpectedValueException('Key appears to be malformed');
+ }
+
+ $publicKey = $key;
+
+ return compact('type', 'publicKey', 'comment');
+ }
+
+ /**
+ * Toggle between binary and printable keys
+ *
+ * Printable keys are what are generated by default. These are the ones that go in
+ * $HOME/.ssh/authorized_key.
+ *
+ * @param bool $enabled
+ */
+ public static function setBinaryOutput($enabled)
+ {
+ self::$binary = $enabled;
+ }
+
+ /**
+ * Checks to see if the type is valid
+ *
+ * @param string $candidate
+ */
+ private static function checkType($candidate)
+ {
+ if (!in_array($candidate, static::$types)) {
+ throw new \RuntimeException("The key type ($candidate) is not equal to: " . implode(',', static::$types));
+ }
+ }
+
+ /**
+ * Wrap a private key appropriately
+ *
+ * @param string $publicKey
+ * @param string $privateKey
+ * @param string $password
+ * @param array $options
+ * @return string
+ */
+ protected static function wrapPrivateKey($publicKey, $privateKey, $password, $options)
+ {
+ list(, $checkint) = unpack('N', Random::string(4));
+
+ $comment = isset($options['comment']) ? $options['comment'] : self::$comment;
+ $paddedKey = Strings::packSSH2('NN', $checkint, $checkint) .
+ $privateKey .
+ Strings::packSSH2('s', $comment);
+
+ $usesEncryption = !empty($password) && is_string($password);
+
+ /*
+ from http://tools.ietf.org/html/rfc4253#section-6 :
+
+ Note that the length of the concatenation of 'packet_length',
+ 'padding_length', 'payload', and 'random padding' MUST be a multiple
+ of the cipher block size or 8, whichever is larger.
+ */
+ $blockSize = $usesEncryption ? 16 : 8;
+ $paddingLength = (($blockSize - 1) * strlen($paddedKey)) % $blockSize;
+ for ($i = 1; $i <= $paddingLength; $i++) {
+ $paddedKey .= chr($i);
+ }
+ if (!$usesEncryption) {
+ $key = Strings::packSSH2('sssNss', 'none', 'none', '', 1, $publicKey, $paddedKey);
+ } else {
+ $rounds = isset($options['rounds']) ? $options['rounds'] : 16;
+ $salt = Random::string(16);
+ $kdfoptions = Strings::packSSH2('sN', $salt, $rounds);
+ $crypto = new AES('ctr');
+ $crypto->setPassword($password, 'bcrypt', $salt, $rounds, 32);
+ $paddedKey = $crypto->encrypt($paddedKey);
+ $key = Strings::packSSH2('sssNss', 'aes256-ctr', 'bcrypt', $kdfoptions, 1, $publicKey, $paddedKey);
+ }
+ $key = "openssh-key-v1\0$key";
+
+ return "-----BEGIN OPENSSH PRIVATE KEY-----\n" .
+ chunk_split(Strings::base64_encode($key), 70, "\n") .
+ "-----END OPENSSH PRIVATE KEY-----\n";
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS.php
new file mode 100644
index 000000000..0219400bc
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * PKCS Formatted Key Handler
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\Common\Formats\Keys;
+
+/**
+ * PKCS1 Formatted Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PKCS
+{
+ /**
+ * Auto-detect the format
+ */
+ const MODE_ANY = 0;
+ /**
+ * Require base64-encoded PEM's be supplied
+ */
+ const MODE_PEM = 1;
+ /**
+ * Require raw DER's be supplied
+ */
+ const MODE_DER = 2;
+ /**#@-*/
+
+ /**
+ * Is the key a base-64 encoded PEM, DER or should it be auto-detected?
+ *
+ * @var int
+ */
+ protected static $format = self::MODE_ANY;
+
+ /**
+ * Require base64-encoded PEM's be supplied
+ *
+ */
+ public static function requirePEM()
+ {
+ self::$format = self::MODE_PEM;
+ }
+
+ /**
+ * Require raw DER's be supplied
+ *
+ */
+ public static function requireDER()
+ {
+ self::$format = self::MODE_DER;
+ }
+
+ /**
+ * Accept any format and auto detect the format
+ *
+ * This is the default setting
+ *
+ */
+ public static function requireAny()
+ {
+ self::$format = self::MODE_ANY;
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS1.php
new file mode 100644
index 000000000..4c639c05e
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS1.php
@@ -0,0 +1,209 @@
+<?php
+
+/**
+ * PKCS1 Formatted Key Handler
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\Common\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\AES;
+use phpseclib3\Crypt\DES;
+use phpseclib3\Crypt\Random;
+use phpseclib3\Crypt\TripleDES;
+use phpseclib3\Exception\UnsupportedAlgorithmException;
+use phpseclib3\File\ASN1;
+
+/**
+ * PKCS1 Formatted Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PKCS1 extends PKCS
+{
+ /**
+ * Default encryption algorithm
+ *
+ * @var string
+ */
+ private static $defaultEncryptionAlgorithm = 'AES-128-CBC';
+
+ /**
+ * Sets the default encryption algorithm
+ *
+ * @param string $algo
+ */
+ public static function setEncryptionAlgorithm($algo)
+ {
+ self::$defaultEncryptionAlgorithm = $algo;
+ }
+
+ /**
+ * Returns the mode constant corresponding to the mode string
+ *
+ * @param string $mode
+ * @return int
+ * @throws \UnexpectedValueException if the block cipher mode is unsupported
+ */
+ private static function getEncryptionMode($mode)
+ {
+ switch ($mode) {
+ case 'CBC':
+ case 'ECB':
+ case 'CFB':
+ case 'OFB':
+ case 'CTR':
+ return $mode;
+ }
+ throw new \UnexpectedValueException('Unsupported block cipher mode of operation');
+ }
+
+ /**
+ * Returns a cipher object corresponding to a string
+ *
+ * @param string $algo
+ * @return string
+ * @throws \UnexpectedValueException if the encryption algorithm is unsupported
+ */
+ private static function getEncryptionObject($algo)
+ {
+ $modes = '(CBC|ECB|CFB|OFB|CTR)';
+ switch (true) {
+ case preg_match("#^AES-(128|192|256)-$modes$#", $algo, $matches):
+ $cipher = new AES(self::getEncryptionMode($matches[2]));
+ $cipher->setKeyLength($matches[1]);
+ return $cipher;
+ case preg_match("#^DES-EDE3-$modes$#", $algo, $matches):
+ return new TripleDES(self::getEncryptionMode($matches[1]));
+ case preg_match("#^DES-$modes$#", $algo, $matches):
+ return new DES(self::getEncryptionMode($matches[1]));
+ default:
+ throw new UnsupportedAlgorithmException($algo . ' is not a supported algorithm');
+ }
+ }
+
+ /**
+ * Generate a symmetric key for PKCS#1 keys
+ *
+ * @param string $password
+ * @param string $iv
+ * @param int $length
+ * @return string
+ */
+ private static function generateSymmetricKey($password, $iv, $length)
+ {
+ $symkey = '';
+ $iv = substr($iv, 0, 8);
+ while (strlen($symkey) < $length) {
+ $symkey .= md5($symkey . $password . $iv, true);
+ }
+ return substr($symkey, 0, $length);
+ }
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ protected static function load($key, $password)
+ {
+ if (!Strings::is_stringable($key)) {
+ throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
+ }
+
+ /* Although PKCS#1 proposes a format that public and private keys can use, encrypting them is
+ "outside the scope" of PKCS#1. PKCS#1 then refers you to PKCS#12 and PKCS#15 if you're wanting to
+ protect private keys, however, that's not what OpenSSL* does. OpenSSL protects private keys by adding
+ two new "fields" to the key - DEK-Info and Proc-Type. These fields are discussed here:
+
+ http://tools.ietf.org/html/rfc1421#section-4.6.1.1
+ http://tools.ietf.org/html/rfc1421#section-4.6.1.3
+
+ DES-EDE3-CBC as an algorithm, however, is not discussed anywhere, near as I can tell.
+ DES-CBC and DES-EDE are discussed in RFC1423, however, DES-EDE3-CBC isn't, nor is its key derivation
+ function. As is, the definitive authority on this encoding scheme isn't the IETF but rather OpenSSL's
+ own implementation. ie. the implementation *is* the standard and any bugs that may exist in that
+ implementation are part of the standard, as well.
+
+ * OpenSSL is the de facto standard. It's utilized by OpenSSH and other projects */
+ if (preg_match('#DEK-Info: (.+),(.+)#', $key, $matches)) {
+ $iv = Strings::hex2bin(trim($matches[2]));
+ // remove the Proc-Type / DEK-Info sections as they're no longer needed
+ $key = preg_replace('#^(?:Proc-Type|DEK-Info): .*#m', '', $key);
+ $ciphertext = ASN1::extractBER($key);
+ if ($ciphertext === false) {
+ $ciphertext = $key;
+ }
+ $crypto = self::getEncryptionObject($matches[1]);
+ $crypto->setKey(self::generateSymmetricKey($password, $iv, $crypto->getKeyLength() >> 3));
+ $crypto->setIV($iv);
+ $key = $crypto->decrypt($ciphertext);
+ } else {
+ if (self::$format != self::MODE_DER) {
+ $decoded = ASN1::extractBER($key);
+ if ($decoded !== false) {
+ $key = $decoded;
+ } elseif (self::$format == self::MODE_PEM) {
+ throw new \UnexpectedValueException('Expected base64-encoded PEM format but was unable to decode base64 text');
+ }
+ }
+ }
+
+ return $key;
+ }
+
+ /**
+ * Wrap a private key appropriately
+ *
+ * @param string $key
+ * @param string $type
+ * @param string $password
+ * @param array $options optional
+ * @return string
+ */
+ protected static function wrapPrivateKey($key, $type, $password, array $options = [])
+ {
+ if (empty($password) || !is_string($password)) {
+ return "-----BEGIN $type PRIVATE KEY-----\r\n" .
+ chunk_split(Strings::base64_encode($key), 64) .
+ "-----END $type PRIVATE KEY-----";
+ }
+
+ $encryptionAlgorithm = isset($options['encryptionAlgorithm']) ? $options['encryptionAlgorithm'] : self::$defaultEncryptionAlgorithm;
+
+ $cipher = self::getEncryptionObject($encryptionAlgorithm);
+ $iv = Random::string($cipher->getBlockLength() >> 3);
+ $cipher->setKey(self::generateSymmetricKey($password, $iv, $cipher->getKeyLength() >> 3));
+ $cipher->setIV($iv);
+ $iv = strtoupper(Strings::bin2hex($iv));
+ return "-----BEGIN $type PRIVATE KEY-----\r\n" .
+ "Proc-Type: 4,ENCRYPTED\r\n" .
+ "DEK-Info: " . $encryptionAlgorithm . ",$iv\r\n" .
+ "\r\n" .
+ chunk_split(Strings::base64_encode($cipher->encrypt($key)), 64) .
+ "-----END $type PRIVATE KEY-----";
+ }
+
+ /**
+ * Wrap a public key appropriately
+ *
+ * @param string $key
+ * @param string $type
+ * @return string
+ */
+ protected static function wrapPublicKey($key, $type)
+ {
+ return "-----BEGIN $type PUBLIC KEY-----\r\n" .
+ chunk_split(Strings::base64_encode($key), 64) .
+ "-----END $type PUBLIC KEY-----";
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS8.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS8.php
new file mode 100644
index 000000000..2211a8747
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PKCS8.php
@@ -0,0 +1,766 @@
+<?php
+
+/**
+ * PKCS#8 Formatted Key Handler
+ *
+ * PHP version 5
+ *
+ * Used by PHP's openssl_public_encrypt() and openssl's rsautl (when -pubin is set)
+ *
+ * Processes keys with the following headers:
+ *
+ * -----BEGIN ENCRYPTED PRIVATE KEY-----
+ * -----BEGIN PRIVATE KEY-----
+ * -----BEGIN PUBLIC KEY-----
+ *
+ * Analogous to ssh-keygen's pkcs8 format (as specified by -m). Although PKCS8
+ * is specific to private keys it's basically creating a DER-encoded wrapper
+ * for keys. This just extends that same concept to public keys (much like ssh-keygen)
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\Common\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\AES;
+use phpseclib3\Crypt\DES;
+use phpseclib3\Crypt\Random;
+use phpseclib3\Crypt\RC2;
+use phpseclib3\Crypt\RC4;
+use phpseclib3\Crypt\TripleDES;
+use phpseclib3\Exception\InsufficientSetupException;
+use phpseclib3\Exception\UnsupportedAlgorithmException;
+use phpseclib3\File\ASN1;
+use phpseclib3\File\ASN1\Maps;
+
+/**
+ * PKCS#8 Formatted Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PKCS8 extends PKCS
+{
+ /**
+ * Default encryption algorithm
+ *
+ * @var string
+ */
+ private static $defaultEncryptionAlgorithm = 'id-PBES2';
+
+ /**
+ * Default encryption scheme
+ *
+ * Only used when defaultEncryptionAlgorithm is id-PBES2
+ *
+ * @var string
+ */
+ private static $defaultEncryptionScheme = 'aes128-CBC-PAD';
+
+ /**
+ * Default PRF
+ *
+ * Only used when defaultEncryptionAlgorithm is id-PBES2
+ *
+ * @var string
+ */
+ private static $defaultPRF = 'id-hmacWithSHA256';
+
+ /**
+ * Default Iteration Count
+ *
+ * @var int
+ */
+ private static $defaultIterationCount = 2048;
+
+ /**
+ * OIDs loaded
+ *
+ * @var bool
+ */
+ private static $oidsLoaded = false;
+
+ /**
+ * Binary key flag
+ *
+ * @var bool
+ */
+ private static $binary = false;
+
+ /**
+ * Sets the default encryption algorithm
+ *
+ * @param string $algo
+ */
+ public static function setEncryptionAlgorithm($algo)
+ {
+ self::$defaultEncryptionAlgorithm = $algo;
+ }
+
+ /**
+ * Sets the default encryption algorithm for PBES2
+ *
+ * @param string $algo
+ */
+ public static function setEncryptionScheme($algo)
+ {
+ self::$defaultEncryptionScheme = $algo;
+ }
+
+ /**
+ * Sets the iteration count
+ *
+ * @param int $count
+ */
+ public static function setIterationCount($count)
+ {
+ self::$defaultIterationCount = $count;
+ }
+
+ /**
+ * Sets the PRF for PBES2
+ *
+ * @param string $algo
+ */
+ public static function setPRF($algo)
+ {
+ self::$defaultPRF = $algo;
+ }
+
+ /**
+ * Returns a SymmetricKey object based on a PBES1 $algo
+ *
+ * @return \phpseclib3\Crypt\Common\SymmetricKey
+ * @param string $algo
+ */
+ private static function getPBES1EncryptionObject($algo)
+ {
+ $algo = preg_match('#^pbeWith(?:MD2|MD5|SHA1|SHA)And(.*?)-CBC$#', $algo, $matches) ?
+ $matches[1] :
+ substr($algo, 13); // strlen('pbeWithSHAAnd') == 13
+
+ switch ($algo) {
+ case 'DES':
+ $cipher = new DES('cbc');
+ break;
+ case 'RC2':
+ $cipher = new RC2('cbc');
+ $cipher->setKeyLength(64);
+ break;
+ case '3-KeyTripleDES':
+ $cipher = new TripleDES('cbc');
+ break;
+ case '2-KeyTripleDES':
+ $cipher = new TripleDES('cbc');
+ $cipher->setKeyLength(128);
+ break;
+ case '128BitRC2':
+ $cipher = new RC2('cbc');
+ $cipher->setKeyLength(128);
+ break;
+ case '40BitRC2':
+ $cipher = new RC2('cbc');
+ $cipher->setKeyLength(40);
+ break;
+ case '128BitRC4':
+ $cipher = new RC4();
+ $cipher->setKeyLength(128);
+ break;
+ case '40BitRC4':
+ $cipher = new RC4();
+ $cipher->setKeyLength(40);
+ break;
+ default:
+ throw new UnsupportedAlgorithmException("$algo is not a supported algorithm");
+ }
+
+ return $cipher;
+ }
+
+ /**
+ * Returns a hash based on a PBES1 $algo
+ *
+ * @return string
+ * @param string $algo
+ */
+ private static function getPBES1Hash($algo)
+ {
+ if (preg_match('#^pbeWith(MD2|MD5|SHA1|SHA)And.*?-CBC$#', $algo, $matches)) {
+ return $matches[1] == 'SHA' ? 'sha1' : $matches[1];
+ }
+
+ return 'sha1';
+ }
+
+ /**
+ * Returns a KDF baesd on a PBES1 $algo
+ *
+ * @return string
+ * @param string $algo
+ */
+ private static function getPBES1KDF($algo)
+ {
+ switch ($algo) {
+ case 'pbeWithMD2AndDES-CBC':
+ case 'pbeWithMD2AndRC2-CBC':
+ case 'pbeWithMD5AndDES-CBC':
+ case 'pbeWithMD5AndRC2-CBC':
+ case 'pbeWithSHA1AndDES-CBC':
+ case 'pbeWithSHA1AndRC2-CBC':
+ return 'pbkdf1';
+ }
+
+ return 'pkcs12';
+ }
+
+ /**
+ * Returns a SymmetricKey object baesd on a PBES2 $algo
+ *
+ * @return SymmetricKey
+ * @param string $algo
+ */
+ private static function getPBES2EncryptionObject($algo)
+ {
+ switch ($algo) {
+ case 'desCBC':
+ $cipher = new DES('cbc');
+ break;
+ case 'des-EDE3-CBC':
+ $cipher = new TripleDES('cbc');
+ break;
+ case 'rc2CBC':
+ $cipher = new RC2('cbc');
+ // in theory this can be changed
+ $cipher->setKeyLength(128);
+ break;
+ case 'rc5-CBC-PAD':
+ throw new UnsupportedAlgorithmException('rc5-CBC-PAD is not supported for PBES2 PKCS#8 keys');
+ case 'aes128-CBC-PAD':
+ case 'aes192-CBC-PAD':
+ case 'aes256-CBC-PAD':
+ $cipher = new AES('cbc');
+ $cipher->setKeyLength(substr($algo, 3, 3));
+ break;
+ default:
+ throw new UnsupportedAlgorithmException("$algo is not supported");
+ }
+
+ return $cipher;
+ }
+
+ /**
+ * Initialize static variables
+ *
+ */
+ private static function initialize_static_variables()
+ {
+ if (!isset(static::$childOIDsLoaded)) {
+ throw new InsufficientSetupException('This class should not be called directly');
+ }
+
+ if (!static::$childOIDsLoaded) {
+ ASN1::loadOIDs(is_array(static::OID_NAME) ?
+ array_combine(static::OID_NAME, static::OID_VALUE) :
+ [static::OID_NAME => static::OID_VALUE]);
+ static::$childOIDsLoaded = true;
+ }
+ if (!self::$oidsLoaded) {
+ // from https://tools.ietf.org/html/rfc2898
+ ASN1::loadOIDs([
+ // PBES1 encryption schemes
+ 'pbeWithMD2AndDES-CBC' => '1.2.840.113549.1.5.1',
+ 'pbeWithMD2AndRC2-CBC' => '1.2.840.113549.1.5.4',
+ 'pbeWithMD5AndDES-CBC' => '1.2.840.113549.1.5.3',
+ 'pbeWithMD5AndRC2-CBC' => '1.2.840.113549.1.5.6',
+ 'pbeWithSHA1AndDES-CBC' => '1.2.840.113549.1.5.10',
+ 'pbeWithSHA1AndRC2-CBC' => '1.2.840.113549.1.5.11',
+
+ // from PKCS#12:
+ // https://tools.ietf.org/html/rfc7292
+ 'pbeWithSHAAnd128BitRC4' => '1.2.840.113549.1.12.1.1',
+ 'pbeWithSHAAnd40BitRC4' => '1.2.840.113549.1.12.1.2',
+ 'pbeWithSHAAnd3-KeyTripleDES-CBC' => '1.2.840.113549.1.12.1.3',
+ 'pbeWithSHAAnd2-KeyTripleDES-CBC' => '1.2.840.113549.1.12.1.4',
+ 'pbeWithSHAAnd128BitRC2-CBC' => '1.2.840.113549.1.12.1.5',
+ 'pbeWithSHAAnd40BitRC2-CBC' => '1.2.840.113549.1.12.1.6',
+
+ 'id-PBKDF2' => '1.2.840.113549.1.5.12',
+ 'id-PBES2' => '1.2.840.113549.1.5.13',
+ 'id-PBMAC1' => '1.2.840.113549.1.5.14',
+
+ // from PKCS#5 v2.1:
+ // http://www.rsa.com/rsalabs/pkcs/files/h11302-wp-pkcs5v2-1-password-based-cryptography-standard.pdf
+ 'id-hmacWithSHA1' => '1.2.840.113549.2.7',
+ 'id-hmacWithSHA224' => '1.2.840.113549.2.8',
+ 'id-hmacWithSHA256' => '1.2.840.113549.2.9',
+ 'id-hmacWithSHA384' => '1.2.840.113549.2.10',
+ 'id-hmacWithSHA512' => '1.2.840.113549.2.11',
+ 'id-hmacWithSHA512-224' => '1.2.840.113549.2.12',
+ 'id-hmacWithSHA512-256' => '1.2.840.113549.2.13',
+
+ 'desCBC' => '1.3.14.3.2.7',
+ 'des-EDE3-CBC' => '1.2.840.113549.3.7',
+ 'rc2CBC' => '1.2.840.113549.3.2',
+ 'rc5-CBC-PAD' => '1.2.840.113549.3.9',
+
+ 'aes128-CBC-PAD' => '2.16.840.1.101.3.4.1.2',
+ 'aes192-CBC-PAD' => '2.16.840.1.101.3.4.1.22',
+ 'aes256-CBC-PAD' => '2.16.840.1.101.3.4.1.42'
+ ]);
+ self::$oidsLoaded = true;
+ }
+ }
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ protected static function load($key, $password = '')
+ {
+ if (!Strings::is_stringable($key)) {
+ throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
+ }
+
+ $isPublic = strpos($key, 'PUBLIC') !== false;
+ $isPrivate = strpos($key, 'PRIVATE') !== false;
+
+ $decoded = self::preParse($key);
+
+ $meta = [];
+
+ $decrypted = ASN1::asn1map($decoded[0], Maps\EncryptedPrivateKeyInfo::MAP);
+ if (strlen($password) && is_array($decrypted)) {
+ $algorithm = $decrypted['encryptionAlgorithm']['algorithm'];
+ switch ($algorithm) {
+ // PBES1
+ case 'pbeWithMD2AndDES-CBC':
+ case 'pbeWithMD2AndRC2-CBC':
+ case 'pbeWithMD5AndDES-CBC':
+ case 'pbeWithMD5AndRC2-CBC':
+ case 'pbeWithSHA1AndDES-CBC':
+ case 'pbeWithSHA1AndRC2-CBC':
+ case 'pbeWithSHAAnd3-KeyTripleDES-CBC':
+ case 'pbeWithSHAAnd2-KeyTripleDES-CBC':
+ case 'pbeWithSHAAnd128BitRC2-CBC':
+ case 'pbeWithSHAAnd40BitRC2-CBC':
+ case 'pbeWithSHAAnd128BitRC4':
+ case 'pbeWithSHAAnd40BitRC4':
+ $cipher = self::getPBES1EncryptionObject($algorithm);
+ $hash = self::getPBES1Hash($algorithm);
+ $kdf = self::getPBES1KDF($algorithm);
+
+ $meta['meta']['algorithm'] = $algorithm;
+
+ $temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
+ if (!$temp) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+ $map = ASN1::asn1map($temp[0], Maps\PBEParameter::MAP);
+ $salt = $map['salt'];
+ $iterationCount = $map['iterationCount'];
+ $iterationCount = (int) $iterationCount->toString();
+ $cipher->setPassword($password, $kdf, $hash, $salt, $iterationCount);
+ $key = $cipher->decrypt($decrypted['encryptedData']);
+ $decoded = ASN1::decodeBER($key);
+ if (!$decoded) {
+ throw new \RuntimeException('Unable to decode BER 2');
+ }
+
+ break;
+ case 'id-PBES2':
+ $meta['meta']['algorithm'] = $algorithm;
+
+ $temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
+ if (!$temp) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+ $temp = ASN1::asn1map($temp[0], Maps\PBES2params::MAP);
+ $keyDerivationFunc = $temp['keyDerivationFunc'];
+ $encryptionScheme = $temp['encryptionScheme'];
+
+ $cipher = self::getPBES2EncryptionObject($encryptionScheme['algorithm']);
+ $meta['meta']['cipher'] = $encryptionScheme['algorithm'];
+
+ $temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
+ if (!$temp) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+ $temp = ASN1::asn1map($temp[0], Maps\PBES2params::MAP);
+ $keyDerivationFunc = $temp['keyDerivationFunc'];
+ $encryptionScheme = $temp['encryptionScheme'];
+
+ if (!$cipher instanceof RC2) {
+ $cipher->setIV($encryptionScheme['parameters']['octetString']);
+ } else {
+ $temp = ASN1::decodeBER($encryptionScheme['parameters']);
+ if (!$temp) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+ $map = ASN1::asn1map($temp[0], Maps\RC2CBCParameter::MAP);
+ $rc2ParametersVersion = $map['rc2ParametersVersion'];
+ $iv = $map['iv'];
+ $effectiveKeyLength = (int) $rc2ParametersVersion->toString();
+ switch ($effectiveKeyLength) {
+ case 160:
+ $effectiveKeyLength = 40;
+ break;
+ case 120:
+ $effectiveKeyLength = 64;
+ break;
+ case 58:
+ $effectiveKeyLength = 128;
+ break;
+ //default: // should be >= 256
+ }
+ $cipher->setIV($iv);
+ $cipher->setKeyLength($effectiveKeyLength);
+ }
+
+ $meta['meta']['keyDerivationFunc'] = $keyDerivationFunc['algorithm'];
+ switch ($keyDerivationFunc['algorithm']) {
+ case 'id-PBKDF2':
+ $temp = ASN1::decodeBER($keyDerivationFunc['parameters']);
+ if (!$temp) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+ $params = ASN1::asn1map($temp[0], Maps\PBKDF2params::MAP);
+ if (empty($params['prf'])) {
+ $params['prf'] = ['algorithm' => 'id-hmacWithSHA1'];
+ }
+ $salt = $params['salt'];
+ $iterationCount = $params['iterationCount'];
+ $prf = $params['prf'];
+ $meta['meta']['prf'] = $prf['algorithm'];
+ $hash = str_replace('-', '/', substr($prf['algorithm'], 11));
+ $params = [
+ $password,
+ 'pbkdf2',
+ $hash,
+ $salt,
+ (int) $iterationCount->toString()
+ ];
+ if (isset($keyLength)) {
+ $params[] = (int) $keyLength->toString();
+ }
+ $cipher->setPassword(...$params);
+ $key = $cipher->decrypt($decrypted['encryptedData']);
+ $decoded = ASN1::decodeBER($key);
+ if (!$decoded) {
+ throw new \RuntimeException('Unable to decode BER 3');
+ }
+ break;
+ default:
+ throw new UnsupportedAlgorithmException('Only PBKDF2 is supported for PBES2 PKCS#8 keys');
+ }
+ break;
+ case 'id-PBMAC1':
+ //$temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
+ //$value = ASN1::asn1map($temp[0], Maps\PBMAC1params::MAP);
+ // since i can't find any implementation that does PBMAC1 it is unsupported
+ throw new UnsupportedAlgorithmException('Only PBES1 and PBES2 PKCS#8 keys are supported.');
+ // at this point we'll assume that the key conforms to PublicKeyInfo
+ }
+ }
+
+ $private = ASN1::asn1map($decoded[0], Maps\OneAsymmetricKey::MAP);
+ if (is_array($private)) {
+ if ($isPublic) {
+ throw new \UnexpectedValueException('Human readable string claims public key but DER encoded string claims private key');
+ }
+
+ if (isset($private['privateKeyAlgorithm']['parameters']) && !$private['privateKeyAlgorithm']['parameters'] instanceof ASN1\Element && isset($decoded[0]['content'][1]['content'][1])) {
+ $temp = $decoded[0]['content'][1]['content'][1];
+ $private['privateKeyAlgorithm']['parameters'] = new ASN1\Element(substr($key, $temp['start'], $temp['length']));
+ }
+ if (is_array(static::OID_NAME)) {
+ if (!in_array($private['privateKeyAlgorithm']['algorithm'], static::OID_NAME)) {
+ throw new UnsupportedAlgorithmException($private['privateKeyAlgorithm']['algorithm'] . ' is not a supported key type');
+ }
+ } else {
+ if ($private['privateKeyAlgorithm']['algorithm'] != static::OID_NAME) {
+ throw new UnsupportedAlgorithmException('Only ' . static::OID_NAME . ' keys are supported; this is a ' . $private['privateKeyAlgorithm']['algorithm'] . ' key');
+ }
+ }
+ if (isset($private['publicKey'])) {
+ if ($private['publicKey'][0] != "\0") {
+ throw new \UnexpectedValueException('The first byte of the public key should be null - not ' . bin2hex($private['publicKey'][0]));
+ }
+ $private['publicKey'] = substr($private['publicKey'], 1);
+ }
+ return $private + $meta;
+ }
+
+ // EncryptedPrivateKeyInfo and PublicKeyInfo have largely identical "signatures". the only difference
+ // is that the former has an octet string and the later has a bit string. the first byte of a bit
+ // string represents the number of bits in the last byte that are to be ignored but, currently,
+ // bit strings wanting a non-zero amount of bits trimmed are not supported
+ $public = ASN1::asn1map($decoded[0], Maps\PublicKeyInfo::MAP);
+
+ if (is_array($public)) {
+ if ($isPrivate) {
+ throw new \UnexpectedValueException('Human readable string claims private key but DER encoded string claims public key');
+ }
+
+ if ($public['publicKey'][0] != "\0") {
+ throw new \UnexpectedValueException('The first byte of the public key should be null - not ' . bin2hex($public['publicKey'][0]));
+ }
+ if (is_array(static::OID_NAME)) {
+ if (!in_array($public['publicKeyAlgorithm']['algorithm'], static::OID_NAME)) {
+ throw new UnsupportedAlgorithmException($public['publicKeyAlgorithm']['algorithm'] . ' is not a supported key type');
+ }
+ } else {
+ if ($public['publicKeyAlgorithm']['algorithm'] != static::OID_NAME) {
+ throw new UnsupportedAlgorithmException('Only ' . static::OID_NAME . ' keys are supported; this is a ' . $public['publicKeyAlgorithm']['algorithm'] . ' key');
+ }
+ }
+ if (isset($public['publicKeyAlgorithm']['parameters']) && !$public['publicKeyAlgorithm']['parameters'] instanceof ASN1\Element && isset($decoded[0]['content'][0]['content'][1])) {
+ $temp = $decoded[0]['content'][0]['content'][1];
+ $public['publicKeyAlgorithm']['parameters'] = new ASN1\Element(substr($key, $temp['start'], $temp['length']));
+ }
+ $public['publicKey'] = substr($public['publicKey'], 1);
+ return $public;
+ }
+
+ throw new \RuntimeException('Unable to parse using either OneAsymmetricKey or PublicKeyInfo ASN1 maps');
+ }
+
+ /**
+ * Toggle between binary (DER) and printable (PEM) keys
+ *
+ * Printable keys are what are generated by default.
+ *
+ * @param bool $enabled
+ */
+ public static function setBinaryOutput($enabled)
+ {
+ self::$binary = $enabled;
+ }
+
+ /**
+ * Wrap a private key appropriately
+ *
+ * @param string $key
+ * @param string $attr
+ * @param mixed $params
+ * @param string $password
+ * @param string $oid optional
+ * @param string $publicKey optional
+ * @param array $options optional
+ * @return string
+ */
+ protected static function wrapPrivateKey($key, $attr, $params, $password, $oid = null, $publicKey = '', array $options = [])
+ {
+ self::initialize_static_variables();
+
+ $key = [
+ 'version' => 'v1',
+ 'privateKeyAlgorithm' => [
+ 'algorithm' => is_string(static::OID_NAME) ? static::OID_NAME : $oid
+ ],
+ 'privateKey' => $key
+ ];
+ if ($oid != 'id-Ed25519' && $oid != 'id-Ed448') {
+ $key['privateKeyAlgorithm']['parameters'] = $params;
+ }
+ if (!empty($attr)) {
+ $key['attributes'] = $attr;
+ }
+ if (!empty($publicKey)) {
+ $key['version'] = 'v2';
+ $key['publicKey'] = $publicKey;
+ }
+ $key = ASN1::encodeDER($key, Maps\OneAsymmetricKey::MAP);
+ if (!empty($password) && is_string($password)) {
+ $salt = Random::string(8);
+
+ $iterationCount = isset($options['iterationCount']) ? $options['iterationCount'] : self::$defaultIterationCount;
+ $encryptionAlgorithm = isset($options['encryptionAlgorithm']) ? $options['encryptionAlgorithm'] : self::$defaultEncryptionAlgorithm;
+ $encryptionScheme = isset($options['encryptionScheme']) ? $options['encryptionScheme'] : self::$defaultEncryptionScheme;
+ $prf = isset($options['PRF']) ? $options['PRF'] : self::$defaultPRF;
+
+ if ($encryptionAlgorithm == 'id-PBES2') {
+ $crypto = self::getPBES2EncryptionObject($encryptionScheme);
+ $hash = str_replace('-', '/', substr($prf, 11));
+ $kdf = 'pbkdf2';
+ $iv = Random::string($crypto->getBlockLength() >> 3);
+
+ $PBKDF2params = [
+ 'salt' => $salt,
+ 'iterationCount' => $iterationCount,
+ 'prf' => ['algorithm' => $prf, 'parameters' => null]
+ ];
+ $PBKDF2params = ASN1::encodeDER($PBKDF2params, Maps\PBKDF2params::MAP);
+
+ if (!$crypto instanceof RC2) {
+ $params = ['octetString' => $iv];
+ } else {
+ $params = [
+ 'rc2ParametersVersion' => 58,
+ 'iv' => $iv
+ ];
+ $params = ASN1::encodeDER($params, Maps\RC2CBCParameter::MAP);
+ $params = new ASN1\Element($params);
+ }
+
+ $params = [
+ 'keyDerivationFunc' => [
+ 'algorithm' => 'id-PBKDF2',
+ 'parameters' => new ASN1\Element($PBKDF2params)
+ ],
+ 'encryptionScheme' => [
+ 'algorithm' => $encryptionScheme,
+ 'parameters' => $params
+ ]
+ ];
+ $params = ASN1::encodeDER($params, Maps\PBES2params::MAP);
+
+ $crypto->setIV($iv);
+ } else {
+ $crypto = self::getPBES1EncryptionObject($encryptionAlgorithm);
+ $hash = self::getPBES1Hash($encryptionAlgorithm);
+ $kdf = self::getPBES1KDF($encryptionAlgorithm);
+
+ $params = [
+ 'salt' => $salt,
+ 'iterationCount' => $iterationCount
+ ];
+ $params = ASN1::encodeDER($params, Maps\PBEParameter::MAP);
+ }
+ $crypto->setPassword($password, $kdf, $hash, $salt, $iterationCount);
+ $key = $crypto->encrypt($key);
+
+ $key = [
+ 'encryptionAlgorithm' => [
+ 'algorithm' => $encryptionAlgorithm,
+ 'parameters' => new ASN1\Element($params)
+ ],
+ 'encryptedData' => $key
+ ];
+
+ $key = ASN1::encodeDER($key, Maps\EncryptedPrivateKeyInfo::MAP);
+
+ if (isset($options['binary']) ? $options['binary'] : self::$binary) {
+ return $key;
+ }
+
+ return "-----BEGIN ENCRYPTED PRIVATE KEY-----\r\n" .
+ chunk_split(Strings::base64_encode($key), 64) .
+ "-----END ENCRYPTED PRIVATE KEY-----";
+ }
+
+ if (isset($options['binary']) ? $options['binary'] : self::$binary) {
+ return $key;
+ }
+
+ return "-----BEGIN PRIVATE KEY-----\r\n" .
+ chunk_split(Strings::base64_encode($key), 64) .
+ "-----END PRIVATE KEY-----";
+ }
+
+ /**
+ * Wrap a public key appropriately
+ *
+ * @param string $key
+ * @param mixed $params
+ * @param string $oid
+ * @return string
+ */
+ protected static function wrapPublicKey($key, $params, $oid = null, array $options = [])
+ {
+ self::initialize_static_variables();
+
+ $key = [
+ 'publicKeyAlgorithm' => [
+ 'algorithm' => is_string(static::OID_NAME) ? static::OID_NAME : $oid
+ ],
+ 'publicKey' => "\0" . $key
+ ];
+
+ if ($oid != 'id-Ed25519' && $oid != 'id-Ed448') {
+ $key['publicKeyAlgorithm']['parameters'] = $params;
+ }
+
+ $key = ASN1::encodeDER($key, Maps\PublicKeyInfo::MAP);
+
+ if (isset($options['binary']) ? $options['binary'] : self::$binary) {
+ return $key;
+ }
+
+ return "-----BEGIN PUBLIC KEY-----\r\n" .
+ chunk_split(Strings::base64_encode($key), 64) .
+ "-----END PUBLIC KEY-----";
+ }
+
+ /**
+ * Perform some preliminary parsing of the key
+ *
+ * @param string $key
+ * @return array
+ */
+ private static function preParse(&$key)
+ {
+ self::initialize_static_variables();
+
+ if (self::$format != self::MODE_DER) {
+ $decoded = ASN1::extractBER($key);
+ if ($decoded !== false) {
+ $key = $decoded;
+ } elseif (self::$format == self::MODE_PEM) {
+ throw new \UnexpectedValueException('Expected base64-encoded PEM format but was unable to decode base64 text');
+ }
+ }
+
+ $decoded = ASN1::decodeBER($key);
+ if (!$decoded) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+
+ return $decoded;
+ }
+
+ /**
+ * Returns the encryption parameters used by the key
+ *
+ * @param string $key
+ * @return array
+ */
+ public static function extractEncryptionAlgorithm($key)
+ {
+ if (!Strings::is_stringable($key)) {
+ throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
+ }
+
+ $decoded = self::preParse($key);
+
+ $r = ASN1::asn1map($decoded[0], Maps\EncryptedPrivateKeyInfo::MAP);
+ if (!is_array($r)) {
+ throw new \RuntimeException('Unable to parse using EncryptedPrivateKeyInfo map');
+ }
+
+ if ($r['encryptionAlgorithm']['algorithm'] == 'id-PBES2') {
+ $decoded = ASN1::decodeBER($r['encryptionAlgorithm']['parameters']->element);
+ if (!$decoded) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+ $r['encryptionAlgorithm']['parameters'] = ASN1::asn1map($decoded[0], Maps\PBES2params::MAP);
+
+ $kdf = &$r['encryptionAlgorithm']['parameters']['keyDerivationFunc'];
+ switch ($kdf['algorithm']) {
+ case 'id-PBKDF2':
+ $decoded = ASN1::decodeBER($kdf['parameters']->element);
+ if (!$decoded) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+ $kdf['parameters'] = ASN1::asn1map($decoded[0], Maps\PBKDF2params::MAP);
+ }
+ }
+
+ return $r['encryptionAlgorithm'];
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PuTTY.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PuTTY.php
new file mode 100644
index 000000000..ff4a95a82
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Keys/PuTTY.php
@@ -0,0 +1,380 @@
+<?php
+
+/**
+ * PuTTY Formatted Key Handler
+ *
+ * See PuTTY's SSHPUBK.C and https://tartarus.org/~simon/putty-snapshots/htmldoc/AppendixC.html
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\Common\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\AES;
+use phpseclib3\Crypt\Hash;
+use phpseclib3\Crypt\Random;
+use phpseclib3\Exception\UnsupportedAlgorithmException;
+
+/**
+ * PuTTY Formatted Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PuTTY
+{
+ /**
+ * Default comment
+ *
+ * @var string
+ */
+ private static $comment = 'phpseclib-generated-key';
+
+ /**
+ * Default version
+ *
+ * @var int
+ */
+ private static $version = 2;
+
+ /**
+ * Sets the default comment
+ *
+ * @param string $comment
+ */
+ public static function setComment($comment)
+ {
+ self::$comment = str_replace(["\r", "\n"], '', $comment);
+ }
+
+ /**
+ * Sets the default version
+ *
+ * @param int $version
+ */
+ public static function setVersion($version)
+ {
+ if ($version != 2 && $version != 3) {
+ throw new \RuntimeException('Only supported versions are 2 and 3');
+ }
+ self::$version = $version;
+ }
+
+ /**
+ * Generate a symmetric key for PuTTY v2 keys
+ *
+ * @param string $password
+ * @param int $length
+ * @return string
+ */
+ private static function generateV2Key($password, $length)
+ {
+ $symkey = '';
+ $sequence = 0;
+ while (strlen($symkey) < $length) {
+ $temp = pack('Na*', $sequence++, $password);
+ $symkey .= Strings::hex2bin(sha1($temp));
+ }
+ return substr($symkey, 0, $length);
+ }
+
+ /**
+ * Generate a symmetric key for PuTTY v3 keys
+ *
+ * @param string $password
+ * @param string $flavour
+ * @param int $memory
+ * @param int $passes
+ * @param string $salt
+ * @return array
+ */
+ private static function generateV3Key($password, $flavour, $memory, $passes, $salt)
+ {
+ if (!function_exists('sodium_crypto_pwhash')) {
+ throw new \RuntimeException('sodium_crypto_pwhash needs to exist for Argon2 password hasing');
+ }
+
+ switch ($flavour) {
+ case 'Argon2i':
+ $flavour = SODIUM_CRYPTO_PWHASH_ALG_ARGON2I13;
+ break;
+ case 'Argon2id':
+ $flavour = SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13;
+ break;
+ default:
+ throw new UnsupportedAlgorithmException('Only Argon2i and Argon2id are supported');
+ }
+
+ $length = 80; // keylen + ivlen + mac_keylen
+ $temp = sodium_crypto_pwhash($length, $password, $salt, $passes, $memory << 10, $flavour);
+
+ $symkey = substr($temp, 0, 32);
+ $symiv = substr($temp, 32, 16);
+ $hashkey = substr($temp, -32);
+
+ return compact('symkey', 'symiv', 'hashkey');
+ }
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password
+ * @return array
+ */
+ public static function load($key, $password)
+ {
+ if (!Strings::is_stringable($key)) {
+ throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
+ }
+
+ if (strpos($key, 'BEGIN SSH2 PUBLIC KEY') !== false) {
+ $lines = preg_split('#[\r\n]+#', $key);
+ switch (true) {
+ case $lines[0] != '---- BEGIN SSH2 PUBLIC KEY ----':
+ throw new \UnexpectedValueException('Key doesn\'t start with ---- BEGIN SSH2 PUBLIC KEY ----');
+ case $lines[count($lines) - 1] != '---- END SSH2 PUBLIC KEY ----':
+ throw new \UnexpectedValueException('Key doesn\'t end with ---- END SSH2 PUBLIC KEY ----');
+ }
+ $lines = array_splice($lines, 1, -1);
+ $lines = array_map(function ($line) {
+ return rtrim($line, "\r\n");
+ }, $lines);
+ $data = $current = '';
+ $values = [];
+ $in_value = false;
+ foreach ($lines as $line) {
+ switch (true) {
+ case preg_match('#^(.*?): (.*)#', $line, $match):
+ $in_value = $line[strlen($line) - 1] == '\\';
+ $current = strtolower($match[1]);
+ $values[$current] = $in_value ? substr($match[2], 0, -1) : $match[2];
+ break;
+ case $in_value:
+ $in_value = $line[strlen($line) - 1] == '\\';
+ $values[$current] .= $in_value ? substr($line, 0, -1) : $line;
+ break;
+ default:
+ $data .= $line;
+ }
+ }
+
+ $components = call_user_func([static::PUBLIC_HANDLER, 'load'], $data);
+ if ($components === false) {
+ throw new \UnexpectedValueException('Unable to decode public key');
+ }
+ $components += $values;
+ $components['comment'] = str_replace(['\\\\', '\"'], ['\\', '"'], $values['comment']);
+
+ return $components;
+ }
+
+ $components = [];
+
+ $key = preg_split('#\r\n|\r|\n#', trim($key));
+ if (Strings::shift($key[0], strlen('PuTTY-User-Key-File-')) != 'PuTTY-User-Key-File-') {
+ return false;
+ }
+ $version = (int) Strings::shift($key[0], 3); // should be either "2: " or "3: 0" prior to int casting
+ if ($version != 2 && $version != 3) {
+ throw new \RuntimeException('Only v2 and v3 PuTTY private keys are supported');
+ }
+ $components['type'] = $type = rtrim($key[0]);
+ if (!in_array($type, static::$types)) {
+ $error = count(static::$types) == 1 ?
+ 'Only ' . static::$types[0] . ' keys are supported. ' :
+ '';
+ throw new UnsupportedAlgorithmException($error . 'This is an unsupported ' . $type . ' key');
+ }
+ $encryption = trim(preg_replace('#Encryption: (.+)#', '$1', $key[1]));
+ $components['comment'] = trim(preg_replace('#Comment: (.+)#', '$1', $key[2]));
+
+ $publicLength = trim(preg_replace('#Public-Lines: (\d+)#', '$1', $key[3]));
+ $public = Strings::base64_decode(implode('', array_map('trim', array_slice($key, 4, $publicLength))));
+
+ $source = Strings::packSSH2('ssss', $type, $encryption, $components['comment'], $public);
+
+ $length = unpack('Nlength', Strings::shift($public, 4))['length'];
+ $newtype = Strings::shift($public, $length);
+ if ($newtype != $type) {
+ throw new \RuntimeException('The binary type does not match the human readable type field');
+ }
+
+ $components['public'] = $public;
+
+ switch ($version) {
+ case 3:
+ $hashkey = '';
+ break;
+ case 2:
+ $hashkey = 'putty-private-key-file-mac-key';
+ }
+
+ $offset = $publicLength + 4;
+ switch ($encryption) {
+ case 'aes256-cbc':
+ $crypto = new AES('cbc');
+ switch ($version) {
+ case 3:
+ $flavour = trim(preg_replace('#Key-Derivation: (.*)#', '$1', $key[$offset++]));
+ $memory = trim(preg_replace('#Argon2-Memory: (\d+)#', '$1', $key[$offset++]));
+ $passes = trim(preg_replace('#Argon2-Passes: (\d+)#', '$1', $key[$offset++]));
+ $parallelism = trim(preg_replace('#Argon2-Parallelism: (\d+)#', '$1', $key[$offset++]));
+ $salt = Strings::hex2bin(trim(preg_replace('#Argon2-Salt: ([0-9a-f]+)#', '$1', $key[$offset++])));
+
+ $v3key = self::generateV3Key($password, $flavour, $memory, $passes, $salt);
+ $symkey = $v3key['symkey'];
+ $symiv = $v3key['symiv'];
+ $hashkey = $v3key['hashkey'];
+
+ break;
+ case 2:
+ $symkey = self::generateV2Key($password, 32);
+ $symiv = str_repeat("\0", $crypto->getBlockLength() >> 3);
+ $hashkey .= $password;
+ }
+ }
+
+ switch ($version) {
+ case 3:
+ $hash = new Hash('sha256');
+ $hash->setKey($hashkey);
+ break;
+ case 2:
+ $hash = new Hash('sha1');
+ $hash->setKey(sha1($hashkey, true));
+ }
+
+ $privateLength = trim(preg_replace('#Private-Lines: (\d+)#', '$1', $key[$offset++]));
+ $private = Strings::base64_decode(implode('', array_map('trim', array_slice($key, $offset, $privateLength))));
+
+ if ($encryption != 'none') {
+ $crypto->setKey($symkey);
+ $crypto->setIV($symiv);
+ $crypto->disablePadding();
+ $private = $crypto->decrypt($private);
+ }
+
+ $source .= Strings::packSSH2('s', $private);
+
+ $hmac = trim(preg_replace('#Private-MAC: (.+)#', '$1', $key[$offset + $privateLength]));
+ $hmac = Strings::hex2bin($hmac);
+
+ if (!hash_equals($hash->hash($source), $hmac)) {
+ throw new \UnexpectedValueException('MAC validation error');
+ }
+
+ $components['private'] = $private;
+
+ return $components;
+ }
+
+ /**
+ * Wrap a private key appropriately
+ *
+ * @param string $public
+ * @param string $private
+ * @param string $type
+ * @param string $password
+ * @param array $options optional
+ * @return string
+ */
+ protected static function wrapPrivateKey($public, $private, $type, $password, array $options = [])
+ {
+ $encryption = (!empty($password) || is_string($password)) ? 'aes256-cbc' : 'none';
+ $comment = isset($options['comment']) ? $options['comment'] : self::$comment;
+ $version = isset($options['version']) ? $options['version'] : self::$version;
+
+ $key = "PuTTY-User-Key-File-$version: $type\r\n";
+ $key .= "Encryption: $encryption\r\n";
+ $key .= "Comment: $comment\r\n";
+
+ $public = Strings::packSSH2('s', $type) . $public;
+
+ $source = Strings::packSSH2('ssss', $type, $encryption, $comment, $public);
+
+ $public = Strings::base64_encode($public);
+ $key .= "Public-Lines: " . ((strlen($public) + 63) >> 6) . "\r\n";
+ $key .= chunk_split($public, 64);
+
+ if (empty($password) && !is_string($password)) {
+ $source .= Strings::packSSH2('s', $private);
+ switch ($version) {
+ case 3:
+ $hash = new Hash('sha256');
+ $hash->setKey('');
+ break;
+ case 2:
+ $hash = new Hash('sha1');
+ $hash->setKey(sha1('putty-private-key-file-mac-key', true));
+ }
+ } else {
+ $private .= Random::string(16 - (strlen($private) & 15));
+ $source .= Strings::packSSH2('s', $private);
+ $crypto = new AES('cbc');
+
+ switch ($version) {
+ case 3:
+ $salt = Random::string(16);
+ $key .= "Key-Derivation: Argon2id\r\n";
+ $key .= "Argon2-Memory: 8192\r\n";
+ $key .= "Argon2-Passes: 13\r\n";
+ $key .= "Argon2-Parallelism: 1\r\n";
+ $key .= "Argon2-Salt: " . Strings::bin2hex($salt) . "\r\n";
+ $v3key = self::generateV3Key($password, 'Argon2id', 8192, 13, $salt);
+ $symkey = $v3key['symkey'];
+ $symiv = $v3key['symiv'];
+ $hashkey = $v3key['hashkey'];
+
+ $hash = new Hash('sha256');
+ $hash->setKey($hashkey);
+
+ break;
+ case 2:
+ $symkey = self::generateV2Key($password, 32);
+ $symiv = str_repeat("\0", $crypto->getBlockLength() >> 3);
+ $hashkey = 'putty-private-key-file-mac-key' . $password;
+
+ $hash = new Hash('sha1');
+ $hash->setKey(sha1($hashkey, true));
+ }
+
+ $crypto->setKey($symkey);
+ $crypto->setIV($symiv);
+ $crypto->disablePadding();
+ $private = $crypto->encrypt($private);
+ $mac = $hash->hash($source);
+ }
+
+ $private = Strings::base64_encode($private);
+ $key .= 'Private-Lines: ' . ((strlen($private) + 63) >> 6) . "\r\n";
+ $key .= chunk_split($private, 64);
+ $key .= 'Private-MAC: ' . Strings::bin2hex($hash->hash($source)) . "\r\n";
+
+ return $key;
+ }
+
+ /**
+ * Wrap a public key appropriately
+ *
+ * This is basically the format described in RFC 4716 (https://tools.ietf.org/html/rfc4716)
+ *
+ * @param string $key
+ * @param string $type
+ * @return string
+ */
+ protected static function wrapPublicKey($key, $type)
+ {
+ $key = pack('Na*a*', strlen($type), $type, $key);
+ $key = "---- BEGIN SSH2 PUBLIC KEY ----\r\n" .
+ 'Comment: "' . str_replace(['\\', '"'], ['\\\\', '\"'], self::$comment) . "\"\r\n" .
+ chunk_split(Strings::base64_encode($key), 64) .
+ '---- END SSH2 PUBLIC KEY ----';
+ return $key;
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Signature/Raw.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Signature/Raw.php
new file mode 100644
index 000000000..42a65afa4
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Formats/Signature/Raw.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * Raw Signature Handler
+ *
+ * PHP version 5
+ *
+ * Handles signatures as arrays
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\Common\Formats\Signature;
+
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Raw Signature Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Raw
+{
+ /**
+ * Loads a signature
+ *
+ * @param array $sig
+ * @return array|bool
+ */
+ public static function load($sig)
+ {
+ switch (true) {
+ case !is_array($sig):
+ case !isset($sig['r']) || !isset($sig['s']):
+ case !$sig['r'] instanceof BigInteger:
+ case !$sig['s'] instanceof BigInteger:
+ return false;
+ }
+
+ return [
+ 'r' => $sig['r'],
+ 's' => $sig['s']
+ ];
+ }
+
+ /**
+ * Returns a signature in the appropriate format
+ *
+ * @param BigInteger $r
+ * @param BigInteger $s
+ * @return string
+ */
+ public static function save(BigInteger $r, BigInteger $s)
+ {
+ return compact('r', 's');
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/PrivateKey.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/PrivateKey.php
new file mode 100644
index 000000000..a6e1eb0b4
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/PrivateKey.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * PrivateKey interface
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2009 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\Common;
+
+/**
+ * PrivateKey interface
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+interface PrivateKey
+{
+ public function sign($message);
+ //public function decrypt($ciphertext);
+ public function getPublicKey();
+ public function toString($type, array $options = []);
+
+ /**
+ * @param string|false $password
+ * @return mixed
+ */
+ public function withPassword($password = false);
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/PublicKey.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/PublicKey.php
new file mode 100644
index 000000000..48a5875b1
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/PublicKey.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * PublicKey interface
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2009 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\Common;
+
+/**
+ * PublicKey interface
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+interface PublicKey
+{
+ public function verify($message, $signature);
+ //public function encrypt($plaintext);
+ public function toString($type, array $options = []);
+ public function getFingerprint($algorithm);
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/StreamCipher.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/StreamCipher.php
new file mode 100644
index 000000000..c7c080f4e
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/StreamCipher.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * Base Class for all stream ciphers
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @author Hans-Juergen Petrich <petrich@tronic-media.com>
+ * @copyright 2007 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\Common;
+
+/**
+ * Base Class for all stream cipher classes
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class StreamCipher extends SymmetricKey
+{
+ /**
+ * Block Length of the cipher
+ *
+ * Stream ciphers do not have a block size
+ *
+ * @see SymmetricKey::block_size
+ * @var int
+ */
+ protected $block_size = 0;
+
+ /**
+ * Default Constructor.
+ *
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
+ * @return StreamCipher
+ */
+ public function __construct()
+ {
+ parent::__construct('stream');
+ }
+
+ /**
+ * Stream ciphers not use an IV
+ *
+ * @return bool
+ */
+ public function usesIV()
+ {
+ return false;
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/SymmetricKey.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/SymmetricKey.php
new file mode 100644
index 000000000..35d7a7d7a
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/SymmetricKey.php
@@ -0,0 +1,3398 @@
+<?php
+
+/**
+ * Base Class for all \phpseclib3\Crypt\* cipher classes
+ *
+ * PHP version 5
+ *
+ * Internally for phpseclib developers:
+ * If you plan to add a new cipher class, please note following rules:
+ *
+ * - The new \phpseclib3\Crypt\* cipher class should extend \phpseclib3\Crypt\Common\SymmetricKey
+ *
+ * - Following methods are then required to be overridden/overloaded:
+ *
+ * - encryptBlock()
+ *
+ * - decryptBlock()
+ *
+ * - setupKey()
+ *
+ * - All other methods are optional to be overridden/overloaded
+ *
+ * - Look at the source code of the current ciphers how they extend \phpseclib3\Crypt\Common\SymmetricKey
+ * and take one of them as a start up for the new cipher class.
+ *
+ * - Please read all the other comments/notes/hints here also for each class var/method
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @author Hans-Juergen Petrich <petrich@tronic-media.com>
+ * @copyright 2007 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\Common;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Blowfish;
+use phpseclib3\Crypt\Hash;
+use phpseclib3\Exception\BadDecryptionException;
+use phpseclib3\Exception\BadModeException;
+use phpseclib3\Exception\InconsistentSetupException;
+use phpseclib3\Exception\InsufficientSetupException;
+use phpseclib3\Exception\UnsupportedAlgorithmException;
+use phpseclib3\Math\BigInteger;
+use phpseclib3\Math\BinaryField;
+use phpseclib3\Math\PrimeField;
+
+/**
+ * Base Class for all \phpseclib3\Crypt\* cipher classes
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @author Hans-Juergen Petrich <petrich@tronic-media.com>
+ */
+abstract class SymmetricKey
+{
+ /**
+ * Encrypt / decrypt using the Counter mode.
+ *
+ * Set to -1 since that's what Crypt/Random.php uses to index the CTR mode.
+ *
+ * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Counter_.28CTR.29
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
+ */
+ const MODE_CTR = -1;
+ /**
+ * Encrypt / decrypt using the Electronic Code Book mode.
+ *
+ * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
+ */
+ const MODE_ECB = 1;
+ /**
+ * Encrypt / decrypt using the Code Book Chaining mode.
+ *
+ * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
+ */
+ const MODE_CBC = 2;
+ /**
+ * Encrypt / decrypt using the Cipher Feedback mode.
+ *
+ * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
+ */
+ const MODE_CFB = 3;
+ /**
+ * Encrypt / decrypt using the Cipher Feedback mode (8bit)
+ *
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
+ */
+ const MODE_CFB8 = 7;
+ /**
+ * Encrypt / decrypt using the Output Feedback mode (8bit)
+ *
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
+ */
+ const MODE_OFB8 = 8;
+ /**
+ * Encrypt / decrypt using the Output Feedback mode.
+ *
+ * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Output_feedback_.28OFB.29
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
+ */
+ const MODE_OFB = 4;
+ /**
+ * Encrypt / decrypt using Galois/Counter mode.
+ *
+ * @link https://en.wikipedia.org/wiki/Galois/Counter_Mode
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
+ */
+ const MODE_GCM = 5;
+ /**
+ * Encrypt / decrypt using streaming mode.
+ *
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
+ */
+ const MODE_STREAM = 6;
+
+ /**
+ * Mode Map
+ *
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
+ */
+ const MODE_MAP = [
+ 'ctr' => self::MODE_CTR,
+ 'ecb' => self::MODE_ECB,
+ 'cbc' => self::MODE_CBC,
+ 'cfb' => self::MODE_CFB,
+ 'cfb8' => self::MODE_CFB8,
+ 'ofb' => self::MODE_OFB,
+ 'ofb8' => self::MODE_OFB8,
+ 'gcm' => self::MODE_GCM,
+ 'stream' => self::MODE_STREAM
+ ];
+
+ /**
+ * Base value for the internal implementation $engine switch
+ *
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
+ */
+ const ENGINE_INTERNAL = 1;
+ /**
+ * Base value for the eval() implementation $engine switch
+ *
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
+ */
+ const ENGINE_EVAL = 2;
+ /**
+ * Base value for the mcrypt implementation $engine switch
+ *
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
+ */
+ const ENGINE_MCRYPT = 3;
+ /**
+ * Base value for the openssl implementation $engine switch
+ *
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
+ */
+ const ENGINE_OPENSSL = 4;
+ /**
+ * Base value for the libsodium implementation $engine switch
+ *
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
+ */
+ const ENGINE_LIBSODIUM = 5;
+ /**
+ * Base value for the openssl / gcm implementation $engine switch
+ *
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
+ */
+ const ENGINE_OPENSSL_GCM = 6;
+
+ /**
+ * Engine Reverse Map
+ *
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::getEngine()
+ */
+ const ENGINE_MAP = [
+ self::ENGINE_INTERNAL => 'PHP',
+ self::ENGINE_EVAL => 'Eval',
+ self::ENGINE_MCRYPT => 'mcrypt',
+ self::ENGINE_OPENSSL => 'OpenSSL',
+ self::ENGINE_LIBSODIUM => 'libsodium',
+ self::ENGINE_OPENSSL_GCM => 'OpenSSL (GCM)'
+ ];
+
+ /**
+ * The Encryption Mode
+ *
+ * @see self::__construct()
+ * @var int
+ */
+ protected $mode;
+
+ /**
+ * The Block Length of the block cipher
+ *
+ * @var int
+ */
+ protected $block_size = 16;
+
+ /**
+ * The Key
+ *
+ * @see self::setKey()
+ * @var string
+ */
+ protected $key = false;
+
+ /**
+ * HMAC Key
+ *
+ * @see self::setupGCM()
+ * @var ?string
+ */
+ protected $hKey = false;
+
+ /**
+ * The Initialization Vector
+ *
+ * @see self::setIV()
+ * @var string
+ */
+ protected $iv = false;
+
+ /**
+ * A "sliding" Initialization Vector
+ *
+ * @see self::enableContinuousBuffer()
+ * @see self::clearBuffers()
+ * @var string
+ */
+ protected $encryptIV;
+
+ /**
+ * A "sliding" Initialization Vector
+ *
+ * @see self::enableContinuousBuffer()
+ * @see self::clearBuffers()
+ * @var string
+ */
+ protected $decryptIV;
+
+ /**
+ * Continuous Buffer status
+ *
+ * @see self::enableContinuousBuffer()
+ * @var bool
+ */
+ protected $continuousBuffer = false;
+
+ /**
+ * Encryption buffer for CTR, OFB and CFB modes
+ *
+ * @see self::encrypt()
+ * @see self::clearBuffers()
+ * @var array
+ */
+ protected $enbuffer;
+
+ /**
+ * Decryption buffer for CTR, OFB and CFB modes
+ *
+ * @see self::decrypt()
+ * @see self::clearBuffers()
+ * @var array
+ */
+ protected $debuffer;
+
+ /**
+ * mcrypt resource for encryption
+ *
+ * The mcrypt resource can be recreated every time something needs to be created or it can be created just once.
+ * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode.
+ *
+ * @see self::encrypt()
+ * @var resource
+ */
+ private $enmcrypt;
+
+ /**
+ * mcrypt resource for decryption
+ *
+ * The mcrypt resource can be recreated every time something needs to be created or it can be created just once.
+ * Since mcrypt operates in continuous mode, by default, it'll need to be recreated when in non-continuous mode.
+ *
+ * @see self::decrypt()
+ * @var resource
+ */
+ private $demcrypt;
+
+ /**
+ * Does the enmcrypt resource need to be (re)initialized?
+ *
+ * @see \phpseclib3\Crypt\Twofish::setKey()
+ * @see \phpseclib3\Crypt\Twofish::setIV()
+ * @var bool
+ */
+ private $enchanged = true;
+
+ /**
+ * Does the demcrypt resource need to be (re)initialized?
+ *
+ * @see \phpseclib3\Crypt\Twofish::setKey()
+ * @see \phpseclib3\Crypt\Twofish::setIV()
+ * @var bool
+ */
+ private $dechanged = true;
+
+ /**
+ * mcrypt resource for CFB mode
+ *
+ * mcrypt's CFB mode, in (and only in) buffered context,
+ * is broken, so phpseclib implements the CFB mode by it self,
+ * even when the mcrypt php extension is available.
+ *
+ * In order to do the CFB-mode work (fast) phpseclib
+ * use a separate ECB-mode mcrypt resource.
+ *
+ * @link http://phpseclib.sourceforge.net/cfb-demo.phps
+ * @see self::encrypt()
+ * @see self::decrypt()
+ * @see self::setupMcrypt()
+ * @var resource
+ */
+ private $ecb;
+
+ /**
+ * Optimizing value while CFB-encrypting
+ *
+ * Only relevant if $continuousBuffer enabled
+ * and $engine == self::ENGINE_MCRYPT
+ *
+ * It's faster to re-init $enmcrypt if
+ * $buffer bytes > $cfb_init_len than
+ * using the $ecb resource furthermore.
+ *
+ * This value depends of the chosen cipher
+ * and the time it would be needed for it's
+ * initialization [by mcrypt_generic_init()]
+ * which, typically, depends on the complexity
+ * on its internaly Key-expanding algorithm.
+ *
+ * @see self::encrypt()
+ * @var int
+ */
+ protected $cfb_init_len = 600;
+
+ /**
+ * Does internal cipher state need to be (re)initialized?
+ *
+ * @see self::setKey()
+ * @see self::setIV()
+ * @see self::disableContinuousBuffer()
+ * @var bool
+ */
+ protected $changed = true;
+
+ /**
+ * Does Eval engie need to be (re)initialized?
+ *
+ * @see self::setup()
+ * @var bool
+ */
+ protected $nonIVChanged = true;
+
+ /**
+ * Padding status
+ *
+ * @see self::enablePadding()
+ * @var bool
+ */
+ private $padding = true;
+
+ /**
+ * Is the mode one that is paddable?
+ *
+ * @see self::__construct()
+ * @var bool
+ */
+ private $paddable = false;
+
+ /**
+ * Holds which crypt engine internaly should be use,
+ * which will be determined automatically on __construct()
+ *
+ * Currently available $engines are:
+ * - self::ENGINE_LIBSODIUM (very fast, php-extension: libsodium, extension_loaded('libsodium') required)
+ * - self::ENGINE_OPENSSL_GCM (very fast, php-extension: openssl, extension_loaded('openssl') required)
+ * - self::ENGINE_OPENSSL (very fast, php-extension: openssl, extension_loaded('openssl') required)
+ * - self::ENGINE_MCRYPT (fast, php-extension: mcrypt, extension_loaded('mcrypt') required)
+ * - self::ENGINE_EVAL (medium, pure php-engine, no php-extension required)
+ * - self::ENGINE_INTERNAL (slower, pure php-engine, no php-extension required)
+ *
+ * @see self::setEngine()
+ * @see self::encrypt()
+ * @see self::decrypt()
+ * @var int
+ */
+ protected $engine;
+
+ /**
+ * Holds the preferred crypt engine
+ *
+ * @see self::setEngine()
+ * @see self::setPreferredEngine()
+ * @var int
+ */
+ private $preferredEngine;
+
+ /**
+ * The mcrypt specific name of the cipher
+ *
+ * Only used if $engine == self::ENGINE_MCRYPT
+ *
+ * @link http://www.php.net/mcrypt_module_open
+ * @link http://www.php.net/mcrypt_list_algorithms
+ * @see self::setupMcrypt()
+ * @var string
+ */
+ protected $cipher_name_mcrypt;
+
+ /**
+ * The openssl specific name of the cipher
+ *
+ * Only used if $engine == self::ENGINE_OPENSSL
+ *
+ * @link http://www.php.net/openssl-get-cipher-methods
+ * @var string
+ */
+ protected $cipher_name_openssl;
+
+ /**
+ * The openssl specific name of the cipher in ECB mode
+ *
+ * If OpenSSL does not support the mode we're trying to use (CTR)
+ * it can still be emulated with ECB mode.
+ *
+ * @link http://www.php.net/openssl-get-cipher-methods
+ * @var string
+ */
+ protected $cipher_name_openssl_ecb;
+
+ /**
+ * The default salt used by setPassword()
+ *
+ * @see self::setPassword()
+ * @var string
+ */
+ private $password_default_salt = 'phpseclib/salt';
+
+ /**
+ * The name of the performance-optimized callback function
+ *
+ * Used by encrypt() / decrypt()
+ * only if $engine == self::ENGINE_INTERNAL
+ *
+ * @see self::encrypt()
+ * @see self::decrypt()
+ * @see self::setupInlineCrypt()
+ * @var Callback
+ */
+ protected $inline_crypt;
+
+ /**
+ * If OpenSSL can be used in ECB but not in CTR we can emulate CTR
+ *
+ * @see self::openssl_ctr_process()
+ * @var bool
+ */
+ private $openssl_emulate_ctr = false;
+
+ /**
+ * Don't truncate / null pad key
+ *
+ * @see self::clearBuffers()
+ * @var bool
+ */
+ private $skip_key_adjustment = false;
+
+ /**
+ * Has the key length explicitly been set or should it be derived from the key, itself?
+ *
+ * @see self::setKeyLength()
+ * @var bool
+ */
+ protected $explicit_key_length = false;
+
+ /**
+ * Hash subkey for GHASH
+ *
+ * @see self::setupGCM()
+ * @see self::ghash()
+ * @var BinaryField\Integer
+ */
+ private $h;
+
+ /**
+ * Additional authenticated data
+ *
+ * @var string
+ */
+ protected $aad = '';
+
+ /**
+ * Authentication Tag produced after a round of encryption
+ *
+ * @var string
+ */
+ protected $newtag = false;
+
+ /**
+ * Authentication Tag to be verified during decryption
+ *
+ * @var string
+ */
+ protected $oldtag = false;
+
+ /**
+ * GCM Binary Field
+ *
+ * @see self::__construct()
+ * @see self::ghash()
+ * @var BinaryField
+ */
+ private static $gcmField;
+
+ /**
+ * Poly1305 Prime Field
+ *
+ * @see self::enablePoly1305()
+ * @see self::poly1305()
+ * @var PrimeField
+ */
+ private static $poly1305Field;
+
+ /**
+ * Flag for using regular vs "safe" intval
+ *
+ * @see self::initialize_static_variables()
+ * @var boolean
+ */
+ protected static $use_reg_intval;
+
+ /**
+ * Poly1305 Key
+ *
+ * @see self::setPoly1305Key()
+ * @see self::poly1305()
+ * @var string
+ */
+ protected $poly1305Key;
+
+ /**
+ * Poly1305 Flag
+ *
+ * @see self::setPoly1305Key()
+ * @see self::enablePoly1305()
+ * @var boolean
+ */
+ protected $usePoly1305 = false;
+
+ /**
+ * The Original Initialization Vector
+ *
+ * GCM uses the nonce to build the IV but we want to be able to distinguish between nonce-derived
+ * IV's and user-set IV's
+ *
+ * @see self::setIV()
+ * @var string
+ */
+ private $origIV = false;
+
+ /**
+ * Nonce
+ *
+ * Only used with GCM. We could re-use setIV() but nonce's can be of a different length and
+ * toggling between GCM and other modes could be more complicated if we re-used setIV()
+ *
+ * @see self::setNonce()
+ * @var string
+ */
+ protected $nonce = false;
+
+ /**
+ * Default Constructor.
+ *
+ * $mode could be:
+ *
+ * - ecb
+ *
+ * - cbc
+ *
+ * - ctr
+ *
+ * - cfb
+ *
+ * - cfb8
+ *
+ * - ofb
+ *
+ * - ofb8
+ *
+ * - gcm
+ *
+ * @param string $mode
+ * @throws BadModeException if an invalid / unsupported mode is provided
+ */
+ public function __construct($mode)
+ {
+ $mode = strtolower($mode);
+ // necessary because of 5.6 compatibility; we can't do isset(self::MODE_MAP[$mode]) in 5.6
+ $map = self::MODE_MAP;
+ if (!isset($map[$mode])) {
+ throw new BadModeException('No valid mode has been specified');
+ }
+
+ $mode = self::MODE_MAP[$mode];
+
+ // $mode dependent settings
+ switch ($mode) {
+ case self::MODE_ECB:
+ case self::MODE_CBC:
+ $this->paddable = true;
+ break;
+ case self::MODE_CTR:
+ case self::MODE_CFB:
+ case self::MODE_CFB8:
+ case self::MODE_OFB:
+ case self::MODE_OFB8:
+ case self::MODE_STREAM:
+ $this->paddable = false;
+ break;
+ case self::MODE_GCM:
+ if ($this->block_size != 16) {
+ throw new BadModeException('GCM is only valid for block ciphers with a block size of 128 bits');
+ }
+ if (!isset(self::$gcmField)) {
+ self::$gcmField = new BinaryField(128, 7, 2, 1, 0);
+ }
+ $this->paddable = false;
+ break;
+ default:
+ throw new BadModeException('No valid mode has been specified');
+ }
+
+ $this->mode = $mode;
+
+ static::initialize_static_variables();
+ }
+
+ /**
+ * Initialize static variables
+ */
+ protected static function initialize_static_variables()
+ {
+ if (!isset(self::$use_reg_intval)) {
+ switch (true) {
+ // PHP_OS & "\xDF\xDF\xDF" == strtoupper(substr(PHP_OS, 0, 3)), but a lot faster
+ case (PHP_OS & "\xDF\xDF\xDF") === 'WIN':
+ case !function_exists('php_uname'):
+ case !is_string(php_uname('m')):
+ case (php_uname('m') & "\xDF\xDF\xDF") != 'ARM':
+ case defined('PHP_INT_SIZE') && PHP_INT_SIZE == 8:
+ self::$use_reg_intval = true;
+ break;
+ case (php_uname('m') & "\xDF\xDF\xDF") == 'ARM':
+ switch (true) {
+ /* PHP 7.0.0 introduced a bug that affected 32-bit ARM processors:
+
+ https://github.com/php/php-src/commit/716da71446ebbd40fa6cf2cea8a4b70f504cc3cd
+
+ altho the changelogs make no mention of it, this bug was fixed with this commit:
+
+ https://github.com/php/php-src/commit/c1729272b17a1fe893d1a54e423d3b71470f3ee8
+
+ affected versions of PHP are: 7.0.x, 7.1.0 - 7.1.23 and 7.2.0 - 7.2.11 */
+ case PHP_VERSION_ID >= 70000 && PHP_VERSION_ID <= 70123:
+ case PHP_VERSION_ID >= 70200 && PHP_VERSION_ID <= 70211:
+ self::$use_reg_intval = false;
+ break;
+ default:
+ self::$use_reg_intval = true;
+ }
+ }
+ }
+ }
+
+ /**
+ * Sets the initialization vector.
+ *
+ * setIV() is not required when ecb or gcm modes are being used.
+ *
+ * {@internal Can be overwritten by a sub class, but does not have to be}
+ *
+ * @param string $iv
+ * @throws \LengthException if the IV length isn't equal to the block size
+ * @throws \BadMethodCallException if an IV is provided when one shouldn't be
+ */
+ public function setIV($iv)
+ {
+ if ($this->mode == self::MODE_ECB) {
+ throw new \BadMethodCallException('This mode does not require an IV.');
+ }
+
+ if ($this->mode == self::MODE_GCM) {
+ throw new \BadMethodCallException('Use setNonce instead');
+ }
+
+ if (!$this->usesIV()) {
+ throw new \BadMethodCallException('This algorithm does not use an IV.');
+ }
+
+ if (strlen($iv) != $this->block_size) {
+ throw new \LengthException('Received initialization vector of size ' . strlen($iv) . ', but size ' . $this->block_size . ' is required');
+ }
+
+ $this->iv = $this->origIV = $iv;
+ $this->changed = true;
+ }
+
+ /**
+ * Enables Poly1305 mode.
+ *
+ * Once enabled Poly1305 cannot be disabled.
+ *
+ * @throws \BadMethodCallException if Poly1305 is enabled whilst in GCM mode
+ */
+ public function enablePoly1305()
+ {
+ if ($this->mode == self::MODE_GCM) {
+ throw new \BadMethodCallException('Poly1305 cannot be used in GCM mode');
+ }
+
+ $this->usePoly1305 = true;
+ }
+
+ /**
+ * Enables Poly1305 mode.
+ *
+ * Once enabled Poly1305 cannot be disabled. If $key is not passed then an attempt to call createPoly1305Key
+ * will be made.
+ *
+ * @param string $key optional
+ * @throws \LengthException if the key isn't long enough
+ * @throws \BadMethodCallException if Poly1305 is enabled whilst in GCM mode
+ */
+ public function setPoly1305Key($key = null)
+ {
+ if ($this->mode == self::MODE_GCM) {
+ throw new \BadMethodCallException('Poly1305 cannot be used in GCM mode');
+ }
+
+ if (!is_string($key) || strlen($key) != 32) {
+ throw new \LengthException('The Poly1305 key must be 32 bytes long (256 bits)');
+ }
+
+ if (!isset(self::$poly1305Field)) {
+ // 2^130-5
+ self::$poly1305Field = new PrimeField(new BigInteger('3fffffffffffffffffffffffffffffffb', 16));
+ }
+
+ $this->poly1305Key = $key;
+ $this->usePoly1305 = true;
+ }
+
+ /**
+ * Sets the nonce.
+ *
+ * setNonce() is only required when gcm is used
+ *
+ * @param string $nonce
+ * @throws \BadMethodCallException if an nonce is provided when one shouldn't be
+ */
+ public function setNonce($nonce)
+ {
+ if ($this->mode != self::MODE_GCM) {
+ throw new \BadMethodCallException('Nonces are only used in GCM mode.');
+ }
+
+ $this->nonce = $nonce;
+ $this->setEngine();
+ }
+
+ /**
+ * Sets additional authenticated data
+ *
+ * setAAD() is only used by gcm or in poly1305 mode
+ *
+ * @param string $aad
+ * @throws \BadMethodCallException if mode isn't GCM or if poly1305 isn't being utilized
+ */
+ public function setAAD($aad)
+ {
+ if ($this->mode != self::MODE_GCM && !$this->usePoly1305) {
+ throw new \BadMethodCallException('Additional authenticated data is only utilized in GCM mode or with Poly1305');
+ }
+
+ $this->aad = $aad;
+ }
+
+ /**
+ * Returns whether or not the algorithm uses an IV
+ *
+ * @return bool
+ */
+ public function usesIV()
+ {
+ return $this->mode != self::MODE_GCM && $this->mode != self::MODE_ECB;
+ }
+
+ /**
+ * Returns whether or not the algorithm uses a nonce
+ *
+ * @return bool
+ */
+ public function usesNonce()
+ {
+ return $this->mode == self::MODE_GCM;
+ }
+
+ /**
+ * Returns the current key length in bits
+ *
+ * @return int
+ */
+ public function getKeyLength()
+ {
+ return $this->key_length << 3;
+ }
+
+ /**
+ * Returns the current block length in bits
+ *
+ * @return int
+ */
+ public function getBlockLength()
+ {
+ return $this->block_size << 3;
+ }
+
+ /**
+ * Returns the current block length in bytes
+ *
+ * @return int
+ */
+ public function getBlockLengthInBytes()
+ {
+ return $this->block_size;
+ }
+
+ /**
+ * Sets the key length.
+ *
+ * Keys with explicitly set lengths need to be treated accordingly
+ *
+ * @param int $length
+ */
+ public function setKeyLength($length)
+ {
+ $this->explicit_key_length = $length >> 3;
+
+ if (is_string($this->key) && strlen($this->key) != $this->explicit_key_length) {
+ $this->key = false;
+ throw new InconsistentSetupException('Key has already been set and is not ' . $this->explicit_key_length . ' bytes long');
+ }
+ }
+
+ /**
+ * Sets the key.
+ *
+ * The min/max length(s) of the key depends on the cipher which is used.
+ * If the key not fits the length(s) of the cipher it will paded with null bytes
+ * up to the closest valid key length. If the key is more than max length,
+ * we trim the excess bits.
+ *
+ * If the key is not explicitly set, it'll be assumed to be all null bytes.
+ *
+ * {@internal Could, but not must, extend by the child Crypt_* class}
+ *
+ * @param string $key
+ */
+ public function setKey($key)
+ {
+ if ($this->explicit_key_length !== false && strlen($key) != $this->explicit_key_length) {
+ throw new InconsistentSetupException('Key length has already been set to ' . $this->explicit_key_length . ' bytes and this key is ' . strlen($key) . ' bytes');
+ }
+
+ $this->key = $key;
+ $this->key_length = strlen($key);
+ $this->setEngine();
+ }
+
+ /**
+ * Sets the password.
+ *
+ * Depending on what $method is set to, setPassword()'s (optional) parameters are as follows:
+ * {@link http://en.wikipedia.org/wiki/PBKDF2 pbkdf2} or pbkdf1:
+ * $hash, $salt, $count, $dkLen
+ *
+ * Where $hash (default = sha1) currently supports the following hashes: see: Crypt/Hash.php
+ * {@link https://en.wikipedia.org/wiki/Bcrypt bcypt}:
+ * $salt, $rounds, $keylen
+ *
+ * This is a modified version of bcrypt used by OpenSSH.
+ *
+ * {@internal Could, but not must, extend by the child Crypt_* class}
+ *
+ * @see Crypt/Hash.php
+ * @param string $password
+ * @param string $method
+ * @param int|string ...$func_args
+ * @throws \LengthException if pbkdf1 is being used and the derived key length exceeds the hash length
+ * @throws \RuntimeException if bcrypt is being used and a salt isn't provided
+ * @return bool
+ */
+ public function setPassword($password, $method = 'pbkdf2', ...$func_args)
+ {
+ $key = '';
+
+ $method = strtolower($method);
+ switch ($method) {
+ case 'bcrypt':
+ if (!isset($func_args[2])) {
+ throw new \RuntimeException('A salt must be provided for bcrypt to work');
+ }
+
+ $salt = $func_args[0];
+
+ $rounds = isset($func_args[1]) ? $func_args[1] : 16;
+ $keylen = isset($func_args[2]) ? $func_args[2] : $this->key_length;
+
+ $key = Blowfish::bcrypt_pbkdf($password, $salt, $keylen + $this->block_size, $rounds);
+
+ $this->setKey(substr($key, 0, $keylen));
+ $this->setIV(substr($key, $keylen));
+
+ return true;
+ case 'pkcs12': // from https://tools.ietf.org/html/rfc7292#appendix-B.2
+ case 'pbkdf1':
+ case 'pbkdf2':
+ // Hash function
+ $hash = isset($func_args[0]) ? strtolower($func_args[0]) : 'sha1';
+ $hashObj = new Hash();
+ $hashObj->setHash($hash);
+
+ // WPA and WPA2 use the SSID as the salt
+ $salt = isset($func_args[1]) ? $func_args[1] : $this->password_default_salt;
+
+ // RFC2898#section-4.2 uses 1,000 iterations by default
+ // WPA and WPA2 use 4,096.
+ $count = isset($func_args[2]) ? $func_args[2] : 1000;
+
+ // Keylength
+ if (isset($func_args[3])) {
+ if ($func_args[3] <= 0) {
+ throw new \LengthException('Derived key length cannot be longer 0 or less');
+ }
+ $dkLen = $func_args[3];
+ } else {
+ $key_length = $this->explicit_key_length !== false ? $this->explicit_key_length : $this->key_length;
+ $dkLen = $method == 'pbkdf1' ? 2 * $key_length : $key_length;
+ }
+
+ switch (true) {
+ case $method == 'pkcs12':
+ /*
+ In this specification, however, all passwords are created from
+ BMPStrings with a NULL terminator. This means that each character in
+ the original BMPString is encoded in 2 bytes in big-endian format
+ (most-significant byte first). There are no Unicode byte order
+ marks. The 2 bytes produced from the last character in the BMPString
+ are followed by 2 additional bytes with the value 0x00.
+
+ -- https://tools.ietf.org/html/rfc7292#appendix-B.1
+ */
+ $password = "\0" . chunk_split($password, 1, "\0") . "\0";
+
+ /*
+ This standard specifies 3 different values for the ID byte mentioned
+ above:
+
+ 1. If ID=1, then the pseudorandom bits being produced are to be used
+ as key material for performing encryption or decryption.
+
+ 2. If ID=2, then the pseudorandom bits being produced are to be used
+ as an IV (Initial Value) for encryption or decryption.
+
+ 3. If ID=3, then the pseudorandom bits being produced are to be used
+ as an integrity key for MACing.
+ */
+ // Construct a string, D (the "diversifier"), by concatenating v/8
+ // copies of ID.
+ $blockLength = $hashObj->getBlockLengthInBytes();
+ $d1 = str_repeat(chr(1), $blockLength);
+ $d2 = str_repeat(chr(2), $blockLength);
+ $s = '';
+ if (strlen($salt)) {
+ while (strlen($s) < $blockLength) {
+ $s .= $salt;
+ }
+ }
+ $s = substr($s, 0, $blockLength);
+
+ $p = '';
+ if (strlen($password)) {
+ while (strlen($p) < $blockLength) {
+ $p .= $password;
+ }
+ }
+ $p = substr($p, 0, $blockLength);
+
+ $i = $s . $p;
+
+ $this->setKey(self::pkcs12helper($dkLen, $hashObj, $i, $d1, $count));
+ if ($this->usesIV()) {
+ $this->setIV(self::pkcs12helper($this->block_size, $hashObj, $i, $d2, $count));
+ }
+
+ return true;
+ case $method == 'pbkdf1':
+ if ($dkLen > $hashObj->getLengthInBytes()) {
+ throw new \LengthException('Derived key length cannot be longer than the hash length');
+ }
+ $t = $password . $salt;
+ for ($i = 0; $i < $count; ++$i) {
+ $t = $hashObj->hash($t);
+ }
+ $key = substr($t, 0, $dkLen);
+
+ $this->setKey(substr($key, 0, $dkLen >> 1));
+ if ($this->usesIV()) {
+ $this->setIV(substr($key, $dkLen >> 1));
+ }
+
+ return true;
+ case !in_array($hash, hash_algos()):
+ $i = 1;
+ $hashObj->setKey($password);
+ while (strlen($key) < $dkLen) {
+ $f = $u = $hashObj->hash($salt . pack('N', $i++));
+ for ($j = 2; $j <= $count; ++$j) {
+ $u = $hashObj->hash($u);
+ $f ^= $u;
+ }
+ $key .= $f;
+ }
+ $key = substr($key, 0, $dkLen);
+ break;
+ default:
+ $key = hash_pbkdf2($hash, $password, $salt, $count, $dkLen, true);
+ }
+ break;
+ default:
+ throw new UnsupportedAlgorithmException($method . ' is not a supported password hashing method');
+ }
+
+ $this->setKey($key);
+
+ return true;
+ }
+
+ /**
+ * PKCS#12 KDF Helper Function
+ *
+ * As discussed here:
+ *
+ * {@link https://tools.ietf.org/html/rfc7292#appendix-B}
+ *
+ * @see self::setPassword()
+ * @param int $n
+ * @param Hash $hashObj
+ * @param string $i
+ * @param string $d
+ * @param int $count
+ * @return string $a
+ */
+ private static function pkcs12helper($n, $hashObj, $i, $d, $count)
+ {
+ static $one;
+ if (!isset($one)) {
+ $one = new BigInteger(1);
+ }
+
+ $blockLength = $hashObj->getBlockLength() >> 3;
+
+ $c = ceil($n / $hashObj->getLengthInBytes());
+ $a = '';
+ for ($j = 1; $j <= $c; $j++) {
+ $ai = $d . $i;
+ for ($k = 0; $k < $count; $k++) {
+ $ai = $hashObj->hash($ai);
+ }
+ $b = '';
+ while (strlen($b) < $blockLength) {
+ $b .= $ai;
+ }
+ $b = substr($b, 0, $blockLength);
+ $b = new BigInteger($b, 256);
+ $newi = '';
+ for ($k = 0; $k < strlen($i); $k += $blockLength) {
+ $temp = substr($i, $k, $blockLength);
+ $temp = new BigInteger($temp, 256);
+ $temp->setPrecision($blockLength << 3);
+ $temp = $temp->add($b);
+ $temp = $temp->add($one);
+ $newi .= $temp->toBytes(false);
+ }
+ $i = $newi;
+ $a .= $ai;
+ }
+
+ return substr($a, 0, $n);
+ }
+
+ /**
+ * Encrypts a message.
+ *
+ * $plaintext will be padded with additional bytes such that it's length is a multiple of the block size. Other cipher
+ * implementations may or may not pad in the same manner. Other common approaches to padding and the reasons why it's
+ * necessary are discussed in the following
+ * URL:
+ *
+ * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html}
+ *
+ * An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does.
+ * strlen($plaintext) will still need to be a multiple of the block size, however, arbitrary values can be added to make it that
+ * length.
+ *
+ * {@internal Could, but not must, extend by the child Crypt_* class}
+ *
+ * @see self::decrypt()
+ * @param string $plaintext
+ * @return string $ciphertext
+ */
+ public function encrypt($plaintext)
+ {
+ if ($this->paddable) {
+ $plaintext = $this->pad($plaintext);
+ }
+
+ $this->setup();
+
+ if ($this->mode == self::MODE_GCM) {
+ $oldIV = $this->iv;
+ Strings::increment_str($this->iv);
+ $cipher = new static('ctr');
+ $cipher->setKey($this->key);
+ $cipher->setIV($this->iv);
+ $ciphertext = $cipher->encrypt($plaintext);
+
+ $s = $this->ghash(
+ self::nullPad128($this->aad) .
+ self::nullPad128($ciphertext) .
+ self::len64($this->aad) .
+ self::len64($ciphertext)
+ );
+ $cipher->encryptIV = $this->iv = $this->encryptIV = $this->decryptIV = $oldIV;
+ $this->newtag = $cipher->encrypt($s);
+ return $ciphertext;
+ }
+
+ if (isset($this->poly1305Key)) {
+ $cipher = clone $this;
+ unset($cipher->poly1305Key);
+ $this->usePoly1305 = false;
+ $ciphertext = $cipher->encrypt($plaintext);
+ $this->newtag = $this->poly1305($ciphertext);
+ return $ciphertext;
+ }
+
+ if ($this->engine === self::ENGINE_OPENSSL) {
+ switch ($this->mode) {
+ case self::MODE_STREAM:
+ return openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
+ case self::MODE_ECB:
+ return openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
+ case self::MODE_CBC:
+ $result = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->encryptIV);
+ if ($this->continuousBuffer) {
+ $this->encryptIV = substr($result, -$this->block_size);
+ }
+ return $result;
+ case self::MODE_CTR:
+ return $this->openssl_ctr_process($plaintext, $this->encryptIV, $this->enbuffer);
+ case self::MODE_CFB:
+ // cfb loosely routines inspired by openssl's:
+ // {@link http://cvs.openssl.org/fileview?f=openssl/crypto/modes/cfb128.c&v=1.3.2.2.2.1}
+ $ciphertext = '';
+ if ($this->continuousBuffer) {
+ $iv = &$this->encryptIV;
+ $pos = &$this->enbuffer['pos'];
+ } else {
+ $iv = $this->encryptIV;
+ $pos = 0;
+ }
+ $len = strlen($plaintext);
+ $i = 0;
+ if ($pos) {
+ $orig_pos = $pos;
+ $max = $this->block_size - $pos;
+ if ($len >= $max) {
+ $i = $max;
+ $len -= $max;
+ $pos = 0;
+ } else {
+ $i = $len;
+ $pos += $len;
+ $len = 0;
+ }
+ // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize
+ $ciphertext = substr($iv, $orig_pos) ^ $plaintext;
+ $iv = substr_replace($iv, $ciphertext, $orig_pos, $i);
+ $plaintext = substr($plaintext, $i);
+ }
+
+ $overflow = $len % $this->block_size;
+
+ if ($overflow) {
+ $ciphertext .= openssl_encrypt(substr($plaintext, 0, -$overflow) . str_repeat("\0", $this->block_size), $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
+ $iv = Strings::pop($ciphertext, $this->block_size);
+
+ $size = $len - $overflow;
+ $block = $iv ^ substr($plaintext, -$overflow);
+ $iv = substr_replace($iv, $block, 0, $overflow);
+ $ciphertext .= $block;
+ $pos = $overflow;
+ } elseif ($len) {
+ $ciphertext = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
+ $iv = substr($ciphertext, -$this->block_size);
+ }
+
+ return $ciphertext;
+ case self::MODE_CFB8:
+ $ciphertext = openssl_encrypt($plaintext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->encryptIV);
+ if ($this->continuousBuffer) {
+ if (($len = strlen($ciphertext)) >= $this->block_size) {
+ $this->encryptIV = substr($ciphertext, -$this->block_size);
+ } else {
+ $this->encryptIV = substr($this->encryptIV, $len - $this->block_size) . substr($ciphertext, -$len);
+ }
+ }
+ return $ciphertext;
+ case self::MODE_OFB8:
+ $ciphertext = '';
+ $len = strlen($plaintext);
+ $iv = $this->encryptIV;
+
+ for ($i = 0; $i < $len; ++$i) {
+ $xor = openssl_encrypt($iv, $this->cipher_name_openssl_ecb, $this->key, $this->openssl_options, $this->decryptIV);
+ $ciphertext .= $plaintext[$i] ^ $xor;
+ $iv = substr($iv, 1) . $xor[0];
+ }
+
+ if ($this->continuousBuffer) {
+ $this->encryptIV = $iv;
+ }
+ break;
+ case self::MODE_OFB:
+ return $this->openssl_ofb_process($plaintext, $this->encryptIV, $this->enbuffer);
+ }
+ }
+
+ if ($this->engine === self::ENGINE_MCRYPT) {
+ set_error_handler(function () {
+ });
+ if ($this->enchanged) {
+ mcrypt_generic_init($this->enmcrypt, $this->key, $this->getIV($this->encryptIV));
+ $this->enchanged = false;
+ }
+
+ // re: {@link http://phpseclib.sourceforge.net/cfb-demo.phps}
+ // using mcrypt's default handing of CFB the above would output two different things. using phpseclib's
+ // rewritten CFB implementation the above outputs the same thing twice.
+ if ($this->mode == self::MODE_CFB && $this->continuousBuffer) {
+ $block_size = $this->block_size;
+ $iv = &$this->encryptIV;
+ $pos = &$this->enbuffer['pos'];
+ $len = strlen($plaintext);
+ $ciphertext = '';
+ $i = 0;
+ if ($pos) {
+ $orig_pos = $pos;
+ $max = $block_size - $pos;
+ if ($len >= $max) {
+ $i = $max;
+ $len -= $max;
+ $pos = 0;
+ } else {
+ $i = $len;
+ $pos += $len;
+ $len = 0;
+ }
+ $ciphertext = substr($iv, $orig_pos) ^ $plaintext;
+ $iv = substr_replace($iv, $ciphertext, $orig_pos, $i);
+ $this->enbuffer['enmcrypt_init'] = true;
+ }
+ if ($len >= $block_size) {
+ if ($this->enbuffer['enmcrypt_init'] === false || $len > $this->cfb_init_len) {
+ if ($this->enbuffer['enmcrypt_init'] === true) {
+ mcrypt_generic_init($this->enmcrypt, $this->key, $iv);
+ $this->enbuffer['enmcrypt_init'] = false;
+ }
+ $ciphertext .= mcrypt_generic($this->enmcrypt, substr($plaintext, $i, $len - $len % $block_size));
+ $iv = substr($ciphertext, -$block_size);
+ $len %= $block_size;
+ } else {
+ while ($len >= $block_size) {
+ $iv = mcrypt_generic($this->ecb, $iv) ^ substr($plaintext, $i, $block_size);
+ $ciphertext .= $iv;
+ $len -= $block_size;
+ $i += $block_size;
+ }
+ }
+ }
+
+ if ($len) {
+ $iv = mcrypt_generic($this->ecb, $iv);
+ $block = $iv ^ substr($plaintext, -$len);
+ $iv = substr_replace($iv, $block, 0, $len);
+ $ciphertext .= $block;
+ $pos = $len;
+ }
+
+ restore_error_handler();
+
+ return $ciphertext;
+ }
+
+ $ciphertext = mcrypt_generic($this->enmcrypt, $plaintext);
+
+ if (!$this->continuousBuffer) {
+ mcrypt_generic_init($this->enmcrypt, $this->key, $this->getIV($this->encryptIV));
+ }
+
+ restore_error_handler();
+
+ return $ciphertext;
+ }
+
+ if ($this->engine === self::ENGINE_EVAL) {
+ $inline = $this->inline_crypt;
+ return $inline('encrypt', $plaintext);
+ }
+
+ $buffer = &$this->enbuffer;
+ $block_size = $this->block_size;
+ $ciphertext = '';
+ switch ($this->mode) {
+ case self::MODE_ECB:
+ for ($i = 0; $i < strlen($plaintext); $i += $block_size) {
+ $ciphertext .= $this->encryptBlock(substr($plaintext, $i, $block_size));
+ }
+ break;
+ case self::MODE_CBC:
+ $xor = $this->encryptIV;
+ for ($i = 0; $i < strlen($plaintext); $i += $block_size) {
+ $block = substr($plaintext, $i, $block_size);
+ $block = $this->encryptBlock($block ^ $xor);
+ $xor = $block;
+ $ciphertext .= $block;
+ }
+ if ($this->continuousBuffer) {
+ $this->encryptIV = $xor;
+ }
+ break;
+ case self::MODE_CTR:
+ $xor = $this->encryptIV;
+ if (strlen($buffer['ciphertext'])) {
+ for ($i = 0; $i < strlen($plaintext); $i += $block_size) {
+ $block = substr($plaintext, $i, $block_size);
+ if (strlen($block) > strlen($buffer['ciphertext'])) {
+ $buffer['ciphertext'] .= $this->encryptBlock($xor);
+ Strings::increment_str($xor);
+ }
+ $key = Strings::shift($buffer['ciphertext'], $block_size);
+ $ciphertext .= $block ^ $key;
+ }
+ } else {
+ for ($i = 0; $i < strlen($plaintext); $i += $block_size) {
+ $block = substr($plaintext, $i, $block_size);
+ $key = $this->encryptBlock($xor);
+ Strings::increment_str($xor);
+ $ciphertext .= $block ^ $key;
+ }
+ }
+ if ($this->continuousBuffer) {
+ $this->encryptIV = $xor;
+ if ($start = strlen($plaintext) % $block_size) {
+ $buffer['ciphertext'] = substr($key, $start) . $buffer['ciphertext'];
+ }
+ }
+ break;
+ case self::MODE_CFB:
+ // cfb loosely routines inspired by openssl's:
+ // {@link http://cvs.openssl.org/fileview?f=openssl/crypto/modes/cfb128.c&v=1.3.2.2.2.1}
+ if ($this->continuousBuffer) {
+ $iv = &$this->encryptIV;
+ $pos = &$buffer['pos'];
+ } else {
+ $iv = $this->encryptIV;
+ $pos = 0;
+ }
+ $len = strlen($plaintext);
+ $i = 0;
+ if ($pos) {
+ $orig_pos = $pos;
+ $max = $block_size - $pos;
+ if ($len >= $max) {
+ $i = $max;
+ $len -= $max;
+ $pos = 0;
+ } else {
+ $i = $len;
+ $pos += $len;
+ $len = 0;
+ }
+ // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize
+ $ciphertext = substr($iv, $orig_pos) ^ $plaintext;
+ $iv = substr_replace($iv, $ciphertext, $orig_pos, $i);
+ }
+ while ($len >= $block_size) {
+ $iv = $this->encryptBlock($iv) ^ substr($plaintext, $i, $block_size);
+ $ciphertext .= $iv;
+ $len -= $block_size;
+ $i += $block_size;
+ }
+ if ($len) {
+ $iv = $this->encryptBlock($iv);
+ $block = $iv ^ substr($plaintext, $i);
+ $iv = substr_replace($iv, $block, 0, $len);
+ $ciphertext .= $block;
+ $pos = $len;
+ }
+ break;
+ case self::MODE_CFB8:
+ $ciphertext = '';
+ $len = strlen($plaintext);
+ $iv = $this->encryptIV;
+
+ for ($i = 0; $i < $len; ++$i) {
+ $ciphertext .= ($c = $plaintext[$i] ^ $this->encryptBlock($iv));
+ $iv = substr($iv, 1) . $c;
+ }
+
+ if ($this->continuousBuffer) {
+ if ($len >= $block_size) {
+ $this->encryptIV = substr($ciphertext, -$block_size);
+ } else {
+ $this->encryptIV = substr($this->encryptIV, $len - $block_size) . substr($ciphertext, -$len);
+ }
+ }
+ break;
+ case self::MODE_OFB8:
+ $ciphertext = '';
+ $len = strlen($plaintext);
+ $iv = $this->encryptIV;
+
+ for ($i = 0; $i < $len; ++$i) {
+ $xor = $this->encryptBlock($iv);
+ $ciphertext .= $plaintext[$i] ^ $xor;
+ $iv = substr($iv, 1) . $xor[0];
+ }
+
+ if ($this->continuousBuffer) {
+ $this->encryptIV = $iv;
+ }
+ break;
+ case self::MODE_OFB:
+ $xor = $this->encryptIV;
+ if (strlen($buffer['xor'])) {
+ for ($i = 0; $i < strlen($plaintext); $i += $block_size) {
+ $block = substr($plaintext, $i, $block_size);
+ if (strlen($block) > strlen($buffer['xor'])) {
+ $xor = $this->encryptBlock($xor);
+ $buffer['xor'] .= $xor;
+ }
+ $key = Strings::shift($buffer['xor'], $block_size);
+ $ciphertext .= $block ^ $key;
+ }
+ } else {
+ for ($i = 0; $i < strlen($plaintext); $i += $block_size) {
+ $xor = $this->encryptBlock($xor);
+ $ciphertext .= substr($plaintext, $i, $block_size) ^ $xor;
+ }
+ $key = $xor;
+ }
+ if ($this->continuousBuffer) {
+ $this->encryptIV = $xor;
+ if ($start = strlen($plaintext) % $block_size) {
+ $buffer['xor'] = substr($key, $start) . $buffer['xor'];
+ }
+ }
+ break;
+ case self::MODE_STREAM:
+ $ciphertext = $this->encryptBlock($plaintext);
+ break;
+ }
+
+ return $ciphertext;
+ }
+
+ /**
+ * Decrypts a message.
+ *
+ * If strlen($ciphertext) is not a multiple of the block size, null bytes will be added to the end of the string until
+ * it is.
+ *
+ * {@internal Could, but not must, extend by the child Crypt_* class}
+ *
+ * @see self::encrypt()
+ * @param string $ciphertext
+ * @return string $plaintext
+ * @throws \LengthException if we're inside a block cipher and the ciphertext length is not a multiple of the block size
+ */
+ public function decrypt($ciphertext)
+ {
+ if ($this->paddable && strlen($ciphertext) % $this->block_size) {
+ throw new \LengthException('The ciphertext length (' . strlen($ciphertext) . ') needs to be a multiple of the block size (' . $this->block_size . ')');
+ }
+ $this->setup();
+
+ if ($this->mode == self::MODE_GCM || isset($this->poly1305Key)) {
+ if ($this->oldtag === false) {
+ throw new InsufficientSetupException('Authentication Tag has not been set');
+ }
+
+ if (isset($this->poly1305Key)) {
+ $newtag = $this->poly1305($ciphertext);
+ } else {
+ $oldIV = $this->iv;
+ Strings::increment_str($this->iv);
+ $cipher = new static('ctr');
+ $cipher->setKey($this->key);
+ $cipher->setIV($this->iv);
+ $plaintext = $cipher->decrypt($ciphertext);
+
+ $s = $this->ghash(
+ self::nullPad128($this->aad) .
+ self::nullPad128($ciphertext) .
+ self::len64($this->aad) .
+ self::len64($ciphertext)
+ );
+ $cipher->encryptIV = $this->iv = $this->encryptIV = $this->decryptIV = $oldIV;
+ $newtag = $cipher->encrypt($s);
+ }
+ if ($this->oldtag != substr($newtag, 0, strlen($newtag))) {
+ $cipher = clone $this;
+ unset($cipher->poly1305Key);
+ $this->usePoly1305 = false;
+ $plaintext = $cipher->decrypt($ciphertext);
+ $this->oldtag = false;
+ throw new BadDecryptionException('Derived authentication tag and supplied authentication tag do not match');
+ }
+ $this->oldtag = false;
+ return $plaintext;
+ }
+
+ if ($this->engine === self::ENGINE_OPENSSL) {
+ switch ($this->mode) {
+ case self::MODE_STREAM:
+ $plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
+ break;
+ case self::MODE_ECB:
+ $plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
+ break;
+ case self::MODE_CBC:
+ $offset = $this->block_size;
+ $plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->decryptIV);
+ if ($this->continuousBuffer) {
+ $this->decryptIV = substr($ciphertext, -$offset, $this->block_size);
+ }
+ break;
+ case self::MODE_CTR:
+ $plaintext = $this->openssl_ctr_process($ciphertext, $this->decryptIV, $this->debuffer);
+ break;
+ case self::MODE_CFB:
+ // cfb loosely routines inspired by openssl's:
+ // {@link http://cvs.openssl.org/fileview?f=openssl/crypto/modes/cfb128.c&v=1.3.2.2.2.1}
+ $plaintext = '';
+ if ($this->continuousBuffer) {
+ $iv = &$this->decryptIV;
+ $pos = &$this->debuffer['pos'];
+ } else {
+ $iv = $this->decryptIV;
+ $pos = 0;
+ }
+ $len = strlen($ciphertext);
+ $i = 0;
+ if ($pos) {
+ $orig_pos = $pos;
+ $max = $this->block_size - $pos;
+ if ($len >= $max) {
+ $i = $max;
+ $len -= $max;
+ $pos = 0;
+ } else {
+ $i = $len;
+ $pos += $len;
+ $len = 0;
+ }
+ // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $this->blocksize
+ $plaintext = substr($iv, $orig_pos) ^ $ciphertext;
+ $iv = substr_replace($iv, substr($ciphertext, 0, $i), $orig_pos, $i);
+ $ciphertext = substr($ciphertext, $i);
+ }
+ $overflow = $len % $this->block_size;
+ if ($overflow) {
+ $plaintext .= openssl_decrypt(substr($ciphertext, 0, -$overflow), $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
+ if ($len - $overflow) {
+ $iv = substr($ciphertext, -$overflow - $this->block_size, -$overflow);
+ }
+ $iv = openssl_encrypt(str_repeat("\0", $this->block_size), $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
+ $plaintext .= $iv ^ substr($ciphertext, -$overflow);
+ $iv = substr_replace($iv, substr($ciphertext, -$overflow), 0, $overflow);
+ $pos = $overflow;
+ } elseif ($len) {
+ $plaintext .= openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $iv);
+ $iv = substr($ciphertext, -$this->block_size);
+ }
+ break;
+ case self::MODE_CFB8:
+ $plaintext = openssl_decrypt($ciphertext, $this->cipher_name_openssl, $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->decryptIV);
+ if ($this->continuousBuffer) {
+ if (($len = strlen($ciphertext)) >= $this->block_size) {
+ $this->decryptIV = substr($ciphertext, -$this->block_size);
+ } else {
+ $this->decryptIV = substr($this->decryptIV, $len - $this->block_size) . substr($ciphertext, -$len);
+ }
+ }
+ break;
+ case self::MODE_OFB8:
+ $plaintext = '';
+ $len = strlen($ciphertext);
+ $iv = $this->decryptIV;
+
+ for ($i = 0; $i < $len; ++$i) {
+ $xor = openssl_encrypt($iv, $this->cipher_name_openssl_ecb, $this->key, $this->openssl_options, $this->decryptIV);
+ $plaintext .= $ciphertext[$i] ^ $xor;
+ $iv = substr($iv, 1) . $xor[0];
+ }
+
+ if ($this->continuousBuffer) {
+ $this->decryptIV = $iv;
+ }
+ break;
+ case self::MODE_OFB:
+ $plaintext = $this->openssl_ofb_process($ciphertext, $this->decryptIV, $this->debuffer);
+ }
+
+ return $this->paddable ? $this->unpad($plaintext) : $plaintext;
+ }
+
+ if ($this->engine === self::ENGINE_MCRYPT) {
+ set_error_handler(function () {
+ });
+ $block_size = $this->block_size;
+ if ($this->dechanged) {
+ mcrypt_generic_init($this->demcrypt, $this->key, $this->getIV($this->decryptIV));
+ $this->dechanged = false;
+ }
+
+ if ($this->mode == self::MODE_CFB && $this->continuousBuffer) {
+ $iv = &$this->decryptIV;
+ $pos = &$this->debuffer['pos'];
+ $len = strlen($ciphertext);
+ $plaintext = '';
+ $i = 0;
+ if ($pos) {
+ $orig_pos = $pos;
+ $max = $block_size - $pos;
+ if ($len >= $max) {
+ $i = $max;
+ $len -= $max;
+ $pos = 0;
+ } else {
+ $i = $len;
+ $pos += $len;
+ $len = 0;
+ }
+ // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize
+ $plaintext = substr($iv, $orig_pos) ^ $ciphertext;
+ $iv = substr_replace($iv, substr($ciphertext, 0, $i), $orig_pos, $i);
+ }
+ if ($len >= $block_size) {
+ $cb = substr($ciphertext, $i, $len - $len % $block_size);
+ $plaintext .= mcrypt_generic($this->ecb, $iv . $cb) ^ $cb;
+ $iv = substr($cb, -$block_size);
+ $len %= $block_size;
+ }
+ if ($len) {
+ $iv = mcrypt_generic($this->ecb, $iv);
+ $plaintext .= $iv ^ substr($ciphertext, -$len);
+ $iv = substr_replace($iv, substr($ciphertext, -$len), 0, $len);
+ $pos = $len;
+ }
+
+ restore_error_handler();
+
+ return $plaintext;
+ }
+
+ $plaintext = mdecrypt_generic($this->demcrypt, $ciphertext);
+
+ if (!$this->continuousBuffer) {
+ mcrypt_generic_init($this->demcrypt, $this->key, $this->getIV($this->decryptIV));
+ }
+
+ restore_error_handler();
+
+ return $this->paddable ? $this->unpad($plaintext) : $plaintext;
+ }
+
+ if ($this->engine === self::ENGINE_EVAL) {
+ $inline = $this->inline_crypt;
+ return $inline('decrypt', $ciphertext);
+ }
+
+ $block_size = $this->block_size;
+
+ $buffer = &$this->debuffer;
+ $plaintext = '';
+ switch ($this->mode) {
+ case self::MODE_ECB:
+ for ($i = 0; $i < strlen($ciphertext); $i += $block_size) {
+ $plaintext .= $this->decryptBlock(substr($ciphertext, $i, $block_size));
+ }
+ break;
+ case self::MODE_CBC:
+ $xor = $this->decryptIV;
+ for ($i = 0; $i < strlen($ciphertext); $i += $block_size) {
+ $block = substr($ciphertext, $i, $block_size);
+ $plaintext .= $this->decryptBlock($block) ^ $xor;
+ $xor = $block;
+ }
+ if ($this->continuousBuffer) {
+ $this->decryptIV = $xor;
+ }
+ break;
+ case self::MODE_CTR:
+ $xor = $this->decryptIV;
+ if (strlen($buffer['ciphertext'])) {
+ for ($i = 0; $i < strlen($ciphertext); $i += $block_size) {
+ $block = substr($ciphertext, $i, $block_size);
+ if (strlen($block) > strlen($buffer['ciphertext'])) {
+ $buffer['ciphertext'] .= $this->encryptBlock($xor);
+ Strings::increment_str($xor);
+ }
+ $key = Strings::shift($buffer['ciphertext'], $block_size);
+ $plaintext .= $block ^ $key;
+ }
+ } else {
+ for ($i = 0; $i < strlen($ciphertext); $i += $block_size) {
+ $block = substr($ciphertext, $i, $block_size);
+ $key = $this->encryptBlock($xor);
+ Strings::increment_str($xor);
+ $plaintext .= $block ^ $key;
+ }
+ }
+ if ($this->continuousBuffer) {
+ $this->decryptIV = $xor;
+ if ($start = strlen($ciphertext) % $block_size) {
+ $buffer['ciphertext'] = substr($key, $start) . $buffer['ciphertext'];
+ }
+ }
+ break;
+ case self::MODE_CFB:
+ if ($this->continuousBuffer) {
+ $iv = &$this->decryptIV;
+ $pos = &$buffer['pos'];
+ } else {
+ $iv = $this->decryptIV;
+ $pos = 0;
+ }
+ $len = strlen($ciphertext);
+ $i = 0;
+ if ($pos) {
+ $orig_pos = $pos;
+ $max = $block_size - $pos;
+ if ($len >= $max) {
+ $i = $max;
+ $len -= $max;
+ $pos = 0;
+ } else {
+ $i = $len;
+ $pos += $len;
+ $len = 0;
+ }
+ // ie. $i = min($max, $len), $len-= $i, $pos+= $i, $pos%= $blocksize
+ $plaintext = substr($iv, $orig_pos) ^ $ciphertext;
+ $iv = substr_replace($iv, substr($ciphertext, 0, $i), $orig_pos, $i);
+ }
+ while ($len >= $block_size) {
+ $iv = $this->encryptBlock($iv);
+ $cb = substr($ciphertext, $i, $block_size);
+ $plaintext .= $iv ^ $cb;
+ $iv = $cb;
+ $len -= $block_size;
+ $i += $block_size;
+ }
+ if ($len) {
+ $iv = $this->encryptBlock($iv);
+ $plaintext .= $iv ^ substr($ciphertext, $i);
+ $iv = substr_replace($iv, substr($ciphertext, $i), 0, $len);
+ $pos = $len;
+ }
+ break;
+ case self::MODE_CFB8:
+ $plaintext = '';
+ $len = strlen($ciphertext);
+ $iv = $this->decryptIV;
+
+ for ($i = 0; $i < $len; ++$i) {
+ $plaintext .= $ciphertext[$i] ^ $this->encryptBlock($iv);
+ $iv = substr($iv, 1) . $ciphertext[$i];
+ }
+
+ if ($this->continuousBuffer) {
+ if ($len >= $block_size) {
+ $this->decryptIV = substr($ciphertext, -$block_size);
+ } else {
+ $this->decryptIV = substr($this->decryptIV, $len - $block_size) . substr($ciphertext, -$len);
+ }
+ }
+ break;
+ case self::MODE_OFB8:
+ $plaintext = '';
+ $len = strlen($ciphertext);
+ $iv = $this->decryptIV;
+
+ for ($i = 0; $i < $len; ++$i) {
+ $xor = $this->encryptBlock($iv);
+ $plaintext .= $ciphertext[$i] ^ $xor;
+ $iv = substr($iv, 1) . $xor[0];
+ }
+
+ if ($this->continuousBuffer) {
+ $this->decryptIV = $iv;
+ }
+ break;
+ case self::MODE_OFB:
+ $xor = $this->decryptIV;
+ if (strlen($buffer['xor'])) {
+ for ($i = 0; $i < strlen($ciphertext); $i += $block_size) {
+ $block = substr($ciphertext, $i, $block_size);
+ if (strlen($block) > strlen($buffer['xor'])) {
+ $xor = $this->encryptBlock($xor);
+ $buffer['xor'] .= $xor;
+ }
+ $key = Strings::shift($buffer['xor'], $block_size);
+ $plaintext .= $block ^ $key;
+ }
+ } else {
+ for ($i = 0; $i < strlen($ciphertext); $i += $block_size) {
+ $xor = $this->encryptBlock($xor);
+ $plaintext .= substr($ciphertext, $i, $block_size) ^ $xor;
+ }
+ $key = $xor;
+ }
+ if ($this->continuousBuffer) {
+ $this->decryptIV = $xor;
+ if ($start = strlen($ciphertext) % $block_size) {
+ $buffer['xor'] = substr($key, $start) . $buffer['xor'];
+ }
+ }
+ break;
+ case self::MODE_STREAM:
+ $plaintext = $this->decryptBlock($ciphertext);
+ break;
+ }
+ return $this->paddable ? $this->unpad($plaintext) : $plaintext;
+ }
+
+ /**
+ * Get the authentication tag
+ *
+ * Only used in GCM or Poly1305 mode
+ *
+ * @see self::encrypt()
+ * @param int $length optional
+ * @return string
+ * @throws \LengthException if $length isn't of a sufficient length
+ * @throws \RuntimeException if GCM mode isn't being used
+ */
+ public function getTag($length = 16)
+ {
+ if ($this->mode != self::MODE_GCM && !$this->usePoly1305) {
+ throw new \BadMethodCallException('Authentication tags are only utilized in GCM mode or with Poly1305');
+ }
+
+ if ($this->newtag === false) {
+ throw new \BadMethodCallException('A tag can only be returned after a round of encryption has been performed');
+ }
+
+ // the tag is 128-bits. it can't be greater than 16 bytes because that's bigger than the tag is. if it
+ // were 0 you might as well be doing CTR and less than 4 provides minimal security that could be trivially
+ // easily brute forced.
+ // see https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf#page=36
+ // for more info
+ if ($length < 4 || $length > 16) {
+ throw new \LengthException('The authentication tag must be between 4 and 16 bytes long');
+ }
+
+ return $length == 16 ?
+ $this->newtag :
+ substr($this->newtag, 0, $length);
+ }
+
+ /**
+ * Sets the authentication tag
+ *
+ * Only used in GCM mode
+ *
+ * @see self::decrypt()
+ * @param string $tag
+ * @throws \LengthException if $length isn't of a sufficient length
+ * @throws \RuntimeException if GCM mode isn't being used
+ */
+ public function setTag($tag)
+ {
+ if ($this->usePoly1305 && !isset($this->poly1305Key) && method_exists($this, 'createPoly1305Key')) {
+ $this->createPoly1305Key();
+ }
+
+ if ($this->mode != self::MODE_GCM && !$this->usePoly1305) {
+ throw new \BadMethodCallException('Authentication tags are only utilized in GCM mode or with Poly1305');
+ }
+
+ $length = strlen($tag);
+ if ($length < 4 || $length > 16) {
+ throw new \LengthException('The authentication tag must be between 4 and 16 bytes long');
+ }
+ $this->oldtag = $tag;
+ }
+
+ /**
+ * Get the IV
+ *
+ * mcrypt requires an IV even if ECB is used
+ *
+ * @see self::encrypt()
+ * @see self::decrypt()
+ * @param string $iv
+ * @return string
+ */
+ protected function getIV($iv)
+ {
+ return $this->mode == self::MODE_ECB ? str_repeat("\0", $this->block_size) : $iv;
+ }
+
+ /**
+ * OpenSSL CTR Processor
+ *
+ * PHP's OpenSSL bindings do not operate in continuous mode so we'll wrap around it. Since the keystream
+ * for CTR is the same for both encrypting and decrypting this function is re-used by both SymmetricKey::encrypt()
+ * and SymmetricKey::decrypt(). Also, OpenSSL doesn't implement CTR for all of it's symmetric ciphers so this
+ * function will emulate CTR with ECB when necessary.
+ *
+ * @see self::encrypt()
+ * @see self::decrypt()
+ * @param string $plaintext
+ * @param string $encryptIV
+ * @param array $buffer
+ * @return string
+ */
+ private function openssl_ctr_process($plaintext, &$encryptIV, &$buffer)
+ {
+ $ciphertext = '';
+
+ $block_size = $this->block_size;
+ $key = $this->key;
+
+ if ($this->openssl_emulate_ctr) {
+ $xor = $encryptIV;
+ if (strlen($buffer['ciphertext'])) {
+ for ($i = 0; $i < strlen($plaintext); $i += $block_size) {
+ $block = substr($plaintext, $i, $block_size);
+ if (strlen($block) > strlen($buffer['ciphertext'])) {
+ $buffer['ciphertext'] .= openssl_encrypt($xor, $this->cipher_name_openssl_ecb, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
+ }
+ Strings::increment_str($xor);
+ $otp = Strings::shift($buffer['ciphertext'], $block_size);
+ $ciphertext .= $block ^ $otp;
+ }
+ } else {
+ for ($i = 0; $i < strlen($plaintext); $i += $block_size) {
+ $block = substr($plaintext, $i, $block_size);
+ $otp = openssl_encrypt($xor, $this->cipher_name_openssl_ecb, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
+ Strings::increment_str($xor);
+ $ciphertext .= $block ^ $otp;
+ }
+ }
+ if ($this->continuousBuffer) {
+ $encryptIV = $xor;
+ if ($start = strlen($plaintext) % $block_size) {
+ $buffer['ciphertext'] = substr($key, $start) . $buffer['ciphertext'];
+ }
+ }
+
+ return $ciphertext;
+ }
+
+ if (strlen($buffer['ciphertext'])) {
+ $ciphertext = $plaintext ^ Strings::shift($buffer['ciphertext'], strlen($plaintext));
+ $plaintext = substr($plaintext, strlen($ciphertext));
+
+ if (!strlen($plaintext)) {
+ return $ciphertext;
+ }
+ }
+
+ $overflow = strlen($plaintext) % $block_size;
+ if ($overflow) {
+ $plaintext2 = Strings::pop($plaintext, $overflow); // ie. trim $plaintext to a multiple of $block_size and put rest of $plaintext in $plaintext2
+ $encrypted = openssl_encrypt($plaintext . str_repeat("\0", $block_size), $this->cipher_name_openssl, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $encryptIV);
+ $temp = Strings::pop($encrypted, $block_size);
+ $ciphertext .= $encrypted . ($plaintext2 ^ $temp);
+ if ($this->continuousBuffer) {
+ $buffer['ciphertext'] = substr($temp, $overflow);
+ $encryptIV = $temp;
+ }
+ } elseif (!strlen($buffer['ciphertext'])) {
+ $ciphertext .= openssl_encrypt($plaintext . str_repeat("\0", $block_size), $this->cipher_name_openssl, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $encryptIV);
+ $temp = Strings::pop($ciphertext, $block_size);
+ if ($this->continuousBuffer) {
+ $encryptIV = $temp;
+ }
+ }
+ if ($this->continuousBuffer) {
+ $encryptIV = openssl_decrypt($encryptIV, $this->cipher_name_openssl_ecb, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING);
+ if ($overflow) {
+ Strings::increment_str($encryptIV);
+ }
+ }
+
+ return $ciphertext;
+ }
+
+ /**
+ * OpenSSL OFB Processor
+ *
+ * PHP's OpenSSL bindings do not operate in continuous mode so we'll wrap around it. Since the keystream
+ * for OFB is the same for both encrypting and decrypting this function is re-used by both SymmetricKey::encrypt()
+ * and SymmetricKey::decrypt().
+ *
+ * @see self::encrypt()
+ * @see self::decrypt()
+ * @param string $plaintext
+ * @param string $encryptIV
+ * @param array $buffer
+ * @return string
+ */
+ private function openssl_ofb_process($plaintext, &$encryptIV, &$buffer)
+ {
+ if (strlen($buffer['xor'])) {
+ $ciphertext = $plaintext ^ $buffer['xor'];
+ $buffer['xor'] = substr($buffer['xor'], strlen($ciphertext));
+ $plaintext = substr($plaintext, strlen($ciphertext));
+ } else {
+ $ciphertext = '';
+ }
+
+ $block_size = $this->block_size;
+
+ $len = strlen($plaintext);
+ $key = $this->key;
+ $overflow = $len % $block_size;
+
+ if (strlen($plaintext)) {
+ if ($overflow) {
+ $ciphertext .= openssl_encrypt(substr($plaintext, 0, -$overflow) . str_repeat("\0", $block_size), $this->cipher_name_openssl, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $encryptIV);
+ $xor = Strings::pop($ciphertext, $block_size);
+ if ($this->continuousBuffer) {
+ $encryptIV = $xor;
+ }
+ $ciphertext .= Strings::shift($xor, $overflow) ^ substr($plaintext, -$overflow);
+ if ($this->continuousBuffer) {
+ $buffer['xor'] = $xor;
+ }
+ } else {
+ $ciphertext = openssl_encrypt($plaintext, $this->cipher_name_openssl, $key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $encryptIV);
+ if ($this->continuousBuffer) {
+ $encryptIV = substr($ciphertext, -$block_size) ^ substr($plaintext, -$block_size);
+ }
+ }
+ }
+
+ return $ciphertext;
+ }
+
+ /**
+ * phpseclib <-> OpenSSL Mode Mapper
+ *
+ * May need to be overwritten by classes extending this one in some cases
+ *
+ * @return string
+ */
+ protected function openssl_translate_mode()
+ {
+ switch ($this->mode) {
+ case self::MODE_ECB:
+ return 'ecb';
+ case self::MODE_CBC:
+ return 'cbc';
+ case self::MODE_CTR:
+ case self::MODE_GCM:
+ return 'ctr';
+ case self::MODE_CFB:
+ return 'cfb';
+ case self::MODE_CFB8:
+ return 'cfb8';
+ case self::MODE_OFB:
+ return 'ofb';
+ }
+ }
+
+ /**
+ * Pad "packets".
+ *
+ * Block ciphers working by encrypting between their specified [$this->]block_size at a time
+ * If you ever need to encrypt or decrypt something that isn't of the proper length, it becomes necessary to
+ * pad the input so that it is of the proper length.
+ *
+ * Padding is enabled by default. Sometimes, however, it is undesirable to pad strings. Such is the case in SSH,
+ * where "packets" are padded with random bytes before being encrypted. Unpad these packets and you risk stripping
+ * away characters that shouldn't be stripped away. (SSH knows how many bytes are added because the length is
+ * transmitted separately)
+ *
+ * @see self::disablePadding()
+ */
+ public function enablePadding()
+ {
+ $this->padding = true;
+ }
+
+ /**
+ * Do not pad packets.
+ *
+ * @see self::enablePadding()
+ */
+ public function disablePadding()
+ {
+ $this->padding = false;
+ }
+
+ /**
+ * Treat consecutive "packets" as if they are a continuous buffer.
+ *
+ * Say you have a 32-byte plaintext $plaintext. Using the default behavior, the two following code snippets
+ * will yield different outputs:
+ *
+ * <code>
+ * echo $rijndael->encrypt(substr($plaintext, 0, 16));
+ * echo $rijndael->encrypt(substr($plaintext, 16, 16));
+ * </code>
+ * <code>
+ * echo $rijndael->encrypt($plaintext);
+ * </code>
+ *
+ * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates
+ * another, as demonstrated with the following:
+ *
+ * <code>
+ * $rijndael->encrypt(substr($plaintext, 0, 16));
+ * echo $rijndael->decrypt($rijndael->encrypt(substr($plaintext, 16, 16)));
+ * </code>
+ * <code>
+ * echo $rijndael->decrypt($rijndael->encrypt(substr($plaintext, 16, 16)));
+ * </code>
+ *
+ * With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different
+ * outputs. The reason is due to the fact that the initialization vector's change after every encryption /
+ * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant.
+ *
+ * Put another way, when the continuous buffer is enabled, the state of the \phpseclib3\Crypt\*() object changes after each
+ * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that
+ * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them),
+ * however, they are also less intuitive and more likely to cause you problems.
+ *
+ * {@internal Could, but not must, extend by the child Crypt_* class}
+ *
+ * @see self::disableContinuousBuffer()
+ */
+ public function enableContinuousBuffer()
+ {
+ if ($this->mode == self::MODE_ECB) {
+ return;
+ }
+
+ if ($this->mode == self::MODE_GCM) {
+ throw new \BadMethodCallException('This mode does not run in continuous mode');
+ }
+
+ $this->continuousBuffer = true;
+
+ $this->setEngine();
+ }
+
+ /**
+ * Treat consecutive packets as if they are a discontinuous buffer.
+ *
+ * The default behavior.
+ *
+ * {@internal Could, but not must, extend by the child Crypt_* class}
+ *
+ * @see self::enableContinuousBuffer()
+ */
+ public function disableContinuousBuffer()
+ {
+ if ($this->mode == self::MODE_ECB) {
+ return;
+ }
+ if (!$this->continuousBuffer) {
+ return;
+ }
+
+ $this->continuousBuffer = false;
+
+ $this->setEngine();
+ }
+
+ /**
+ * Test for engine validity
+ *
+ * @see self::__construct()
+ * @param int $engine
+ * @return bool
+ */
+ protected function isValidEngineHelper($engine)
+ {
+ switch ($engine) {
+ case self::ENGINE_OPENSSL:
+ $this->openssl_emulate_ctr = false;
+ $result = $this->cipher_name_openssl &&
+ extension_loaded('openssl');
+ if (!$result) {
+ return false;
+ }
+
+ $methods = openssl_get_cipher_methods();
+ if (in_array($this->cipher_name_openssl, $methods)) {
+ return true;
+ }
+ // not all of openssl's symmetric cipher's support ctr. for those
+ // that don't we'll emulate it
+ switch ($this->mode) {
+ case self::MODE_CTR:
+ if (in_array($this->cipher_name_openssl_ecb, $methods)) {
+ $this->openssl_emulate_ctr = true;
+ return true;
+ }
+ }
+ return false;
+ case self::ENGINE_MCRYPT:
+ set_error_handler(function () {
+ });
+ $result = $this->cipher_name_mcrypt &&
+ extension_loaded('mcrypt') &&
+ in_array($this->cipher_name_mcrypt, mcrypt_list_algorithms());
+ restore_error_handler();
+ return $result;
+ case self::ENGINE_EVAL:
+ return method_exists($this, 'setupInlineCrypt');
+ case self::ENGINE_INTERNAL:
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Test for engine validity
+ *
+ * @see self::__construct()
+ * @param string $engine
+ * @return bool
+ */
+ public function isValidEngine($engine)
+ {
+ static $reverseMap;
+ if (!isset($reverseMap)) {
+ $reverseMap = array_map('strtolower', self::ENGINE_MAP);
+ $reverseMap = array_flip($reverseMap);
+ }
+ $engine = strtolower($engine);
+ if (!isset($reverseMap[$engine])) {
+ return false;
+ }
+
+ return $this->isValidEngineHelper($reverseMap[$engine]);
+ }
+
+ /**
+ * Sets the preferred crypt engine
+ *
+ * Currently, $engine could be:
+ *
+ * - libsodium[very fast]
+ *
+ * - OpenSSL [very fast]
+ *
+ * - mcrypt [fast]
+ *
+ * - Eval [slow]
+ *
+ * - PHP [slowest]
+ *
+ * If the preferred crypt engine is not available the fastest available one will be used
+ *
+ * @see self::__construct()
+ * @param string $engine
+ */
+ public function setPreferredEngine($engine)
+ {
+ static $reverseMap;
+ if (!isset($reverseMap)) {
+ $reverseMap = array_map('strtolower', self::ENGINE_MAP);
+ $reverseMap = array_flip($reverseMap);
+ }
+ $engine = is_string($engine) ? strtolower($engine) : '';
+ $this->preferredEngine = isset($reverseMap[$engine]) ? $reverseMap[$engine] : self::ENGINE_LIBSODIUM;
+
+ $this->setEngine();
+ }
+
+ /**
+ * Returns the engine currently being utilized
+ *
+ * @see self::setEngine()
+ */
+ public function getEngine()
+ {
+ return self::ENGINE_MAP[$this->engine];
+ }
+
+ /**
+ * Sets the engine as appropriate
+ *
+ * @see self::__construct()
+ */
+ protected function setEngine()
+ {
+ $this->engine = null;
+
+ $candidateEngines = [
+ self::ENGINE_LIBSODIUM,
+ self::ENGINE_OPENSSL_GCM,
+ self::ENGINE_OPENSSL,
+ self::ENGINE_MCRYPT,
+ self::ENGINE_EVAL
+ ];
+ if (isset($this->preferredEngine)) {
+ $temp = [$this->preferredEngine];
+ $candidateEngines = array_merge(
+ $temp,
+ array_diff($candidateEngines, $temp)
+ );
+ }
+ foreach ($candidateEngines as $engine) {
+ if ($this->isValidEngineHelper($engine)) {
+ $this->engine = $engine;
+ break;
+ }
+ }
+ if (!$this->engine) {
+ $this->engine = self::ENGINE_INTERNAL;
+ }
+
+ if ($this->engine != self::ENGINE_MCRYPT && $this->enmcrypt) {
+ set_error_handler(function () {
+ });
+ // Closing the current mcrypt resource(s). _mcryptSetup() will, if needed,
+ // (re)open them with the module named in $this->cipher_name_mcrypt
+ mcrypt_module_close($this->enmcrypt);
+ mcrypt_module_close($this->demcrypt);
+ $this->enmcrypt = null;
+ $this->demcrypt = null;
+
+ if ($this->ecb) {
+ mcrypt_module_close($this->ecb);
+ $this->ecb = null;
+ }
+ restore_error_handler();
+ }
+
+ $this->changed = $this->nonIVChanged = true;
+ }
+
+ /**
+ * Encrypts a block
+ *
+ * Note: Must be extended by the child \phpseclib3\Crypt\* class
+ *
+ * @param string $in
+ * @return string
+ */
+ abstract protected function encryptBlock($in);
+
+ /**
+ * Decrypts a block
+ *
+ * Note: Must be extended by the child \phpseclib3\Crypt\* class
+ *
+ * @param string $in
+ * @return string
+ */
+ abstract protected function decryptBlock($in);
+
+ /**
+ * Setup the key (expansion)
+ *
+ * Only used if $engine == self::ENGINE_INTERNAL
+ *
+ * Note: Must extend by the child \phpseclib3\Crypt\* class
+ *
+ * @see self::setup()
+ */
+ abstract protected function setupKey();
+
+ /**
+ * Setup the self::ENGINE_INTERNAL $engine
+ *
+ * (re)init, if necessary, the internal cipher $engine and flush all $buffers
+ * Used (only) if $engine == self::ENGINE_INTERNAL
+ *
+ * _setup() will be called each time if $changed === true
+ * typically this happens when using one or more of following public methods:
+ *
+ * - setKey()
+ *
+ * - setIV()
+ *
+ * - disableContinuousBuffer()
+ *
+ * - First run of encrypt() / decrypt() with no init-settings
+ *
+ * {@internal setup() is always called before en/decryption.}
+ *
+ * {@internal Could, but not must, extend by the child Crypt_* class}
+ *
+ * @see self::setKey()
+ * @see self::setIV()
+ * @see self::disableContinuousBuffer()
+ */
+ protected function setup()
+ {
+ if (!$this->changed) {
+ return;
+ }
+
+ $this->changed = false;
+
+ if ($this->usePoly1305 && !isset($this->poly1305Key) && method_exists($this, 'createPoly1305Key')) {
+ $this->createPoly1305Key();
+ }
+
+ $this->enbuffer = $this->debuffer = ['ciphertext' => '', 'xor' => '', 'pos' => 0, 'enmcrypt_init' => true];
+ //$this->newtag = $this->oldtag = false;
+
+ if ($this->usesNonce()) {
+ if ($this->nonce === false) {
+ throw new InsufficientSetupException('No nonce has been defined');
+ }
+ if ($this->mode == self::MODE_GCM && !in_array($this->engine, [self::ENGINE_LIBSODIUM, self::ENGINE_OPENSSL_GCM])) {
+ $this->setupGCM();
+ }
+ } else {
+ $this->iv = $this->origIV;
+ }
+
+ if ($this->iv === false && !in_array($this->mode, [self::MODE_STREAM, self::MODE_ECB])) {
+ if ($this->mode != self::MODE_GCM || !in_array($this->engine, [self::ENGINE_LIBSODIUM, self::ENGINE_OPENSSL_GCM])) {
+ throw new InsufficientSetupException('No IV has been defined');
+ }
+ }
+
+ if ($this->key === false) {
+ throw new InsufficientSetupException('No key has been defined');
+ }
+
+ $this->encryptIV = $this->decryptIV = $this->iv;
+
+ switch ($this->engine) {
+ case self::ENGINE_MCRYPT:
+ $this->enchanged = $this->dechanged = true;
+
+ set_error_handler(function () {
+ });
+
+ if (!isset($this->enmcrypt)) {
+ static $mcrypt_modes = [
+ self::MODE_CTR => 'ctr',
+ self::MODE_ECB => MCRYPT_MODE_ECB,
+ self::MODE_CBC => MCRYPT_MODE_CBC,
+ self::MODE_CFB => 'ncfb',
+ self::MODE_CFB8 => MCRYPT_MODE_CFB,
+ self::MODE_OFB => MCRYPT_MODE_NOFB,
+ self::MODE_OFB8 => MCRYPT_MODE_OFB,
+ self::MODE_STREAM => MCRYPT_MODE_STREAM,
+ ];
+
+ $this->demcrypt = mcrypt_module_open($this->cipher_name_mcrypt, '', $mcrypt_modes[$this->mode], '');
+ $this->enmcrypt = mcrypt_module_open($this->cipher_name_mcrypt, '', $mcrypt_modes[$this->mode], '');
+
+ // we need the $ecb mcrypt resource (only) in MODE_CFB with enableContinuousBuffer()
+ // to workaround mcrypt's broken ncfb implementation in buffered mode
+ // see: {@link http://phpseclib.sourceforge.net/cfb-demo.phps}
+ if ($this->mode == self::MODE_CFB) {
+ $this->ecb = mcrypt_module_open($this->cipher_name_mcrypt, '', MCRYPT_MODE_ECB, '');
+ }
+ } // else should mcrypt_generic_deinit be called?
+
+ if ($this->mode == self::MODE_CFB) {
+ mcrypt_generic_init($this->ecb, $this->key, str_repeat("\0", $this->block_size));
+ }
+
+ restore_error_handler();
+
+ break;
+ case self::ENGINE_INTERNAL:
+ $this->setupKey();
+ break;
+ case self::ENGINE_EVAL:
+ if ($this->nonIVChanged) {
+ $this->setupKey();
+ $this->setupInlineCrypt();
+ }
+ }
+
+ $this->nonIVChanged = false;
+ }
+
+ /**
+ * Pads a string
+ *
+ * Pads a string using the RSA PKCS padding standards so that its length is a multiple of the blocksize.
+ * $this->block_size - (strlen($text) % $this->block_size) bytes are added, each of which is equal to
+ * chr($this->block_size - (strlen($text) % $this->block_size)
+ *
+ * If padding is disabled and $text is not a multiple of the blocksize, the string will be padded regardless
+ * and padding will, hence forth, be enabled.
+ *
+ * @see self::unpad()
+ * @param string $text
+ * @throws \LengthException if padding is disabled and the plaintext's length is not a multiple of the block size
+ * @return string
+ */
+ protected function pad($text)
+ {
+ $length = strlen($text);
+
+ if (!$this->padding) {
+ if ($length % $this->block_size == 0) {
+ return $text;
+ } else {
+ throw new \LengthException("The plaintext's length ($length) is not a multiple of the block size ({$this->block_size}). Try enabling padding.");
+ }
+ }
+
+ $pad = $this->block_size - ($length % $this->block_size);
+
+ return str_pad($text, $length + $pad, chr($pad));
+ }
+
+ /**
+ * Unpads a string.
+ *
+ * If padding is enabled and the reported padding length is invalid the encryption key will be assumed to be wrong
+ * and false will be returned.
+ *
+ * @see self::pad()
+ * @param string $text
+ * @throws \LengthException if the ciphertext's length is not a multiple of the block size
+ * @return string
+ */
+ protected function unpad($text)
+ {
+ if (!$this->padding) {
+ return $text;
+ }
+
+ $length = ord($text[strlen($text) - 1]);
+
+ if (!$length || $length > $this->block_size) {
+ throw new BadDecryptionException("The ciphertext has an invalid padding length ($length) compared to the block size ({$this->block_size})");
+ }
+
+ return substr($text, 0, -$length);
+ }
+
+ /**
+ * Setup the performance-optimized function for de/encrypt()
+ *
+ * Stores the created (or existing) callback function-name
+ * in $this->inline_crypt
+ *
+ * Internally for phpseclib developers:
+ *
+ * _setupInlineCrypt() would be called only if:
+ *
+ * - $this->engine === self::ENGINE_EVAL
+ *
+ * - each time on _setup(), after(!) _setupKey()
+ *
+ *
+ * This ensures that _setupInlineCrypt() has always a
+ * full ready2go initializated internal cipher $engine state
+ * where, for example, the keys already expanded,
+ * keys/block_size calculated and such.
+ *
+ * It is, each time if called, the responsibility of _setupInlineCrypt():
+ *
+ * - to set $this->inline_crypt to a valid and fully working callback function
+ * as a (faster) replacement for encrypt() / decrypt()
+ *
+ * - NOT to create unlimited callback functions (for memory reasons!)
+ * no matter how often _setupInlineCrypt() would be called. At some
+ * point of amount they must be generic re-useable.
+ *
+ * - the code of _setupInlineCrypt() it self,
+ * and the generated callback code,
+ * must be, in following order:
+ * - 100% safe
+ * - 100% compatible to encrypt()/decrypt()
+ * - using only php5+ features/lang-constructs/php-extensions if
+ * compatibility (down to php4) or fallback is provided
+ * - readable/maintainable/understandable/commented and... not-cryptic-styled-code :-)
+ * - >= 10% faster than encrypt()/decrypt() [which is, by the way,
+ * the reason for the existence of _setupInlineCrypt() :-)]
+ * - memory-nice
+ * - short (as good as possible)
+ *
+ * Note: - _setupInlineCrypt() is using _createInlineCryptFunction() to create the full callback function code.
+ * - In case of using inline crypting, _setupInlineCrypt() must extend by the child \phpseclib3\Crypt\* class.
+ * - The following variable names are reserved:
+ * - $_* (all variable names prefixed with an underscore)
+ * - $self (object reference to it self. Do not use $this, but $self instead)
+ * - $in (the content of $in has to en/decrypt by the generated code)
+ * - The callback function should not use the 'return' statement, but en/decrypt'ing the content of $in only
+ *
+ * {@internal If a Crypt_* class providing inline crypting it must extend _setupInlineCrypt()}
+ *
+ * @see self::setup()
+ * @see self::createInlineCryptFunction()
+ * @see self::encrypt()
+ * @see self::decrypt()
+ */
+ //protected function setupInlineCrypt();
+
+ /**
+ * Creates the performance-optimized function for en/decrypt()
+ *
+ * Internally for phpseclib developers:
+ *
+ * _createInlineCryptFunction():
+ *
+ * - merge the $cipher_code [setup'ed by _setupInlineCrypt()]
+ * with the current [$this->]mode of operation code
+ *
+ * - create the $inline function, which called by encrypt() / decrypt()
+ * as its replacement to speed up the en/decryption operations.
+ *
+ * - return the name of the created $inline callback function
+ *
+ * - used to speed up en/decryption
+ *
+ *
+ *
+ * The main reason why can speed up things [up to 50%] this way are:
+ *
+ * - using variables more effective then regular.
+ * (ie no use of expensive arrays but integers $k_0, $k_1 ...
+ * or even, for example, the pure $key[] values hardcoded)
+ *
+ * - avoiding 1000's of function calls of ie _encryptBlock()
+ * but inlining the crypt operations.
+ * in the mode of operation for() loop.
+ *
+ * - full loop unroll the (sometimes key-dependent) rounds
+ * avoiding this way ++$i counters and runtime-if's etc...
+ *
+ * The basic code architectur of the generated $inline en/decrypt()
+ * lambda function, in pseudo php, is:
+ *
+ * <code>
+ * +----------------------------------------------------------------------------------------------+
+ * | callback $inline = create_function: |
+ * | lambda_function_0001_crypt_ECB($action, $text) |
+ * | { |
+ * | INSERT PHP CODE OF: |
+ * | $cipher_code['init_crypt']; // general init code. |
+ * | // ie: $sbox'es declarations used for |
+ * | // encrypt and decrypt'ing. |
+ * | |
+ * | switch ($action) { |
+ * | case 'encrypt': |
+ * | INSERT PHP CODE OF: |
+ * | $cipher_code['init_encrypt']; // encrypt sepcific init code. |
+ * | ie: specified $key or $box |
+ * | declarations for encrypt'ing. |
+ * | |
+ * | foreach ($ciphertext) { |
+ * | $in = $block_size of $ciphertext; |
+ * | |
+ * | INSERT PHP CODE OF: |
+ * | $cipher_code['encrypt_block']; // encrypt's (string) $in, which is always: |
+ * | // strlen($in) == $this->block_size |
+ * | // here comes the cipher algorithm in action |
+ * | // for encryption. |
+ * | // $cipher_code['encrypt_block'] has to |
+ * | // encrypt the content of the $in variable |
+ * | |
+ * | $plaintext .= $in; |
+ * | } |
+ * | return $plaintext; |
+ * | |
+ * | case 'decrypt': |
+ * | INSERT PHP CODE OF: |
+ * | $cipher_code['init_decrypt']; // decrypt sepcific init code |
+ * | ie: specified $key or $box |
+ * | declarations for decrypt'ing. |
+ * | foreach ($plaintext) { |
+ * | $in = $block_size of $plaintext; |
+ * | |
+ * | INSERT PHP CODE OF: |
+ * | $cipher_code['decrypt_block']; // decrypt's (string) $in, which is always |
+ * | // strlen($in) == $this->block_size |
+ * | // here comes the cipher algorithm in action |
+ * | // for decryption. |
+ * | // $cipher_code['decrypt_block'] has to |
+ * | // decrypt the content of the $in variable |
+ * | $ciphertext .= $in; |
+ * | } |
+ * | return $ciphertext; |
+ * | } |
+ * | } |
+ * +----------------------------------------------------------------------------------------------+
+ * </code>
+ *
+ * See also the \phpseclib3\Crypt\*::_setupInlineCrypt()'s for
+ * productive inline $cipher_code's how they works.
+ *
+ * Structure of:
+ * <code>
+ * $cipher_code = [
+ * 'init_crypt' => (string) '', // optional
+ * 'init_encrypt' => (string) '', // optional
+ * 'init_decrypt' => (string) '', // optional
+ * 'encrypt_block' => (string) '', // required
+ * 'decrypt_block' => (string) '' // required
+ * ];
+ * </code>
+ *
+ * @see self::setupInlineCrypt()
+ * @see self::encrypt()
+ * @see self::decrypt()
+ * @param array $cipher_code
+ * @return string (the name of the created callback function)
+ */
+ protected function createInlineCryptFunction($cipher_code)
+ {
+ $block_size = $this->block_size;
+
+ // optional
+ $init_crypt = isset($cipher_code['init_crypt']) ? $cipher_code['init_crypt'] : '';
+ $init_encrypt = isset($cipher_code['init_encrypt']) ? $cipher_code['init_encrypt'] : '';
+ $init_decrypt = isset($cipher_code['init_decrypt']) ? $cipher_code['init_decrypt'] : '';
+ // required
+ $encrypt_block = $cipher_code['encrypt_block'];
+ $decrypt_block = $cipher_code['decrypt_block'];
+
+ // Generating mode of operation inline code,
+ // merged with the $cipher_code algorithm
+ // for encrypt- and decryption.
+ switch ($this->mode) {
+ case self::MODE_ECB:
+ $encrypt = $init_encrypt . '
+ $_ciphertext = "";
+ $_plaintext_len = strlen($_text);
+
+ for ($_i = 0; $_i < $_plaintext_len; $_i+= ' . $block_size . ') {
+ $in = substr($_text, $_i, ' . $block_size . ');
+ ' . $encrypt_block . '
+ $_ciphertext.= $in;
+ }
+
+ return $_ciphertext;
+ ';
+
+ $decrypt = $init_decrypt . '
+ $_plaintext = "";
+ $_text = str_pad($_text, strlen($_text) + (' . $block_size . ' - strlen($_text) % ' . $block_size . ') % ' . $block_size . ', chr(0));
+ $_ciphertext_len = strlen($_text);
+
+ for ($_i = 0; $_i < $_ciphertext_len; $_i+= ' . $block_size . ') {
+ $in = substr($_text, $_i, ' . $block_size . ');
+ ' . $decrypt_block . '
+ $_plaintext.= $in;
+ }
+
+ return $this->unpad($_plaintext);
+ ';
+ break;
+ case self::MODE_CTR:
+ $encrypt = $init_encrypt . '
+ $_ciphertext = "";
+ $_plaintext_len = strlen($_text);
+ $_xor = $this->encryptIV;
+ $_buffer = &$this->enbuffer;
+ if (strlen($_buffer["ciphertext"])) {
+ for ($_i = 0; $_i < $_plaintext_len; $_i+= ' . $block_size . ') {
+ $_block = substr($_text, $_i, ' . $block_size . ');
+ if (strlen($_block) > strlen($_buffer["ciphertext"])) {
+ $in = $_xor;
+ ' . $encrypt_block . '
+ \phpseclib3\Common\Functions\Strings::increment_str($_xor);
+ $_buffer["ciphertext"].= $in;
+ }
+ $_key = \phpseclib3\Common\Functions\Strings::shift($_buffer["ciphertext"], ' . $block_size . ');
+ $_ciphertext.= $_block ^ $_key;
+ }
+ } else {
+ for ($_i = 0; $_i < $_plaintext_len; $_i+= ' . $block_size . ') {
+ $_block = substr($_text, $_i, ' . $block_size . ');
+ $in = $_xor;
+ ' . $encrypt_block . '
+ \phpseclib3\Common\Functions\Strings::increment_str($_xor);
+ $_key = $in;
+ $_ciphertext.= $_block ^ $_key;
+ }
+ }
+ if ($this->continuousBuffer) {
+ $this->encryptIV = $_xor;
+ if ($_start = $_plaintext_len % ' . $block_size . ') {
+ $_buffer["ciphertext"] = substr($_key, $_start) . $_buffer["ciphertext"];
+ }
+ }
+
+ return $_ciphertext;
+ ';
+
+ $decrypt = $init_encrypt . '
+ $_plaintext = "";
+ $_ciphertext_len = strlen($_text);
+ $_xor = $this->decryptIV;
+ $_buffer = &$this->debuffer;
+
+ if (strlen($_buffer["ciphertext"])) {
+ for ($_i = 0; $_i < $_ciphertext_len; $_i+= ' . $block_size . ') {
+ $_block = substr($_text, $_i, ' . $block_size . ');
+ if (strlen($_block) > strlen($_buffer["ciphertext"])) {
+ $in = $_xor;
+ ' . $encrypt_block . '
+ \phpseclib3\Common\Functions\Strings::increment_str($_xor);
+ $_buffer["ciphertext"].= $in;
+ }
+ $_key = \phpseclib3\Common\Functions\Strings::shift($_buffer["ciphertext"], ' . $block_size . ');
+ $_plaintext.= $_block ^ $_key;
+ }
+ } else {
+ for ($_i = 0; $_i < $_ciphertext_len; $_i+= ' . $block_size . ') {
+ $_block = substr($_text, $_i, ' . $block_size . ');
+ $in = $_xor;
+ ' . $encrypt_block . '
+ \phpseclib3\Common\Functions\Strings::increment_str($_xor);
+ $_key = $in;
+ $_plaintext.= $_block ^ $_key;
+ }
+ }
+ if ($this->continuousBuffer) {
+ $this->decryptIV = $_xor;
+ if ($_start = $_ciphertext_len % ' . $block_size . ') {
+ $_buffer["ciphertext"] = substr($_key, $_start) . $_buffer["ciphertext"];
+ }
+ }
+
+ return $_plaintext;
+ ';
+ break;
+ case self::MODE_CFB:
+ $encrypt = $init_encrypt . '
+ $_ciphertext = "";
+ $_buffer = &$this->enbuffer;
+
+ if ($this->continuousBuffer) {
+ $_iv = &$this->encryptIV;
+ $_pos = &$_buffer["pos"];
+ } else {
+ $_iv = $this->encryptIV;
+ $_pos = 0;
+ }
+ $_len = strlen($_text);
+ $_i = 0;
+ if ($_pos) {
+ $_orig_pos = $_pos;
+ $_max = ' . $block_size . ' - $_pos;
+ if ($_len >= $_max) {
+ $_i = $_max;
+ $_len-= $_max;
+ $_pos = 0;
+ } else {
+ $_i = $_len;
+ $_pos+= $_len;
+ $_len = 0;
+ }
+ $_ciphertext = substr($_iv, $_orig_pos) ^ $_text;
+ $_iv = substr_replace($_iv, $_ciphertext, $_orig_pos, $_i);
+ }
+ while ($_len >= ' . $block_size . ') {
+ $in = $_iv;
+ ' . $encrypt_block . ';
+ $_iv = $in ^ substr($_text, $_i, ' . $block_size . ');
+ $_ciphertext.= $_iv;
+ $_len-= ' . $block_size . ';
+ $_i+= ' . $block_size . ';
+ }
+ if ($_len) {
+ $in = $_iv;
+ ' . $encrypt_block . '
+ $_iv = $in;
+ $_block = $_iv ^ substr($_text, $_i);
+ $_iv = substr_replace($_iv, $_block, 0, $_len);
+ $_ciphertext.= $_block;
+ $_pos = $_len;
+ }
+ return $_ciphertext;
+ ';
+
+ $decrypt = $init_encrypt . '
+ $_plaintext = "";
+ $_buffer = &$this->debuffer;
+
+ if ($this->continuousBuffer) {
+ $_iv = &$this->decryptIV;
+ $_pos = &$_buffer["pos"];
+ } else {
+ $_iv = $this->decryptIV;
+ $_pos = 0;
+ }
+ $_len = strlen($_text);
+ $_i = 0;
+ if ($_pos) {
+ $_orig_pos = $_pos;
+ $_max = ' . $block_size . ' - $_pos;
+ if ($_len >= $_max) {
+ $_i = $_max;
+ $_len-= $_max;
+ $_pos = 0;
+ } else {
+ $_i = $_len;
+ $_pos+= $_len;
+ $_len = 0;
+ }
+ $_plaintext = substr($_iv, $_orig_pos) ^ $_text;
+ $_iv = substr_replace($_iv, substr($_text, 0, $_i), $_orig_pos, $_i);
+ }
+ while ($_len >= ' . $block_size . ') {
+ $in = $_iv;
+ ' . $encrypt_block . '
+ $_iv = $in;
+ $cb = substr($_text, $_i, ' . $block_size . ');
+ $_plaintext.= $_iv ^ $cb;
+ $_iv = $cb;
+ $_len-= ' . $block_size . ';
+ $_i+= ' . $block_size . ';
+ }
+ if ($_len) {
+ $in = $_iv;
+ ' . $encrypt_block . '
+ $_iv = $in;
+ $_plaintext.= $_iv ^ substr($_text, $_i);
+ $_iv = substr_replace($_iv, substr($_text, $_i), 0, $_len);
+ $_pos = $_len;
+ }
+
+ return $_plaintext;
+ ';
+ break;
+ case self::MODE_CFB8:
+ $encrypt = $init_encrypt . '
+ $_ciphertext = "";
+ $_len = strlen($_text);
+ $_iv = $this->encryptIV;
+
+ for ($_i = 0; $_i < $_len; ++$_i) {
+ $in = $_iv;
+ ' . $encrypt_block . '
+ $_ciphertext .= ($_c = $_text[$_i] ^ $in);
+ $_iv = substr($_iv, 1) . $_c;
+ }
+
+ if ($this->continuousBuffer) {
+ if ($_len >= ' . $block_size . ') {
+ $this->encryptIV = substr($_ciphertext, -' . $block_size . ');
+ } else {
+ $this->encryptIV = substr($this->encryptIV, $_len - ' . $block_size . ') . substr($_ciphertext, -$_len);
+ }
+ }
+
+ return $_ciphertext;
+ ';
+ $decrypt = $init_encrypt . '
+ $_plaintext = "";
+ $_len = strlen($_text);
+ $_iv = $this->decryptIV;
+
+ for ($_i = 0; $_i < $_len; ++$_i) {
+ $in = $_iv;
+ ' . $encrypt_block . '
+ $_plaintext .= $_text[$_i] ^ $in;
+ $_iv = substr($_iv, 1) . $_text[$_i];
+ }
+
+ if ($this->continuousBuffer) {
+ if ($_len >= ' . $block_size . ') {
+ $this->decryptIV = substr($_text, -' . $block_size . ');
+ } else {
+ $this->decryptIV = substr($this->decryptIV, $_len - ' . $block_size . ') . substr($_text, -$_len);
+ }
+ }
+
+ return $_plaintext;
+ ';
+ break;
+ case self::MODE_OFB8:
+ $encrypt = $init_encrypt . '
+ $_ciphertext = "";
+ $_len = strlen($_text);
+ $_iv = $this->encryptIV;
+
+ for ($_i = 0; $_i < $_len; ++$_i) {
+ $in = $_iv;
+ ' . $encrypt_block . '
+ $_ciphertext.= $_text[$_i] ^ $in;
+ $_iv = substr($_iv, 1) . $in[0];
+ }
+
+ if ($this->continuousBuffer) {
+ $this->encryptIV = $_iv;
+ }
+
+ return $_ciphertext;
+ ';
+ $decrypt = $init_encrypt . '
+ $_plaintext = "";
+ $_len = strlen($_text);
+ $_iv = $this->decryptIV;
+
+ for ($_i = 0; $_i < $_len; ++$_i) {
+ $in = $_iv;
+ ' . $encrypt_block . '
+ $_plaintext.= $_text[$_i] ^ $in;
+ $_iv = substr($_iv, 1) . $in[0];
+ }
+
+ if ($this->continuousBuffer) {
+ $this->decryptIV = $_iv;
+ }
+
+ return $_plaintext;
+ ';
+ break;
+ case self::MODE_OFB:
+ $encrypt = $init_encrypt . '
+ $_ciphertext = "";
+ $_plaintext_len = strlen($_text);
+ $_xor = $this->encryptIV;
+ $_buffer = &$this->enbuffer;
+
+ if (strlen($_buffer["xor"])) {
+ for ($_i = 0; $_i < $_plaintext_len; $_i+= ' . $block_size . ') {
+ $_block = substr($_text, $_i, ' . $block_size . ');
+ if (strlen($_block) > strlen($_buffer["xor"])) {
+ $in = $_xor;
+ ' . $encrypt_block . '
+ $_xor = $in;
+ $_buffer["xor"].= $_xor;
+ }
+ $_key = \phpseclib3\Common\Functions\Strings::shift($_buffer["xor"], ' . $block_size . ');
+ $_ciphertext.= $_block ^ $_key;
+ }
+ } else {
+ for ($_i = 0; $_i < $_plaintext_len; $_i+= ' . $block_size . ') {
+ $in = $_xor;
+ ' . $encrypt_block . '
+ $_xor = $in;
+ $_ciphertext.= substr($_text, $_i, ' . $block_size . ') ^ $_xor;
+ }
+ $_key = $_xor;
+ }
+ if ($this->continuousBuffer) {
+ $this->encryptIV = $_xor;
+ if ($_start = $_plaintext_len % ' . $block_size . ') {
+ $_buffer["xor"] = substr($_key, $_start) . $_buffer["xor"];
+ }
+ }
+ return $_ciphertext;
+ ';
+
+ $decrypt = $init_encrypt . '
+ $_plaintext = "";
+ $_ciphertext_len = strlen($_text);
+ $_xor = $this->decryptIV;
+ $_buffer = &$this->debuffer;
+
+ if (strlen($_buffer["xor"])) {
+ for ($_i = 0; $_i < $_ciphertext_len; $_i+= ' . $block_size . ') {
+ $_block = substr($_text, $_i, ' . $block_size . ');
+ if (strlen($_block) > strlen($_buffer["xor"])) {
+ $in = $_xor;
+ ' . $encrypt_block . '
+ $_xor = $in;
+ $_buffer["xor"].= $_xor;
+ }
+ $_key = \phpseclib3\Common\Functions\Strings::shift($_buffer["xor"], ' . $block_size . ');
+ $_plaintext.= $_block ^ $_key;
+ }
+ } else {
+ for ($_i = 0; $_i < $_ciphertext_len; $_i+= ' . $block_size . ') {
+ $in = $_xor;
+ ' . $encrypt_block . '
+ $_xor = $in;
+ $_plaintext.= substr($_text, $_i, ' . $block_size . ') ^ $_xor;
+ }
+ $_key = $_xor;
+ }
+ if ($this->continuousBuffer) {
+ $this->decryptIV = $_xor;
+ if ($_start = $_ciphertext_len % ' . $block_size . ') {
+ $_buffer["xor"] = substr($_key, $_start) . $_buffer["xor"];
+ }
+ }
+ return $_plaintext;
+ ';
+ break;
+ case self::MODE_STREAM:
+ $encrypt = $init_encrypt . '
+ $_ciphertext = "";
+ ' . $encrypt_block . '
+ return $_ciphertext;
+ ';
+ $decrypt = $init_decrypt . '
+ $_plaintext = "";
+ ' . $decrypt_block . '
+ return $_plaintext;
+ ';
+ break;
+ // case self::MODE_CBC:
+ default:
+ $encrypt = $init_encrypt . '
+ $_ciphertext = "";
+ $_plaintext_len = strlen($_text);
+
+ $in = $this->encryptIV;
+
+ for ($_i = 0; $_i < $_plaintext_len; $_i+= ' . $block_size . ') {
+ $in = substr($_text, $_i, ' . $block_size . ') ^ $in;
+ ' . $encrypt_block . '
+ $_ciphertext.= $in;
+ }
+
+ if ($this->continuousBuffer) {
+ $this->encryptIV = $in;
+ }
+
+ return $_ciphertext;
+ ';
+
+ $decrypt = $init_decrypt . '
+ $_plaintext = "";
+ $_text = str_pad($_text, strlen($_text) + (' . $block_size . ' - strlen($_text) % ' . $block_size . ') % ' . $block_size . ', chr(0));
+ $_ciphertext_len = strlen($_text);
+
+ $_iv = $this->decryptIV;
+
+ for ($_i = 0; $_i < $_ciphertext_len; $_i+= ' . $block_size . ') {
+ $in = $_block = substr($_text, $_i, ' . $block_size . ');
+ ' . $decrypt_block . '
+ $_plaintext.= $in ^ $_iv;
+ $_iv = $_block;
+ }
+
+ if ($this->continuousBuffer) {
+ $this->decryptIV = $_iv;
+ }
+
+ return $this->unpad($_plaintext);
+ ';
+ break;
+ }
+
+ // Before discrediting this, please read the following:
+ // @see https://github.com/phpseclib/phpseclib/issues/1293
+ // @see https://github.com/phpseclib/phpseclib/pull/1143
+ eval('$func = function ($_action, $_text) { ' . $init_crypt . 'if ($_action == "encrypt") { ' . $encrypt . ' } else { ' . $decrypt . ' }};');
+
+ return \Closure::bind($func, $this, static::class);
+ }
+
+ /**
+ * Convert float to int
+ *
+ * On ARM CPUs converting floats to ints doesn't always work
+ *
+ * @param string $x
+ * @return int
+ */
+ protected static function safe_intval($x)
+ {
+ if (is_int($x)) {
+ return $x;
+ }
+
+ if (self::$use_reg_intval) {
+ return PHP_INT_SIZE == 4 && PHP_VERSION_ID >= 80100 ? intval($x) : $x;
+ }
+
+ return (fmod($x, 0x80000000) & 0x7FFFFFFF) |
+ ((fmod(floor($x / 0x80000000), 2) & 1) << 31);
+ }
+
+ /**
+ * eval()'able string for in-line float to int
+ *
+ * @return string
+ */
+ protected static function safe_intval_inline()
+ {
+ if (self::$use_reg_intval) {
+ return PHP_INT_SIZE == 4 && PHP_VERSION_ID >= 80100 ? 'intval(%s)' : '%s';
+ }
+
+ $safeint = '(is_int($temp = %s) ? $temp : (fmod($temp, 0x80000000) & 0x7FFFFFFF) | ';
+ return $safeint . '((fmod(floor($temp / 0x80000000), 2) & 1) << 31))';
+ }
+
+ /**
+ * Sets up GCM parameters
+ *
+ * See steps 1-2 of https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf#page=23
+ * for more info
+ *
+ */
+ private function setupGCM()
+ {
+ // don't keep on re-calculating $this->h
+ if (!$this->h || $this->hKey != $this->key) {
+ $cipher = new static('ecb');
+ $cipher->setKey($this->key);
+ $cipher->disablePadding();
+
+ $this->h = self::$gcmField->newInteger(
+ Strings::switchEndianness($cipher->encrypt("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"))
+ );
+ $this->hKey = $this->key;
+ }
+
+ if (strlen($this->nonce) == 12) {
+ $this->iv = $this->nonce . "\0\0\0\1";
+ } else {
+ $this->iv = $this->ghash(
+ self::nullPad128($this->nonce) . str_repeat("\0", 8) . self::len64($this->nonce)
+ );
+ }
+ }
+
+ /**
+ * Performs GHASH operation
+ *
+ * See https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf#page=20
+ * for more info
+ *
+ * @see self::decrypt()
+ * @see self::encrypt()
+ * @param string $x
+ * @return string
+ */
+ private function ghash($x)
+ {
+ $h = $this->h;
+ $y = ["\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"];
+ $x = str_split($x, 16);
+ $n = 0;
+ // the switchEndianness calls are necessary because the multiplication algorithm in BinaryField/Integer
+ // interprets strings as polynomials in big endian order whereas in GCM they're interpreted in little
+ // endian order per https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf#page=19.
+ // big endian order is what binary field elliptic curves use per http://www.secg.org/sec1-v2.pdf#page=18.
+
+ // we could switchEndianness here instead of in the while loop but doing so in the while loop seems like it
+ // might be slightly more performant
+ //$x = Strings::switchEndianness($x);
+ foreach ($x as $xn) {
+ $xn = Strings::switchEndianness($xn);
+ $t = $y[$n] ^ $xn;
+ $temp = self::$gcmField->newInteger($t);
+ $y[++$n] = $temp->multiply($h)->toBytes();
+ $y[$n] = substr($y[$n], 1);
+ }
+ $y[$n] = Strings::switchEndianness($y[$n]);
+ return $y[$n];
+ }
+
+ /**
+ * Returns the bit length of a string in a packed format
+ *
+ * @see self::decrypt()
+ * @see self::encrypt()
+ * @see self::setupGCM()
+ * @param string $str
+ * @return string
+ */
+ private static function len64($str)
+ {
+ return "\0\0\0\0" . pack('N', 8 * strlen($str));
+ }
+
+ /**
+ * NULL pads a string to be a multiple of 128
+ *
+ * @see self::decrypt()
+ * @see self::encrypt()
+ * @see self::setupGCM()
+ * @param string $str
+ * @return string
+ */
+ protected static function nullPad128($str)
+ {
+ $len = strlen($str);
+ return $str . str_repeat("\0", 16 * ceil($len / 16) - $len);
+ }
+
+ /**
+ * Calculates Poly1305 MAC
+ *
+ * On my system ChaCha20, with libsodium, takes 0.5s. With this custom Poly1305 implementation
+ * it takes 1.2s.
+ *
+ * @see self::decrypt()
+ * @see self::encrypt()
+ * @param string $text
+ * @return string
+ */
+ protected function poly1305($text)
+ {
+ $s = $this->poly1305Key; // strlen($this->poly1305Key) == 32
+ $r = Strings::shift($s, 16);
+ $r = strrev($r);
+ $r &= "\x0f\xff\xff\xfc\x0f\xff\xff\xfc\x0f\xff\xff\xfc\x0f\xff\xff\xff";
+ $s = strrev($s);
+
+ $r = self::$poly1305Field->newInteger(new BigInteger($r, 256));
+ $s = self::$poly1305Field->newInteger(new BigInteger($s, 256));
+ $a = self::$poly1305Field->newInteger(new BigInteger());
+
+ $blocks = str_split($text, 16);
+ foreach ($blocks as $block) {
+ $n = strrev($block . chr(1));
+ $n = self::$poly1305Field->newInteger(new BigInteger($n, 256));
+ $a = $a->add($n);
+ $a = $a->multiply($r);
+ }
+ $r = $a->toBigInteger()->add($s->toBigInteger());
+ $mask = "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF";
+ return strrev($r->toBytes()) & $mask;
+ }
+
+ /**
+ * Return the mode
+ *
+ * You can do $obj instanceof AES or whatever to get the cipher but you can't do that to get the mode
+ *
+ * @return string
+ */
+ public function getMode()
+ {
+ return array_flip(self::MODE_MAP)[$this->mode];
+ }
+
+ /**
+ * Is the continuous buffer enabled?
+ *
+ * @return boolean
+ */
+ public function continuousBufferEnabled()
+ {
+ return $this->continuousBuffer;
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Traits/Fingerprint.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Traits/Fingerprint.php
new file mode 100644
index 000000000..9ca8926d3
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Traits/Fingerprint.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * Fingerprint Trait for Public Keys
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\Common\Traits;
+
+use phpseclib3\Crypt\Hash;
+
+/**
+ * Fingerprint Trait for Private Keys
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+trait Fingerprint
+{
+ /**
+ * Returns the public key's fingerprint
+ *
+ * The public key's fingerprint is returned, which is equivalent to running `ssh-keygen -lf rsa.pub`. If there is
+ * no public key currently loaded, false is returned.
+ * Example output (md5): "c1:b1:30:29:d7:b8:de:6c:97:77:10:d7:46:41:63:87" (as specified by RFC 4716)
+ *
+ * @param string $algorithm The hashing algorithm to be used. Valid options are 'md5' and 'sha256'. False is returned
+ * for invalid values.
+ * @return mixed
+ */
+ public function getFingerprint($algorithm = 'md5')
+ {
+ $type = self::validatePlugin('Keys', 'OpenSSH', 'savePublicKey');
+ if ($type === false) {
+ return false;
+ }
+ $key = $this->toString('OpenSSH', ['binary' => true]);
+ if ($key === false) {
+ return false;
+ }
+ switch ($algorithm) {
+ case 'sha256':
+ $hash = new Hash('sha256');
+ $base = base64_encode($hash->hash($key));
+ return substr($base, 0, strlen($base) - 1);
+ case 'md5':
+ return substr(chunk_split(md5($key), 2, ':'), 0, -1);
+ default:
+ return false;
+ }
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Traits/PasswordProtected.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Traits/PasswordProtected.php
new file mode 100644
index 000000000..0ac274e8d
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Common/Traits/PasswordProtected.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * Password Protected Trait for Private Keys
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\Common\Traits;
+
+/**
+ * Password Protected Trait for Private Keys
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+trait PasswordProtected
+{
+ /**
+ * Password
+ *
+ * @var string|bool
+ */
+ private $password = false;
+
+ /**
+ * Sets the password
+ *
+ * Private keys can be encrypted with a password. To unset the password, pass in the empty string or false.
+ * Or rather, pass in $password such that empty($password) && !is_string($password) is true.
+ *
+ * @see self::createKey()
+ * @see self::load()
+ * @param string|bool $password
+ */
+ public function withPassword($password = false)
+ {
+ $new = clone $this;
+ $new->password = $password;
+ return $new;
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DES.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DES.php
index 26bd385f5..93d7ad2ed 100644
--- a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DES.php
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DES.php
@@ -18,7 +18,7 @@
* <?php
* include 'vendor/autoload.php';
*
- * $des = new \phpseclib\Crypt\DES();
+ * $des = new \phpseclib3\Crypt\DES('ctr');
*
* $des->setKey('abcdefgh');
*
@@ -32,120 +32,119 @@
* ?>
* </code>
*
- * @category Crypt
- * @package DES
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
-namespace phpseclib\Crypt;
+namespace phpseclib3\Crypt;
+
+use phpseclib3\Crypt\Common\BlockCipher;
+use phpseclib3\Exception\BadModeException;
/**
* Pure-PHP implementation of DES.
*
- * @package DES
* @author Jim Wigginton <terrafrost@php.net>
- * @access public
*/
-class DES extends Base
+class DES extends BlockCipher
{
- /**#@+
- * @access private
- * @see \phpseclib\Crypt\DES::_setupKey()
- * @see \phpseclib\Crypt\DES::_processBlock()
- */
/**
* Contains $keys[self::ENCRYPT]
+ *
+ * @see \phpseclib3\Crypt\DES::setupKey()
+ * @see \phpseclib3\Crypt\DES::processBlock()
*/
const ENCRYPT = 0;
/**
* Contains $keys[self::DECRYPT]
+ *
+ * @see \phpseclib3\Crypt\DES::setupKey()
+ * @see \phpseclib3\Crypt\DES::processBlock()
*/
const DECRYPT = 1;
- /**#@-*/
/**
* Block Length of the cipher
*
- * @see \phpseclib\Crypt\Base::block_size
+ * @see Common\SymmetricKey::block_size
* @var int
- * @access private
*/
- var $block_size = 8;
+ protected $block_size = 8;
/**
* Key Length (in bytes)
*
- * @see \phpseclib\Crypt\Base::setKeyLength()
+ * @see Common\SymmetricKey::setKeyLength()
* @var int
- * @access private
*/
- var $key_length = 8;
+ protected $key_length = 8;
/**
* The mcrypt specific name of the cipher
*
- * @see \phpseclib\Crypt\Base::cipher_name_mcrypt
+ * @see Common\SymmetricKey::cipher_name_mcrypt
* @var string
- * @access private
*/
- var $cipher_name_mcrypt = 'des';
+ protected $cipher_name_mcrypt = 'des';
/**
* The OpenSSL names of the cipher / modes
*
- * @see \phpseclib\Crypt\Base::openssl_mode_names
+ * @see Common\SymmetricKey::openssl_mode_names
* @var array
- * @access private
*/
- var $openssl_mode_names = array(
+ protected $openssl_mode_names = [
self::MODE_ECB => 'des-ecb',
self::MODE_CBC => 'des-cbc',
self::MODE_CFB => 'des-cfb',
self::MODE_OFB => 'des-ofb'
// self::MODE_CTR is undefined for DES
- );
+ ];
/**
* Optimizing value while CFB-encrypting
*
- * @see \phpseclib\Crypt\Base::cfb_init_len
+ * @see Common\SymmetricKey::cfb_init_len
* @var int
- * @access private
*/
- var $cfb_init_len = 500;
+ protected $cfb_init_len = 500;
/**
* Switch for DES/3DES encryption
*
* Used only if $engine == self::ENGINE_INTERNAL
*
- * @see self::_setupKey()
- * @see self::_processBlock()
+ * @see self::setupKey()
+ * @see self::processBlock()
* @var int
- * @access private
*/
- var $des_rounds = 1;
+ protected $des_rounds = 1;
/**
* max possible size of $key
*
* @see self::setKey()
* @var string
- * @access private
*/
- var $key_length_max = 8;
+ protected $key_length_max = 8;
/**
* The Key Schedule
*
- * @see self::_setupKey()
+ * @see self::setupKey()
* @var array
- * @access private
*/
- var $keys;
+ private $keys;
+
+ /**
+ * Key Cache "key"
+ *
+ * @see self::setupKey()
+ * @var array
+ */
+ private $kl;
/**
* Shuffle table.
@@ -154,12 +153,11 @@ class DES extends Base
* with each byte containing all bits in the same state as the
* corresponding bit in the index value.
*
- * @see self::_processBlock()
- * @see self::_setupKey()
+ * @see self::processBlock()
+ * @see self::setupKey()
* @var array
- * @access private
*/
- var $shuffle = array(
+ protected static $shuffle = [
"\x00\x00\x00\x00\x00\x00\x00\x00", "\x00\x00\x00\x00\x00\x00\x00\xFF",
"\x00\x00\x00\x00\x00\x00\xFF\x00", "\x00\x00\x00\x00\x00\x00\xFF\xFF",
"\x00\x00\x00\x00\x00\xFF\x00\x00", "\x00\x00\x00\x00\x00\xFF\x00\xFF",
@@ -288,7 +286,7 @@ class DES extends Base
"\xFF\xFF\xFF\xFF\xFF\x00\xFF\x00", "\xFF\xFF\xFF\xFF\xFF\x00\xFF\xFF",
"\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00", "\xFF\xFF\xFF\xFF\xFF\xFF\x00\xFF",
"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00", "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
- );
+ ];
/**
* IP mapping helper table.
@@ -296,9 +294,8 @@ class DES extends Base
* Indexing this table with each source byte performs the initial bit permutation.
*
* @var array
- * @access private
*/
- var $ipmap = array(
+ protected static $ipmap = [
0x00, 0x10, 0x01, 0x11, 0x20, 0x30, 0x21, 0x31,
0x02, 0x12, 0x03, 0x13, 0x22, 0x32, 0x23, 0x33,
0x40, 0x50, 0x41, 0x51, 0x60, 0x70, 0x61, 0x71,
@@ -331,16 +328,15 @@ class DES extends Base
0x8E, 0x9E, 0x8F, 0x9F, 0xAE, 0xBE, 0xAF, 0xBF,
0xCC, 0xDC, 0xCD, 0xDD, 0xEC, 0xFC, 0xED, 0xFD,
0xCE, 0xDE, 0xCF, 0xDF, 0xEE, 0xFE, 0xEF, 0xFF
- );
+ ];
/**
* Inverse IP mapping helper table.
* Indexing this table with a byte value reverses the bit order.
*
* @var array
- * @access private
*/
- var $invipmap = array(
+ protected static $invipmap = [
0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0,
0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8,
@@ -373,7 +369,7 @@ class DES extends Base
0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF,
0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
- );
+ ];
/**
* Pre-permuted S-box1
@@ -382,9 +378,8 @@ class DES extends Base
* P table: concatenation can then be replaced by exclusive ORs.
*
* @var array
- * @access private
*/
- var $sbox1 = array(
+ protected static $sbox1 = [
0x00808200, 0x00000000, 0x00008000, 0x00808202,
0x00808002, 0x00008202, 0x00000002, 0x00008000,
0x00000200, 0x00808200, 0x00808202, 0x00000200,
@@ -401,15 +396,14 @@ class DES extends Base
0x00800002, 0x00000202, 0x00008202, 0x00808200,
0x00000202, 0x00800200, 0x00800200, 0x00000000,
0x00008002, 0x00008200, 0x00000000, 0x00808002
- );
+ ];
/**
* Pre-permuted S-box2
*
* @var array
- * @access private
*/
- var $sbox2 = array(
+ protected static $sbox2 = [
0x40084010, 0x40004000, 0x00004000, 0x00084010,
0x00080000, 0x00000010, 0x40080010, 0x40004010,
0x40000010, 0x40084010, 0x40084000, 0x40000000,
@@ -426,15 +420,14 @@ class DES extends Base
0x00080010, 0x40004010, 0x40000010, 0x00080010,
0x00084000, 0x00000000, 0x40004000, 0x00004010,
0x40000000, 0x40080010, 0x40084010, 0x00084000
- );
+ ];
/**
* Pre-permuted S-box3
*
* @var array
- * @access private
*/
- var $sbox3 = array(
+ protected static $sbox3 = [
0x00000104, 0x04010100, 0x00000000, 0x04010004,
0x04000100, 0x00000000, 0x00010104, 0x04000100,
0x00010004, 0x04000004, 0x04000004, 0x00010000,
@@ -451,15 +444,14 @@ class DES extends Base
0x00000004, 0x00010104, 0x00010100, 0x04000004,
0x04010000, 0x04000104, 0x00000104, 0x04010000,
0x00010104, 0x00000004, 0x04010004, 0x00010100
- );
+ ];
/**
* Pre-permuted S-box4
*
* @var array
- * @access private
*/
- var $sbox4 = array(
+ protected static $sbox4 = [
0x80401000, 0x80001040, 0x80001040, 0x00000040,
0x00401040, 0x80400040, 0x80400000, 0x80001000,
0x00000000, 0x00401000, 0x00401000, 0x80401040,
@@ -476,15 +468,14 @@ class DES extends Base
0x80400000, 0x80001000, 0x00401040, 0x80400040,
0x80001000, 0x00001040, 0x00400000, 0x80401000,
0x00000040, 0x00400000, 0x00001000, 0x00401040
- );
+ ];
/**
* Pre-permuted S-box5
*
* @var array
- * @access private
*/
- var $sbox5 = array(
+ protected static $sbox5 = [
0x00000080, 0x01040080, 0x01040000, 0x21000080,
0x00040000, 0x00000080, 0x20000000, 0x01040000,
0x20040080, 0x00040000, 0x01000080, 0x20040080,
@@ -501,15 +492,14 @@ class DES extends Base
0x01040000, 0x00000000, 0x20040000, 0x21000000,
0x00040080, 0x01000080, 0x20000080, 0x00040000,
0x00000000, 0x20040000, 0x01040080, 0x20000080
- );
+ ];
/**
* Pre-permuted S-box6
*
* @var array
- * @access private
*/
- var $sbox6 = array(
+ protected static $sbox6 = [
0x10000008, 0x10200000, 0x00002000, 0x10202008,
0x10200000, 0x00000008, 0x10202008, 0x00200000,
0x10002000, 0x00202008, 0x00200000, 0x10000008,
@@ -526,15 +516,14 @@ class DES extends Base
0x00000008, 0x00002000, 0x10200000, 0x00202008,
0x00002000, 0x00200008, 0x10002008, 0x00000000,
0x10202000, 0x10000000, 0x00200008, 0x10002008
- );
+ ];
/**
* Pre-permuted S-box7
*
* @var array
- * @access private
*/
- var $sbox7 = array(
+ protected static $sbox7 = [
0x00100000, 0x02100001, 0x02000401, 0x00000000,
0x00000400, 0x02000401, 0x00100401, 0x02100400,
0x02100401, 0x00100000, 0x00000000, 0x02000001,
@@ -551,15 +540,14 @@ class DES extends Base
0x00100400, 0x00000000, 0x00000001, 0x02100401,
0x00000000, 0x00100401, 0x02100000, 0x00000400,
0x02000001, 0x02000400, 0x00000400, 0x00100001
- );
+ ];
/**
* Pre-permuted S-box8
*
* @var array
- * @access private
*/
- var $sbox8 = array(
+ protected static $sbox8 = [
0x08000820, 0x00000800, 0x00020000, 0x08020820,
0x08000000, 0x08000820, 0x00000020, 0x08000000,
0x00020020, 0x08020000, 0x08020820, 0x00020800,
@@ -576,19 +564,33 @@ class DES extends Base
0x08020000, 0x08000800, 0x08000820, 0x00000000,
0x08020820, 0x00020800, 0x00020800, 0x00000820,
0x00000820, 0x00020020, 0x08000000, 0x08020800
- );
+ ];
+
+ /**
+ * Default Constructor.
+ *
+ * @param string $mode
+ * @throws BadModeException if an invalid / unsupported mode is provided
+ */
+ public function __construct($mode)
+ {
+ parent::__construct($mode);
+
+ if ($this->mode == self::MODE_STREAM) {
+ throw new BadModeException('Block ciphers cannot be ran in stream mode');
+ }
+ }
/**
* Test for engine validity
*
- * This is mainly just a wrapper to set things up for \phpseclib\Crypt\Base::isValidEngine()
+ * This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine()
*
- * @see \phpseclib\Crypt\Base::isValidEngine()
+ * @see Common\SymmetricKey::isValidEngine()
* @param int $engine
- * @access public
* @return bool
*/
- function isValidEngine($engine)
+ protected function isValidEngineHelper($engine)
{
if ($this->key_length_max == 8) {
if ($engine == self::ENGINE_OPENSSL) {
@@ -599,34 +601,27 @@ class DES extends Base
return false;
}
$this->cipher_name_openssl_ecb = 'des-ecb';
- $this->cipher_name_openssl = 'des-' . $this->_openssl_translate_mode();
+ $this->cipher_name_openssl = 'des-' . $this->openssl_translate_mode();
}
}
- return parent::isValidEngine($engine);
+ return parent::isValidEngineHelper($engine);
}
/**
* Sets the key.
*
- * Keys can be of any length. DES, itself, uses 64-bit keys (eg. strlen($key) == 8), however, we
- * only use the first eight, if $key has more then eight characters in it, and pad $key with the
- * null byte if it is less then eight characters long.
+ * Keys must be 64-bits long or 8 bytes long.
*
* DES also requires that every eighth bit be a parity bit, however, we'll ignore that.
*
- * If the key is not explicitly set, it'll be assumed to be all zero's.
- *
- * @see \phpseclib\Crypt\Base::setKey()
- * @access public
+ * @see Common\SymmetricKey::setKey()
* @param string $key
*/
- function setKey($key)
+ public function setKey($key)
{
- // We check/cut here only up to max length of the key.
- // Key padding to the proper length will be done in _setupKey()
- if (strlen($key) > $this->key_length_max) {
- $key = substr($key, 0, $this->key_length_max);
+ if (!($this instanceof TripleDES) && strlen($key) != 8) {
+ throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of size 8 are supported');
}
// Sets the key
@@ -636,31 +631,29 @@ class DES extends Base
/**
* Encrypts a block
*
- * @see \phpseclib\Crypt\Base::_encryptBlock()
- * @see \phpseclib\Crypt\Base::encrypt()
+ * @see Common\SymmetricKey::encryptBlock()
+ * @see Common\SymmetricKey::encrypt()
* @see self::encrypt()
- * @access private
* @param string $in
* @return string
*/
- function _encryptBlock($in)
+ protected function encryptBlock($in)
{
- return $this->_processBlock($in, self::ENCRYPT);
+ return $this->processBlock($in, self::ENCRYPT);
}
/**
* Decrypts a block
*
- * @see \phpseclib\Crypt\Base::_decryptBlock()
- * @see \phpseclib\Crypt\Base::decrypt()
+ * @see Common\SymmetricKey::decryptBlock()
+ * @see Common\SymmetricKey::decrypt()
* @see self::decrypt()
- * @access private
* @param string $in
* @return string
*/
- function _decryptBlock($in)
+ protected function decryptBlock($in)
{
- return $this->_processBlock($in, self::DECRYPT);
+ return $this->processBlock($in, self::DECRYPT);
}
/**
@@ -670,29 +663,28 @@ class DES extends Base
* {@link http://en.wikipedia.org/wiki/Image:Feistel.png Feistel.png} to get a general
* idea of what this function does.
*
- * @see self::_encryptBlock()
- * @see self::_decryptBlock()
- * @access private
+ * @see self::encryptBlock()
+ * @see self::decryptBlock()
* @param string $block
* @param int $mode
* @return string
*/
- function _processBlock($block, $mode)
+ private function processBlock($block, $mode)
{
static $sbox1, $sbox2, $sbox3, $sbox4, $sbox5, $sbox6, $sbox7, $sbox8, $shuffleip, $shuffleinvip;
if (!$sbox1) {
- $sbox1 = array_map("intval", $this->sbox1);
- $sbox2 = array_map("intval", $this->sbox2);
- $sbox3 = array_map("intval", $this->sbox3);
- $sbox4 = array_map("intval", $this->sbox4);
- $sbox5 = array_map("intval", $this->sbox5);
- $sbox6 = array_map("intval", $this->sbox6);
- $sbox7 = array_map("intval", $this->sbox7);
- $sbox8 = array_map("intval", $this->sbox8);
+ $sbox1 = array_map('intval', self::$sbox1);
+ $sbox2 = array_map('intval', self::$sbox2);
+ $sbox3 = array_map('intval', self::$sbox3);
+ $sbox4 = array_map('intval', self::$sbox4);
+ $sbox5 = array_map('intval', self::$sbox5);
+ $sbox6 = array_map('intval', self::$sbox6);
+ $sbox7 = array_map('intval', self::$sbox7);
+ $sbox8 = array_map('intval', self::$sbox8);
/* Merge $shuffle with $[inv]ipmap */
for ($i = 0; $i < 256; ++$i) {
- $shuffleip[] = $this->shuffle[$this->ipmap[$i]];
- $shuffleinvip[] = $this->shuffle[$this->invipmap[$i]];
+ $shuffleip[] = self::$shuffle[self::$ipmap[$i]];
+ $shuffleinvip[] = self::$shuffle[self::$invipmap[$i]];
}
}
@@ -701,7 +693,7 @@ class DES extends Base
// Do the initial IP permutation.
$t = unpack('Nl/Nr', $block);
- list($l, $r) = array($t['l'], $t['r']);
+ list($l, $r) = [$t['l'], $t['r']];
$block = ($shuffleip[ $r & 0xFF] & "\x80\x80\x80\x80\x80\x80\x80\x80") |
($shuffleip[($r >> 8) & 0xFF] & "\x40\x40\x40\x40\x40\x40\x40\x40") |
($shuffleip[($r >> 16) & 0xFF] & "\x20\x20\x20\x20\x20\x20\x20\x20") |
@@ -713,7 +705,7 @@ class DES extends Base
// Extract L0 and R0.
$t = unpack('Nl/Nr', $block);
- list($l, $r) = array($t['l'], $t['r']);
+ list($l, $r) = [$t['l'], $t['r']];
for ($des_round = 0; $des_round < $this->des_rounds; ++$des_round) {
// Perform the 16 steps.
@@ -755,22 +747,21 @@ class DES extends Base
/**
* Creates the key schedule
*
- * @see \phpseclib\Crypt\Base::_setupKey()
- * @access private
+ * @see Common\SymmetricKey::setupKey()
*/
- function _setupKey()
+ protected function setupKey()
{
if (isset($this->kl['key']) && $this->key === $this->kl['key'] && $this->des_rounds === $this->kl['des_rounds']) {
// already expanded
return;
}
- $this->kl = array('key' => $this->key, 'des_rounds' => $this->des_rounds);
+ $this->kl = ['key' => $this->key, 'des_rounds' => $this->des_rounds];
- static $shifts = array( // number of key bits shifted per round
+ static $shifts = [ // number of key bits shifted per round
1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
- );
+ ];
- static $pc1map = array(
+ static $pc1map = [
0x00, 0x00, 0x08, 0x08, 0x04, 0x04, 0x0C, 0x0C,
0x02, 0x02, 0x0A, 0x0A, 0x06, 0x06, 0x0E, 0x0E,
0x10, 0x10, 0x18, 0x18, 0x14, 0x14, 0x1C, 0x1C,
@@ -803,16 +794,16 @@ class DES extends Base
0xE2, 0xE2, 0xEA, 0xEA, 0xE6, 0xE6, 0xEE, 0xEE,
0xF0, 0xF0, 0xF8, 0xF8, 0xF4, 0xF4, 0xFC, 0xFC,
0xF2, 0xF2, 0xFA, 0xFA, 0xF6, 0xF6, 0xFE, 0xFE
- );
+ ];
// Mapping tables for the PC-2 transformation.
- static $pc2mapc1 = array(
+ static $pc2mapc1 = [
0x00000000, 0x00000400, 0x00200000, 0x00200400,
0x00000001, 0x00000401, 0x00200001, 0x00200401,
0x02000000, 0x02000400, 0x02200000, 0x02200400,
0x02000001, 0x02000401, 0x02200001, 0x02200401
- );
- static $pc2mapc2 = array(
+ ];
+ static $pc2mapc2 = [
0x00000000, 0x00000800, 0x08000000, 0x08000800,
0x00010000, 0x00010800, 0x08010000, 0x08010800,
0x00000000, 0x00000800, 0x08000000, 0x08000800,
@@ -877,8 +868,8 @@ class DES extends Base
0x01050110, 0x01050910, 0x09050110, 0x09050910,
0x01040110, 0x01040910, 0x09040110, 0x09040910,
0x01050110, 0x01050910, 0x09050110, 0x09050910
- );
- static $pc2mapc3 = array(
+ ];
+ static $pc2mapc3 = [
0x00000000, 0x00000004, 0x00001000, 0x00001004,
0x00000000, 0x00000004, 0x00001000, 0x00001004,
0x10000000, 0x10000004, 0x10001000, 0x10001004,
@@ -943,8 +934,8 @@ class DES extends Base
0x20080022, 0x20080026, 0x20081022, 0x20081026,
0x30080022, 0x30080026, 0x30081022, 0x30081026,
0x30080022, 0x30080026, 0x30081022, 0x30081026
- );
- static $pc2mapc4 = array(
+ ];
+ static $pc2mapc4 = [
0x00000000, 0x00100000, 0x00000008, 0x00100008,
0x00000200, 0x00100200, 0x00000208, 0x00100208,
0x00000000, 0x00100000, 0x00000008, 0x00100008,
@@ -1009,14 +1000,14 @@ class DES extends Base
0x04022200, 0x04122200, 0x04022208, 0x04122208,
0x04022000, 0x04122000, 0x04022008, 0x04122008,
0x04022200, 0x04122200, 0x04022208, 0x04122208
- );
- static $pc2mapd1 = array(
+ ];
+ static $pc2mapd1 = [
0x00000000, 0x00000001, 0x08000000, 0x08000001,
0x00200000, 0x00200001, 0x08200000, 0x08200001,
0x00000002, 0x00000003, 0x08000002, 0x08000003,
0x00200002, 0x00200003, 0x08200002, 0x08200003
- );
- static $pc2mapd2 = array(
+ ];
+ static $pc2mapd2 = [
0x00000000, 0x00100000, 0x00000800, 0x00100800,
0x00000000, 0x00100000, 0x00000800, 0x00100800,
0x04000000, 0x04100000, 0x04000800, 0x04100800,
@@ -1081,8 +1072,8 @@ class DES extends Base
0x00020204, 0x00120204, 0x00020A04, 0x00120A04,
0x04020204, 0x04120204, 0x04020A04, 0x04120A04,
0x04020204, 0x04120204, 0x04020A04, 0x04120A04
- );
- static $pc2mapd3 = array(
+ ];
+ static $pc2mapd3 = [
0x00000000, 0x00010000, 0x02000000, 0x02010000,
0x00000020, 0x00010020, 0x02000020, 0x02010020,
0x00040000, 0x00050000, 0x02040000, 0x02050000,
@@ -1147,8 +1138,8 @@ class DES extends Base
0x20002030, 0x20012030, 0x22002030, 0x22012030,
0x20042010, 0x20052010, 0x22042010, 0x22052010,
0x20042030, 0x20052030, 0x22042030, 0x22052030
- );
- static $pc2mapd4 = array(
+ ];
+ static $pc2mapd4 = [
0x00000000, 0x00000400, 0x01000000, 0x01000400,
0x00000000, 0x00000400, 0x01000000, 0x01000400,
0x00000100, 0x00000500, 0x01000100, 0x01000500,
@@ -1213,33 +1204,33 @@ class DES extends Base
0x10081008, 0x10081408, 0x11081008, 0x11081408,
0x10081108, 0x10081508, 0x11081108, 0x11081508,
0x10081108, 0x10081508, 0x11081108, 0x11081508
- );
+ ];
- $keys = array();
+ $keys = [];
for ($des_round = 0; $des_round < $this->des_rounds; ++$des_round) {
// pad the key and remove extra characters as appropriate.
$key = str_pad(substr($this->key, $des_round * 8, 8), 8, "\0");
// Perform the PC/1 transformation and compute C and D.
$t = unpack('Nl/Nr', $key);
- list($l, $r) = array($t['l'], $t['r']);
- $key = ($this->shuffle[$pc1map[ $r & 0xFF]] & "\x80\x80\x80\x80\x80\x80\x80\x00") |
- ($this->shuffle[$pc1map[($r >> 8) & 0xFF]] & "\x40\x40\x40\x40\x40\x40\x40\x00") |
- ($this->shuffle[$pc1map[($r >> 16) & 0xFF]] & "\x20\x20\x20\x20\x20\x20\x20\x00") |
- ($this->shuffle[$pc1map[($r >> 24) & 0xFF]] & "\x10\x10\x10\x10\x10\x10\x10\x00") |
- ($this->shuffle[$pc1map[ $l & 0xFF]] & "\x08\x08\x08\x08\x08\x08\x08\x00") |
- ($this->shuffle[$pc1map[($l >> 8) & 0xFF]] & "\x04\x04\x04\x04\x04\x04\x04\x00") |
- ($this->shuffle[$pc1map[($l >> 16) & 0xFF]] & "\x02\x02\x02\x02\x02\x02\x02\x00") |
- ($this->shuffle[$pc1map[($l >> 24) & 0xFF]] & "\x01\x01\x01\x01\x01\x01\x01\x00");
+ list($l, $r) = [$t['l'], $t['r']];
+ $key = (self::$shuffle[$pc1map[ $r & 0xFF]] & "\x80\x80\x80\x80\x80\x80\x80\x00") |
+ (self::$shuffle[$pc1map[($r >> 8) & 0xFF]] & "\x40\x40\x40\x40\x40\x40\x40\x00") |
+ (self::$shuffle[$pc1map[($r >> 16) & 0xFF]] & "\x20\x20\x20\x20\x20\x20\x20\x00") |
+ (self::$shuffle[$pc1map[($r >> 24) & 0xFF]] & "\x10\x10\x10\x10\x10\x10\x10\x00") |
+ (self::$shuffle[$pc1map[ $l & 0xFF]] & "\x08\x08\x08\x08\x08\x08\x08\x00") |
+ (self::$shuffle[$pc1map[($l >> 8) & 0xFF]] & "\x04\x04\x04\x04\x04\x04\x04\x00") |
+ (self::$shuffle[$pc1map[($l >> 16) & 0xFF]] & "\x02\x02\x02\x02\x02\x02\x02\x00") |
+ (self::$shuffle[$pc1map[($l >> 24) & 0xFF]] & "\x01\x01\x01\x01\x01\x01\x01\x00");
$key = unpack('Nc/Nd', $key);
$c = ( $key['c'] >> 4) & 0x0FFFFFFF;
$d = (($key['d'] >> 4) & 0x0FFFFFF0) | ($key['c'] & 0x0F);
- $keys[$des_round] = array(
- self::ENCRYPT => array(),
+ $keys[$des_round] = [
+ self::ENCRYPT => [],
self::DECRYPT => array_fill(0, 32, 0)
- );
- for ($i = 0, $ki = 31; $i < 16; ++$i, $ki-= 2) {
+ ];
+ for ($i = 0, $ki = 31; $i < 16; ++$i, $ki -= 2) {
$c <<= $shifts[$i];
$c = ($c | ($c >> 28)) & 0x0FFFFFFF;
$d <<= $shifts[$i];
@@ -1265,7 +1256,7 @@ class DES extends Base
switch ($this->des_rounds) {
case 3: // 3DES keys
- $this->keys = array(
+ $this->keys = [
self::ENCRYPT => array_merge(
$keys[0][self::ENCRYPT],
$keys[1][self::DECRYPT],
@@ -1276,174 +1267,126 @@ class DES extends Base
$keys[1][self::ENCRYPT],
$keys[0][self::DECRYPT]
)
- );
+ ];
break;
// case 1: // DES keys
default:
- $this->keys = array(
+ $this->keys = [
self::ENCRYPT => $keys[0][self::ENCRYPT],
self::DECRYPT => $keys[0][self::DECRYPT]
- );
+ ];
}
}
/**
* Setup the performance-optimized function for de/encrypt()
*
- * @see \phpseclib\Crypt\Base::_setupInlineCrypt()
- * @access private
+ * @see Common\SymmetricKey::setupInlineCrypt()
*/
- function _setupInlineCrypt()
+ protected function setupInlineCrypt()
{
- $lambda_functions =& self::_getLambdaFunctions();
-
// Engine configuration for:
// - DES ($des_rounds == 1) or
// - 3DES ($des_rounds == 3)
$des_rounds = $this->des_rounds;
- // We create max. 10 hi-optimized code for memory reason. Means: For each $key one ultra fast inline-crypt function.
- // (Currently, for DES, one generated $lambda_function cost on php5.5@32bit ~135kb unfreeable mem and ~230kb on php5.5@64bit)
- // (Currently, for TripleDES, one generated $lambda_function cost on php5.5@32bit ~240kb unfreeable mem and ~340kb on php5.5@64bit)
- // After that, we'll still create very fast optimized code but not the hi-ultimative code, for each $mode one
- $gen_hi_opt_code = (bool)( count($lambda_functions) < 10 );
-
- // Generation of a unique hash for our generated code
- $code_hash = "Crypt_DES, $des_rounds, {$this->mode}";
- if ($gen_hi_opt_code) {
- // For hi-optimized code, we create for each combination of
- // $mode, $des_rounds and $this->key its own encrypt/decrypt function.
- // After max 10 hi-optimized functions, we create generic
- // (still very fast.. but not ultra) functions for each $mode/$des_rounds
- // Currently 2 * 5 generic functions will be then max. possible.
- $code_hash = str_pad($code_hash, 32) . $this->_hashInlineCryptFunction($this->key);
- }
-
- // Is there a re-usable $lambda_functions in there? If not, we have to create it.
- if (!isset($lambda_functions[$code_hash])) {
- // Init code for both, encrypt and decrypt.
- $init_crypt = 'static $sbox1, $sbox2, $sbox3, $sbox4, $sbox5, $sbox6, $sbox7, $sbox8, $shuffleip, $shuffleinvip;
- if (!$sbox1) {
- $sbox1 = array_map("intval", $self->sbox1);
- $sbox2 = array_map("intval", $self->sbox2);
- $sbox3 = array_map("intval", $self->sbox3);
- $sbox4 = array_map("intval", $self->sbox4);
- $sbox5 = array_map("intval", $self->sbox5);
- $sbox6 = array_map("intval", $self->sbox6);
- $sbox7 = array_map("intval", $self->sbox7);
- $sbox8 = array_map("intval", $self->sbox8);'
- /* Merge $shuffle with $[inv]ipmap */ . '
- for ($i = 0; $i < 256; ++$i) {
- $shuffleip[] = $self->shuffle[$self->ipmap[$i]];
- $shuffleinvip[] = $self->shuffle[$self->invipmap[$i]];
- }
+ $init_crypt = 'static $sbox1, $sbox2, $sbox3, $sbox4, $sbox5, $sbox6, $sbox7, $sbox8, $shuffleip, $shuffleinvip;
+ if (!$sbox1) {
+ $sbox1 = array_map("intval", self::$sbox1);
+ $sbox2 = array_map("intval", self::$sbox2);
+ $sbox3 = array_map("intval", self::$sbox3);
+ $sbox4 = array_map("intval", self::$sbox4);
+ $sbox5 = array_map("intval", self::$sbox5);
+ $sbox6 = array_map("intval", self::$sbox6);
+ $sbox7 = array_map("intval", self::$sbox7);
+ $sbox8 = array_map("intval", self::$sbox8);'
+ /* Merge $shuffle with $[inv]ipmap */ . '
+ for ($i = 0; $i < 256; ++$i) {
+ $shuffleip[] = self::$shuffle[self::$ipmap[$i]];
+ $shuffleinvip[] = self::$shuffle[self::$invipmap[$i]];
}
- ';
-
- switch (true) {
- case $gen_hi_opt_code:
- // In Hi-optimized code mode, we use our [3]DES key schedule as hardcoded integers.
- // No futher initialisation of the $keys schedule is necessary.
- // That is the extra performance boost.
- $k = array(
- self::ENCRYPT => $this->keys[self::ENCRYPT],
- self::DECRYPT => $this->keys[self::DECRYPT]
- );
- $init_encrypt = '';
- $init_decrypt = '';
- break;
- default:
- // In generic optimized code mode, we have to use, as the best compromise [currently],
- // our key schedule as $ke/$kd arrays. (with hardcoded indexes...)
- $k = array(
- self::ENCRYPT => array(),
- self::DECRYPT => array()
- );
- for ($i = 0, $c = count($this->keys[self::ENCRYPT]); $i < $c; ++$i) {
- $k[self::ENCRYPT][$i] = '$ke[' . $i . ']';
- $k[self::DECRYPT][$i] = '$kd[' . $i . ']';
- }
- $init_encrypt = '$ke = $self->keys[$self::ENCRYPT];';
- $init_decrypt = '$kd = $self->keys[$self::DECRYPT];';
- break;
}
+ ';
+
+ $k = [
+ self::ENCRYPT => $this->keys[self::ENCRYPT],
+ self::DECRYPT => $this->keys[self::DECRYPT]
+ ];
+ $init_encrypt = '';
+ $init_decrypt = '';
- // Creating code for en- and decryption.
- $crypt_block = array();
- foreach (array(self::ENCRYPT, self::DECRYPT) as $c) {
- /* Do the initial IP permutation. */
- $crypt_block[$c] = '
- $in = unpack("N*", $in);
- $l = $in[1];
- $r = $in[2];
- $in = unpack("N*",
- ($shuffleip[ $r & 0xFF] & "\x80\x80\x80\x80\x80\x80\x80\x80") |
- ($shuffleip[($r >> 8) & 0xFF] & "\x40\x40\x40\x40\x40\x40\x40\x40") |
- ($shuffleip[($r >> 16) & 0xFF] & "\x20\x20\x20\x20\x20\x20\x20\x20") |
- ($shuffleip[($r >> 24) & 0xFF] & "\x10\x10\x10\x10\x10\x10\x10\x10") |
- ($shuffleip[ $l & 0xFF] & "\x08\x08\x08\x08\x08\x08\x08\x08") |
- ($shuffleip[($l >> 8) & 0xFF] & "\x04\x04\x04\x04\x04\x04\x04\x04") |
- ($shuffleip[($l >> 16) & 0xFF] & "\x02\x02\x02\x02\x02\x02\x02\x02") |
- ($shuffleip[($l >> 24) & 0xFF] & "\x01\x01\x01\x01\x01\x01\x01\x01")
- );
- ' . /* Extract L0 and R0 */ '
- $l = $in[1];
- $r = $in[2];
- ';
+ // Creating code for en- and decryption.
+ $crypt_block = [];
+ foreach ([self::ENCRYPT, self::DECRYPT] as $c) {
+ /* Do the initial IP permutation. */
+ $crypt_block[$c] = '
+ $in = unpack("N*", $in);
+ $l = $in[1];
+ $r = $in[2];
+ $in = unpack("N*",
+ ($shuffleip[ $r & 0xFF] & "\x80\x80\x80\x80\x80\x80\x80\x80") |
+ ($shuffleip[($r >> 8) & 0xFF] & "\x40\x40\x40\x40\x40\x40\x40\x40") |
+ ($shuffleip[($r >> 16) & 0xFF] & "\x20\x20\x20\x20\x20\x20\x20\x20") |
+ ($shuffleip[($r >> 24) & 0xFF] & "\x10\x10\x10\x10\x10\x10\x10\x10") |
+ ($shuffleip[ $l & 0xFF] & "\x08\x08\x08\x08\x08\x08\x08\x08") |
+ ($shuffleip[($l >> 8) & 0xFF] & "\x04\x04\x04\x04\x04\x04\x04\x04") |
+ ($shuffleip[($l >> 16) & 0xFF] & "\x02\x02\x02\x02\x02\x02\x02\x02") |
+ ($shuffleip[($l >> 24) & 0xFF] & "\x01\x01\x01\x01\x01\x01\x01\x01")
+ );
+ ' . /* Extract L0 and R0 */ '
+ $l = $in[1];
+ $r = $in[2];
+ ';
- $l = '$l';
- $r = '$r';
+ $l = '$l';
+ $r = '$r';
- // Perform DES or 3DES.
- for ($ki = -1, $des_round = 0; $des_round < $des_rounds; ++$des_round) {
- // Perform the 16 steps.
- for ($i = 0; $i < 16; ++$i) {
- // start of "the Feistel (F) function" - see the following URL:
- // http://en.wikipedia.org/wiki/Image:Data_Encryption_Standard_InfoBox_Diagram.png
- // Merge key schedule.
- $crypt_block[$c].= '
- $b1 = ((' . $r . ' >> 3) & 0x1FFFFFFF) ^ (' . $r . ' << 29) ^ ' . $k[$c][++$ki] . ';
- $b2 = ((' . $r . ' >> 31) & 0x00000001) ^ (' . $r . ' << 1) ^ ' . $k[$c][++$ki] . ';' .
- /* S-box indexing. */
- $l . ' = $sbox1[($b1 >> 24) & 0x3F] ^ $sbox2[($b2 >> 24) & 0x3F] ^
- $sbox3[($b1 >> 16) & 0x3F] ^ $sbox4[($b2 >> 16) & 0x3F] ^
- $sbox5[($b1 >> 8) & 0x3F] ^ $sbox6[($b2 >> 8) & 0x3F] ^
- $sbox7[ $b1 & 0x3F] ^ $sbox8[ $b2 & 0x3F] ^ ' . $l . ';
- ';
- // end of "the Feistel (F) function"
+ // Perform DES or 3DES.
+ for ($ki = -1, $des_round = 0; $des_round < $des_rounds; ++$des_round) {
+ // Perform the 16 steps.
+ for ($i = 0; $i < 16; ++$i) {
+ // start of "the Feistel (F) function" - see the following URL:
+ // http://en.wikipedia.org/wiki/Image:Data_Encryption_Standard_InfoBox_Diagram.png
+ // Merge key schedule.
+ $crypt_block[$c] .= '
+ $b1 = ((' . $r . ' >> 3) & 0x1FFFFFFF) ^ (' . $r . ' << 29) ^ ' . $k[$c][++$ki] . ';
+ $b2 = ((' . $r . ' >> 31) & 0x00000001) ^ (' . $r . ' << 1) ^ ' . $k[$c][++$ki] . ';' .
+ /* S-box indexing. */
+ $l . ' = $sbox1[($b1 >> 24) & 0x3F] ^ $sbox2[($b2 >> 24) & 0x3F] ^
+ $sbox3[($b1 >> 16) & 0x3F] ^ $sbox4[($b2 >> 16) & 0x3F] ^
+ $sbox5[($b1 >> 8) & 0x3F] ^ $sbox6[($b2 >> 8) & 0x3F] ^
+ $sbox7[ $b1 & 0x3F] ^ $sbox8[ $b2 & 0x3F] ^ ' . $l . ';
+ ';
+ // end of "the Feistel (F) function"
- // swap L & R
- list($l, $r) = array($r, $l);
- }
- list($l, $r) = array($r, $l);
+ // swap L & R
+ list($l, $r) = [$r, $l];
}
-
- // Perform the inverse IP permutation.
- $crypt_block[$c].= '$in =
- ($shuffleinvip[($l >> 24) & 0xFF] & "\x80\x80\x80\x80\x80\x80\x80\x80") |
- ($shuffleinvip[($r >> 24) & 0xFF] & "\x40\x40\x40\x40\x40\x40\x40\x40") |
- ($shuffleinvip[($l >> 16) & 0xFF] & "\x20\x20\x20\x20\x20\x20\x20\x20") |
- ($shuffleinvip[($r >> 16) & 0xFF] & "\x10\x10\x10\x10\x10\x10\x10\x10") |
- ($shuffleinvip[($l >> 8) & 0xFF] & "\x08\x08\x08\x08\x08\x08\x08\x08") |
- ($shuffleinvip[($r >> 8) & 0xFF] & "\x04\x04\x04\x04\x04\x04\x04\x04") |
- ($shuffleinvip[ $l & 0xFF] & "\x02\x02\x02\x02\x02\x02\x02\x02") |
- ($shuffleinvip[ $r & 0xFF] & "\x01\x01\x01\x01\x01\x01\x01\x01");
- ';
+ list($l, $r) = [$r, $l];
}
- // Creates the inline-crypt function
- $lambda_functions[$code_hash] = $this->_createInlineCryptFunction(
- array(
- 'init_crypt' => $init_crypt,
- 'init_encrypt' => $init_encrypt,
- 'init_decrypt' => $init_decrypt,
- 'encrypt_block' => $crypt_block[self::ENCRYPT],
- 'decrypt_block' => $crypt_block[self::DECRYPT]
- )
- );
+ // Perform the inverse IP permutation.
+ $crypt_block[$c] .= '$in =
+ ($shuffleinvip[($l >> 24) & 0xFF] & "\x80\x80\x80\x80\x80\x80\x80\x80") |
+ ($shuffleinvip[($r >> 24) & 0xFF] & "\x40\x40\x40\x40\x40\x40\x40\x40") |
+ ($shuffleinvip[($l >> 16) & 0xFF] & "\x20\x20\x20\x20\x20\x20\x20\x20") |
+ ($shuffleinvip[($r >> 16) & 0xFF] & "\x10\x10\x10\x10\x10\x10\x10\x10") |
+ ($shuffleinvip[($l >> 8) & 0xFF] & "\x08\x08\x08\x08\x08\x08\x08\x08") |
+ ($shuffleinvip[($r >> 8) & 0xFF] & "\x04\x04\x04\x04\x04\x04\x04\x04") |
+ ($shuffleinvip[ $l & 0xFF] & "\x02\x02\x02\x02\x02\x02\x02\x02") |
+ ($shuffleinvip[ $r & 0xFF] & "\x01\x01\x01\x01\x01\x01\x01\x01");
+ ';
}
- // Set the inline-crypt function as callback in: $this->inline_crypt
- $this->inline_crypt = $lambda_functions[$code_hash];
+ // Creates the inline-crypt function
+ $this->inline_crypt = $this->createInlineCryptFunction(
+ [
+ 'init_crypt' => $init_crypt,
+ 'init_encrypt' => $init_encrypt,
+ 'init_decrypt' => $init_decrypt,
+ 'encrypt_block' => $crypt_block[self::ENCRYPT],
+ 'decrypt_block' => $crypt_block[self::DECRYPT]
+ ]
+ );
}
}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH.php
new file mode 100644
index 000000000..b2301986f
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH.php
@@ -0,0 +1,405 @@
+<?php
+
+/**
+ * Pure-PHP (EC)DH implementation
+ *
+ * PHP version 5
+ *
+ * Here's an example of how to compute a shared secret with this library:
+ * <code>
+ * <?php
+ * include 'vendor/autoload.php';
+ *
+ * $ourPrivate = \phpseclib3\Crypt\DH::createKey();
+ * $secret = DH::computeSecret($ourPrivate, $theirPublic);
+ *
+ * ?>
+ * </code>
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt;
+
+use phpseclib3\Crypt\Common\AsymmetricKey;
+use phpseclib3\Crypt\DH\Parameters;
+use phpseclib3\Crypt\DH\PrivateKey;
+use phpseclib3\Crypt\DH\PublicKey;
+use phpseclib3\Exception\NoKeyLoadedException;
+use phpseclib3\Exception\UnsupportedOperationException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Pure-PHP (EC)DH implementation
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class DH extends AsymmetricKey
+{
+ /**
+ * Algorithm Name
+ *
+ * @var string
+ */
+ const ALGORITHM = 'DH';
+
+ /**
+ * DH prime
+ *
+ * @var BigInteger
+ */
+ protected $prime;
+
+ /**
+ * DH Base
+ *
+ * Prime divisor of p-1
+ *
+ * @var BigInteger
+ */
+ protected $base;
+
+ /**
+ * Public Key
+ *
+ * @var BigInteger
+ */
+ protected $publicKey;
+
+ /**
+ * Create DH parameters
+ *
+ * This method is a bit polymorphic. It can take any of the following:
+ * - two BigInteger's (prime and base)
+ * - an integer representing the size of the prime in bits (the base is assumed to be 2)
+ * - a string (eg. diffie-hellman-group14-sha1)
+ *
+ * @return Parameters
+ */
+ public static function createParameters(...$args)
+ {
+ $class = new \ReflectionClass(static::class);
+ if ($class->isFinal()) {
+ throw new \RuntimeException('createParameters() should not be called from final classes (' . static::class . ')');
+ }
+
+ $params = new Parameters();
+ if (count($args) == 2 && $args[0] instanceof BigInteger && $args[1] instanceof BigInteger) {
+ //if (!$args[0]->isPrime()) {
+ // throw new \InvalidArgumentException('The first parameter should be a prime number');
+ //}
+ $params->prime = $args[0];
+ $params->base = $args[1];
+ return $params;
+ } elseif (count($args) == 1 && is_numeric($args[0])) {
+ $params->prime = BigInteger::randomPrime($args[0]);
+ $params->base = new BigInteger(2);
+ return $params;
+ } elseif (count($args) != 1 || !is_string($args[0])) {
+ throw new \InvalidArgumentException('Valid parameters are either: two BigInteger\'s (prime and base), a single integer (the length of the prime; base is assumed to be 2) or a string');
+ }
+ switch ($args[0]) {
+ // see http://tools.ietf.org/html/rfc2409#section-6.2 and
+ // http://tools.ietf.org/html/rfc2412, appendex E
+ case 'diffie-hellman-group1-sha1':
+ $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
+ '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
+ '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
+ 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF';
+ break;
+ // see http://tools.ietf.org/html/rfc3526#section-3
+ case 'diffie-hellman-group14-sha1': // 2048-bit MODP Group
+ case 'diffie-hellman-group14-sha256':
+ $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
+ '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
+ '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
+ 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
+ '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
+ '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
+ 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
+ '3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF';
+ break;
+ // see https://tools.ietf.org/html/rfc3526#section-4
+ case 'diffie-hellman-group15-sha512': // 3072-bit MODP Group
+ $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
+ '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
+ '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
+ 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
+ '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
+ '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
+ 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
+ '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' .
+ 'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' .
+ 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' .
+ 'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' .
+ '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF';
+ break;
+ // see https://tools.ietf.org/html/rfc3526#section-5
+ case 'diffie-hellman-group16-sha512': // 4096-bit MODP Group
+ $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
+ '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
+ '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
+ 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
+ '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
+ '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
+ 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
+ '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' .
+ 'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' .
+ 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' .
+ 'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' .
+ '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7' .
+ '88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8' .
+ 'DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2' .
+ '233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9' .
+ '93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C934063199FFFFFFFFFFFFFFFF';
+ break;
+ // see https://tools.ietf.org/html/rfc3526#section-6
+ case 'diffie-hellman-group17-sha512': // 6144-bit MODP Group
+ $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
+ '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
+ '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
+ 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
+ '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
+ '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
+ 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
+ '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' .
+ 'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' .
+ 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' .
+ 'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' .
+ '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7' .
+ '88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8' .
+ 'DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2' .
+ '233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9' .
+ '93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026' .
+ 'C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AE' .
+ 'B06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B' .
+ 'DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92EC' .
+ 'F032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E' .
+ '59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA' .
+ 'CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76' .
+ 'F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468' .
+ '043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DCC4024FFFFFFFFFFFFFFFF';
+ break;
+ // see https://tools.ietf.org/html/rfc3526#section-7
+ case 'diffie-hellman-group18-sha512': // 8192-bit MODP Group
+ $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
+ '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
+ '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
+ 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
+ '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
+ '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
+ 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
+ '3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33' .
+ 'A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7' .
+ 'ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864' .
+ 'D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2' .
+ '08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A92108011A723C12A787E6D7' .
+ '88719A10BDBA5B2699C327186AF4E23C1A946834B6150BDA2583E9CA2AD44CE8' .
+ 'DBBBC2DB04DE8EF92E8EFC141FBECAA6287C59474E6BC05D99B2964FA090C3A2' .
+ '233BA186515BE7ED1F612970CEE2D7AFB81BDD762170481CD0069127D5B05AA9' .
+ '93B4EA988D8FDDC186FFB7DC90A6C08F4DF435C93402849236C3FAB4D27C7026' .
+ 'C1D4DCB2602646DEC9751E763DBA37BDF8FF9406AD9E530EE5DB382F413001AE' .
+ 'B06A53ED9027D831179727B0865A8918DA3EDBEBCF9B14ED44CE6CBACED4BB1B' .
+ 'DB7F1447E6CC254B332051512BD7AF426FB8F401378CD2BF5983CA01C64B92EC' .
+ 'F032EA15D1721D03F482D7CE6E74FEF6D55E702F46980C82B5A84031900B1C9E' .
+ '59E7C97FBEC7E8F323A97A7E36CC88BE0F1D45B7FF585AC54BD407B22B4154AA' .
+ 'CC8F6D7EBF48E1D814CC5ED20F8037E0A79715EEF29BE32806A1D58BB7C5DA76' .
+ 'F550AA3D8A1FBFF0EB19CCB1A313D55CDA56C9EC2EF29632387FE8D76E3C0468' .
+ '043E8F663F4860EE12BF2D5B0B7474D6E694F91E6DBE115974A3926F12FEE5E4' .
+ '38777CB6A932DF8CD8BEC4D073B931BA3BC832B68D9DD300741FA7BF8AFC47ED' .
+ '2576F6936BA424663AAB639C5AE4F5683423B4742BF1C978238F16CBE39D652D' .
+ 'E3FDB8BEFC848AD922222E04A4037C0713EB57A81A23F0C73473FC646CEA306B' .
+ '4BCBC8862F8385DDFA9D4B7FA2C087E879683303ED5BDD3A062B3CF5B3A278A6' .
+ '6D2A13F83F44F82DDF310EE074AB6A364597E899A0255DC164F31CC50846851D' .
+ 'F9AB48195DED7EA1B1D510BD7EE74D73FAF36BC31ECFA268359046F4EB879F92' .
+ '4009438B481C6CD7889A002ED5EE382BC9190DA6FC026E479558E4475677E9AA' .
+ '9E3050E2765694DFC81F56E880B96E7160C980DD98EDD3DFFFFFFFFFFFFFFFFF';
+ break;
+ default:
+ throw new \InvalidArgumentException('Invalid named prime provided');
+ }
+
+ $params->prime = new BigInteger($prime, 16);
+ $params->base = new BigInteger(2);
+
+ return $params;
+ }
+
+ /**
+ * Create public / private key pair.
+ *
+ * The rationale for the second parameter is described in http://tools.ietf.org/html/rfc4419#section-6.2 :
+ *
+ * "To increase the speed of the key exchange, both client and server may
+ * reduce the size of their private exponents. It should be at least
+ * twice as long as the key material that is generated from the shared
+ * secret. For more details, see the paper by van Oorschot and Wiener
+ * [VAN-OORSCHOT]."
+ *
+ * $length is in bits
+ *
+ * @param Parameters $params
+ * @param int $length optional
+ * @return PrivateKey
+ */
+ public static function createKey(Parameters $params, $length = 0)
+ {
+ $class = new \ReflectionClass(static::class);
+ if ($class->isFinal()) {
+ throw new \RuntimeException('createKey() should not be called from final classes (' . static::class . ')');
+ }
+
+ $one = new BigInteger(1);
+ if ($length) {
+ $max = $one->bitwise_leftShift($length);
+ $max = $max->subtract($one);
+ } else {
+ $max = $params->prime->subtract($one);
+ }
+
+ $key = new PrivateKey();
+ $key->prime = $params->prime;
+ $key->base = $params->base;
+ $key->privateKey = BigInteger::randomRange($one, $max);
+ $key->publicKey = $key->base->powMod($key->privateKey, $key->prime);
+ return $key;
+ }
+
+ /**
+ * Compute Shared Secret
+ *
+ * @param PrivateKey|EC $private
+ * @param PublicKey|BigInteger|string $public
+ * @return mixed
+ */
+ public static function computeSecret($private, $public)
+ {
+ if ($private instanceof PrivateKey) { // DH\PrivateKey
+ switch (true) {
+ case $public instanceof PublicKey:
+ if (!$private->prime->equals($public->prime) || !$private->base->equals($public->base)) {
+ throw new \InvalidArgumentException('The public and private key do not share the same prime and / or base numbers');
+ }
+ return $public->publicKey->powMod($private->privateKey, $private->prime)->toBytes(true);
+ case is_string($public):
+ $public = new BigInteger($public, -256);
+ // fall-through
+ case $public instanceof BigInteger:
+ return $public->powMod($private->privateKey, $private->prime)->toBytes(true);
+ default:
+ throw new \InvalidArgumentException('$public needs to be an instance of DH\PublicKey, a BigInteger or a string');
+ }
+ }
+
+ if ($private instanceof EC\PrivateKey) {
+ switch (true) {
+ case $public instanceof EC\PublicKey:
+ $public = $public->getEncodedCoordinates();
+ // fall-through
+ case is_string($public):
+ $point = $private->multiply($public);
+ switch ($private->getCurve()) {
+ case 'Curve25519':
+ case 'Curve448':
+ $secret = $point;
+ break;
+ default:
+ // according to https://www.secg.org/sec1-v2.pdf#page=33 only X is returned
+ $secret = substr($point, 1, (strlen($point) - 1) >> 1);
+ }
+ /*
+ if (($secret[0] & "\x80") === "\x80") {
+ $secret = "\0$secret";
+ }
+ */
+ return $secret;
+ default:
+ throw new \InvalidArgumentException('$public needs to be an instance of EC\PublicKey or a string (an encoded coordinate)');
+ }
+ }
+ }
+
+ /**
+ * Load the key
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return AsymmetricKey
+ */
+ public static function load($key, $password = false)
+ {
+ try {
+ return EC::load($key, $password);
+ } catch (NoKeyLoadedException $e) {
+ }
+
+ return parent::load($key, $password);
+ }
+
+ /**
+ * OnLoad Handler
+ *
+ * @return bool
+ */
+ protected static function onLoad(array $components)
+ {
+ if (!isset($components['privateKey']) && !isset($components['publicKey'])) {
+ $new = new Parameters();
+ } else {
+ $new = isset($components['privateKey']) ?
+ new PrivateKey() :
+ new PublicKey();
+ }
+
+ $new->prime = $components['prime'];
+ $new->base = $components['base'];
+
+ if (isset($components['privateKey'])) {
+ $new->privateKey = $components['privateKey'];
+ }
+ if (isset($components['publicKey'])) {
+ $new->publicKey = $components['publicKey'];
+ }
+
+ return $new;
+ }
+
+ /**
+ * Determines which hashing function should be used
+ *
+ * @param string $hash
+ */
+ public function withHash($hash)
+ {
+ throw new UnsupportedOperationException('DH does not use a hash algorithm');
+ }
+
+ /**
+ * Returns the hash algorithm currently being used
+ *
+ */
+ public function getHash()
+ {
+ throw new UnsupportedOperationException('DH does not use a hash algorithm');
+ }
+
+ /**
+ * Returns the parameters
+ *
+ * A public / private key is only returned if the currently loaded "key" contains an x or y
+ * value.
+ *
+ * @see self::getPublicKey()
+ * @return mixed
+ */
+ public function getParameters()
+ {
+ $type = DH::validatePlugin('Keys', 'PKCS1', 'saveParameters');
+
+ $key = $type::saveParameters($this->prime, $this->base);
+ return DH::load($key, 'PKCS1');
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/Formats/Keys/PKCS1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/Formats/Keys/PKCS1.php
new file mode 100644
index 000000000..65a0a5dbc
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/Formats/Keys/PKCS1.php
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * "PKCS1" Formatted EC Key Handler
+ *
+ * PHP version 5
+ *
+ * Processes keys with the following headers:
+ *
+ * -----BEGIN DH PARAMETERS-----
+ *
+ * Technically, PKCS1 is for RSA keys, only, but we're using PKCS1 to describe
+ * DSA, whose format isn't really formally described anywhere, so might as well
+ * use it to describe this, too.
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DH\Formats\Keys;
+
+use phpseclib3\Crypt\Common\Formats\Keys\PKCS1 as Progenitor;
+use phpseclib3\File\ASN1;
+use phpseclib3\File\ASN1\Maps;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * "PKCS1" Formatted DH Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PKCS1 extends Progenitor
+{
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ $key = parent::load($key, $password);
+
+ $decoded = ASN1::decodeBER($key);
+ if (!$decoded) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+
+ $components = ASN1::asn1map($decoded[0], Maps\DHParameter::MAP);
+ if (!is_array($components)) {
+ throw new \RuntimeException('Unable to perform ASN1 mapping on parameters');
+ }
+
+ return $components;
+ }
+
+ /**
+ * Convert EC parameters to the appropriate format
+ *
+ * @return string
+ */
+ public static function saveParameters(BigInteger $prime, BigInteger $base, array $options = [])
+ {
+ $params = [
+ 'prime' => $prime,
+ 'base' => $base
+ ];
+ $params = ASN1::encodeDER($params, Maps\DHParameter::MAP);
+
+ return "-----BEGIN DH PARAMETERS-----\r\n" .
+ chunk_split(base64_encode($params), 64) .
+ "-----END DH PARAMETERS-----\r\n";
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/Formats/Keys/PKCS8.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/Formats/Keys/PKCS8.php
new file mode 100644
index 000000000..3b83a4290
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/Formats/Keys/PKCS8.php
@@ -0,0 +1,132 @@
+<?php
+
+/**
+ * PKCS#8 Formatted DH Key Handler
+ *
+ * PHP version 5
+ *
+ * Processes keys with the following headers:
+ *
+ * -----BEGIN ENCRYPTED PRIVATE KEY-----
+ * -----BEGIN PRIVATE KEY-----
+ * -----BEGIN PUBLIC KEY-----
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DH\Formats\Keys;
+
+use phpseclib3\Crypt\Common\Formats\Keys\PKCS8 as Progenitor;
+use phpseclib3\File\ASN1;
+use phpseclib3\File\ASN1\Maps;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * PKCS#8 Formatted DH Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PKCS8 extends Progenitor
+{
+ /**
+ * OID Name
+ *
+ * @var string
+ */
+ const OID_NAME = 'dhKeyAgreement';
+
+ /**
+ * OID Value
+ *
+ * @var string
+ */
+ const OID_VALUE = '1.2.840.113549.1.3.1';
+
+ /**
+ * Child OIDs loaded
+ *
+ * @var bool
+ */
+ protected static $childOIDsLoaded = false;
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ $key = parent::load($key, $password);
+
+ $type = isset($key['privateKey']) ? 'privateKey' : 'publicKey';
+
+ $decoded = ASN1::decodeBER($key[$type . 'Algorithm']['parameters']->element);
+ if (empty($decoded)) {
+ throw new \RuntimeException('Unable to decode BER of parameters');
+ }
+ $components = ASN1::asn1map($decoded[0], Maps\DHParameter::MAP);
+ if (!is_array($components)) {
+ throw new \RuntimeException('Unable to perform ASN1 mapping on parameters');
+ }
+
+ $decoded = ASN1::decodeBER($key[$type]);
+ switch (true) {
+ case !isset($decoded):
+ case !isset($decoded[0]['content']):
+ case !$decoded[0]['content'] instanceof BigInteger:
+ throw new \RuntimeException('Unable to decode BER of parameters');
+ }
+ $components[$type] = $decoded[0]['content'];
+
+ return $components;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $prime
+ * @param BigInteger $base
+ * @param BigInteger $privateKey
+ * @param BigInteger $publicKey
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $prime, BigInteger $base, BigInteger $privateKey, BigInteger $publicKey, $password = '', array $options = [])
+ {
+ $params = [
+ 'prime' => $prime,
+ 'base' => $base
+ ];
+ $params = ASN1::encodeDER($params, Maps\DHParameter::MAP);
+ $params = new ASN1\Element($params);
+ $key = ASN1::encodeDER($privateKey, ['type' => ASN1::TYPE_INTEGER]);
+ return self::wrapPrivateKey($key, [], $params, $password, null, '', $options);
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $prime
+ * @param BigInteger $base
+ * @param BigInteger $publicKey
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $prime, BigInteger $base, BigInteger $publicKey, array $options = [])
+ {
+ $params = [
+ 'prime' => $prime,
+ 'base' => $base
+ ];
+ $params = ASN1::encodeDER($params, Maps\DHParameter::MAP);
+ $params = new ASN1\Element($params);
+ $key = ASN1::encodeDER($publicKey, ['type' => ASN1::TYPE_INTEGER]);
+ return self::wrapPublicKey($key, $params, null, $options);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/Parameters.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/Parameters.php
new file mode 100644
index 000000000..c0ded84cd
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/Parameters.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * DH Parameters
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DH;
+
+use phpseclib3\Crypt\DH;
+
+/**
+ * DH Parameters
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+final class Parameters extends DH
+{
+ /**
+ * Returns the parameters
+ *
+ * @param string $type
+ * @param array $options optional
+ * @return string
+ */
+ public function toString($type = 'PKCS1', array $options = [])
+ {
+ $type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters');
+
+ return $type::saveParameters($this->prime, $this->base, $options);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/PrivateKey.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/PrivateKey.php
new file mode 100644
index 000000000..e2407e35e
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/PrivateKey.php
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * DH Private Key
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DH;
+
+use phpseclib3\Crypt\Common;
+use phpseclib3\Crypt\DH;
+
+/**
+ * DH Private Key
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+final class PrivateKey extends DH
+{
+ use Common\Traits\PasswordProtected;
+
+ /**
+ * Private Key
+ *
+ * @var \phpseclib3\Math\BigInteger
+ */
+ protected $privateKey;
+
+ /**
+ * Public Key
+ *
+ * @var \phpseclib3\Math\BigInteger
+ */
+ protected $publicKey;
+
+ /**
+ * Returns the public key
+ *
+ * @return PublicKey
+ */
+ public function getPublicKey()
+ {
+ $type = self::validatePlugin('Keys', 'PKCS8', 'savePublicKey');
+
+ if (!isset($this->publicKey)) {
+ $this->publicKey = $this->base->powMod($this->privateKey, $this->prime);
+ }
+
+ $key = $type::savePublicKey($this->prime, $this->base, $this->publicKey);
+
+ return DH::loadFormat('PKCS8', $key);
+ }
+
+ /**
+ * Returns the private key
+ *
+ * @param string $type
+ * @param array $options optional
+ * @return string
+ */
+ public function toString($type, array $options = [])
+ {
+ $type = self::validatePlugin('Keys', $type, 'savePrivateKey');
+
+ if (!isset($this->publicKey)) {
+ $this->publicKey = $this->base->powMod($this->privateKey, $this->prime);
+ }
+
+ return $type::savePrivateKey($this->prime, $this->base, $this->privateKey, $this->publicKey, $this->password, $options);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/PublicKey.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/PublicKey.php
new file mode 100644
index 000000000..87726a5a3
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DH/PublicKey.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * DH Public Key
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DH;
+
+use phpseclib3\Crypt\Common;
+use phpseclib3\Crypt\DH;
+
+/**
+ * DH Public Key
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+final class PublicKey extends DH
+{
+ use Common\Traits\Fingerprint;
+
+ /**
+ * Returns the public key
+ *
+ * @param string $type
+ * @param array $options optional
+ * @return string
+ */
+ public function toString($type, array $options = [])
+ {
+ $type = self::validatePlugin('Keys', $type, 'savePublicKey');
+
+ return $type::savePublicKey($this->prime, $this->base, $this->publicKey, $options);
+ }
+
+ /**
+ * Returns the public key as a BigInteger
+ *
+ * @return \phpseclib3\Math\BigInteger
+ */
+ public function toBigInteger()
+ {
+ return $this->publicKey;
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA.php
new file mode 100644
index 000000000..92c777d6a
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA.php
@@ -0,0 +1,337 @@
+<?php
+
+/**
+ * Pure-PHP FIPS 186-4 compliant implementation of DSA.
+ *
+ * PHP version 5
+ *
+ * Here's an example of how to create signatures and verify signatures with this library:
+ * <code>
+ * <?php
+ * include 'vendor/autoload.php';
+ *
+ * $private = \phpseclib3\Crypt\DSA::createKey();
+ * $public = $private->getPublicKey();
+ *
+ * $plaintext = 'terrafrost';
+ *
+ * $signature = $private->sign($plaintext);
+ *
+ * echo $public->verify($plaintext, $signature) ? 'verified' : 'unverified';
+ * ?>
+ * </code>
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt;
+
+use phpseclib3\Crypt\Common\AsymmetricKey;
+use phpseclib3\Crypt\DSA\Parameters;
+use phpseclib3\Crypt\DSA\PrivateKey;
+use phpseclib3\Crypt\DSA\PublicKey;
+use phpseclib3\Exception\InsufficientSetupException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Pure-PHP FIPS 186-4 compliant implementation of DSA.
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class DSA extends AsymmetricKey
+{
+ /**
+ * Algorithm Name
+ *
+ * @var string
+ */
+ const ALGORITHM = 'DSA';
+
+ /**
+ * DSA Prime P
+ *
+ * @var BigInteger
+ */
+ protected $p;
+
+ /**
+ * DSA Group Order q
+ *
+ * Prime divisor of p-1
+ *
+ * @var BigInteger
+ */
+ protected $q;
+
+ /**
+ * DSA Group Generator G
+ *
+ * @var BigInteger
+ */
+ protected $g;
+
+ /**
+ * DSA public key value y
+ *
+ * @var BigInteger
+ */
+ protected $y;
+
+ /**
+ * Signature Format
+ *
+ * @var string
+ */
+ protected $sigFormat;
+
+ /**
+ * Signature Format (Short)
+ *
+ * @var string
+ */
+ protected $shortFormat;
+
+ /**
+ * Create DSA parameters
+ *
+ * @param int $L
+ * @param int $N
+ * @return DSA|bool
+ */
+ public static function createParameters($L = 2048, $N = 224)
+ {
+ self::initialize_static_variables();
+
+ $class = new \ReflectionClass(static::class);
+ if ($class->isFinal()) {
+ throw new \RuntimeException('createParameters() should not be called from final classes (' . static::class . ')');
+ }
+
+ if (!isset(self::$engines['PHP'])) {
+ self::useBestEngine();
+ }
+
+ switch (true) {
+ case $N == 160:
+ /*
+ in FIPS 186-1 and 186-2 N was fixed at 160 whereas K had an upper bound of 1024.
+ RFC 4253 (SSH Transport Layer Protocol) references FIPS 186-2 and as such most
+ SSH DSA implementations only support keys with an N of 160.
+ puttygen let's you set the size of L (but not the size of N) and uses 2048 as the
+ default L value. that's not really compliant with any of the FIPS standards, however,
+ for the purposes of maintaining compatibility with puttygen, we'll support it
+ */
+ //case ($L >= 512 || $L <= 1024) && (($L & 0x3F) == 0) && $N == 160:
+ // FIPS 186-3 changed this as follows:
+ //case $L == 1024 && $N == 160:
+ case $L == 2048 && $N == 224:
+ case $L == 2048 && $N == 256:
+ case $L == 3072 && $N == 256:
+ break;
+ default:
+ throw new \InvalidArgumentException('Invalid values for N and L');
+ }
+
+ $two = new BigInteger(2);
+
+ $q = BigInteger::randomPrime($N);
+ $divisor = $q->multiply($two);
+
+ do {
+ $x = BigInteger::random($L);
+ list(, $c) = $x->divide($divisor);
+ $p = $x->subtract($c->subtract(self::$one));
+ } while ($p->getLength() != $L || !$p->isPrime());
+
+ $p_1 = $p->subtract(self::$one);
+ list($e) = $p_1->divide($q);
+
+ // quoting http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf#page=50 ,
+ // "h could be obtained from a random number generator or from a counter that
+ // changes after each use". PuTTY (sshdssg.c) starts h off at 1 and increments
+ // it on each loop. wikipedia says "commonly h = 2 is used" so we'll just do that
+ $h = clone $two;
+ while (true) {
+ $g = $h->powMod($e, $p);
+ if (!$g->equals(self::$one)) {
+ break;
+ }
+ $h = $h->add(self::$one);
+ }
+
+ $dsa = new Parameters();
+ $dsa->p = $p;
+ $dsa->q = $q;
+ $dsa->g = $g;
+
+ return $dsa;
+ }
+
+ /**
+ * Create public / private key pair.
+ *
+ * This method is a bit polymorphic. It can take a DSA/Parameters object, L / N as two distinct parameters or
+ * no parameters (at which point L and N will be generated with this method)
+ *
+ * Returns the private key, from which the publickey can be extracted
+ *
+ * @param int[] ...$args
+ * @return PrivateKey
+ */
+ public static function createKey(...$args)
+ {
+ self::initialize_static_variables();
+
+ $class = new \ReflectionClass(static::class);
+ if ($class->isFinal()) {
+ throw new \RuntimeException('createKey() should not be called from final classes (' . static::class . ')');
+ }
+
+ if (!isset(self::$engines['PHP'])) {
+ self::useBestEngine();
+ }
+
+ if (count($args) == 2 && is_int($args[0]) && is_int($args[1])) {
+ $params = self::createParameters($args[0], $args[1]);
+ } elseif (count($args) == 1 && $args[0] instanceof Parameters) {
+ $params = $args[0];
+ } elseif (!count($args)) {
+ $params = self::createParameters();
+ } else {
+ throw new InsufficientSetupException('Valid parameters are either two integers (L and N), a single DSA object or no parameters at all.');
+ }
+
+ $private = new PrivateKey();
+ $private->p = $params->p;
+ $private->q = $params->q;
+ $private->g = $params->g;
+
+ $private->x = BigInteger::randomRange(self::$one, $private->q->subtract(self::$one));
+ $private->y = $private->g->powMod($private->x, $private->p);
+
+ //$public = clone $private;
+ //unset($public->x);
+
+ return $private
+ ->withHash($params->hash->getHash())
+ ->withSignatureFormat($params->shortFormat);
+ }
+
+ /**
+ * OnLoad Handler
+ *
+ * @return bool
+ */
+ protected static function onLoad(array $components)
+ {
+ if (!isset(self::$engines['PHP'])) {
+ self::useBestEngine();
+ }
+
+ if (!isset($components['x']) && !isset($components['y'])) {
+ $new = new Parameters();
+ } elseif (isset($components['x'])) {
+ $new = new PrivateKey();
+ $new->x = $components['x'];
+ } else {
+ $new = new PublicKey();
+ }
+
+ $new->p = $components['p'];
+ $new->q = $components['q'];
+ $new->g = $components['g'];
+
+ if (isset($components['y'])) {
+ $new->y = $components['y'];
+ }
+
+ return $new;
+ }
+
+ /**
+ * Constructor
+ *
+ * PublicKey and PrivateKey objects can only be created from abstract RSA class
+ */
+ protected function __construct()
+ {
+ $this->sigFormat = self::validatePlugin('Signature', 'ASN1');
+ $this->shortFormat = 'ASN1';
+
+ parent::__construct();
+ }
+
+ /**
+ * Returns the key size
+ *
+ * More specifically, this L (the length of DSA Prime P) and N (the length of DSA Group Order q)
+ *
+ * @return array
+ */
+ public function getLength()
+ {
+ return ['L' => $this->p->getLength(), 'N' => $this->q->getLength()];
+ }
+
+ /**
+ * Returns the current engine being used
+ *
+ * @see self::useInternalEngine()
+ * @see self::useBestEngine()
+ * @return string
+ */
+ public function getEngine()
+ {
+ if (!isset(self::$engines['PHP'])) {
+ self::useBestEngine();
+ }
+ return self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods()) ?
+ 'OpenSSL' : 'PHP';
+ }
+
+ /**
+ * Returns the parameters
+ *
+ * A public / private key is only returned if the currently loaded "key" contains an x or y
+ * value.
+ *
+ * @see self::getPublicKey()
+ * @return mixed
+ */
+ public function getParameters()
+ {
+ $type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters');
+
+ $key = $type::saveParameters($this->p, $this->q, $this->g);
+ return DSA::load($key, 'PKCS1')
+ ->withHash($this->hash->getHash())
+ ->withSignatureFormat($this->shortFormat);
+ }
+
+ /**
+ * Determines the signature padding mode
+ *
+ * Valid values are: ASN1, SSH2, Raw
+ *
+ * @param string $format
+ */
+ public function withSignatureFormat($format)
+ {
+ $new = clone $this;
+ $new->shortFormat = $format;
+ $new->sigFormat = self::validatePlugin('Signature', $format);
+ return $new;
+ }
+
+ /**
+ * Returns the signature format currently being used
+ *
+ */
+ public function getSignatureFormat()
+ {
+ return $this->shortFormat;
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/OpenSSH.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/OpenSSH.php
new file mode 100644
index 000000000..bc41fcf5e
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/OpenSSH.php
@@ -0,0 +1,118 @@
+<?php
+
+/**
+ * OpenSSH Formatted DSA Key Handler
+ *
+ * PHP version 5
+ *
+ * Place in $HOME/.ssh/authorized_keys
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DSA\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\Formats\Keys\OpenSSH as Progenitor;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * OpenSSH Formatted DSA Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class OpenSSH extends Progenitor
+{
+ /**
+ * Supported Key Types
+ *
+ * @var array
+ */
+ protected static $types = ['ssh-dss'];
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ $parsed = parent::load($key, $password);
+
+ if (isset($parsed['paddedKey'])) {
+ list($type) = Strings::unpackSSH2('s', $parsed['paddedKey']);
+ if ($type != $parsed['type']) {
+ throw new \RuntimeException("The public and private keys are not of the same type ($type vs $parsed[type])");
+ }
+
+ list($p, $q, $g, $y, $x, $comment) = Strings::unpackSSH2('i5s', $parsed['paddedKey']);
+
+ return compact('p', 'q', 'g', 'y', 'x', 'comment');
+ }
+
+ list($p, $q, $g, $y) = Strings::unpackSSH2('iiii', $parsed['publicKey']);
+
+ $comment = $parsed['comment'];
+
+ return compact('p', 'q', 'g', 'y', 'comment');
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $p
+ * @param BigInteger $q
+ * @param BigInteger $g
+ * @param BigInteger $y
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, array $options = [])
+ {
+ if ($q->getLength() != 160) {
+ throw new \InvalidArgumentException('SSH only supports keys with an N (length of Group Order q) of 160');
+ }
+
+ // from <http://tools.ietf.org/html/rfc4253#page-15>:
+ // string "ssh-dss"
+ // mpint p
+ // mpint q
+ // mpint g
+ // mpint y
+ $DSAPublicKey = Strings::packSSH2('siiii', 'ssh-dss', $p, $q, $g, $y);
+
+ if (isset($options['binary']) ? $options['binary'] : self::$binary) {
+ return $DSAPublicKey;
+ }
+
+ $comment = isset($options['comment']) ? $options['comment'] : self::$comment;
+ $DSAPublicKey = 'ssh-dss ' . base64_encode($DSAPublicKey) . ' ' . $comment;
+
+ return $DSAPublicKey;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $p
+ * @param BigInteger $q
+ * @param BigInteger $g
+ * @param BigInteger $y
+ * @param BigInteger $x
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, $password = '', array $options = [])
+ {
+ $publicKey = self::savePublicKey($p, $q, $g, $y, ['binary' => true]);
+ $privateKey = Strings::packSSH2('si5', 'ssh-dss', $p, $q, $g, $y, $x);
+
+ return self::wrapPrivateKey($publicKey, $privateKey, $password, $options);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PKCS1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PKCS1.php
new file mode 100644
index 000000000..800cfb38c
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PKCS1.php
@@ -0,0 +1,143 @@
+<?php
+
+/**
+ * PKCS#1 Formatted DSA Key Handler
+ *
+ * PHP version 5
+ *
+ * Used by File/X509.php
+ *
+ * Processes keys with the following headers:
+ *
+ * -----BEGIN DSA PRIVATE KEY-----
+ * -----BEGIN DSA PUBLIC KEY-----
+ * -----BEGIN DSA PARAMETERS-----
+ *
+ * Analogous to ssh-keygen's pem format (as specified by -m)
+ *
+ * Also, technically, PKCS1 decribes RSA but I am not aware of a formal specification for DSA.
+ * The DSA private key format seems to have been adapted from the RSA private key format so
+ * we're just re-using that as the name.
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DSA\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\Formats\Keys\PKCS1 as Progenitor;
+use phpseclib3\File\ASN1;
+use phpseclib3\File\ASN1\Maps;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * PKCS#1 Formatted DSA Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PKCS1 extends Progenitor
+{
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ $key = parent::load($key, $password);
+
+ $decoded = ASN1::decodeBER($key);
+ if (!$decoded) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+
+ $key = ASN1::asn1map($decoded[0], Maps\DSAParams::MAP);
+ if (is_array($key)) {
+ return $key;
+ }
+
+ $key = ASN1::asn1map($decoded[0], Maps\DSAPrivateKey::MAP);
+ if (is_array($key)) {
+ return $key;
+ }
+
+ $key = ASN1::asn1map($decoded[0], Maps\DSAPublicKey::MAP);
+ if (is_array($key)) {
+ return $key;
+ }
+
+ throw new \RuntimeException('Unable to perform ASN1 mapping');
+ }
+
+ /**
+ * Convert DSA parameters to the appropriate format
+ *
+ * @param BigInteger $p
+ * @param BigInteger $q
+ * @param BigInteger $g
+ * @return string
+ */
+ public static function saveParameters(BigInteger $p, BigInteger $q, BigInteger $g)
+ {
+ $key = [
+ 'p' => $p,
+ 'q' => $q,
+ 'g' => $g
+ ];
+
+ $key = ASN1::encodeDER($key, Maps\DSAParams::MAP);
+
+ return "-----BEGIN DSA PARAMETERS-----\r\n" .
+ chunk_split(Strings::base64_encode($key), 64) .
+ "-----END DSA PARAMETERS-----\r\n";
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $p
+ * @param BigInteger $q
+ * @param BigInteger $g
+ * @param BigInteger $y
+ * @param BigInteger $x
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, $password = '', array $options = [])
+ {
+ $key = [
+ 'version' => 0,
+ 'p' => $p,
+ 'q' => $q,
+ 'g' => $g,
+ 'y' => $y,
+ 'x' => $x
+ ];
+
+ $key = ASN1::encodeDER($key, Maps\DSAPrivateKey::MAP);
+
+ return self::wrapPrivateKey($key, 'DSA', $password, $options);
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $p
+ * @param BigInteger $q
+ * @param BigInteger $g
+ * @param BigInteger $y
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y)
+ {
+ $key = ASN1::encodeDER($y, Maps\DSAPublicKey::MAP);
+
+ return self::wrapPublicKey($key, 'DSA');
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PKCS8.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PKCS8.php
new file mode 100644
index 000000000..359ed09ea
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PKCS8.php
@@ -0,0 +1,146 @@
+<?php
+
+/**
+ * PKCS#8 Formatted DSA Key Handler
+ *
+ * PHP version 5
+ *
+ * Processes keys with the following headers:
+ *
+ * -----BEGIN ENCRYPTED PRIVATE KEY-----
+ * -----BEGIN PRIVATE KEY-----
+ * -----BEGIN PUBLIC KEY-----
+ *
+ * Analogous to ssh-keygen's pkcs8 format (as specified by -m). Although PKCS8
+ * is specific to private keys it's basically creating a DER-encoded wrapper
+ * for keys. This just extends that same concept to public keys (much like ssh-keygen)
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DSA\Formats\Keys;
+
+use phpseclib3\Crypt\Common\Formats\Keys\PKCS8 as Progenitor;
+use phpseclib3\File\ASN1;
+use phpseclib3\File\ASN1\Maps;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * PKCS#8 Formatted DSA Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PKCS8 extends Progenitor
+{
+ /**
+ * OID Name
+ *
+ * @var string
+ */
+ const OID_NAME = 'id-dsa';
+
+ /**
+ * OID Value
+ *
+ * @var string
+ */
+ const OID_VALUE = '1.2.840.10040.4.1';
+
+ /**
+ * Child OIDs loaded
+ *
+ * @var bool
+ */
+ protected static $childOIDsLoaded = false;
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ $key = parent::load($key, $password);
+
+ $type = isset($key['privateKey']) ? 'privateKey' : 'publicKey';
+
+ $decoded = ASN1::decodeBER($key[$type . 'Algorithm']['parameters']->element);
+ if (!$decoded) {
+ throw new \RuntimeException('Unable to decode BER of parameters');
+ }
+ $components = ASN1::asn1map($decoded[0], Maps\DSAParams::MAP);
+ if (!is_array($components)) {
+ throw new \RuntimeException('Unable to perform ASN1 mapping on parameters');
+ }
+
+ $decoded = ASN1::decodeBER($key[$type]);
+ if (empty($decoded)) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+
+ $var = $type == 'privateKey' ? 'x' : 'y';
+ $components[$var] = ASN1::asn1map($decoded[0], Maps\DSAPublicKey::MAP);
+ if (!$components[$var] instanceof BigInteger) {
+ throw new \RuntimeException('Unable to perform ASN1 mapping');
+ }
+
+ if (isset($key['meta'])) {
+ $components['meta'] = $key['meta'];
+ }
+
+ return $components;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $p
+ * @param BigInteger $q
+ * @param BigInteger $g
+ * @param BigInteger $y
+ * @param BigInteger $x
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, $password = '', array $options = [])
+ {
+ $params = [
+ 'p' => $p,
+ 'q' => $q,
+ 'g' => $g
+ ];
+ $params = ASN1::encodeDER($params, Maps\DSAParams::MAP);
+ $params = new ASN1\Element($params);
+ $key = ASN1::encodeDER($x, Maps\DSAPublicKey::MAP);
+ return self::wrapPrivateKey($key, [], $params, $password, null, '', $options);
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $p
+ * @param BigInteger $q
+ * @param BigInteger $g
+ * @param BigInteger $y
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, array $options = [])
+ {
+ $params = [
+ 'p' => $p,
+ 'q' => $q,
+ 'g' => $g
+ ];
+ $params = ASN1::encodeDER($params, Maps\DSAParams::MAP);
+ $params = new ASN1\Element($params);
+ $key = ASN1::encodeDER($y, Maps\DSAPublicKey::MAP);
+ return self::wrapPublicKey($key, $params, null, $options);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PuTTY.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PuTTY.php
new file mode 100644
index 000000000..8549a2ec7
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/PuTTY.php
@@ -0,0 +1,112 @@
+<?php
+
+/**
+ * PuTTY Formatted DSA Key Handler
+ *
+ * puttygen does not generate DSA keys with an N of anything other than 160, however,
+ * it can still load them and convert them. PuTTY will load them, too, but SSH servers
+ * won't accept them. Since PuTTY formatted keys are primarily used with SSH this makes
+ * keys with N > 160 kinda useless, hence this handlers not supporting such keys.
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DSA\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\Formats\Keys\PuTTY as Progenitor;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * PuTTY Formatted DSA Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PuTTY extends Progenitor
+{
+ /**
+ * Public Handler
+ *
+ * @var string
+ */
+ const PUBLIC_HANDLER = 'phpseclib3\Crypt\DSA\Formats\Keys\OpenSSH';
+
+ /**
+ * Algorithm Identifier
+ *
+ * @var array
+ */
+ protected static $types = ['ssh-dss'];
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ $components = parent::load($key, $password);
+ if (!isset($components['private'])) {
+ return $components;
+ }
+ $type = $components['type'];
+ $comment = $components['comment'];
+ $public = $components['public'];
+ $private = $components['private'];
+ unset($components['public'], $components['private']);
+
+ list($p, $q, $g, $y) = Strings::unpackSSH2('iiii', $public);
+ list($x) = Strings::unpackSSH2('i', $private);
+
+ return compact('p', 'q', 'g', 'y', 'x', 'comment');
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $p
+ * @param BigInteger $q
+ * @param BigInteger $g
+ * @param BigInteger $y
+ * @param BigInteger $x
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, $password = false, array $options = [])
+ {
+ if ($q->getLength() != 160) {
+ throw new \InvalidArgumentException('SSH only supports keys with an N (length of Group Order q) of 160');
+ }
+
+ $public = Strings::packSSH2('iiii', $p, $q, $g, $y);
+ $private = Strings::packSSH2('i', $x);
+
+ return self::wrapPrivateKey($public, $private, 'ssh-dss', $password, $options);
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $p
+ * @param BigInteger $q
+ * @param BigInteger $g
+ * @param BigInteger $y
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y)
+ {
+ if ($q->getLength() != 160) {
+ throw new \InvalidArgumentException('SSH only supports keys with an N (length of Group Order q) of 160');
+ }
+
+ return self::wrapPublicKey(Strings::packSSH2('iiii', $p, $q, $g, $y), 'ssh-dss');
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/Raw.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/Raw.php
new file mode 100644
index 000000000..8e2ef01f1
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/Raw.php
@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * Raw DSA Key Handler
+ *
+ * PHP version 5
+ *
+ * Reads and creates arrays as DSA keys
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DSA\Formats\Keys;
+
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Raw DSA Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Raw
+{
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param array $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ if (!is_array($key)) {
+ throw new \UnexpectedValueException('Key should be a array - not a ' . gettype($key));
+ }
+
+ switch (true) {
+ case !isset($key['p']) || !isset($key['q']) || !isset($key['g']):
+ case !$key['p'] instanceof BigInteger:
+ case !$key['q'] instanceof BigInteger:
+ case !$key['g'] instanceof BigInteger:
+ case !isset($key['x']) && !isset($key['y']):
+ case isset($key['x']) && !$key['x'] instanceof BigInteger:
+ case isset($key['y']) && !$key['y'] instanceof BigInteger:
+ throw new \UnexpectedValueException('Key appears to be malformed');
+ }
+
+ $options = ['p' => 1, 'q' => 1, 'g' => 1, 'x' => 1, 'y' => 1];
+
+ return array_intersect_key($key, $options);
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $p
+ * @param BigInteger $q
+ * @param BigInteger $g
+ * @param BigInteger $y
+ * @param BigInteger $x
+ * @param string $password optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y, BigInteger $x, $password = '')
+ {
+ return compact('p', 'q', 'g', 'y', 'x');
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $p
+ * @param BigInteger $q
+ * @param BigInteger $g
+ * @param BigInteger $y
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y)
+ {
+ return compact('p', 'q', 'g', 'y');
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/XML.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/XML.php
new file mode 100644
index 000000000..f77cbf20d
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Keys/XML.php
@@ -0,0 +1,132 @@
+<?php
+
+/**
+ * XML Formatted DSA Key Handler
+ *
+ * While XKMS defines a private key format for RSA it does not do so for DSA. Quoting that standard:
+ *
+ * "[XKMS] does not specify private key parameters for the DSA signature algorithm since the algorithm only
+ * supports signature modes and so the application of server generated keys and key recovery is of limited
+ * value"
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DSA\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Exception\BadConfigurationException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * XML Formatted DSA Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class XML
+{
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ if (!Strings::is_stringable($key)) {
+ throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
+ }
+
+ if (!class_exists('DOMDocument')) {
+ throw new BadConfigurationException('The dom extension is not setup correctly on this system');
+ }
+
+ $use_errors = libxml_use_internal_errors(true);
+
+ $dom = new \DOMDocument();
+ if (substr($key, 0, 5) != '<?xml') {
+ $key = '<xml>' . $key . '</xml>';
+ }
+ if (!$dom->loadXML($key)) {
+ libxml_use_internal_errors($use_errors);
+ throw new \UnexpectedValueException('Key does not appear to contain XML');
+ }
+ $xpath = new \DOMXPath($dom);
+ $keys = ['p', 'q', 'g', 'y', 'j', 'seed', 'pgencounter'];
+ foreach ($keys as $key) {
+ // $dom->getElementsByTagName($key) is case-sensitive
+ $temp = $xpath->query("//*[translate(local-name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='$key']");
+ if (!$temp->length) {
+ continue;
+ }
+ $value = new BigInteger(Strings::base64_decode($temp->item(0)->nodeValue), 256);
+ switch ($key) {
+ case 'p': // a prime modulus meeting the [DSS] requirements
+ // Parameters P, Q, and G can be public and common to a group of users. They might be known
+ // from application context. As such, they are optional but P and Q must either both appear
+ // or both be absent
+ $components['p'] = $value;
+ break;
+ case 'q': // an integer in the range 2**159 < Q < 2**160 which is a prime divisor of P-1
+ $components['q'] = $value;
+ break;
+ case 'g': // an integer with certain properties with respect to P and Q
+ $components['g'] = $value;
+ break;
+ case 'y': // G**X mod P (where X is part of the private key and not made public)
+ $components['y'] = $value;
+ // the remaining options do not do anything
+ case 'j': // (P - 1) / Q
+ // Parameter J is available for inclusion solely for efficiency as it is calculatable from
+ // P and Q
+ case 'seed': // a DSA prime generation seed
+ // Parameters seed and pgenCounter are used in the DSA prime number generation algorithm
+ // specified in [DSS]. As such, they are optional but must either both be present or both
+ // be absent
+ case 'pgencounter': // a DSA prime generation counter
+ }
+ }
+
+ libxml_use_internal_errors($use_errors);
+
+ if (!isset($components['y'])) {
+ throw new \UnexpectedValueException('Key is missing y component');
+ }
+
+ switch (true) {
+ case !isset($components['p']):
+ case !isset($components['q']):
+ case !isset($components['g']):
+ return ['y' => $components['y']];
+ }
+
+ return $components;
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * See https://www.w3.org/TR/xmldsig-core/#sec-DSAKeyValue
+ *
+ * @param BigInteger $p
+ * @param BigInteger $q
+ * @param BigInteger $g
+ * @param BigInteger $y
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $p, BigInteger $q, BigInteger $g, BigInteger $y)
+ {
+ return "<DSAKeyValue>\r\n" .
+ ' <P>' . Strings::base64_encode($p->toBytes()) . "</P>\r\n" .
+ ' <Q>' . Strings::base64_encode($q->toBytes()) . "</Q>\r\n" .
+ ' <G>' . Strings::base64_encode($g->toBytes()) . "</G>\r\n" .
+ ' <Y>' . Strings::base64_encode($y->toBytes()) . "</Y>\r\n" .
+ '</DSAKeyValue>';
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/ASN1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/ASN1.php
new file mode 100644
index 000000000..f80060286
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/ASN1.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * ASN1 Signature Handler
+ *
+ * PHP version 5
+ *
+ * Handles signatures in the format described in
+ * https://tools.ietf.org/html/rfc3279#section-2.2.2
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DSA\Formats\Signature;
+
+use phpseclib3\File\ASN1 as Encoder;
+use phpseclib3\File\ASN1\Maps;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * ASN1 Signature Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class ASN1
+{
+ /**
+ * Loads a signature
+ *
+ * @param string $sig
+ * @return array|bool
+ */
+ public static function load($sig)
+ {
+ if (!is_string($sig)) {
+ return false;
+ }
+
+ $decoded = Encoder::decodeBER($sig);
+ if (empty($decoded)) {
+ return false;
+ }
+ $components = Encoder::asn1map($decoded[0], Maps\DssSigValue::MAP);
+
+ return $components;
+ }
+
+ /**
+ * Returns a signature in the appropriate format
+ *
+ * @param BigInteger $r
+ * @param BigInteger $s
+ * @return string
+ */
+ public static function save(BigInteger $r, BigInteger $s)
+ {
+ return Encoder::encodeDER(compact('r', 's'), Maps\DssSigValue::MAP);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/Raw.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/Raw.php
new file mode 100644
index 000000000..2657a2a87
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/Raw.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * Raw DSA Signature Handler
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DSA\Formats\Signature;
+
+use phpseclib3\Crypt\Common\Formats\Signature\Raw as Progenitor;
+
+/**
+ * Raw DSA Signature Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Raw extends Progenitor
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/SSH2.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/SSH2.php
new file mode 100644
index 000000000..88807b5b8
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Formats/Signature/SSH2.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * SSH2 Signature Handler
+ *
+ * PHP version 5
+ *
+ * Handles signatures in the format used by SSH2
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DSA\Formats\Signature;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * SSH2 Signature Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class SSH2
+{
+ /**
+ * Loads a signature
+ *
+ * @param string $sig
+ * @return mixed
+ */
+ public static function load($sig)
+ {
+ if (!is_string($sig)) {
+ return false;
+ }
+
+ $result = Strings::unpackSSH2('ss', $sig);
+ if ($result === false) {
+ return false;
+ }
+ list($type, $blob) = $result;
+ if ($type != 'ssh-dss' || strlen($blob) != 40) {
+ return false;
+ }
+
+ return [
+ 'r' => new BigInteger(substr($blob, 0, 20), 256),
+ 's' => new BigInteger(substr($blob, 20), 256)
+ ];
+ }
+
+ /**
+ * Returns a signature in the appropriate format
+ *
+ * @param BigInteger $r
+ * @param BigInteger $s
+ * @return string
+ */
+ public static function save(BigInteger $r, BigInteger $s)
+ {
+ if ($r->getLength() > 160 || $s->getLength() > 160) {
+ return false;
+ }
+ return Strings::packSSH2(
+ 'ss',
+ 'ssh-dss',
+ str_pad($r->toBytes(), 20, "\0", STR_PAD_LEFT) .
+ str_pad($s->toBytes(), 20, "\0", STR_PAD_LEFT)
+ );
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Parameters.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Parameters.php
new file mode 100644
index 000000000..84d16ba68
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/Parameters.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * DSA Parameters
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DSA;
+
+use phpseclib3\Crypt\DSA;
+
+/**
+ * DSA Parameters
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+final class Parameters extends DSA
+{
+ /**
+ * Returns the parameters
+ *
+ * @param string $type
+ * @param array $options optional
+ * @return string
+ */
+ public function toString($type = 'PKCS1', array $options = [])
+ {
+ $type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters');
+
+ return $type::saveParameters($this->p, $this->q, $this->g, $options);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/PrivateKey.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/PrivateKey.php
new file mode 100644
index 000000000..90252139d
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/PrivateKey.php
@@ -0,0 +1,154 @@
+<?php
+
+/**
+ * DSA Private Key
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DSA;
+
+use phpseclib3\Crypt\Common;
+use phpseclib3\Crypt\DSA;
+use phpseclib3\Crypt\DSA\Formats\Signature\ASN1 as ASN1Signature;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * DSA Private Key
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+final class PrivateKey extends DSA implements Common\PrivateKey
+{
+ use Common\Traits\PasswordProtected;
+
+ /**
+ * DSA secret exponent x
+ *
+ * @var BigInteger
+ */
+ protected $x;
+
+ /**
+ * Returns the public key
+ *
+ * If you do "openssl rsa -in private.rsa -pubout -outform PEM" you get a PKCS8 formatted key
+ * that contains a publicKeyAlgorithm AlgorithmIdentifier and a publicKey BIT STRING.
+ * An AlgorithmIdentifier contains an OID and a parameters field. With RSA public keys this
+ * parameters field is NULL. With DSA PKCS8 public keys it is not - it contains the p, q and g
+ * variables. The publicKey BIT STRING contains, simply, the y variable. This can be verified
+ * by getting a DSA PKCS8 public key:
+ *
+ * "openssl dsa -in private.dsa -pubout -outform PEM"
+ *
+ * ie. just swap out rsa with dsa in the rsa command above.
+ *
+ * A PKCS1 public key corresponds to the publicKey portion of the PKCS8 key. In the case of RSA
+ * the publicKey portion /is/ the key. In the case of DSA it is not. You cannot verify a signature
+ * without the parameters and the PKCS1 DSA public key format does not include the parameters.
+ *
+ * @see self::getPrivateKey()
+ * @return mixed
+ */
+ public function getPublicKey()
+ {
+ $type = self::validatePlugin('Keys', 'PKCS8', 'savePublicKey');
+
+ if (!isset($this->y)) {
+ $this->y = $this->g->powMod($this->x, $this->p);
+ }
+
+ $key = $type::savePublicKey($this->p, $this->q, $this->g, $this->y);
+
+ return DSA::loadFormat('PKCS8', $key)
+ ->withHash($this->hash->getHash())
+ ->withSignatureFormat($this->shortFormat);
+ }
+
+ /**
+ * Create a signature
+ *
+ * @see self::verify()
+ * @param string $message
+ * @return mixed
+ */
+ public function sign($message)
+ {
+ $format = $this->sigFormat;
+
+ if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) {
+ $signature = '';
+ $result = openssl_sign($message, $signature, $this->toString('PKCS8'), $this->hash->getHash());
+
+ if ($result) {
+ if ($this->shortFormat == 'ASN1') {
+ return $signature;
+ }
+
+ $loaded = ASN1Signature::load($signature);
+ $r = $loaded['r'];
+ $s = $loaded['s'];
+
+ return $format::save($r, $s);
+ }
+ }
+
+ $h = $this->hash->hash($message);
+ $h = $this->bits2int($h);
+
+ while (true) {
+ $k = BigInteger::randomRange(self::$one, $this->q->subtract(self::$one));
+ $r = $this->g->powMod($k, $this->p);
+ list(, $r) = $r->divide($this->q);
+ if ($r->equals(self::$zero)) {
+ continue;
+ }
+ $kinv = $k->modInverse($this->q);
+ $temp = $h->add($this->x->multiply($r));
+ $temp = $kinv->multiply($temp);
+ list(, $s) = $temp->divide($this->q);
+ if (!$s->equals(self::$zero)) {
+ break;
+ }
+ }
+
+ // the following is an RFC6979 compliant implementation of deterministic DSA
+ // it's unused because it's mainly intended for use when a good CSPRNG isn't
+ // available. if phpseclib's CSPRNG isn't good then even key generation is
+ // suspect
+ /*
+ $h1 = $this->hash->hash($message);
+ $k = $this->computek($h1);
+ $r = $this->g->powMod($k, $this->p);
+ list(, $r) = $r->divide($this->q);
+ $kinv = $k->modInverse($this->q);
+ $h1 = $this->bits2int($h1);
+ $temp = $h1->add($this->x->multiply($r));
+ $temp = $kinv->multiply($temp);
+ list(, $s) = $temp->divide($this->q);
+ */
+
+ return $format::save($r, $s);
+ }
+
+ /**
+ * Returns the private key
+ *
+ * @param string $type
+ * @param array $options optional
+ * @return string
+ */
+ public function toString($type, array $options = [])
+ {
+ $type = self::validatePlugin('Keys', $type, 'savePrivateKey');
+
+ if (!isset($this->y)) {
+ $this->y = $this->g->powMod($this->x, $this->p);
+ }
+
+ return $type::savePrivateKey($this->p, $this->q, $this->g, $this->y, $this->x, $this->password, $options);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/PublicKey.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/PublicKey.php
new file mode 100644
index 000000000..3e16762b8
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/DSA/PublicKey.php
@@ -0,0 +1,87 @@
+<?php
+
+/**
+ * DSA Public Key
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\DSA;
+
+use phpseclib3\Crypt\Common;
+use phpseclib3\Crypt\DSA;
+use phpseclib3\Crypt\DSA\Formats\Signature\ASN1 as ASN1Signature;
+
+/**
+ * DSA Public Key
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+final class PublicKey extends DSA implements Common\PublicKey
+{
+ use Common\Traits\Fingerprint;
+
+ /**
+ * Verify a signature
+ *
+ * @see self::verify()
+ * @param string $message
+ * @param string $signature
+ * @return mixed
+ */
+ public function verify($message, $signature)
+ {
+ $format = $this->sigFormat;
+
+ $params = $format::load($signature);
+ if ($params === false || count($params) != 2) {
+ return false;
+ }
+ $r = $params['r'];
+ $s = $params['s'];
+
+ if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) {
+ $sig = $format != 'ASN1' ? ASN1Signature::save($r, $s) : $signature;
+
+ $result = openssl_verify($message, $sig, $this->toString('PKCS8'), $this->hash->getHash());
+
+ if ($result != -1) {
+ return (bool) $result;
+ }
+ }
+
+ $q_1 = $this->q->subtract(self::$one);
+ if (!$r->between(self::$one, $q_1) || !$s->between(self::$one, $q_1)) {
+ return false;
+ }
+
+ $w = $s->modInverse($this->q);
+ $h = $this->hash->hash($message);
+ $h = $this->bits2int($h);
+ list(, $u1) = $h->multiply($w)->divide($this->q);
+ list(, $u2) = $r->multiply($w)->divide($this->q);
+ $v1 = $this->g->powMod($u1, $this->p);
+ $v2 = $this->y->powMod($u2, $this->p);
+ list(, $v) = $v1->multiply($v2)->divide($this->p);
+ list(, $v) = $v->divide($this->q);
+
+ return $v->equals($r);
+ }
+
+ /**
+ * Returns the public key
+ *
+ * @param string $type
+ * @param array $options optional
+ * @return string
+ */
+ public function toString($type, array $options = [])
+ {
+ $type = self::validatePlugin('Keys', $type, 'savePublicKey');
+
+ return $type::savePublicKey($this->p, $this->q, $this->g, $this->y, $options);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC.php
new file mode 100644
index 000000000..dc82dd049
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC.php
@@ -0,0 +1,480 @@
+<?php
+
+/**
+ * Pure-PHP implementation of EC.
+ *
+ * PHP version 5
+ *
+ * Here's an example of how to create signatures and verify signatures with this library:
+ * <code>
+ * <?php
+ * include 'vendor/autoload.php';
+ *
+ * $private = \phpseclib3\Crypt\EC::createKey('secp256k1');
+ * $public = $private->getPublicKey();
+ *
+ * $plaintext = 'terrafrost';
+ *
+ * $signature = $private->sign($plaintext);
+ *
+ * echo $public->verify($plaintext, $signature) ? 'verified' : 'unverified';
+ * ?>
+ * </code>
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt;
+
+use phpseclib3\Crypt\Common\AsymmetricKey;
+use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
+use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
+use phpseclib3\Crypt\EC\Curves\Curve25519;
+use phpseclib3\Crypt\EC\Curves\Ed25519;
+use phpseclib3\Crypt\EC\Curves\Ed448;
+use phpseclib3\Crypt\EC\Formats\Keys\PKCS1;
+use phpseclib3\Crypt\EC\Parameters;
+use phpseclib3\Crypt\EC\PrivateKey;
+use phpseclib3\Crypt\EC\PublicKey;
+use phpseclib3\Exception\UnsupportedAlgorithmException;
+use phpseclib3\Exception\UnsupportedCurveException;
+use phpseclib3\Exception\UnsupportedOperationException;
+use phpseclib3\File\ASN1;
+use phpseclib3\File\ASN1\Maps\ECParameters;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Pure-PHP implementation of EC.
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class EC extends AsymmetricKey
+{
+ /**
+ * Algorithm Name
+ *
+ * @var string
+ */
+ const ALGORITHM = 'EC';
+
+ /**
+ * Public Key QA
+ *
+ * @var object[]
+ */
+ protected $QA;
+
+ /**
+ * Curve
+ *
+ * @var EC\BaseCurves\Base
+ */
+ protected $curve;
+
+ /**
+ * Signature Format
+ *
+ * @var string
+ */
+ protected $format;
+
+ /**
+ * Signature Format (Short)
+ *
+ * @var string
+ */
+ protected $shortFormat;
+
+ /**
+ * Curve Name
+ *
+ * @var string
+ */
+ private $curveName;
+
+ /**
+ * Curve Order
+ *
+ * Used for deterministic ECDSA
+ *
+ * @var BigInteger
+ */
+ protected $q;
+
+ /**
+ * Alias for the private key
+ *
+ * Used for deterministic ECDSA. AsymmetricKey expects $x. I don't like x because
+ * with x you have x * the base point yielding an (x, y)-coordinate that is the
+ * public key. But the x is different depending on which side of the equal sign
+ * you're on. It's less ambiguous if you do dA * base point = (x, y)-coordinate.
+ *
+ * @var BigInteger
+ */
+ protected $x;
+
+ /**
+ * Context
+ *
+ * @var string
+ */
+ protected $context;
+
+ /**
+ * Signature Format
+ *
+ * @var string
+ */
+ protected $sigFormat;
+
+ /**
+ * Create public / private key pair.
+ *
+ * @param string $curve
+ * @return PrivateKey
+ */
+ public static function createKey($curve)
+ {
+ self::initialize_static_variables();
+
+ $class = new \ReflectionClass(static::class);
+ if ($class->isFinal()) {
+ throw new \RuntimeException('createKey() should not be called from final classes (' . static::class . ')');
+ }
+
+ if (!isset(self::$engines['PHP'])) {
+ self::useBestEngine();
+ }
+
+ $curve = strtolower($curve);
+ if (self::$engines['libsodium'] && $curve == 'ed25519' && function_exists('sodium_crypto_sign_keypair')) {
+ $kp = sodium_crypto_sign_keypair();
+
+ $privatekey = EC::loadFormat('libsodium', sodium_crypto_sign_secretkey($kp));
+ //$publickey = EC::loadFormat('libsodium', sodium_crypto_sign_publickey($kp));
+
+ $privatekey->curveName = 'Ed25519';
+ //$publickey->curveName = $curve;
+
+ return $privatekey;
+ }
+
+ $privatekey = new PrivateKey();
+
+ $curveName = $curve;
+ if (preg_match('#(?:^curve|^ed)\d+$#', $curveName)) {
+ $curveName = ucfirst($curveName);
+ } elseif (substr($curveName, 0, 10) == 'brainpoolp') {
+ $curveName = 'brainpoolP' . substr($curveName, 10);
+ }
+ $curve = '\phpseclib3\Crypt\EC\Curves\\' . $curveName;
+
+ if (!class_exists($curve)) {
+ throw new UnsupportedCurveException('Named Curve of ' . $curveName . ' is not supported');
+ }
+
+ $reflect = new \ReflectionClass($curve);
+ $curveName = $reflect->isFinal() ?
+ $reflect->getParentClass()->getShortName() :
+ $reflect->getShortName();
+
+ $curve = new $curve();
+ if ($curve instanceof TwistedEdwardsCurve) {
+ $arr = $curve->extractSecret(Random::string($curve instanceof Ed448 ? 57 : 32));
+ $privatekey->dA = $dA = $arr['dA'];
+ $privatekey->secret = $arr['secret'];
+ } else {
+ $privatekey->dA = $dA = $curve->createRandomMultiplier();
+ }
+ if ($curve instanceof Curve25519 && self::$engines['libsodium']) {
+ //$r = pack('H*', '0900000000000000000000000000000000000000000000000000000000000000');
+ //$QA = sodium_crypto_scalarmult($dA->toBytes(), $r);
+ $QA = sodium_crypto_box_publickey_from_secretkey($dA->toBytes());
+ $privatekey->QA = [$curve->convertInteger(new BigInteger(strrev($QA), 256))];
+ } else {
+ $privatekey->QA = $curve->multiplyPoint($curve->getBasePoint(), $dA);
+ }
+ $privatekey->curve = $curve;
+
+ //$publickey = clone $privatekey;
+ //unset($publickey->dA);
+ //unset($publickey->x);
+
+ $privatekey->curveName = $curveName;
+ //$publickey->curveName = $curveName;
+
+ if ($privatekey->curve instanceof TwistedEdwardsCurve) {
+ return $privatekey->withHash($curve::HASH);
+ }
+
+ return $privatekey;
+ }
+
+ /**
+ * OnLoad Handler
+ *
+ * @return bool
+ */
+ protected static function onLoad(array $components)
+ {
+ if (!isset(self::$engines['PHP'])) {
+ self::useBestEngine();
+ }
+
+ if (!isset($components['dA']) && !isset($components['QA'])) {
+ $new = new Parameters();
+ $new->curve = $components['curve'];
+ return $new;
+ }
+
+ $new = isset($components['dA']) ?
+ new PrivateKey() :
+ new PublicKey();
+ $new->curve = $components['curve'];
+ $new->QA = $components['QA'];
+
+ if (isset($components['dA'])) {
+ $new->dA = $components['dA'];
+ $new->secret = $components['secret'];
+ }
+
+ if ($new->curve instanceof TwistedEdwardsCurve) {
+ return $new->withHash($components['curve']::HASH);
+ }
+
+ return $new;
+ }
+
+ /**
+ * Constructor
+ *
+ * PublicKey and PrivateKey objects can only be created from abstract RSA class
+ */
+ protected function __construct()
+ {
+ $this->sigFormat = self::validatePlugin('Signature', 'ASN1');
+ $this->shortFormat = 'ASN1';
+
+ parent::__construct();
+ }
+
+ /**
+ * Returns the curve
+ *
+ * Returns a string if it's a named curve, an array if not
+ *
+ * @return string|array
+ */
+ public function getCurve()
+ {
+ if ($this->curveName) {
+ return $this->curveName;
+ }
+
+ if ($this->curve instanceof MontgomeryCurve) {
+ $this->curveName = $this->curve instanceof Curve25519 ? 'Curve25519' : 'Curve448';
+ return $this->curveName;
+ }
+
+ if ($this->curve instanceof TwistedEdwardsCurve) {
+ $this->curveName = $this->curve instanceof Ed25519 ? 'Ed25519' : 'Ed448';
+ return $this->curveName;
+ }
+
+ $params = $this->getParameters()->toString('PKCS8', ['namedCurve' => true]);
+ $decoded = ASN1::extractBER($params);
+ $decoded = ASN1::decodeBER($decoded);
+ $decoded = ASN1::asn1map($decoded[0], ECParameters::MAP);
+ if (isset($decoded['namedCurve'])) {
+ $this->curveName = $decoded['namedCurve'];
+ return $decoded['namedCurve'];
+ }
+
+ if (!$namedCurves) {
+ PKCS1::useSpecifiedCurve();
+ }
+
+ return $decoded;
+ }
+
+ /**
+ * Returns the key size
+ *
+ * Quoting https://tools.ietf.org/html/rfc5656#section-2,
+ *
+ * "The size of a set of elliptic curve domain parameters on a prime
+ * curve is defined as the number of bits in the binary representation
+ * of the field order, commonly denoted by p. Size on a
+ * characteristic-2 curve is defined as the number of bits in the binary
+ * representation of the field, commonly denoted by m. A set of
+ * elliptic curve domain parameters defines a group of order n generated
+ * by a base point P"
+ *
+ * @return int
+ */
+ public function getLength()
+ {
+ return $this->curve->getLength();
+ }
+
+ /**
+ * Returns the current engine being used
+ *
+ * @see self::useInternalEngine()
+ * @see self::useBestEngine()
+ * @return string
+ */
+ public function getEngine()
+ {
+ if (!isset(self::$engines['PHP'])) {
+ self::useBestEngine();
+ }
+ if ($this->curve instanceof TwistedEdwardsCurve) {
+ return $this->curve instanceof Ed25519 && self::$engines['libsodium'] && !isset($this->context) ?
+ 'libsodium' : 'PHP';
+ }
+
+ return self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods()) ?
+ 'OpenSSL' : 'PHP';
+ }
+
+ /**
+ * Returns the public key coordinates as a string
+ *
+ * Used by ECDH
+ *
+ * @return string
+ */
+ public function getEncodedCoordinates()
+ {
+ if ($this->curve instanceof MontgomeryCurve) {
+ return strrev($this->QA[0]->toBytes(true));
+ }
+ if ($this->curve instanceof TwistedEdwardsCurve) {
+ return $this->curve->encodePoint($this->QA);
+ }
+ return "\4" . $this->QA[0]->toBytes(true) . $this->QA[1]->toBytes(true);
+ }
+
+ /**
+ * Returns the parameters
+ *
+ * @see self::getPublicKey()
+ * @param string $type optional
+ * @return mixed
+ */
+ public function getParameters($type = 'PKCS1')
+ {
+ $type = self::validatePlugin('Keys', $type, 'saveParameters');
+
+ $key = $type::saveParameters($this->curve);
+
+ return EC::load($key, 'PKCS1')
+ ->withHash($this->hash->getHash())
+ ->withSignatureFormat($this->shortFormat);
+ }
+
+ /**
+ * Determines the signature padding mode
+ *
+ * Valid values are: ASN1, SSH2, Raw
+ *
+ * @param string $format
+ */
+ public function withSignatureFormat($format)
+ {
+ if ($this->curve instanceof MontgomeryCurve) {
+ throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures');
+ }
+
+ $new = clone $this;
+ $new->shortFormat = $format;
+ $new->sigFormat = self::validatePlugin('Signature', $format);
+ return $new;
+ }
+
+ /**
+ * Returns the signature format currently being used
+ *
+ */
+ public function getSignatureFormat()
+ {
+ return $this->shortFormat;
+ }
+
+ /**
+ * Sets the context
+ *
+ * Used by Ed25519 / Ed448.
+ *
+ * @see self::sign()
+ * @see self::verify()
+ * @param string $context optional
+ */
+ public function withContext($context = null)
+ {
+ if (!$this->curve instanceof TwistedEdwardsCurve) {
+ throw new UnsupportedCurveException('Only Ed25519 and Ed448 support contexts');
+ }
+
+ $new = clone $this;
+ if (!isset($context)) {
+ $new->context = null;
+ return $new;
+ }
+ if (!is_string($context)) {
+ throw new \InvalidArgumentException('setContext expects a string');
+ }
+ if (strlen($context) > 255) {
+ throw new \LengthException('The context is supposed to be, at most, 255 bytes long');
+ }
+ $new->context = $context;
+ return $new;
+ }
+
+ /**
+ * Returns the signature format currently being used
+ *
+ */
+ public function getContext()
+ {
+ return $this->context;
+ }
+
+ /**
+ * Determines which hashing function should be used
+ *
+ * @param string $hash
+ */
+ public function withHash($hash)
+ {
+ if ($this->curve instanceof MontgomeryCurve) {
+ throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures');
+ }
+ if ($this->curve instanceof Ed25519 && $hash != 'sha512') {
+ throw new UnsupportedAlgorithmException('Ed25519 only supports sha512 as a hash');
+ }
+ if ($this->curve instanceof Ed448 && $hash != 'shake256-912') {
+ throw new UnsupportedAlgorithmException('Ed448 only supports shake256 with a length of 114 bytes');
+ }
+
+ return parent::withHash($hash);
+ }
+
+ /**
+ * __toString() magic method
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ if ($this->curve instanceof MontgomeryCurve) {
+ return '';
+ }
+
+ return parent::__toString();
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Base.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Base.php
new file mode 100644
index 000000000..d76562d0d
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Base.php
@@ -0,0 +1,218 @@
+<?php
+
+/**
+ * Curve methods common to all curves
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\BaseCurves;
+
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Base
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Base
+{
+ /**
+ * The Order
+ *
+ * @var BigInteger
+ */
+ protected $order;
+
+ /**
+ * Finite Field Integer factory
+ *
+ * @var FiniteField\Integer
+ */
+ protected $factory;
+
+ /**
+ * Returns a random integer
+ *
+ * @return object
+ */
+ public function randomInteger()
+ {
+ return $this->factory->randomInteger();
+ }
+
+ /**
+ * Converts a BigInteger to a FiniteField\Integer integer
+ *
+ * @return object
+ */
+ public function convertInteger(BigInteger $x)
+ {
+ return $this->factory->newInteger($x);
+ }
+
+ /**
+ * Returns the length, in bytes, of the modulo
+ *
+ * @return integer
+ */
+ public function getLengthInBytes()
+ {
+ return $this->factory->getLengthInBytes();
+ }
+
+ /**
+ * Returns the length, in bits, of the modulo
+ *
+ * @return integer
+ */
+ public function getLength()
+ {
+ return $this->factory->getLength();
+ }
+
+ /**
+ * Multiply a point on the curve by a scalar
+ *
+ * Uses the montgomery ladder technique as described here:
+ *
+ * https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Montgomery_ladder
+ * https://github.com/phpecc/phpecc/issues/16#issuecomment-59176772
+ *
+ * @return array
+ */
+ public function multiplyPoint(array $p, BigInteger $d)
+ {
+ $alreadyInternal = isset($p[2]);
+ $r = $alreadyInternal ?
+ [[], $p] :
+ [[], $this->convertToInternal($p)];
+
+ $d = $d->toBits();
+ for ($i = 0; $i < strlen($d); $i++) {
+ $d_i = (int) $d[$i];
+ $r[1 - $d_i] = $this->addPoint($r[0], $r[1]);
+ $r[$d_i] = $this->doublePoint($r[$d_i]);
+ }
+
+ return $alreadyInternal ? $r[0] : $this->convertToAffine($r[0]);
+ }
+
+ /**
+ * Creates a random scalar multiplier
+ *
+ * @return BigInteger
+ */
+ public function createRandomMultiplier()
+ {
+ static $one;
+ if (!isset($one)) {
+ $one = new BigInteger(1);
+ }
+
+ return BigInteger::randomRange($one, $this->order->subtract($one));
+ }
+
+ /**
+ * Performs range check
+ */
+ public function rangeCheck(BigInteger $x)
+ {
+ static $zero;
+ if (!isset($zero)) {
+ $zero = new BigInteger();
+ }
+
+ if (!isset($this->order)) {
+ throw new \RuntimeException('setOrder needs to be called before this method');
+ }
+ if ($x->compare($this->order) > 0 || $x->compare($zero) <= 0) {
+ throw new \RangeException('x must be between 1 and the order of the curve');
+ }
+ }
+
+ /**
+ * Sets the Order
+ */
+ public function setOrder(BigInteger $order)
+ {
+ $this->order = $order;
+ }
+
+ /**
+ * Returns the Order
+ *
+ * @return BigInteger
+ */
+ public function getOrder()
+ {
+ return $this->order;
+ }
+
+ /**
+ * Use a custom defined modular reduction function
+ *
+ * @return object
+ */
+ public function setReduction(callable $func)
+ {
+ $this->factory->setReduction($func);
+ }
+
+ /**
+ * Returns the affine point
+ *
+ * @return object[]
+ */
+ public function convertToAffine(array $p)
+ {
+ return $p;
+ }
+
+ /**
+ * Converts an affine point to a jacobian coordinate
+ *
+ * @return object[]
+ */
+ public function convertToInternal(array $p)
+ {
+ return $p;
+ }
+
+ /**
+ * Negates a point
+ *
+ * @return object[]
+ */
+ public function negatePoint(array $p)
+ {
+ $temp = [
+ $p[0],
+ $p[1]->negate()
+ ];
+ if (isset($p[2])) {
+ $temp[] = $p[2];
+ }
+ return $temp;
+ }
+
+ /**
+ * Multiply and Add Points
+ *
+ * @return int[]
+ */
+ public function multiplyAddPoints(array $points, array $scalars)
+ {
+ $p1 = $this->convertToInternal($points[0]);
+ $p2 = $this->convertToInternal($points[1]);
+ $p1 = $this->multiplyPoint($p1, $scalars[0]);
+ $p2 = $this->multiplyPoint($p2, $scalars[1]);
+ $r = $this->addPoint($p1, $p2);
+ return $this->convertToAffine($r);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Binary.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Binary.php
new file mode 100644
index 000000000..66da11da7
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Binary.php
@@ -0,0 +1,373 @@
+<?php
+
+/**
+ * Curves over y^2 + x*y = x^3 + a*x^2 + b
+ *
+ * These are curves used in SEC 2 over prime fields: http://www.secg.org/SEC2-Ver-1.0.pdf
+ * The curve is a weierstrass curve with a[3] and a[2] set to 0.
+ *
+ * Uses Jacobian Coordinates for speed if able:
+ *
+ * https://en.wikipedia.org/wiki/Jacobian_curve
+ * https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\BaseCurves;
+
+use phpseclib3\Math\BigInteger;
+use phpseclib3\Math\BinaryField;
+use phpseclib3\Math\BinaryField\Integer as BinaryInteger;
+
+/**
+ * Curves over y^2 + x*y = x^3 + a*x^2 + b
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class Binary extends Base
+{
+ /**
+ * Binary Field Integer factory
+ *
+ * @var BinaryField
+ */
+ protected $factory;
+
+ /**
+ * Cofficient for x^1
+ *
+ * @var object
+ */
+ protected $a;
+
+ /**
+ * Cofficient for x^0
+ *
+ * @var object
+ */
+ protected $b;
+
+ /**
+ * Base Point
+ *
+ * @var object
+ */
+ protected $p;
+
+ /**
+ * The number one over the specified finite field
+ *
+ * @var object
+ */
+ protected $one;
+
+ /**
+ * The modulo
+ *
+ * @var BigInteger
+ */
+ protected $modulo;
+
+ /**
+ * The Order
+ *
+ * @var BigInteger
+ */
+ protected $order;
+
+ /**
+ * Sets the modulo
+ */
+ public function setModulo(...$modulo)
+ {
+ $this->modulo = $modulo;
+ $this->factory = new BinaryField(...$modulo);
+
+ $this->one = $this->factory->newInteger("\1");
+ }
+
+ /**
+ * Set coefficients a and b
+ *
+ * @param string $a
+ * @param string $b
+ */
+ public function setCoefficients($a, $b)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+ $this->a = $this->factory->newInteger(pack('H*', $a));
+ $this->b = $this->factory->newInteger(pack('H*', $b));
+ }
+
+ /**
+ * Set x and y coordinates for the base point
+ *
+ * @param string|BinaryInteger $x
+ * @param string|BinaryInteger $y
+ */
+ public function setBasePoint($x, $y)
+ {
+ switch (true) {
+ case !is_string($x) && !$x instanceof BinaryInteger:
+ throw new \UnexpectedValueException('Argument 1 passed to Binary::setBasePoint() must be a string or an instance of BinaryField\Integer');
+ case !is_string($y) && !$y instanceof BinaryInteger:
+ throw new \UnexpectedValueException('Argument 2 passed to Binary::setBasePoint() must be a string or an instance of BinaryField\Integer');
+ }
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+ $this->p = [
+ is_string($x) ? $this->factory->newInteger(pack('H*', $x)) : $x,
+ is_string($y) ? $this->factory->newInteger(pack('H*', $y)) : $y
+ ];
+ }
+
+ /**
+ * Retrieve the base point as an array
+ *
+ * @return array
+ */
+ public function getBasePoint()
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+ /*
+ if (!isset($this->p)) {
+ throw new \RuntimeException('setBasePoint needs to be called before this method');
+ }
+ */
+ return $this->p;
+ }
+
+ /**
+ * Adds two points on the curve
+ *
+ * @return FiniteField[]
+ */
+ public function addPoint(array $p, array $q)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+
+ if (!count($p) || !count($q)) {
+ if (count($q)) {
+ return $q;
+ }
+ if (count($p)) {
+ return $p;
+ }
+ return [];
+ }
+
+ if (!isset($p[2]) || !isset($q[2])) {
+ throw new \RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa');
+ }
+
+ if ($p[0]->equals($q[0])) {
+ return !$p[1]->equals($q[1]) ? [] : $this->doublePoint($p);
+ }
+
+ // formulas from http://hyperelliptic.org/EFD/g12o/auto-shortw-jacobian.html
+
+ list($x1, $y1, $z1) = $p;
+ list($x2, $y2, $z2) = $q;
+
+ $o1 = $z1->multiply($z1);
+ $b = $x2->multiply($o1);
+
+ if ($z2->equals($this->one)) {
+ $d = $y2->multiply($o1)->multiply($z1);
+ $e = $x1->add($b);
+ $f = $y1->add($d);
+ $z3 = $e->multiply($z1);
+ $h = $f->multiply($x2)->add($z3->multiply($y2));
+ $i = $f->add($z3);
+ $g = $z3->multiply($z3);
+ $p1 = $this->a->multiply($g);
+ $p2 = $f->multiply($i);
+ $p3 = $e->multiply($e)->multiply($e);
+ $x3 = $p1->add($p2)->add($p3);
+ $y3 = $i->multiply($x3)->add($g->multiply($h));
+
+ return [$x3, $y3, $z3];
+ }
+
+ $o2 = $z2->multiply($z2);
+ $a = $x1->multiply($o2);
+ $c = $y1->multiply($o2)->multiply($z2);
+ $d = $y2->multiply($o1)->multiply($z1);
+ $e = $a->add($b);
+ $f = $c->add($d);
+ $g = $e->multiply($z1);
+ $h = $f->multiply($x2)->add($g->multiply($y2));
+ $z3 = $g->multiply($z2);
+ $i = $f->add($z3);
+ $p1 = $this->a->multiply($z3->multiply($z3));
+ $p2 = $f->multiply($i);
+ $p3 = $e->multiply($e)->multiply($e);
+ $x3 = $p1->add($p2)->add($p3);
+ $y3 = $i->multiply($x3)->add($g->multiply($g)->multiply($h));
+
+ return [$x3, $y3, $z3];
+ }
+
+ /**
+ * Doubles a point on a curve
+ *
+ * @return FiniteField[]
+ */
+ public function doublePoint(array $p)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+
+ if (!count($p)) {
+ return [];
+ }
+
+ if (!isset($p[2])) {
+ throw new \RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa');
+ }
+
+ // formulas from http://hyperelliptic.org/EFD/g12o/auto-shortw-jacobian.html
+
+ list($x1, $y1, $z1) = $p;
+
+ $a = $x1->multiply($x1);
+ $b = $a->multiply($a);
+
+ if ($z1->equals($this->one)) {
+ $x3 = $b->add($this->b);
+ $z3 = clone $x1;
+ $p1 = $a->add($y1)->add($z3)->multiply($this->b);
+ $p2 = $a->add($y1)->multiply($b);
+ $y3 = $p1->add($p2);
+
+ return [$x3, $y3, $z3];
+ }
+
+ $c = $z1->multiply($z1);
+ $d = $c->multiply($c);
+ $x3 = $b->add($this->b->multiply($d->multiply($d)));
+ $z3 = $x1->multiply($c);
+ $p1 = $b->multiply($z3);
+ $p2 = $a->add($y1->multiply($z1))->add($z3)->multiply($x3);
+ $y3 = $p1->add($p2);
+
+ return [$x3, $y3, $z3];
+ }
+
+ /**
+ * Returns the X coordinate and the derived Y coordinate
+ *
+ * Not supported because it is covered by patents.
+ * Quoting https://www.openssl.org/docs/man1.1.0/apps/ecparam.html ,
+ *
+ * "Due to patent issues the compressed option is disabled by default for binary curves
+ * and can be enabled by defining the preprocessor macro OPENSSL_EC_BIN_PT_COMP at
+ * compile time."
+ *
+ * @return array
+ */
+ public function derivePoint($m)
+ {
+ throw new \RuntimeException('Point compression on binary finite field elliptic curves is not supported');
+ }
+
+ /**
+ * Tests whether or not the x / y values satisfy the equation
+ *
+ * @return boolean
+ */
+ public function verifyPoint(array $p)
+ {
+ list($x, $y) = $p;
+ $lhs = $y->multiply($y);
+ $lhs = $lhs->add($x->multiply($y));
+ $x2 = $x->multiply($x);
+ $x3 = $x2->multiply($x);
+ $rhs = $x3->add($this->a->multiply($x2))->add($this->b);
+
+ return $lhs->equals($rhs);
+ }
+
+ /**
+ * Returns the modulo
+ *
+ * @return BigInteger
+ */
+ public function getModulo()
+ {
+ return $this->modulo;
+ }
+
+ /**
+ * Returns the a coefficient
+ *
+ * @return \phpseclib3\Math\PrimeField\Integer
+ */
+ public function getA()
+ {
+ return $this->a;
+ }
+
+ /**
+ * Returns the a coefficient
+ *
+ * @return \phpseclib3\Math\PrimeField\Integer
+ */
+ public function getB()
+ {
+ return $this->b;
+ }
+
+ /**
+ * Returns the affine point
+ *
+ * A Jacobian Coordinate is of the form (x, y, z).
+ * To convert a Jacobian Coordinate to an Affine Point
+ * you do (x / z^2, y / z^3)
+ *
+ * @return \phpseclib3\Math\PrimeField\Integer[]
+ */
+ public function convertToAffine(array $p)
+ {
+ if (!isset($p[2])) {
+ return $p;
+ }
+ list($x, $y, $z) = $p;
+ $z = $this->one->divide($z);
+ $z2 = $z->multiply($z);
+ return [
+ $x->multiply($z2),
+ $y->multiply($z2)->multiply($z)
+ ];
+ }
+
+ /**
+ * Converts an affine point to a jacobian coordinate
+ *
+ * @return \phpseclib3\Math\PrimeField\Integer[]
+ */
+ public function convertToInternal(array $p)
+ {
+ if (isset($p[2])) {
+ return $p;
+ }
+
+ $p[2] = clone $this->one;
+ $p['fresh'] = true;
+ return $p;
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/KoblitzPrime.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/KoblitzPrime.php
new file mode 100644
index 000000000..d8492ebc2
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/KoblitzPrime.php
@@ -0,0 +1,335 @@
+<?php
+
+/**
+ * Generalized Koblitz Curves over y^2 = x^3 + b.
+ *
+ * According to http://www.secg.org/SEC2-Ver-1.0.pdf Koblitz curves are over the GF(2**m)
+ * finite field. Both the $a$ and $b$ coefficients are either 0 or 1. However, SEC2
+ * generalizes the definition to include curves over GF(P) "which possess an efficiently
+ * computable endomorphism".
+ *
+ * For these generalized Koblitz curves $b$ doesn't have to be 0 or 1. Whether or not $a$
+ * has any restrictions on it is unclear, however, for all the GF(P) Koblitz curves defined
+ * in SEC2 v1.0 $a$ is $0$ so all of the methods defined herein will assume that it is.
+ *
+ * I suppose we could rename the $b$ coefficient to $a$, however, the documentation refers
+ * to $b$ so we'll just keep it.
+ *
+ * If a later version of SEC2 comes out wherein some $a$ values are non-zero we can create a
+ * new method for those. eg. KoblitzA1Prime.php or something.
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\BaseCurves;
+
+use phpseclib3\Math\BigInteger;
+use phpseclib3\Math\PrimeField;
+
+/**
+ * Curves over y^2 = x^3 + b
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class KoblitzPrime extends Prime
+{
+ /**
+ * Basis
+ *
+ * @var list<array{a: BigInteger, b: BigInteger}>
+ */
+ protected $basis;
+
+ /**
+ * Beta
+ *
+ * @var PrimeField\Integer
+ */
+ protected $beta;
+
+ // don't overwrite setCoefficients() with one that only accepts one parameter so that
+ // one might be able to switch between KoblitzPrime and Prime more easily (for benchmarking
+ // purposes).
+
+ /**
+ * Multiply and Add Points
+ *
+ * Uses a efficiently computable endomorphism to achieve a slight speedup
+ *
+ * Adapted from:
+ * https://github.com/indutny/elliptic/blob/725bd91/lib/elliptic/curve/short.js#L219
+ *
+ * @return int[]
+ */
+ public function multiplyAddPoints(array $points, array $scalars)
+ {
+ static $zero, $one, $two;
+ if (!isset($two)) {
+ $two = new BigInteger(2);
+ $one = new BigInteger(1);
+ }
+
+ if (!isset($this->beta)) {
+ // get roots
+ $inv = $this->one->divide($this->two)->negate();
+ $s = $this->three->negate()->squareRoot()->multiply($inv);
+ $betas = [
+ $inv->add($s),
+ $inv->subtract($s)
+ ];
+ $this->beta = $betas[0]->compare($betas[1]) < 0 ? $betas[0] : $betas[1];
+ //echo strtoupper($this->beta->toHex(true)) . "\n"; exit;
+ }
+
+ if (!isset($this->basis)) {
+ $factory = new PrimeField($this->order);
+ $tempOne = $factory->newInteger($one);
+ $tempTwo = $factory->newInteger($two);
+ $tempThree = $factory->newInteger(new BigInteger(3));
+
+ $inv = $tempOne->divide($tempTwo)->negate();
+ $s = $tempThree->negate()->squareRoot()->multiply($inv);
+
+ $lambdas = [
+ $inv->add($s),
+ $inv->subtract($s)
+ ];
+
+ $lhs = $this->multiplyPoint($this->p, $lambdas[0])[0];
+ $rhs = $this->p[0]->multiply($this->beta);
+ $lambda = $lhs->equals($rhs) ? $lambdas[0] : $lambdas[1];
+
+ $this->basis = static::extendedGCD($lambda->toBigInteger(), $this->order);
+ ///*
+ foreach ($this->basis as $basis) {
+ echo strtoupper($basis['a']->toHex(true)) . "\n";
+ echo strtoupper($basis['b']->toHex(true)) . "\n\n";
+ }
+ exit;
+ //*/
+ }
+
+ $npoints = $nscalars = [];
+ for ($i = 0; $i < count($points); $i++) {
+ $p = $points[$i];
+ $k = $scalars[$i]->toBigInteger();
+
+ // begin split
+ list($v1, $v2) = $this->basis;
+
+ $c1 = $v2['b']->multiply($k);
+ list($c1, $r) = $c1->divide($this->order);
+ if ($this->order->compare($r->multiply($two)) <= 0) {
+ $c1 = $c1->add($one);
+ }
+
+ $c2 = $v1['b']->negate()->multiply($k);
+ list($c2, $r) = $c2->divide($this->order);
+ if ($this->order->compare($r->multiply($two)) <= 0) {
+ $c2 = $c2->add($one);
+ }
+
+ $p1 = $c1->multiply($v1['a']);
+ $p2 = $c2->multiply($v2['a']);
+ $q1 = $c1->multiply($v1['b']);
+ $q2 = $c2->multiply($v2['b']);
+
+ $k1 = $k->subtract($p1)->subtract($p2);
+ $k2 = $q1->add($q2)->negate();
+ // end split
+
+ $beta = [
+ $p[0]->multiply($this->beta),
+ $p[1],
+ clone $this->one
+ ];
+
+ if (isset($p['naf'])) {
+ $beta['naf'] = array_map(function ($p) {
+ return [
+ $p[0]->multiply($this->beta),
+ $p[1],
+ clone $this->one
+ ];
+ }, $p['naf']);
+ $beta['nafwidth'] = $p['nafwidth'];
+ }
+
+ if ($k1->isNegative()) {
+ $k1 = $k1->negate();
+ $p = $this->negatePoint($p);
+ }
+
+ if ($k2->isNegative()) {
+ $k2 = $k2->negate();
+ $beta = $this->negatePoint($beta);
+ }
+
+ $pos = 2 * $i;
+ $npoints[$pos] = $p;
+ $nscalars[$pos] = $this->factory->newInteger($k1);
+
+ $pos++;
+ $npoints[$pos] = $beta;
+ $nscalars[$pos] = $this->factory->newInteger($k2);
+ }
+
+ return parent::multiplyAddPoints($npoints, $nscalars);
+ }
+
+ /**
+ * Returns the numerator and denominator of the slope
+ *
+ * @return FiniteField[]
+ */
+ protected function doublePointHelper(array $p)
+ {
+ $numerator = $this->three->multiply($p[0])->multiply($p[0]);
+ $denominator = $this->two->multiply($p[1]);
+ return [$numerator, $denominator];
+ }
+
+ /**
+ * Doubles a jacobian coordinate on the curve
+ *
+ * See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l
+ *
+ * @return FiniteField[]
+ */
+ protected function jacobianDoublePoint(array $p)
+ {
+ list($x1, $y1, $z1) = $p;
+ $a = $x1->multiply($x1);
+ $b = $y1->multiply($y1);
+ $c = $b->multiply($b);
+ $d = $x1->add($b);
+ $d = $d->multiply($d)->subtract($a)->subtract($c)->multiply($this->two);
+ $e = $this->three->multiply($a);
+ $f = $e->multiply($e);
+ $x3 = $f->subtract($this->two->multiply($d));
+ $y3 = $e->multiply($d->subtract($x3))->subtract(
+ $this->eight->multiply($c)
+ );
+ $z3 = $this->two->multiply($y1)->multiply($z1);
+ return [$x3, $y3, $z3];
+ }
+
+ /**
+ * Doubles a "fresh" jacobian coordinate on the curve
+ *
+ * See http://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-mdbl-2007-bl
+ *
+ * @return FiniteField[]
+ */
+ protected function jacobianDoublePointMixed(array $p)
+ {
+ list($x1, $y1) = $p;
+ $xx = $x1->multiply($x1);
+ $yy = $y1->multiply($y1);
+ $yyyy = $yy->multiply($yy);
+ $s = $x1->add($yy);
+ $s = $s->multiply($s)->subtract($xx)->subtract($yyyy)->multiply($this->two);
+ $m = $this->three->multiply($xx);
+ $t = $m->multiply($m)->subtract($this->two->multiply($s));
+ $x3 = $t;
+ $y3 = $s->subtract($t);
+ $y3 = $m->multiply($y3)->subtract($this->eight->multiply($yyyy));
+ $z3 = $this->two->multiply($y1);
+ return [$x3, $y3, $z3];
+ }
+
+ /**
+ * Tests whether or not the x / y values satisfy the equation
+ *
+ * @return boolean
+ */
+ public function verifyPoint(array $p)
+ {
+ list($x, $y) = $p;
+ $lhs = $y->multiply($y);
+ $temp = $x->multiply($x)->multiply($x);
+ $rhs = $temp->add($this->b);
+
+ return $lhs->equals($rhs);
+ }
+
+ /**
+ * Calculates the parameters needed from the Euclidean algorithm as discussed at
+ * http://diamond.boisestate.edu/~liljanab/MATH308/GuideToECC.pdf#page=148
+ *
+ * @param BigInteger $u
+ * @param BigInteger $v
+ * @return BigInteger[]
+ */
+ protected static function extendedGCD(BigInteger $u, BigInteger $v)
+ {
+ $one = new BigInteger(1);
+ $zero = new BigInteger();
+
+ $a = clone $one;
+ $b = clone $zero;
+ $c = clone $zero;
+ $d = clone $one;
+
+ $stop = $v->bitwise_rightShift($v->getLength() >> 1);
+
+ $a1 = clone $zero;
+ $b1 = clone $zero;
+ $a2 = clone $zero;
+ $b2 = clone $zero;
+
+ $postGreatestIndex = 0;
+
+ while (!$v->equals($zero)) {
+ list($q) = $u->divide($v);
+
+ $temp = $u;
+ $u = $v;
+ $v = $temp->subtract($v->multiply($q));
+
+ $temp = $a;
+ $a = $c;
+ $c = $temp->subtract($a->multiply($q));
+
+ $temp = $b;
+ $b = $d;
+ $d = $temp->subtract($b->multiply($q));
+
+ if ($v->compare($stop) > 0) {
+ $a0 = $v;
+ $b0 = $c;
+ } else {
+ $postGreatestIndex++;
+ }
+
+ if ($postGreatestIndex == 1) {
+ $a1 = $v;
+ $b1 = $c->negate();
+ }
+
+ if ($postGreatestIndex == 2) {
+ $rhs = $a0->multiply($a0)->add($b0->multiply($b0));
+ $lhs = $v->multiply($v)->add($b->multiply($b));
+ if ($lhs->compare($rhs) <= 0) {
+ $a2 = $a0;
+ $b2 = $b0->negate();
+ } else {
+ $a2 = $v;
+ $b2 = $c->negate();
+ }
+
+ break;
+ }
+ }
+
+ return [
+ ['a' => $a1, 'b' => $b1],
+ ['a' => $a2, 'b' => $b2]
+ ];
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Montgomery.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Montgomery.php
new file mode 100644
index 000000000..431f9575c
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Montgomery.php
@@ -0,0 +1,279 @@
+<?php
+
+/**
+ * Curves over y^2 = x^3 + a*x + x
+ *
+ * Technically, a Montgomery curve has a coefficient for y^2 but for Curve25519 and Curve448 that
+ * coefficient is 1.
+ *
+ * Curve25519 and Curve448 do not make use of the y coordinate, which makes it unsuitable for use
+ * with ECDSA / EdDSA. A few other differences between Curve25519 and Ed25519 are discussed at
+ * https://crypto.stackexchange.com/a/43058/4520
+ *
+ * More info:
+ *
+ * https://en.wikipedia.org/wiki/Montgomery_curve
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2019 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\BaseCurves;
+
+use phpseclib3\Crypt\EC\Curves\Curve25519;
+use phpseclib3\Math\BigInteger;
+use phpseclib3\Math\PrimeField;
+use phpseclib3\Math\PrimeField\Integer as PrimeInteger;
+
+/**
+ * Curves over y^2 = x^3 + a*x + x
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class Montgomery extends Base
+{
+ /**
+ * Prime Field Integer factory
+ *
+ * @var PrimeField
+ */
+ protected $factory;
+
+ /**
+ * Cofficient for x
+ *
+ * @var object
+ */
+ protected $a;
+
+ /**
+ * Constant used for point doubling
+ *
+ * @var object
+ */
+ protected $a24;
+
+ /**
+ * The Number Zero
+ *
+ * @var object
+ */
+ protected $zero;
+
+ /**
+ * The Number One
+ *
+ * @var object
+ */
+ protected $one;
+
+ /**
+ * Base Point
+ *
+ * @var object
+ */
+ protected $p;
+
+ /**
+ * The modulo
+ *
+ * @var BigInteger
+ */
+ protected $modulo;
+
+ /**
+ * The Order
+ *
+ * @var BigInteger
+ */
+ protected $order;
+
+ /**
+ * Sets the modulo
+ */
+ public function setModulo(BigInteger $modulo)
+ {
+ $this->modulo = $modulo;
+ $this->factory = new PrimeField($modulo);
+ $this->zero = $this->factory->newInteger(new BigInteger());
+ $this->one = $this->factory->newInteger(new BigInteger(1));
+ }
+
+ /**
+ * Set coefficients a
+ */
+ public function setCoefficients(BigInteger $a)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+ $this->a = $this->factory->newInteger($a);
+ $two = $this->factory->newInteger(new BigInteger(2));
+ $four = $this->factory->newInteger(new BigInteger(4));
+ $this->a24 = $this->a->subtract($two)->divide($four);
+ }
+
+ /**
+ * Set x and y coordinates for the base point
+ *
+ * @param BigInteger|PrimeInteger $x
+ * @param BigInteger|PrimeInteger $y
+ * @return PrimeInteger[]
+ */
+ public function setBasePoint($x, $y)
+ {
+ switch (true) {
+ case !$x instanceof BigInteger && !$x instanceof PrimeInteger:
+ throw new \UnexpectedValueException('Argument 1 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer');
+ case !$y instanceof BigInteger && !$y instanceof PrimeInteger:
+ throw new \UnexpectedValueException('Argument 2 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer');
+ }
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+ $this->p = [
+ $x instanceof BigInteger ? $this->factory->newInteger($x) : $x,
+ $y instanceof BigInteger ? $this->factory->newInteger($y) : $y
+ ];
+ }
+
+ /**
+ * Retrieve the base point as an array
+ *
+ * @return array
+ */
+ public function getBasePoint()
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+ /*
+ if (!isset($this->p)) {
+ throw new \RuntimeException('setBasePoint needs to be called before this method');
+ }
+ */
+ return $this->p;
+ }
+
+ /**
+ * Doubles and adds a point on a curve
+ *
+ * See https://tools.ietf.org/html/draft-ietf-tls-curve25519-01#appendix-A.1.3
+ *
+ * @return FiniteField[][]
+ */
+ private function doubleAndAddPoint(array $p, array $q, PrimeInteger $x1)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+
+ if (!count($p) || !count($q)) {
+ return [];
+ }
+
+ if (!isset($p[1])) {
+ throw new \RuntimeException('Affine coordinates need to be manually converted to XZ coordinates');
+ }
+
+ list($x2, $z2) = $p;
+ list($x3, $z3) = $q;
+
+ $a = $x2->add($z2);
+ $aa = $a->multiply($a);
+ $b = $x2->subtract($z2);
+ $bb = $b->multiply($b);
+ $e = $aa->subtract($bb);
+ $c = $x3->add($z3);
+ $d = $x3->subtract($z3);
+ $da = $d->multiply($a);
+ $cb = $c->multiply($b);
+ $temp = $da->add($cb);
+ $x5 = $temp->multiply($temp);
+ $temp = $da->subtract($cb);
+ $z5 = $x1->multiply($temp->multiply($temp));
+ $x4 = $aa->multiply($bb);
+ $temp = static::class == Curve25519::class ? $bb : $aa;
+ $z4 = $e->multiply($temp->add($this->a24->multiply($e)));
+
+ return [
+ [$x4, $z4],
+ [$x5, $z5]
+ ];
+ }
+
+ /**
+ * Multiply a point on the curve by a scalar
+ *
+ * Uses the montgomery ladder technique as described here:
+ *
+ * https://en.wikipedia.org/wiki/Elliptic_curve_point_multiplication#Montgomery_ladder
+ * https://github.com/phpecc/phpecc/issues/16#issuecomment-59176772
+ *
+ * @return array
+ */
+ public function multiplyPoint(array $p, BigInteger $d)
+ {
+ $p1 = [$this->one, $this->zero];
+ $alreadyInternal = isset($p[1]);
+ $p2 = $this->convertToInternal($p);
+ $x = $p[0];
+
+ $b = $d->toBits();
+ $b = str_pad($b, 256, '0', STR_PAD_LEFT);
+ for ($i = 0; $i < strlen($b); $i++) {
+ $b_i = (int) $b[$i];
+ if ($b_i) {
+ list($p2, $p1) = $this->doubleAndAddPoint($p2, $p1, $x);
+ } else {
+ list($p1, $p2) = $this->doubleAndAddPoint($p1, $p2, $x);
+ }
+ }
+
+ return $alreadyInternal ? $p1 : $this->convertToAffine($p1);
+ }
+
+ /**
+ * Converts an affine point to an XZ coordinate
+ *
+ * From https://hyperelliptic.org/EFD/g1p/auto-montgom-xz.html
+ *
+ * XZ coordinates represent x y as X Z satsfying the following equations:
+ *
+ * x=X/Z
+ *
+ * @return PrimeInteger[]
+ */
+ public function convertToInternal(array $p)
+ {
+ if (empty($p)) {
+ return [clone $this->zero, clone $this->one];
+ }
+
+ if (isset($p[1])) {
+ return $p;
+ }
+
+ $p[1] = clone $this->one;
+
+ return $p;
+ }
+
+ /**
+ * Returns the affine point
+ *
+ * @return PrimeInteger[]
+ */
+ public function convertToAffine(array $p)
+ {
+ if (!isset($p[1])) {
+ return $p;
+ }
+ list($x, $z) = $p;
+ return [$x->divide($z)];
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Prime.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Prime.php
new file mode 100644
index 000000000..b1970557f
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/Prime.php
@@ -0,0 +1,785 @@
+<?php
+
+/**
+ * Curves over y^2 = x^3 + a*x + b
+ *
+ * These are curves used in SEC 2 over prime fields: http://www.secg.org/SEC2-Ver-1.0.pdf
+ * The curve is a weierstrass curve with a[1], a[3] and a[2] set to 0.
+ *
+ * Uses Jacobian Coordinates for speed if able:
+ *
+ * https://en.wikipedia.org/wiki/Jacobian_curve
+ * https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\BaseCurves;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Math\BigInteger;
+use phpseclib3\Math\Common\FiniteField\Integer;
+use phpseclib3\Math\PrimeField;
+use phpseclib3\Math\PrimeField\Integer as PrimeInteger;
+
+/**
+ * Curves over y^2 = x^3 + a*x + b
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class Prime extends Base
+{
+ /**
+ * Prime Field Integer factory
+ *
+ * @var \phpseclib3\Math\PrimeFields
+ */
+ protected $factory;
+
+ /**
+ * Cofficient for x^1
+ *
+ * @var object
+ */
+ protected $a;
+
+ /**
+ * Cofficient for x^0
+ *
+ * @var object
+ */
+ protected $b;
+
+ /**
+ * Base Point
+ *
+ * @var object
+ */
+ protected $p;
+
+ /**
+ * The number one over the specified finite field
+ *
+ * @var object
+ */
+ protected $one;
+
+ /**
+ * The number two over the specified finite field
+ *
+ * @var object
+ */
+ protected $two;
+
+ /**
+ * The number three over the specified finite field
+ *
+ * @var object
+ */
+ protected $three;
+
+ /**
+ * The number four over the specified finite field
+ *
+ * @var object
+ */
+ protected $four;
+
+ /**
+ * The number eight over the specified finite field
+ *
+ * @var object
+ */
+ protected $eight;
+
+ /**
+ * The modulo
+ *
+ * @var BigInteger
+ */
+ protected $modulo;
+
+ /**
+ * The Order
+ *
+ * @var BigInteger
+ */
+ protected $order;
+
+ /**
+ * Sets the modulo
+ */
+ public function setModulo(BigInteger $modulo)
+ {
+ $this->modulo = $modulo;
+ $this->factory = new PrimeField($modulo);
+ $this->two = $this->factory->newInteger(new BigInteger(2));
+ $this->three = $this->factory->newInteger(new BigInteger(3));
+ // used by jacobian coordinates
+ $this->one = $this->factory->newInteger(new BigInteger(1));
+ $this->four = $this->factory->newInteger(new BigInteger(4));
+ $this->eight = $this->factory->newInteger(new BigInteger(8));
+ }
+
+ /**
+ * Set coefficients a and b
+ */
+ public function setCoefficients(BigInteger $a, BigInteger $b)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+ $this->a = $this->factory->newInteger($a);
+ $this->b = $this->factory->newInteger($b);
+ }
+
+ /**
+ * Set x and y coordinates for the base point
+ *
+ * @param BigInteger|PrimeInteger $x
+ * @param BigInteger|PrimeInteger $y
+ * @return PrimeInteger[]
+ */
+ public function setBasePoint($x, $y)
+ {
+ switch (true) {
+ case !$x instanceof BigInteger && !$x instanceof PrimeInteger:
+ throw new \UnexpectedValueException('Argument 1 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer');
+ case !$y instanceof BigInteger && !$y instanceof PrimeInteger:
+ throw new \UnexpectedValueException('Argument 2 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer');
+ }
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+ $this->p = [
+ $x instanceof BigInteger ? $this->factory->newInteger($x) : $x,
+ $y instanceof BigInteger ? $this->factory->newInteger($y) : $y
+ ];
+ }
+
+ /**
+ * Retrieve the base point as an array
+ *
+ * @return array
+ */
+ public function getBasePoint()
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+ /*
+ if (!isset($this->p)) {
+ throw new \RuntimeException('setBasePoint needs to be called before this method');
+ }
+ */
+ return $this->p;
+ }
+
+ /**
+ * Adds two "fresh" jacobian form on the curve
+ *
+ * @return FiniteField[]
+ */
+ protected function jacobianAddPointMixedXY(array $p, array $q)
+ {
+ list($u1, $s1) = $p;
+ list($u2, $s2) = $q;
+ if ($u1->equals($u2)) {
+ if (!$s1->equals($s2)) {
+ return [];
+ } else {
+ return $this->doublePoint($p);
+ }
+ }
+ $h = $u2->subtract($u1);
+ $r = $s2->subtract($s1);
+ $h2 = $h->multiply($h);
+ $h3 = $h2->multiply($h);
+ $v = $u1->multiply($h2);
+ $x3 = $r->multiply($r)->subtract($h3)->subtract($v->multiply($this->two));
+ $y3 = $r->multiply(
+ $v->subtract($x3)
+ )->subtract(
+ $s1->multiply($h3)
+ );
+ return [$x3, $y3, $h];
+ }
+
+ /**
+ * Adds one "fresh" jacobian form on the curve
+ *
+ * The second parameter should be the "fresh" one
+ *
+ * @return FiniteField[]
+ */
+ protected function jacobianAddPointMixedX(array $p, array $q)
+ {
+ list($u1, $s1, $z1) = $p;
+ list($x2, $y2) = $q;
+
+ $z12 = $z1->multiply($z1);
+
+ $u2 = $x2->multiply($z12);
+ $s2 = $y2->multiply($z12->multiply($z1));
+ if ($u1->equals($u2)) {
+ if (!$s1->equals($s2)) {
+ return [];
+ } else {
+ return $this->doublePoint($p);
+ }
+ }
+ $h = $u2->subtract($u1);
+ $r = $s2->subtract($s1);
+ $h2 = $h->multiply($h);
+ $h3 = $h2->multiply($h);
+ $v = $u1->multiply($h2);
+ $x3 = $r->multiply($r)->subtract($h3)->subtract($v->multiply($this->two));
+ $y3 = $r->multiply(
+ $v->subtract($x3)
+ )->subtract(
+ $s1->multiply($h3)
+ );
+ $z3 = $h->multiply($z1);
+ return [$x3, $y3, $z3];
+ }
+
+ /**
+ * Adds two jacobian coordinates on the curve
+ *
+ * @return FiniteField[]
+ */
+ protected function jacobianAddPoint(array $p, array $q)
+ {
+ list($x1, $y1, $z1) = $p;
+ list($x2, $y2, $z2) = $q;
+
+ $z12 = $z1->multiply($z1);
+ $z22 = $z2->multiply($z2);
+
+ $u1 = $x1->multiply($z22);
+ $u2 = $x2->multiply($z12);
+ $s1 = $y1->multiply($z22->multiply($z2));
+ $s2 = $y2->multiply($z12->multiply($z1));
+ if ($u1->equals($u2)) {
+ if (!$s1->equals($s2)) {
+ return [];
+ } else {
+ return $this->doublePoint($p);
+ }
+ }
+ $h = $u2->subtract($u1);
+ $r = $s2->subtract($s1);
+ $h2 = $h->multiply($h);
+ $h3 = $h2->multiply($h);
+ $v = $u1->multiply($h2);
+ $x3 = $r->multiply($r)->subtract($h3)->subtract($v->multiply($this->two));
+ $y3 = $r->multiply(
+ $v->subtract($x3)
+ )->subtract(
+ $s1->multiply($h3)
+ );
+ $z3 = $h->multiply($z1)->multiply($z2);
+ return [$x3, $y3, $z3];
+ }
+
+ /**
+ * Adds two points on the curve
+ *
+ * @return FiniteField[]
+ */
+ public function addPoint(array $p, array $q)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+
+ if (!count($p) || !count($q)) {
+ if (count($q)) {
+ return $q;
+ }
+ if (count($p)) {
+ return $p;
+ }
+ return [];
+ }
+
+ // use jacobian coordinates
+ if (isset($p[2]) && isset($q[2])) {
+ if (isset($p['fresh']) && isset($q['fresh'])) {
+ return $this->jacobianAddPointMixedXY($p, $q);
+ }
+ if (isset($p['fresh'])) {
+ return $this->jacobianAddPointMixedX($q, $p);
+ }
+ if (isset($q['fresh'])) {
+ return $this->jacobianAddPointMixedX($p, $q);
+ }
+ return $this->jacobianAddPoint($p, $q);
+ }
+
+ if (isset($p[2]) || isset($q[2])) {
+ throw new \RuntimeException('Affine coordinates need to be manually converted to Jacobi coordinates or vice versa');
+ }
+
+ if ($p[0]->equals($q[0])) {
+ if (!$p[1]->equals($q[1])) {
+ return [];
+ } else { // eg. doublePoint
+ list($numerator, $denominator) = $this->doublePointHelper($p);
+ }
+ } else {
+ $numerator = $q[1]->subtract($p[1]);
+ $denominator = $q[0]->subtract($p[0]);
+ }
+ $slope = $numerator->divide($denominator);
+ $x = $slope->multiply($slope)->subtract($p[0])->subtract($q[0]);
+ $y = $slope->multiply($p[0]->subtract($x))->subtract($p[1]);
+
+ return [$x, $y];
+ }
+
+ /**
+ * Returns the numerator and denominator of the slope
+ *
+ * @return FiniteField[]
+ */
+ protected function doublePointHelper(array $p)
+ {
+ $numerator = $this->three->multiply($p[0])->multiply($p[0])->add($this->a);
+ $denominator = $this->two->multiply($p[1]);
+ return [$numerator, $denominator];
+ }
+
+ /**
+ * Doubles a jacobian coordinate on the curve
+ *
+ * @return FiniteField[]
+ */
+ protected function jacobianDoublePoint(array $p)
+ {
+ list($x, $y, $z) = $p;
+ $x2 = $x->multiply($x);
+ $y2 = $y->multiply($y);
+ $z2 = $z->multiply($z);
+ $s = $this->four->multiply($x)->multiply($y2);
+ $m1 = $this->three->multiply($x2);
+ $m2 = $this->a->multiply($z2->multiply($z2));
+ $m = $m1->add($m2);
+ $x1 = $m->multiply($m)->subtract($this->two->multiply($s));
+ $y1 = $m->multiply($s->subtract($x1))->subtract(
+ $this->eight->multiply($y2->multiply($y2))
+ );
+ $z1 = $this->two->multiply($y)->multiply($z);
+ return [$x1, $y1, $z1];
+ }
+
+ /**
+ * Doubles a "fresh" jacobian coordinate on the curve
+ *
+ * @return FiniteField[]
+ */
+ protected function jacobianDoublePointMixed(array $p)
+ {
+ list($x, $y) = $p;
+ $x2 = $x->multiply($x);
+ $y2 = $y->multiply($y);
+ $s = $this->four->multiply($x)->multiply($y2);
+ $m1 = $this->three->multiply($x2);
+ $m = $m1->add($this->a);
+ $x1 = $m->multiply($m)->subtract($this->two->multiply($s));
+ $y1 = $m->multiply($s->subtract($x1))->subtract(
+ $this->eight->multiply($y2->multiply($y2))
+ );
+ $z1 = $this->two->multiply($y);
+ return [$x1, $y1, $z1];
+ }
+
+ /**
+ * Doubles a point on a curve
+ *
+ * @return FiniteField[]
+ */
+ public function doublePoint(array $p)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+
+ if (!count($p)) {
+ return [];
+ }
+
+ // use jacobian coordinates
+ if (isset($p[2])) {
+ if (isset($p['fresh'])) {
+ return $this->jacobianDoublePointMixed($p);
+ }
+ return $this->jacobianDoublePoint($p);
+ }
+
+ list($numerator, $denominator) = $this->doublePointHelper($p);
+
+ $slope = $numerator->divide($denominator);
+
+ $x = $slope->multiply($slope)->subtract($p[0])->subtract($p[0]);
+ $y = $slope->multiply($p[0]->subtract($x))->subtract($p[1]);
+
+ return [$x, $y];
+ }
+
+ /**
+ * Returns the X coordinate and the derived Y coordinate
+ *
+ * @return array
+ */
+ public function derivePoint($m)
+ {
+ $y = ord(Strings::shift($m));
+ $x = new BigInteger($m, 256);
+ $xp = $this->convertInteger($x);
+ switch ($y) {
+ case 2:
+ $ypn = false;
+ break;
+ case 3:
+ $ypn = true;
+ break;
+ default:
+ throw new \RuntimeException('Coordinate not in recognized format');
+ }
+ $temp = $xp->multiply($this->a);
+ $temp = $xp->multiply($xp)->multiply($xp)->add($temp);
+ $temp = $temp->add($this->b);
+ $b = $temp->squareRoot();
+ if (!$b) {
+ throw new \RuntimeException('Unable to derive Y coordinate');
+ }
+ $bn = $b->isOdd();
+ $yp = $ypn == $bn ? $b : $b->negate();
+ return [$xp, $yp];
+ }
+
+ /**
+ * Tests whether or not the x / y values satisfy the equation
+ *
+ * @return boolean
+ */
+ public function verifyPoint(array $p)
+ {
+ list($x, $y) = $p;
+ $lhs = $y->multiply($y);
+ $temp = $x->multiply($this->a);
+ $temp = $x->multiply($x)->multiply($x)->add($temp);
+ $rhs = $temp->add($this->b);
+
+ return $lhs->equals($rhs);
+ }
+
+ /**
+ * Returns the modulo
+ *
+ * @return BigInteger
+ */
+ public function getModulo()
+ {
+ return $this->modulo;
+ }
+
+ /**
+ * Returns the a coefficient
+ *
+ * @return PrimeInteger
+ */
+ public function getA()
+ {
+ return $this->a;
+ }
+
+ /**
+ * Returns the a coefficient
+ *
+ * @return PrimeInteger
+ */
+ public function getB()
+ {
+ return $this->b;
+ }
+
+ /**
+ * Multiply and Add Points
+ *
+ * Adapted from:
+ * https://github.com/indutny/elliptic/blob/725bd91/lib/elliptic/curve/base.js#L125
+ *
+ * @return int[]
+ */
+ public function multiplyAddPoints(array $points, array $scalars)
+ {
+ $length = count($points);
+
+ foreach ($points as &$point) {
+ $point = $this->convertToInternal($point);
+ }
+
+ $wnd = [$this->getNAFPoints($points[0], 7)];
+ $wndWidth = [isset($points[0]['nafwidth']) ? $points[0]['nafwidth'] : 7];
+ for ($i = 1; $i < $length; $i++) {
+ $wnd[] = $this->getNAFPoints($points[$i], 1);
+ $wndWidth[] = isset($points[$i]['nafwidth']) ? $points[$i]['nafwidth'] : 1;
+ }
+
+ $naf = [];
+
+ // comb all window NAFs
+
+ $max = 0;
+ for ($i = $length - 1; $i >= 1; $i -= 2) {
+ $a = $i - 1;
+ $b = $i;
+ if ($wndWidth[$a] != 1 || $wndWidth[$b] != 1) {
+ $naf[$a] = $scalars[$a]->getNAF($wndWidth[$a]);
+ $naf[$b] = $scalars[$b]->getNAF($wndWidth[$b]);
+ $max = max(count($naf[$a]), count($naf[$b]), $max);
+ continue;
+ }
+
+ $comb = [
+ $points[$a], // 1
+ null, // 3
+ null, // 5
+ $points[$b] // 7
+ ];
+
+ $comb[1] = $this->addPoint($points[$a], $points[$b]);
+ $comb[2] = $this->addPoint($points[$a], $this->negatePoint($points[$b]));
+
+ $index = [
+ -3, /* -1 -1 */
+ -1, /* -1 0 */
+ -5, /* -1 1 */
+ -7, /* 0 -1 */
+ 0, /* 0 -1 */
+ 7, /* 0 1 */
+ 5, /* 1 -1 */
+ 1, /* 1 0 */
+ 3 /* 1 1 */
+ ];
+
+ $jsf = self::getJSFPoints($scalars[$a], $scalars[$b]);
+
+ $max = max(count($jsf[0]), $max);
+ if ($max > 0) {
+ $naf[$a] = array_fill(0, $max, 0);
+ $naf[$b] = array_fill(0, $max, 0);
+ } else {
+ $naf[$a] = [];
+ $naf[$b] = [];
+ }
+
+ for ($j = 0; $j < $max; $j++) {
+ $ja = isset($jsf[0][$j]) ? $jsf[0][$j] : 0;
+ $jb = isset($jsf[1][$j]) ? $jsf[1][$j] : 0;
+
+ $naf[$a][$j] = $index[3 * ($ja + 1) + $jb + 1];
+ $naf[$b][$j] = 0;
+ $wnd[$a] = $comb;
+ }
+ }
+
+ $acc = [];
+ $temp = [0, 0, 0, 0];
+ for ($i = $max; $i >= 0; $i--) {
+ $k = 0;
+ while ($i >= 0) {
+ $zero = true;
+ for ($j = 0; $j < $length; $j++) {
+ $temp[$j] = isset($naf[$j][$i]) ? $naf[$j][$i] : 0;
+ if ($temp[$j] != 0) {
+ $zero = false;
+ }
+ }
+ if (!$zero) {
+ break;
+ }
+ $k++;
+ $i--;
+ }
+
+ if ($i >= 0) {
+ $k++;
+ }
+ while ($k--) {
+ $acc = $this->doublePoint($acc);
+ }
+
+ if ($i < 0) {
+ break;
+ }
+
+ for ($j = 0; $j < $length; $j++) {
+ $z = $temp[$j];
+ $p = null;
+ if ($z == 0) {
+ continue;
+ }
+ $p = $z > 0 ?
+ $wnd[$j][($z - 1) >> 1] :
+ $this->negatePoint($wnd[$j][(-$z - 1) >> 1]);
+ $acc = $this->addPoint($acc, $p);
+ }
+ }
+
+ return $this->convertToAffine($acc);
+ }
+
+ /**
+ * Precomputes NAF points
+ *
+ * Adapted from:
+ * https://github.com/indutny/elliptic/blob/725bd91/lib/elliptic/curve/base.js#L351
+ *
+ * @return int[]
+ */
+ private function getNAFPoints(array $point, $wnd)
+ {
+ if (isset($point['naf'])) {
+ return $point['naf'];
+ }
+
+ $res = [$point];
+ $max = (1 << $wnd) - 1;
+ $dbl = $max == 1 ? null : $this->doublePoint($point);
+ for ($i = 1; $i < $max; $i++) {
+ $res[] = $this->addPoint($res[$i - 1], $dbl);
+ }
+
+ $point['naf'] = $res;
+
+ /*
+ $str = '';
+ foreach ($res as $re) {
+ $re[0] = bin2hex($re[0]->toBytes());
+ $re[1] = bin2hex($re[1]->toBytes());
+ $str.= " ['$re[0]', '$re[1]'],\r\n";
+ }
+ file_put_contents('temp.txt', $str);
+ exit;
+ */
+
+ return $res;
+ }
+
+ /**
+ * Precomputes points in Joint Sparse Form
+ *
+ * Adapted from:
+ * https://github.com/indutny/elliptic/blob/725bd91/lib/elliptic/utils.js#L96
+ *
+ * @return int[]
+ */
+ private static function getJSFPoints(Integer $k1, Integer $k2)
+ {
+ static $three;
+ if (!isset($three)) {
+ $three = new BigInteger(3);
+ }
+
+ $jsf = [[], []];
+ $k1 = $k1->toBigInteger();
+ $k2 = $k2->toBigInteger();
+ $d1 = 0;
+ $d2 = 0;
+
+ while ($k1->compare(new BigInteger(-$d1)) > 0 || $k2->compare(new BigInteger(-$d2)) > 0) {
+ // first phase
+ $m14 = $k1->testBit(0) + 2 * $k1->testBit(1);
+ $m14 += $d1;
+ $m14 &= 3;
+
+ $m24 = $k2->testBit(0) + 2 * $k2->testBit(1);
+ $m24 += $d2;
+ $m24 &= 3;
+
+ if ($m14 == 3) {
+ $m14 = -1;
+ }
+ if ($m24 == 3) {
+ $m24 = -1;
+ }
+
+ $u1 = 0;
+ if ($m14 & 1) { // if $m14 is odd
+ $m8 = $k1->testBit(0) + 2 * $k1->testBit(1) + 4 * $k1->testBit(2);
+ $m8 += $d1;
+ $m8 &= 7;
+ $u1 = ($m8 == 3 || $m8 == 5) && $m24 == 2 ? -$m14 : $m14;
+ }
+ $jsf[0][] = $u1;
+
+ $u2 = 0;
+ if ($m24 & 1) { // if $m24 is odd
+ $m8 = $k2->testBit(0) + 2 * $k2->testBit(1) + 4 * $k2->testBit(2);
+ $m8 += $d2;
+ $m8 &= 7;
+ $u2 = ($m8 == 3 || $m8 == 5) && $m14 == 2 ? -$m24 : $m24;
+ }
+ $jsf[1][] = $u2;
+
+ // second phase
+ if (2 * $d1 == $u1 + 1) {
+ $d1 = 1 - $d1;
+ }
+ if (2 * $d2 == $u2 + 1) {
+ $d2 = 1 - $d2;
+ }
+ $k1 = $k1->bitwise_rightShift(1);
+ $k2 = $k2->bitwise_rightShift(1);
+ }
+
+ return $jsf;
+ }
+
+ /**
+ * Returns the affine point
+ *
+ * A Jacobian Coordinate is of the form (x, y, z).
+ * To convert a Jacobian Coordinate to an Affine Point
+ * you do (x / z^2, y / z^3)
+ *
+ * @return PrimeInteger[]
+ */
+ public function convertToAffine(array $p)
+ {
+ if (!isset($p[2])) {
+ return $p;
+ }
+ list($x, $y, $z) = $p;
+ $z = $this->one->divide($z);
+ $z2 = $z->multiply($z);
+ return [
+ $x->multiply($z2),
+ $y->multiply($z2)->multiply($z)
+ ];
+ }
+
+ /**
+ * Converts an affine point to a jacobian coordinate
+ *
+ * @return PrimeInteger[]
+ */
+ public function convertToInternal(array $p)
+ {
+ if (isset($p[2])) {
+ return $p;
+ }
+
+ $p[2] = clone $this->one;
+ $p['fresh'] = true;
+ return $p;
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/TwistedEdwards.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/TwistedEdwards.php
new file mode 100644
index 000000000..99aa38b20
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/BaseCurves/TwistedEdwards.php
@@ -0,0 +1,215 @@
+<?php
+
+/**
+ * Curves over a*x^2 + y^2 = 1 + d*x^2*y^2
+ *
+ * http://www.secg.org/SEC2-Ver-1.0.pdf provides for curves with custom parameters.
+ * ie. the coefficients can be arbitrary set through specially formatted keys, etc.
+ * As such, Prime.php is built very generically and it's not able to take full
+ * advantage of curves with 0 coefficients to produce simplified point doubling,
+ * point addition. Twisted Edwards curves, in contrast, do not have a way, currently,
+ * to customize them. As such, we can omit the super generic stuff from this class
+ * and let the named curves (Ed25519 and Ed448) define their own custom tailored
+ * point addition and point doubling methods.
+ *
+ * More info:
+ *
+ * https://en.wikipedia.org/wiki/Twisted_Edwards_curve
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\BaseCurves;
+
+use phpseclib3\Math\BigInteger;
+use phpseclib3\Math\PrimeField;
+use phpseclib3\Math\PrimeField\Integer as PrimeInteger;
+
+/**
+ * Curves over a*x^2 + y^2 = 1 + d*x^2*y^2
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class TwistedEdwards extends Base
+{
+ /**
+ * The modulo
+ *
+ * @var BigInteger
+ */
+ protected $modulo;
+
+ /**
+ * Cofficient for x^2
+ *
+ * @var object
+ */
+ protected $a;
+
+ /**
+ * Cofficient for x^2*y^2
+ *
+ * @var object
+ */
+ protected $d;
+
+ /**
+ * Base Point
+ *
+ * @var object[]
+ */
+ protected $p;
+
+ /**
+ * The number zero over the specified finite field
+ *
+ * @var object
+ */
+ protected $zero;
+
+ /**
+ * The number one over the specified finite field
+ *
+ * @var object
+ */
+ protected $one;
+
+ /**
+ * The number two over the specified finite field
+ *
+ * @var object
+ */
+ protected $two;
+
+ /**
+ * Sets the modulo
+ */
+ public function setModulo(BigInteger $modulo)
+ {
+ $this->modulo = $modulo;
+ $this->factory = new PrimeField($modulo);
+ $this->zero = $this->factory->newInteger(new BigInteger(0));
+ $this->one = $this->factory->newInteger(new BigInteger(1));
+ $this->two = $this->factory->newInteger(new BigInteger(2));
+ }
+
+ /**
+ * Set coefficients a and b
+ */
+ public function setCoefficients(BigInteger $a, BigInteger $d)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+ $this->a = $this->factory->newInteger($a);
+ $this->d = $this->factory->newInteger($d);
+ }
+
+ /**
+ * Set x and y coordinates for the base point
+ */
+ public function setBasePoint($x, $y)
+ {
+ switch (true) {
+ case !$x instanceof BigInteger && !$x instanceof PrimeInteger:
+ throw new \UnexpectedValueException('Argument 1 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer');
+ case !$y instanceof BigInteger && !$y instanceof PrimeInteger:
+ throw new \UnexpectedValueException('Argument 2 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer');
+ }
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+ $this->p = [
+ $x instanceof BigInteger ? $this->factory->newInteger($x) : $x,
+ $y instanceof BigInteger ? $this->factory->newInteger($y) : $y
+ ];
+ }
+
+ /**
+ * Returns the a coefficient
+ *
+ * @return PrimeInteger
+ */
+ public function getA()
+ {
+ return $this->a;
+ }
+
+ /**
+ * Returns the a coefficient
+ *
+ * @return PrimeInteger
+ */
+ public function getD()
+ {
+ return $this->d;
+ }
+
+ /**
+ * Retrieve the base point as an array
+ *
+ * @return array
+ */
+ public function getBasePoint()
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+ /*
+ if (!isset($this->p)) {
+ throw new \RuntimeException('setBasePoint needs to be called before this method');
+ }
+ */
+ return $this->p;
+ }
+
+ /**
+ * Returns the affine point
+ *
+ * @return PrimeInteger[]
+ */
+ public function convertToAffine(array $p)
+ {
+ if (!isset($p[2])) {
+ return $p;
+ }
+ list($x, $y, $z) = $p;
+ $z = $this->one->divide($z);
+ return [
+ $x->multiply($z),
+ $y->multiply($z)
+ ];
+ }
+
+ /**
+ * Returns the modulo
+ *
+ * @return BigInteger
+ */
+ public function getModulo()
+ {
+ return $this->modulo;
+ }
+
+ /**
+ * Tests whether or not the x / y values satisfy the equation
+ *
+ * @return boolean
+ */
+ public function verifyPoint(array $p)
+ {
+ list($x, $y) = $p;
+ $x2 = $x->multiply($x);
+ $y2 = $y->multiply($y);
+
+ $lhs = $this->a->multiply($x2)->add($y2);
+ $rhs = $this->d->multiply($x2)->multiply($y2)->add($this->one);
+
+ return $lhs->equals($rhs);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Curve25519.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Curve25519.php
new file mode 100644
index 000000000..0f3f4d827
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Curve25519.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * Curve25519
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2019 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Montgomery;
+use phpseclib3\Math\BigInteger;
+
+class Curve25519 extends Montgomery
+{
+ public function __construct()
+ {
+ // 2^255 - 19
+ $this->setModulo(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED', 16));
+ $this->a24 = $this->factory->newInteger(new BigInteger('121666'));
+ $this->p = [$this->factory->newInteger(new BigInteger(9))];
+ // 2^252 + 0x14def9dea2f79cd65812631a5cf5d3ed
+ $this->setOrder(new BigInteger('1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED', 16));
+
+ /*
+ $this->setCoefficients(
+ new BigInteger('486662'), // a
+ );
+ $this->setBasePoint(
+ new BigInteger(9),
+ new BigInteger('14781619447589544791020593568409986887264606134616475288964881837755586237401')
+ );
+ */
+ }
+
+ /**
+ * Multiply a point on the curve by a scalar
+ *
+ * Modifies the scalar as described at https://tools.ietf.org/html/rfc7748#page-8
+ *
+ * @return array
+ */
+ public function multiplyPoint(array $p, BigInteger $d)
+ {
+ //$r = strrev(sodium_crypto_scalarmult($d->toBytes(), strrev($p[0]->toBytes())));
+ //return [$this->factory->newInteger(new BigInteger($r, 256))];
+
+ $d = $d->toBytes();
+ $d &= "\xF8" . str_repeat("\xFF", 30) . "\x7F";
+ $d = strrev($d);
+ $d |= "\x40";
+ $d = new BigInteger($d, -256);
+
+ return parent::multiplyPoint($p, $d);
+ }
+
+ /**
+ * Creates a random scalar multiplier
+ *
+ * @return BigInteger
+ */
+ public function createRandomMultiplier()
+ {
+ return BigInteger::random(256);
+ }
+
+ /**
+ * Performs range check
+ */
+ public function rangeCheck(BigInteger $x)
+ {
+ if ($x->getLength() > 256 || $x->isNegative()) {
+ throw new \RangeException('x must be a positive integer less than 256 bytes in length');
+ }
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Curve448.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Curve448.php
new file mode 100644
index 000000000..f4a442315
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Curve448.php
@@ -0,0 +1,92 @@
+<?php
+
+/**
+ * Curve448
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2019 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Montgomery;
+use phpseclib3\Math\BigInteger;
+
+class Curve448 extends Montgomery
+{
+ public function __construct()
+ {
+ // 2^448 - 2^224 - 1
+ $this->setModulo(new BigInteger(
+ 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE' .
+ 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
+ 16
+ ));
+ $this->a24 = $this->factory->newInteger(new BigInteger('39081'));
+ $this->p = [$this->factory->newInteger(new BigInteger(5))];
+ // 2^446 - 0x8335dc163bb124b65129c96fde933d8d723a70aadc873d6d54a7bb0d
+ $this->setOrder(new BigInteger(
+ '3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' .
+ '7CCA23E9C44EDB49AED63690216CC2728DC58F552378C292AB5844F3',
+ 16
+ ));
+
+ /*
+ $this->setCoefficients(
+ new BigInteger('156326'), // a
+ );
+ $this->setBasePoint(
+ new BigInteger(5),
+ new BigInteger(
+ '355293926785568175264127502063783334808976399387714271831880898' .
+ '435169088786967410002932673765864550910142774147268105838985595290' .
+ '606362')
+ );
+ */
+ }
+
+ /**
+ * Multiply a point on the curve by a scalar
+ *
+ * Modifies the scalar as described at https://tools.ietf.org/html/rfc7748#page-8
+ *
+ * @return array
+ */
+ public function multiplyPoint(array $p, BigInteger $d)
+ {
+ //$r = strrev(sodium_crypto_scalarmult($d->toBytes(), strrev($p[0]->toBytes())));
+ //return [$this->factory->newInteger(new BigInteger($r, 256))];
+
+ $d = $d->toBytes();
+ $d[0] = $d[0] & "\xFC";
+ $d = strrev($d);
+ $d |= "\x80";
+ $d = new BigInteger($d, 256);
+
+ return parent::multiplyPoint($p, $d);
+ }
+
+ /**
+ * Creates a random scalar multiplier
+ *
+ * @return BigInteger
+ */
+ public function createRandomMultiplier()
+ {
+ return BigInteger::random(446);
+ }
+
+ /**
+ * Performs range check
+ */
+ public function rangeCheck(BigInteger $x)
+ {
+ if ($x->getLength() > 448 || $x->isNegative()) {
+ throw new \RangeException('x must be a positive integer less than 446 bytes in length');
+ }
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Ed25519.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Ed25519.php
new file mode 100644
index 000000000..9d3de684f
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Ed25519.php
@@ -0,0 +1,333 @@
+<?php
+
+/**
+ * Ed25519
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards;
+use phpseclib3\Crypt\Hash;
+use phpseclib3\Crypt\Random;
+use phpseclib3\Math\BigInteger;
+
+class Ed25519 extends TwistedEdwards
+{
+ const HASH = 'sha512';
+ /*
+ Per https://tools.ietf.org/html/rfc8032#page-6 EdDSA has several parameters, one of which is b:
+
+ 2. An integer b with 2^(b-1) > p. EdDSA public keys have exactly b
+ bits, and EdDSA signatures have exactly 2*b bits. b is
+ recommended to be a multiple of 8, so public key and signature
+ lengths are an integral number of octets.
+
+ SIZE corresponds to b
+ */
+ const SIZE = 32;
+
+ public function __construct()
+ {
+ // 2^255 - 19
+ $this->setModulo(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED', 16));
+ $this->setCoefficients(
+ // -1
+ new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEC', 16), // a
+ // -121665/121666
+ new BigInteger('52036CEE2B6FFE738CC740797779E89800700A4D4141D8AB75EB4DCA135978A3', 16) // d
+ );
+ $this->setBasePoint(
+ new BigInteger('216936D3CD6E53FEC0A4E231FDD6DC5C692CC7609525A7B2C9562D608F25D51A', 16),
+ new BigInteger('6666666666666666666666666666666666666666666666666666666666666658', 16)
+ );
+ $this->setOrder(new BigInteger('1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED', 16));
+ // algorithm 14.47 from http://cacr.uwaterloo.ca/hac/about/chap14.pdf#page=16
+ /*
+ $this->setReduction(function($x) {
+ $parts = $x->bitwise_split(255);
+ $className = $this->className;
+
+ if (count($parts) > 2) {
+ list(, $r) = $x->divide($className::$modulo);
+ return $r;
+ }
+
+ $zero = new BigInteger();
+ $c = new BigInteger(19);
+
+ switch (count($parts)) {
+ case 2:
+ list($qi, $ri) = $parts;
+ break;
+ case 1:
+ $qi = $zero;
+ list($ri) = $parts;
+ break;
+ case 0:
+ return $zero;
+ }
+ $r = $ri;
+
+ while ($qi->compare($zero) > 0) {
+ $temp = $qi->multiply($c)->bitwise_split(255);
+ if (count($temp) == 2) {
+ list($qi, $ri) = $temp;
+ } else {
+ $qi = $zero;
+ list($ri) = $temp;
+ }
+ $r = $r->add($ri);
+ }
+
+ while ($r->compare($className::$modulo) > 0) {
+ $r = $r->subtract($className::$modulo);
+ }
+ return $r;
+ });
+ */
+ }
+
+ /**
+ * Recover X from Y
+ *
+ * Implements steps 2-4 at https://tools.ietf.org/html/rfc8032#section-5.1.3
+ *
+ * Used by EC\Keys\Common.php
+ *
+ * @param BigInteger $y
+ * @param boolean $sign
+ * @return object[]
+ */
+ public function recoverX(BigInteger $y, $sign)
+ {
+ $y = $this->factory->newInteger($y);
+
+ $y2 = $y->multiply($y);
+ $u = $y2->subtract($this->one);
+ $v = $this->d->multiply($y2)->add($this->one);
+ $x2 = $u->divide($v);
+ if ($x2->equals($this->zero)) {
+ if ($sign) {
+ throw new \RuntimeException('Unable to recover X coordinate (x2 = 0)');
+ }
+ return clone $this->zero;
+ }
+ // find the square root
+ /* we don't do $x2->squareRoot() because, quoting from
+ https://tools.ietf.org/html/rfc8032#section-5.1.1:
+
+ "For point decoding or "decompression", square roots modulo p are
+ needed. They can be computed using the Tonelli-Shanks algorithm or
+ the special case for p = 5 (mod 8). To find a square root of a,
+ first compute the candidate root x = a^((p+3)/8) (mod p)."
+ */
+ $exp = $this->getModulo()->add(new BigInteger(3));
+ $exp = $exp->bitwise_rightShift(3);
+ $x = $x2->pow($exp);
+
+ // If v x^2 = -u (mod p), set x <-- x * 2^((p-1)/4), which is a square root.
+ if (!$x->multiply($x)->subtract($x2)->equals($this->zero)) {
+ $temp = $this->getModulo()->subtract(new BigInteger(1));
+ $temp = $temp->bitwise_rightShift(2);
+ $temp = $this->two->pow($temp);
+ $x = $x->multiply($temp);
+ if (!$x->multiply($x)->subtract($x2)->equals($this->zero)) {
+ throw new \RuntimeException('Unable to recover X coordinate');
+ }
+ }
+ if ($x->isOdd() != $sign) {
+ $x = $x->negate();
+ }
+
+ return [$x, $y];
+ }
+
+ /**
+ * Extract Secret Scalar
+ *
+ * Implements steps 1-3 at https://tools.ietf.org/html/rfc8032#section-5.1.5
+ *
+ * Used by the various key handlers
+ *
+ * @param string $str
+ * @return array
+ */
+ public function extractSecret($str)
+ {
+ if (strlen($str) != 32) {
+ throw new \LengthException('Private Key should be 32-bytes long');
+ }
+ // 1. Hash the 32-byte private key using SHA-512, storing the digest in
+ // a 64-octet large buffer, denoted h. Only the lower 32 bytes are
+ // used for generating the public key.
+ $hash = new Hash('sha512');
+ $h = $hash->hash($str);
+ $h = substr($h, 0, 32);
+ // 2. Prune the buffer: The lowest three bits of the first octet are
+ // cleared, the highest bit of the last octet is cleared, and the
+ // second highest bit of the last octet is set.
+ $h[0] = $h[0] & chr(0xF8);
+ $h = strrev($h);
+ $h[0] = ($h[0] & chr(0x3F)) | chr(0x40);
+ // 3. Interpret the buffer as the little-endian integer, forming a
+ // secret scalar s.
+ $dA = new BigInteger($h, 256);
+
+ return [
+ 'dA' => $dA,
+ 'secret' => $str
+ ];
+ }
+
+ /**
+ * Encode a point as a string
+ *
+ * @param array $point
+ * @return string
+ */
+ public function encodePoint($point)
+ {
+ list($x, $y) = $point;
+ $y = $y->toBytes();
+ $y[0] = $y[0] & chr(0x7F);
+ if ($x->isOdd()) {
+ $y[0] = $y[0] | chr(0x80);
+ }
+ $y = strrev($y);
+
+ return $y;
+ }
+
+ /**
+ * Creates a random scalar multiplier
+ *
+ * @return \phpseclib3\Math\PrimeField\Integer
+ */
+ public function createRandomMultiplier()
+ {
+ return $this->extractSecret(Random::string(32))['dA'];
+ }
+
+ /**
+ * Converts an affine point to an extended homogeneous coordinate
+ *
+ * From https://tools.ietf.org/html/rfc8032#section-5.1.4 :
+ *
+ * A point (x,y) is represented in extended homogeneous coordinates (X, Y, Z, T),
+ * with x = X/Z, y = Y/Z, x * y = T/Z.
+ *
+ * @return \phpseclib3\Math\PrimeField\Integer[]
+ */
+ public function convertToInternal(array $p)
+ {
+ if (empty($p)) {
+ return [clone $this->zero, clone $this->one, clone $this->one, clone $this->zero];
+ }
+
+ if (isset($p[2])) {
+ return $p;
+ }
+
+ $p[2] = clone $this->one;
+ $p[3] = $p[0]->multiply($p[1]);
+
+ return $p;
+ }
+
+ /**
+ * Doubles a point on a curve
+ *
+ * @return FiniteField[]
+ */
+ public function doublePoint(array $p)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+
+ if (!count($p)) {
+ return [];
+ }
+
+ if (!isset($p[2])) {
+ throw new \RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa');
+ }
+
+ // from https://tools.ietf.org/html/rfc8032#page-12
+
+ list($x1, $y1, $z1, $t1) = $p;
+
+ $a = $x1->multiply($x1);
+ $b = $y1->multiply($y1);
+ $c = $this->two->multiply($z1)->multiply($z1);
+ $h = $a->add($b);
+ $temp = $x1->add($y1);
+ $e = $h->subtract($temp->multiply($temp));
+ $g = $a->subtract($b);
+ $f = $c->add($g);
+
+ $x3 = $e->multiply($f);
+ $y3 = $g->multiply($h);
+ $t3 = $e->multiply($h);
+ $z3 = $f->multiply($g);
+
+ return [$x3, $y3, $z3, $t3];
+ }
+
+ /**
+ * Adds two points on the curve
+ *
+ * @return FiniteField[]
+ */
+ public function addPoint(array $p, array $q)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+
+ if (!count($p) || !count($q)) {
+ if (count($q)) {
+ return $q;
+ }
+ if (count($p)) {
+ return $p;
+ }
+ return [];
+ }
+
+ if (!isset($p[2]) || !isset($q[2])) {
+ throw new \RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa');
+ }
+
+ if ($p[0]->equals($q[0])) {
+ return !$p[1]->equals($q[1]) ? [] : $this->doublePoint($p);
+ }
+
+ // from https://tools.ietf.org/html/rfc8032#page-12
+
+ list($x1, $y1, $z1, $t1) = $p;
+ list($x2, $y2, $z2, $t2) = $q;
+
+ $a = $y1->subtract($x1)->multiply($y2->subtract($x2));
+ $b = $y1->add($x1)->multiply($y2->add($x2));
+ $c = $t1->multiply($this->two)->multiply($this->d)->multiply($t2);
+ $d = $z1->multiply($this->two)->multiply($z2);
+ $e = $b->subtract($a);
+ $f = $d->subtract($c);
+ $g = $d->add($c);
+ $h = $b->add($a);
+
+ $x3 = $e->multiply($f);
+ $y3 = $g->multiply($h);
+ $t3 = $e->multiply($h);
+ $z3 = $f->multiply($g);
+
+ return [$x3, $y3, $z3, $t3];
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Ed448.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Ed448.php
new file mode 100644
index 000000000..5451f909f
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/Ed448.php
@@ -0,0 +1,273 @@
+<?php
+
+/**
+ * Ed448
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards;
+use phpseclib3\Crypt\Hash;
+use phpseclib3\Crypt\Random;
+use phpseclib3\Math\BigInteger;
+
+class Ed448 extends TwistedEdwards
+{
+ const HASH = 'shake256-912';
+ const SIZE = 57;
+
+ public function __construct()
+ {
+ // 2^448 - 2^224 - 1
+ $this->setModulo(new BigInteger(
+ 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE' .
+ 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
+ 16
+ ));
+ $this->setCoefficients(
+ new BigInteger(1),
+ // -39081
+ new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE' .
+ 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF6756', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('4F1970C66BED0DED221D15A622BF36DA9E146570470F1767EA6DE324' .
+ 'A3D3A46412AE1AF72AB66511433B80E18B00938E2626A82BC70CC05E', 16),
+ new BigInteger('693F46716EB6BC248876203756C9C7624BEA73736CA3984087789C1E' .
+ '05A0C2D73AD3FF1CE67C39C4FDBD132C4ED7C8AD9808795BF230FA14', 16)
+ );
+ $this->setOrder(new BigInteger(
+ '3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' .
+ '7CCA23E9C44EDB49AED63690216CC2728DC58F552378C292AB5844F3',
+ 16
+ ));
+ }
+
+ /**
+ * Recover X from Y
+ *
+ * Implements steps 2-4 at https://tools.ietf.org/html/rfc8032#section-5.2.3
+ *
+ * Used by EC\Keys\Common.php
+ *
+ * @param BigInteger $y
+ * @param boolean $sign
+ * @return object[]
+ */
+ public function recoverX(BigInteger $y, $sign)
+ {
+ $y = $this->factory->newInteger($y);
+
+ $y2 = $y->multiply($y);
+ $u = $y2->subtract($this->one);
+ $v = $this->d->multiply($y2)->subtract($this->one);
+ $x2 = $u->divide($v);
+ if ($x2->equals($this->zero)) {
+ if ($sign) {
+ throw new \RuntimeException('Unable to recover X coordinate (x2 = 0)');
+ }
+ return clone $this->zero;
+ }
+ // find the square root
+ $exp = $this->getModulo()->add(new BigInteger(1));
+ $exp = $exp->bitwise_rightShift(2);
+ $x = $x2->pow($exp);
+
+ if (!$x->multiply($x)->subtract($x2)->equals($this->zero)) {
+ throw new \RuntimeException('Unable to recover X coordinate');
+ }
+ if ($x->isOdd() != $sign) {
+ $x = $x->negate();
+ }
+
+ return [$x, $y];
+ }
+
+ /**
+ * Extract Secret Scalar
+ *
+ * Implements steps 1-3 at https://tools.ietf.org/html/rfc8032#section-5.2.5
+ *
+ * Used by the various key handlers
+ *
+ * @param string $str
+ * @return array
+ */
+ public function extractSecret($str)
+ {
+ if (strlen($str) != 57) {
+ throw new \LengthException('Private Key should be 57-bytes long');
+ }
+ // 1. Hash the 57-byte private key using SHAKE256(x, 114), storing the
+ // digest in a 114-octet large buffer, denoted h. Only the lower 57
+ // bytes are used for generating the public key.
+ $hash = new Hash('shake256-912');
+ $h = $hash->hash($str);
+ $h = substr($h, 0, 57);
+ // 2. Prune the buffer: The two least significant bits of the first
+ // octet are cleared, all eight bits the last octet are cleared, and
+ // the highest bit of the second to last octet is set.
+ $h[0] = $h[0] & chr(0xFC);
+ $h = strrev($h);
+ $h[0] = "\0";
+ $h[1] = $h[1] | chr(0x80);
+ // 3. Interpret the buffer as the little-endian integer, forming a
+ // secret scalar s.
+ $dA = new BigInteger($h, 256);
+
+ return [
+ 'dA' => $dA,
+ 'secret' => $str
+ ];
+
+ $dA->secret = $str;
+ return $dA;
+ }
+
+ /**
+ * Encode a point as a string
+ *
+ * @param array $point
+ * @return string
+ */
+ public function encodePoint($point)
+ {
+ list($x, $y) = $point;
+ $y = "\0" . $y->toBytes();
+ if ($x->isOdd()) {
+ $y[0] = $y[0] | chr(0x80);
+ }
+ $y = strrev($y);
+
+ return $y;
+ }
+
+ /**
+ * Creates a random scalar multiplier
+ *
+ * @return \phpseclib3\Math\PrimeField\Integer
+ */
+ public function createRandomMultiplier()
+ {
+ return $this->extractSecret(Random::string(57))['dA'];
+ }
+
+ /**
+ * Converts an affine point to an extended homogeneous coordinate
+ *
+ * From https://tools.ietf.org/html/rfc8032#section-5.2.4 :
+ *
+ * A point (x,y) is represented in extended homogeneous coordinates (X, Y, Z, T),
+ * with x = X/Z, y = Y/Z, x * y = T/Z.
+ *
+ * @return \phpseclib3\Math\PrimeField\Integer[]
+ */
+ public function convertToInternal(array $p)
+ {
+ if (empty($p)) {
+ return [clone $this->zero, clone $this->one, clone $this->one];
+ }
+
+ if (isset($p[2])) {
+ return $p;
+ }
+
+ $p[2] = clone $this->one;
+
+ return $p;
+ }
+
+ /**
+ * Doubles a point on a curve
+ *
+ * @return FiniteField[]
+ */
+ public function doublePoint(array $p)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+
+ if (!count($p)) {
+ return [];
+ }
+
+ if (!isset($p[2])) {
+ throw new \RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa');
+ }
+
+ // from https://tools.ietf.org/html/rfc8032#page-18
+
+ list($x1, $y1, $z1) = $p;
+
+ $b = $x1->add($y1);
+ $b = $b->multiply($b);
+ $c = $x1->multiply($x1);
+ $d = $y1->multiply($y1);
+ $e = $c->add($d);
+ $h = $z1->multiply($z1);
+ $j = $e->subtract($this->two->multiply($h));
+
+ $x3 = $b->subtract($e)->multiply($j);
+ $y3 = $c->subtract($d)->multiply($e);
+ $z3 = $e->multiply($j);
+
+ return [$x3, $y3, $z3];
+ }
+
+ /**
+ * Adds two points on the curve
+ *
+ * @return FiniteField[]
+ */
+ public function addPoint(array $p, array $q)
+ {
+ if (!isset($this->factory)) {
+ throw new \RuntimeException('setModulo needs to be called before this method');
+ }
+
+ if (!count($p) || !count($q)) {
+ if (count($q)) {
+ return $q;
+ }
+ if (count($p)) {
+ return $p;
+ }
+ return [];
+ }
+
+ if (!isset($p[2]) || !isset($q[2])) {
+ throw new \RuntimeException('Affine coordinates need to be manually converted to "Jacobi" coordinates or vice versa');
+ }
+
+ if ($p[0]->equals($q[0])) {
+ return !$p[1]->equals($q[1]) ? [] : $this->doublePoint($p);
+ }
+
+ // from https://tools.ietf.org/html/rfc8032#page-17
+
+ list($x1, $y1, $z1) = $p;
+ list($x2, $y2, $z2) = $q;
+
+ $a = $z1->multiply($z2);
+ $b = $a->multiply($a);
+ $c = $x1->multiply($x2);
+ $d = $y1->multiply($y2);
+ $e = $this->d->multiply($c)->multiply($d);
+ $f = $b->subtract($e);
+ $g = $b->add($e);
+ $h = $x1->add($y1)->multiply($x2->add($y2));
+
+ $x3 = $a->multiply($f)->multiply($h->subtract($c)->subtract($d));
+ $y3 = $a->multiply($g)->multiply($d->subtract($c));
+ $z3 = $f->multiply($g);
+
+ return [$x3, $y3, $z3];
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP160r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP160r1.php
new file mode 100644
index 000000000..7bc2272a1
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP160r1.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * brainpoolP160r1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP160r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('E95E4A5F737059DC60DFC7AD95B3D8139515620F', 16));
+ $this->setCoefficients(
+ new BigInteger('340E7BE2A280EB74E2BE61BADA745D97E8F7C300', 16),
+ new BigInteger('1E589A8595423412134FAA2DBDEC95C8D8675E58', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('BED5AF16EA3F6A4F62938C4631EB5AF7BDBCDBC3', 16),
+ new BigInteger('1667CB477A1A8EC338F94741669C976316DA6321', 16)
+ );
+ $this->setOrder(new BigInteger('E95E4A5F737059DC60DF5991D45029409E60FC09', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP160t1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP160t1.php
new file mode 100644
index 000000000..ebfb29aeb
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP160t1.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * brainpoolP160t1
+ *
+ * This curve is a twisted version of brainpoolP160r1 with A = -3. With brainpool,
+ * the curves ending in r1 are the "regular" curves and the curves ending in "t1"
+ * are the twisted version of the r1 curves. Per https://tools.ietf.org/html/rfc5639#page-7
+ * you can convert a point on an r1 curve to a point on a t1 curve thusly:
+ *
+ * F(x,y) := (x*Z^2, y*Z^3)
+ *
+ * The advantage of A = -3 is that some of the point doubling and point addition can be
+ * slightly optimized. See http://hyperelliptic.org/EFD/g1p/auto-shortw-projective-3.html
+ * vs http://hyperelliptic.org/EFD/g1p/auto-shortw-projective.html for example.
+ *
+ * phpseclib does not currently take advantage of this optimization opportunity
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP160t1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('E95E4A5F737059DC60DFC7AD95B3D8139515620F', 16));
+ $this->setCoefficients(
+ new BigInteger('E95E4A5F737059DC60DFC7AD95B3D8139515620C', 16), // eg. -3
+ new BigInteger('7A556B6DAE535B7B51ED2C4D7DAA7A0B5C55F380', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('B199B13B9B34EFC1397E64BAEB05ACC265FF2378', 16),
+ new BigInteger('ADD6718B7C7C1961F0991B842443772152C9E0AD', 16)
+ );
+ $this->setOrder(new BigInteger('E95E4A5F737059DC60DF5991D45029409E60FC09', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP192r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP192r1.php
new file mode 100644
index 000000000..6ec848bc9
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP192r1.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * brainpoolP192r1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP192r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('C302F41D932A36CDA7A3463093D18DB78FCE476DE1A86297', 16));
+ $this->setCoefficients(
+ new BigInteger('6A91174076B1E0E19C39C031FE8685C1CAE040E5C69A28EF', 16),
+ new BigInteger('469A28EF7C28CCA3DC721D044F4496BCCA7EF4146FBF25C9', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('C0A0647EAAB6A48753B033C56CB0F0900A2F5C4853375FD6', 16),
+ new BigInteger('14B690866ABD5BB88B5F4828C1490002E6773FA2FA299B8F', 16)
+ );
+ $this->setOrder(new BigInteger('C302F41D932A36CDA7A3462F9E9E916B5BE8F1029AC4ACC1', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP192t1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP192t1.php
new file mode 100644
index 000000000..e6a86bbd3
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP192t1.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * brainpoolP192t1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP192t1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('C302F41D932A36CDA7A3463093D18DB78FCE476DE1A86297', 16));
+ $this->setCoefficients(
+ new BigInteger('C302F41D932A36CDA7A3463093D18DB78FCE476DE1A86294', 16), // eg. -3
+ new BigInteger('13D56FFAEC78681E68F9DEB43B35BEC2FB68542E27897B79', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('3AE9E58C82F63C30282E1FE7BBF43FA72C446AF6F4618129', 16),
+ new BigInteger('097E2C5667C2223A902AB5CA449D0084B7E5B3DE7CCC01C9', 16)
+ );
+ $this->setOrder(new BigInteger('C302F41D932A36CDA7A3462F9E9E916B5BE8F1029AC4ACC1', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP224r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP224r1.php
new file mode 100644
index 000000000..3d7d8726a
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP224r1.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * brainpoolP224r1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP224r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('D7C134AA264366862A18302575D1D787B09F075797DA89F57EC8C0FF', 16));
+ $this->setCoefficients(
+ new BigInteger('68A5E62CA9CE6C1C299803A6C1530B514E182AD8B0042A59CAD29F43', 16),
+ new BigInteger('2580F63CCFE44138870713B1A92369E33E2135D266DBB372386C400B', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('0D9029AD2C7E5CF4340823B2A87DC68C9E4CE3174C1E6EFDEE12C07D', 16),
+ new BigInteger('58AA56F772C0726F24C6B89E4ECDAC24354B9E99CAA3F6D3761402CD', 16)
+ );
+ $this->setOrder(new BigInteger('D7C134AA264366862A18302575D0FB98D116BC4B6DDEBCA3A5A7939F', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP224t1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP224t1.php
new file mode 100644
index 000000000..3d4f9289c
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP224t1.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * brainpoolP224t1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP224t1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('D7C134AA264366862A18302575D1D787B09F075797DA89F57EC8C0FF', 16));
+ $this->setCoefficients(
+ new BigInteger('D7C134AA264366862A18302575D1D787B09F075797DA89F57EC8C0FC', 16), // eg. -3
+ new BigInteger('4B337D934104CD7BEF271BF60CED1ED20DA14C08B3BB64F18A60888D', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('6AB1E344CE25FF3896424E7FFE14762ECB49F8928AC0C76029B4D580', 16),
+ new BigInteger('0374E9F5143E568CD23F3F4D7C0D4B1E41C8CC0D1C6ABD5F1A46DB4C', 16)
+ );
+ $this->setOrder(new BigInteger('D7C134AA264366862A18302575D0FB98D116BC4B6DDEBCA3A5A7939F', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP256r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP256r1.php
new file mode 100644
index 000000000..5780da763
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP256r1.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * brainpoolP256r1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP256r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377', 16));
+ $this->setCoefficients(
+ new BigInteger('7D5A0975FC2C3057EEF67530417AFFE7FB8055C126DC5C6CE94A4B44F330B5D9', 16),
+ new BigInteger('26DC5C6CE94A4B44F330B5D9BBD77CBF958416295CF7E1CE6BCCDC18FF8C07B6', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('8BD2AEB9CB7E57CB2C4B482FFC81B7AFB9DE27E1E3BD23C23A4453BD9ACE3262', 16),
+ new BigInteger('547EF835C3DAC4FD97F8461A14611DC9C27745132DED8E545C1D54C72F046997', 16)
+ );
+ $this->setOrder(new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP256t1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP256t1.php
new file mode 100644
index 000000000..724d8b8f1
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP256t1.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * brainpoolP256t1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP256t1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5377', 16));
+ $this->setCoefficients(
+ new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D726E3BF623D52620282013481D1F6E5374', 16), // eg. -3
+ new BigInteger('662C61C430D84EA4FE66A7733D0B76B7BF93EBC4AF2F49256AE58101FEE92B04', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('A3E8EB3CC1CFE7B7732213B23A656149AFA142C47AAFBC2B79A191562E1305F4', 16),
+ new BigInteger('2D996C823439C56D7F7B22E14644417E69BCB6DE39D027001DABE8F35B25C9BE', 16)
+ );
+ $this->setOrder(new BigInteger('A9FB57DBA1EEA9BC3E660A909D838D718C397AA3B561A6F7901E0E82974856A7', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP320r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP320r1.php
new file mode 100644
index 000000000..182e62270
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP320r1.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * brainpoolP320r1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP320r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('D35E472036BC4FB7E13C785ED201E065F98FCFA6F6F40DEF4F9' .
+ '2B9EC7893EC28FCD412B1F1B32E27', 16));
+ $this->setCoefficients(
+ new BigInteger('3EE30B568FBAB0F883CCEBD46D3F3BB8A2A73513F5EB79DA66190EB085FFA9F4' .
+ '92F375A97D860EB4', 16),
+ new BigInteger('520883949DFDBC42D3AD198640688A6FE13F41349554B49ACC31DCCD88453981' .
+ '6F5EB4AC8FB1F1A6', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('43BD7E9AFB53D8B85289BCC48EE5BFE6F20137D10A087EB6E7871E2A10A599C7' .
+ '10AF8D0D39E20611', 16),
+ new BigInteger('14FDD05545EC1CC8AB4093247F77275E0743FFED117182EAA9C77877AAAC6AC7' .
+ 'D35245D1692E8EE1', 16)
+ );
+ $this->setOrder(new BigInteger('D35E472036BC4FB7E13C785ED201E065F98FCFA5B68F12A32D4' .
+ '82EC7EE8658E98691555B44C59311', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP320t1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP320t1.php
new file mode 100644
index 000000000..d5a620d8b
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP320t1.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * brainpoolP320t1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP320t1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('D35E472036BC4FB7E13C785ED201E065F98FCFA6F6F40DEF4F9' .
+ '2B9EC7893EC28FCD412B1F1B32E27', 16));
+ $this->setCoefficients(
+ new BigInteger('D35E472036BC4FB7E13C785ED201E065F98FCFA6F6F40DEF4F92B9EC7893EC28' .
+ 'FCD412B1F1B32E24', 16), // eg. -3
+ new BigInteger('A7F561E038EB1ED560B3D147DB782013064C19F27ED27C6780AAF77FB8A547CE' .
+ 'B5B4FEF422340353', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('925BE9FB01AFC6FB4D3E7D4990010F813408AB106C4F09CB7EE07868CC136FFF' .
+ '3357F624A21BED52', 16),
+ new BigInteger('63BA3A7A27483EBF6671DBEF7ABB30EBEE084E58A0B077AD42A5A0989D1EE71B' .
+ '1B9BC0455FB0D2C3', 16)
+ );
+ $this->setOrder(new BigInteger('D35E472036BC4FB7E13C785ED201E065F98FCFA5B68F12A32D4' .
+ '82EC7EE8658E98691555B44C59311', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP384r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP384r1.php
new file mode 100644
index 000000000..a20b4b446
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP384r1.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * brainpoolP384r1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP384r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger(
+ '8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901D1A7' .
+ '1874700133107EC53',
+ 16
+ ));
+ $this->setCoefficients(
+ new BigInteger(
+ '7BC382C63D8C150C3C72080ACE05AFA0C2BEA28E4FB22787139165EFBA91F90F8AA5814A503' .
+ 'AD4EB04A8C7DD22CE2826',
+ 16
+ ),
+ new BigInteger(
+ '4A8C7DD22CE28268B39B55416F0447C2FB77DE107DCD2A62E880EA53EEB62D57CB4390295DB' .
+ 'C9943AB78696FA504C11',
+ 16
+ )
+ );
+ $this->setBasePoint(
+ new BigInteger(
+ '1D1C64F068CF45FFA2A63A81B7C13F6B8847A3E77EF14FE3DB7FCAFE0CBD10E8E826E03436D' .
+ '646AAEF87B2E247D4AF1E',
+ 16
+ ),
+ new BigInteger(
+ '8ABE1D7520F9C2A45CB1EB8E95CFD55262B70B29FEEC5864E19C054FF99129280E464621779' .
+ '1811142820341263C5315',
+ 16
+ )
+ );
+ $this->setOrder(new BigInteger(
+ '8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B31F166E6CAC0425A7CF3AB6AF6B7FC31' .
+ '03B883202E9046565',
+ 16
+ ));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP384t1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP384t1.php
new file mode 100644
index 000000000..366660e68
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP384t1.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * brainpoolP384t1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP384t1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger(
+ '8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901D1A7' .
+ '1874700133107EC53',
+ 16
+ ));
+ $this->setCoefficients(
+ new BigInteger(
+ '8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B412B1DA197FB71123ACD3A729901' .
+ 'D1A71874700133107EC50',
+ 16
+ ), // eg. -3
+ new BigInteger(
+ '7F519EADA7BDA81BD826DBA647910F8C4B9346ED8CCDC64E4B1ABD11756DCE1D2074AA263B8' .
+ '8805CED70355A33B471EE',
+ 16
+ )
+ );
+ $this->setBasePoint(
+ new BigInteger(
+ '18DE98B02DB9A306F2AFCD7235F72A819B80AB12EBD653172476FECD462AABFFC4FF191B946' .
+ 'A5F54D8D0AA2F418808CC',
+ 16
+ ),
+ new BigInteger(
+ '25AB056962D30651A114AFD2755AD336747F93475B7A1FCA3B88F2B6A208CCFE469408584DC' .
+ '2B2912675BF5B9E582928',
+ 16
+ )
+ );
+ $this->setOrder(new BigInteger(
+ '8CB91E82A3386D280F5D6F7E50E641DF152F7109ED5456B31F166E6CAC0425A7CF3AB6AF6B7FC31' .
+ '03B883202E9046565',
+ 16
+ ));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP512r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP512r1.php
new file mode 100644
index 000000000..5efe5e1ac
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP512r1.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * brainpoolP512r1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP512r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger(
+ 'AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308717D4D9B009BC' .
+ '66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F3',
+ 16
+ ));
+ $this->setCoefficients(
+ new BigInteger(
+ '7830A3318B603B89E2327145AC234CC594CBDD8D3DF91610A83441CAEA9863BC2DED5D5AA82' .
+ '53AA10A2EF1C98B9AC8B57F1117A72BF2C7B9E7C1AC4D77FC94CA',
+ 16
+ ),
+ new BigInteger(
+ '3DF91610A83441CAEA9863BC2DED5D5AA8253AA10A2EF1C98B9AC8B57F1117A72BF2C7B9E7C' .
+ '1AC4D77FC94CADC083E67984050B75EBAE5DD2809BD638016F723',
+ 16
+ )
+ );
+ $this->setBasePoint(
+ new BigInteger(
+ '81AEE4BDD82ED9645A21322E9C4C6A9385ED9F70B5D916C1B43B62EEF4D0098EFF3B1F78E2D' .
+ '0D48D50D1687B93B97D5F7C6D5047406A5E688B352209BCB9F822',
+ 16
+ ),
+ new BigInteger(
+ '7DDE385D566332ECC0EABFA9CF7822FDF209F70024A57B1AA000C55B881F8111B2DCDE494A5' .
+ 'F485E5BCA4BD88A2763AED1CA2B2FA8F0540678CD1E0F3AD80892',
+ 16
+ )
+ );
+ $this->setOrder(new BigInteger(
+ 'AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA70330870553E5C414CA' .
+ '92619418661197FAC10471DB1D381085DDADDB58796829CA90069',
+ 16
+ ));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP512t1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP512t1.php
new file mode 100644
index 000000000..745863a63
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/brainpoolP512t1.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * brainpoolP512t1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class brainpoolP512t1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger(
+ 'AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308717D4D9B009BC' .
+ '66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F3',
+ 16
+ ));
+ $this->setCoefficients(
+ new BigInteger(
+ 'AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA703308717D4D9B009BC' .
+ '66842AECDA12AE6A380E62881FF2F2D82C68528AA6056583A48F0',
+ 16
+ ), // eg. -3
+ new BigInteger(
+ '7CBBBCF9441CFAB76E1890E46884EAE321F70C0BCB4981527897504BEC3E36A62BCDFA23049' .
+ '76540F6450085F2DAE145C22553B465763689180EA2571867423E',
+ 16
+ )
+ );
+ $this->setBasePoint(
+ new BigInteger(
+ '640ECE5C12788717B9C1BA06CBC2A6FEBA85842458C56DDE9DB1758D39C0313D82BA51735CD' .
+ 'B3EA499AA77A7D6943A64F7A3F25FE26F06B51BAA2696FA9035DA',
+ 16
+ ),
+ new BigInteger(
+ '5B534BD595F5AF0FA2C892376C84ACE1BB4E3019B71634C01131159CAE03CEE9D9932184BEE' .
+ 'F216BD71DF2DADF86A627306ECFF96DBB8BACE198B61E00F8B332',
+ 16
+ )
+ );
+ $this->setOrder(new BigInteger(
+ 'AADD9DB8DBE9C48B3FD4E6AE33C9FC07CB308DB3B3C9D20ED6639CCA70330870553E5C414CA' .
+ '92619418661197FAC10471DB1D381085DDADDB58796829CA90069',
+ 16
+ ));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistb233.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistb233.php
new file mode 100644
index 000000000..bae12b06f
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistb233.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * nistb233
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class nistb233 extends sect233r1
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistb409.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistb409.php
new file mode 100644
index 000000000..a46153d3c
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistb409.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * nistb409
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class nistb409 extends sect409r1
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk163.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk163.php
new file mode 100644
index 000000000..8b2637617
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk163.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * nistk163
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class nistk163 extends sect163k1
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk233.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk233.php
new file mode 100644
index 000000000..69e141382
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk233.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * nistk233
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class nistk233 extends sect233k1
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk283.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk283.php
new file mode 100644
index 000000000..9e95f10e7
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk283.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * sect283k1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class nistk283 extends sect283k1
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk409.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk409.php
new file mode 100644
index 000000000..06bd9af76
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistk409.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * nistk409
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class nistk409 extends sect409k1
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp192.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp192.php
new file mode 100644
index 000000000..ddead3cff
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp192.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * nistp192
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class nistp192 extends secp192r1
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp224.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp224.php
new file mode 100644
index 000000000..746571b4d
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp224.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * nistp224
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class nistp224 extends secp224r1
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp256.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp256.php
new file mode 100644
index 000000000..a26c0f992
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp256.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * nistp256
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class nistp256 extends secp256r1
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp384.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp384.php
new file mode 100644
index 000000000..1f20c02d1
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp384.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * nistp384
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class nistp384 extends secp384r1
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp521.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp521.php
new file mode 100644
index 000000000..86fa05084
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistp521.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * nistp521
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class nistp521 extends secp521r1
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistt571.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistt571.php
new file mode 100644
index 000000000..7908b38b9
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/nistt571.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * nistt571
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class nistt571 extends sect571k1
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v1.php
new file mode 100644
index 000000000..e9c13cd8c
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v1.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * prime192v1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class prime192v1 extends secp192r1
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v2.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v2.php
new file mode 100644
index 000000000..e3e341f26
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v2.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * prime192v2
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class prime192v2 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF', 16));
+ $this->setCoefficients(
+ new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFC', 16),
+ new BigInteger('CC22D6DFB95C6B25E49C0D6364A4E5980C393AA21668D953', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('EEA2BAE7E1497842F2DE7769CFE9C989C072AD696F48034A', 16),
+ new BigInteger('6574D11D69B6EC7A672BB82A083DF2F2B0847DE970B2DE15', 16)
+ );
+ $this->setOrder(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFE5FB1A724DC80418648D8DD31', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v3.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v3.php
new file mode 100644
index 000000000..1e97992dc
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime192v3.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * prime192v3
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class prime192v3 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF', 16));
+ $this->setCoefficients(
+ new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFC', 16),
+ new BigInteger('22123DC2395A05CAA7423DAECCC94760A7D462256BD56916', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('7D29778100C65A1DA1783716588DCE2B8B4AEE8E228F1896', 16),
+ new BigInteger('38A90F22637337334B49DCB66A6DC8F9978ACA7648A943B0', 16)
+ );
+ $this->setOrder(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFF7A62D031C83F4294F640EC13', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v1.php
new file mode 100644
index 000000000..084be9d7c
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v1.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * prime239v1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class prime239v1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFF8000000000007FFFFFFFFFFF', 16));
+ $this->setCoefficients(
+ new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFF8000000000007FFFFFFFFFFC', 16),
+ new BigInteger('6B016C3BDCF18941D0D654921475CA71A9DB2FB27D1D37796185C2942C0A', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('0FFA963CDCA8816CCC33B8642BEDF905C3D358573D3F27FBBD3B3CB9AAAF', 16),
+ new BigInteger('7DEBE8E4E90A5DAE6E4054CA530BA04654B36818CE226B39FCCB7B02F1AE', 16)
+ );
+ $this->setOrder(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF7FFFFF9E5E9A9F5D9071FBD1522688909D0B', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v2.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v2.php
new file mode 100644
index 000000000..21941b834
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v2.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * prime239v2
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class prime239v2 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFF8000000000007FFFFFFFFFFF', 16));
+ $this->setCoefficients(
+ new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFF8000000000007FFFFFFFFFFC', 16),
+ new BigInteger('617FAB6832576CBBFED50D99F0249C3FEE58B94BA0038C7AE84C8C832F2C', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('38AF09D98727705120C921BB5E9E26296A3CDCF2F35757A0EAFD87B830E7', 16),
+ new BigInteger('5B0125E4DBEA0EC7206DA0FC01D9B081329FB555DE6EF460237DFF8BE4BA', 16)
+ );
+ $this->setOrder(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF800000CFA7E8594377D414C03821BC582063', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v3.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v3.php
new file mode 100644
index 000000000..78c50f069
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime239v3.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * prime239v3
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class prime239v3 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFF8000000000007FFFFFFFFFFF', 16));
+ $this->setCoefficients(
+ new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFF8000000000007FFFFFFFFFFC', 16),
+ new BigInteger('255705FA2A306654B1F4CB03D6A750A30C250102D4988717D9BA15AB6D3E', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('6768AE8E18BB92CFCF005C949AA2C6D94853D0E660BBF854B1C9505FE95A', 16),
+ new BigInteger('1607E6898F390C06BC1D552BAD226F3B6FCFE48B6E818499AF18E3ED6CF3', 16)
+ );
+ $this->setOrder(new BigInteger('7FFFFFFFFFFFFFFFFFFFFFFF7FFFFF975DEB41B3A6057C3C432146526551', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime256v1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime256v1.php
new file mode 100644
index 000000000..c72b22e8a
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/prime256v1.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * prime256v1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+final class prime256v1 extends secp256r1
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp112r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp112r1.php
new file mode 100644
index 000000000..d1d3194b2
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp112r1.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * secp112r1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class secp112r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('DB7C2ABF62E35E668076BEAD208B', 16));
+ $this->setCoefficients(
+ new BigInteger('DB7C2ABF62E35E668076BEAD2088', 16),
+ new BigInteger('659EF8BA043916EEDE8911702B22', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('09487239995A5EE76B55F9C2F098', 16),
+ new BigInteger('A89CE5AF8724C0A23E0E0FF77500', 16)
+ );
+ $this->setOrder(new BigInteger('DB7C2ABF62E35E7628DFAC6561C5', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp112r2.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp112r2.php
new file mode 100644
index 000000000..da44e7fd8
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp112r2.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * secp112r2
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class secp112r2 extends Prime
+{
+ public function __construct()
+ {
+ // same modulo as secp112r1
+ $this->setModulo(new BigInteger('DB7C2ABF62E35E668076BEAD208B', 16));
+ $this->setCoefficients(
+ new BigInteger('6127C24C05F38A0AAAF65C0EF02C', 16),
+ new BigInteger('51DEF1815DB5ED74FCC34C85D709', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('4BA30AB5E892B4E1649DD0928643', 16),
+ new BigInteger('ADCD46F5882E3747DEF36E956E97', 16)
+ );
+ $this->setOrder(new BigInteger('36DF0AAFD8B8D7597CA10520D04B', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp128r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp128r1.php
new file mode 100644
index 000000000..34456bc07
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp128r1.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * secp128r1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class secp128r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('FFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFF', 16));
+ $this->setCoefficients(
+ new BigInteger('FFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFC', 16),
+ new BigInteger('E87579C11079F43DD824993C2CEE5ED3', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('161FF7528B899B2D0C28607CA52C5B86', 16),
+ new BigInteger('CF5AC8395BAFEB13C02DA292DDED7A83', 16)
+ );
+ $this->setOrder(new BigInteger('FFFFFFFE0000000075A30D1B9038A115', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp128r2.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp128r2.php
new file mode 100644
index 000000000..e102c3409
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp128r2.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * secp128r2
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class secp128r2 extends Prime
+{
+ public function __construct()
+ {
+ // same as secp128r1
+ $this->setModulo(new BigInteger('FFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFF', 16));
+ $this->setCoefficients(
+ new BigInteger('D6031998D1B3BBFEBF59CC9BBFF9AEE1', 16),
+ new BigInteger('5EEEFCA380D02919DC2C6558BB6D8A5D', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('7B6AA5D85E572983E6FB32A7CDEBC140', 16),
+ new BigInteger('27B6916A894D3AEE7106FE805FC34B44', 16)
+ );
+ $this->setOrder(new BigInteger('3FFFFFFF7FFFFFFFBE0024720613B5A3', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160k1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160k1.php
new file mode 100644
index 000000000..c6a33344a
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160k1.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * secp160k1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\KoblitzPrime;
+use phpseclib3\Math\BigInteger;
+
+class secp160k1 extends KoblitzPrime
+{
+ public function __construct()
+ {
+ // same as secp160r2
+ $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC73', 16));
+ $this->setCoefficients(
+ new BigInteger('0000000000000000000000000000000000000000', 16),
+ new BigInteger('0000000000000000000000000000000000000007', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('3B4C382CE37AA192A4019E763036F4F5DD4D7EBB', 16),
+ new BigInteger('938CF935318FDCED6BC28286531733C3F03C4FEE', 16)
+ );
+ $this->setOrder(new BigInteger('0100000000000000000001B8FA16DFAB9ACA16B6B3', 16));
+
+ $this->basis = [];
+ $this->basis[] = [
+ 'a' => new BigInteger('0096341F1138933BC2F505', -16),
+ 'b' => new BigInteger('FF6E9D0418C67BB8D5F562', -16)
+ ];
+ $this->basis[] = [
+ 'a' => new BigInteger('01BDCB3A09AAAABEAFF4A8', -16),
+ 'b' => new BigInteger('04D12329FF0EF498EA67', -16)
+ ];
+ $this->beta = $this->factory->newInteger(new BigInteger('645B7345A143464942CC46D7CF4D5D1E1E6CBB68', -16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160r1.php
new file mode 100644
index 000000000..af4687749
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160r1.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * secp160r1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class secp160r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFF', 16));
+ $this->setCoefficients(
+ new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFC', 16),
+ new BigInteger('1C97BEFC54BD7A8B65ACF89F81D4D4ADC565FA45', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('4A96B5688EF573284664698968C38BB913CBFC82', 16),
+ new BigInteger('23A628553168947D59DCC912042351377AC5FB32', 16)
+ );
+ $this->setOrder(new BigInteger('0100000000000000000001F4C8F927AED3CA752257', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160r2.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160r2.php
new file mode 100644
index 000000000..9bd23d23c
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp160r2.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * secp160r2
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class secp160r2 extends Prime
+{
+ public function __construct()
+ {
+ // same as secp160k1
+ $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC73', 16));
+ $this->setCoefficients(
+ new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC70', 16),
+ new BigInteger('B4E134D3FB59EB8BAB57274904664D5AF50388BA', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('52DCB034293A117E1F4FF11B30F7199D3144CE6D', 16),
+ new BigInteger('FEAFFEF2E331F296E071FA0DF9982CFEA7D43F2E', 16)
+ );
+ $this->setOrder(new BigInteger('0100000000000000000000351EE786A818F3A1A16B', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp192k1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp192k1.php
new file mode 100644
index 000000000..79ff2e097
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp192k1.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * secp192k1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\KoblitzPrime;
+use phpseclib3\Math\BigInteger;
+
+class secp192k1 extends KoblitzPrime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFEE37', 16));
+ $this->setCoefficients(
+ new BigInteger('000000000000000000000000000000000000000000000000', 16),
+ new BigInteger('000000000000000000000000000000000000000000000003', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('DB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D', 16),
+ new BigInteger('9B2F2F6D9C5628A7844163D015BE86344082AA88D95E2F9D', 16)
+ );
+ $this->setOrder(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFE26F2FC170F69466A74DEFD8D', 16));
+
+ $this->basis = [];
+ $this->basis[] = [
+ 'a' => new BigInteger('00B3FB3400DEC5C4ADCEB8655C', -16),
+ 'b' => new BigInteger('8EE96418CCF4CFC7124FDA0F', -16)
+ ];
+ $this->basis[] = [
+ 'a' => new BigInteger('01D90D03E8F096B9948B20F0A9', -16),
+ 'b' => new BigInteger('42E49819ABBA9474E1083F6B', -16)
+ ];
+ $this->beta = $this->factory->newInteger(new BigInteger('447A96E6C647963E2F7809FEAAB46947F34B0AA3CA0BBA74', -16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp192r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp192r1.php
new file mode 100644
index 000000000..83ab1c706
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp192r1.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * secp192r1
+ *
+ * This is the NIST P-192 curve
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class secp192r1 extends Prime
+{
+ public function __construct()
+ {
+ $modulo = new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF', 16);
+ $this->setModulo($modulo);
+
+ // algorithm 2.27 from http://diamond.boisestate.edu/~liljanab/MATH308/GuideToECC.pdf#page=66
+ /* in theory this should be faster than regular modular reductions save for one small issue.
+ to convert to / from base-2**8 with BCMath you have to call bcmul() and bcdiv() a lot.
+ to convert to / from base-2**8 with PHP64 you have to call base256_rshift() a lot.
+ in short, converting to / from base-2**8 is pretty expensive and that expense is
+ enough to offset whatever else might be gained by a simplified reduction algorithm.
+ now, if PHP supported unsigned integers things might be different. no bit-shifting
+ would be required for the PHP engine and it'd be a lot faster. but as is, BigInteger
+ uses base-2**31 or base-2**26 depending on whether or not the system is has a 32-bit
+ or a 64-bit OS.
+ */
+ /*
+ $m_length = $this->getLengthInBytes();
+ $this->setReduction(function($c) use ($m_length) {
+ $cBytes = $c->toBytes();
+ $className = $this->className;
+
+ if (strlen($cBytes) > 2 * $m_length) {
+ list(, $r) = $c->divide($className::$modulo);
+ return $r;
+ }
+
+ $c = str_pad($cBytes, 48, "\0", STR_PAD_LEFT);
+ $c = array_reverse(str_split($c, 8));
+
+ $null = "\0\0\0\0\0\0\0\0";
+ $s1 = new BigInteger($c[2] . $c[1] . $c[0], 256);
+ $s2 = new BigInteger($null . $c[3] . $c[3], 256);
+ $s3 = new BigInteger($c[4] . $c[4] . $null, 256);
+ $s4 = new BigInteger($c[5] . $c[5] . $c[5], 256);
+
+ $r = $s1->add($s2)->add($s3)->add($s4);
+ while ($r->compare($className::$modulo) >= 0) {
+ $r = $r->subtract($className::$modulo);
+ }
+
+ return $r;
+ });
+ */
+
+ $this->setCoefficients(
+ new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFC', 16),
+ new BigInteger('64210519E59C80E70FA7E9AB72243049FEB8DEECC146B9B1', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('188DA80EB03090F67CBF20EB43A18800F4FF0AFD82FF1012', 16),
+ new BigInteger('07192B95FFC8DA78631011ED6B24CDD573F977A11E794811', 16)
+ );
+ $this->setOrder(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFF99DEF836146BC9B1B4D22831', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp224k1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp224k1.php
new file mode 100644
index 000000000..79a5c5417
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp224k1.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * secp224k1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\KoblitzPrime;
+use phpseclib3\Math\BigInteger;
+
+class secp224k1 extends KoblitzPrime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFE56D', 16));
+ $this->setCoefficients(
+ new BigInteger('00000000000000000000000000000000000000000000000000000000', 16),
+ new BigInteger('00000000000000000000000000000000000000000000000000000005', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('A1455B334DF099DF30FC28A169A467E9E47075A90F7E650EB6B7A45C', 16),
+ new BigInteger('7E089FED7FBA344282CAFBD6F7E319F7C0B0BD59E2CA4BDB556D61A5', 16)
+ );
+ $this->setOrder(new BigInteger('010000000000000000000000000001DCE8D2EC6184CAF0A971769FB1F7', 16));
+
+ $this->basis = [];
+ $this->basis[] = [
+ 'a' => new BigInteger('00B8ADF1378A6EB73409FA6C9C637D', -16),
+ 'b' => new BigInteger('94730F82B358A3776A826298FA6F', -16)
+ ];
+ $this->basis[] = [
+ 'a' => new BigInteger('01DCE8D2EC6184CAF0A972769FCC8B', -16),
+ 'b' => new BigInteger('4D2100BA3DC75AAB747CCF355DEC', -16)
+ ];
+ $this->beta = $this->factory->newInteger(new BigInteger('01F178FFA4B17C89E6F73AECE2AAD57AF4C0A748B63C830947B27E04', -16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp224r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp224r1.php
new file mode 100644
index 000000000..a9e474a3c
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp224r1.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * secp224r1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class secp224r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000001', 16));
+ $this->setCoefficients(
+ new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFE', 16),
+ new BigInteger('B4050A850C04B3ABF54132565044B0B7D7BFD8BA270B39432355FFB4', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('B70E0CBD6BB4BF7F321390B94A03C1D356C21122343280D6115C1D21', 16),
+ new BigInteger('BD376388B5F723FB4C22DFE6CD4375A05A07476444D5819985007E34', 16)
+ );
+ $this->setOrder(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFF16A2E0B8F03E13DD29455C5C2A3D', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp256k1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp256k1.php
new file mode 100644
index 000000000..462e7a1ca
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp256k1.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * secp256k1
+ *
+ * This is the curve used in Bitcoin
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+//use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Crypt\EC\BaseCurves\KoblitzPrime;
+use phpseclib3\Math\BigInteger;
+
+//class secp256k1 extends Prime
+class secp256k1 extends KoblitzPrime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F', 16));
+ $this->setCoefficients(
+ new BigInteger('0000000000000000000000000000000000000000000000000000000000000000', 16),
+ new BigInteger('0000000000000000000000000000000000000000000000000000000000000007', 16)
+ );
+ $this->setOrder(new BigInteger('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 16));
+ $this->setBasePoint(
+ new BigInteger('79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798', 16),
+ new BigInteger('483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8', 16)
+ );
+
+ $this->basis = [];
+ $this->basis[] = [
+ 'a' => new BigInteger('3086D221A7D46BCDE86C90E49284EB15', -16),
+ 'b' => new BigInteger('FF1BBC8129FEF177D790AB8056F5401B3D', -16)
+ ];
+ $this->basis[] = [
+ 'a' => new BigInteger('114CA50F7A8E2F3F657C1108D9D44CFD8', -16),
+ 'b' => new BigInteger('3086D221A7D46BCDE86C90E49284EB15', -16)
+ ];
+ $this->beta = $this->factory->newInteger(new BigInteger('7AE96A2B657C07106E64479EAC3434E99CF0497512F58995C1396C28719501EE', -16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp256r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp256r1.php
new file mode 100644
index 000000000..9003373cf
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp256r1.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * secp256r1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class secp256r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF', 16));
+ $this->setCoefficients(
+ new BigInteger('FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC', 16),
+ new BigInteger('5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296', 16),
+ new BigInteger('4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5', 16)
+ );
+ $this->setOrder(new BigInteger('FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp384r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp384r1.php
new file mode 100644
index 000000000..98764a341
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp384r1.php
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * secp384r1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class secp384r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger(
+ 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF',
+ 16
+ ));
+ $this->setCoefficients(
+ new BigInteger(
+ 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC',
+ 16
+ ),
+ new BigInteger(
+ 'B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF',
+ 16
+ )
+ );
+ $this->setBasePoint(
+ new BigInteger(
+ 'AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7',
+ 16
+ ),
+ new BigInteger(
+ '3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F',
+ 16
+ )
+ );
+ $this->setOrder(new BigInteger(
+ 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973',
+ 16
+ ));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp521r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp521r1.php
new file mode 100644
index 000000000..b89a4ea74
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/secp521r1.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * secp521r1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Prime;
+use phpseclib3\Math\BigInteger;
+
+class secp521r1 extends Prime
+{
+ public function __construct()
+ {
+ $this->setModulo(new BigInteger('01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' .
+ 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' .
+ 'FFFF', 16));
+ $this->setCoefficients(
+ new BigInteger('01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' .
+ 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' .
+ 'FFFC', 16),
+ new BigInteger('0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF1' .
+ '09E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B50' .
+ '3F00', 16)
+ );
+ $this->setBasePoint(
+ new BigInteger('00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D' .
+ '3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5' .
+ 'BD66', 16),
+ new BigInteger('011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E' .
+ '662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD1' .
+ '6650', 16)
+ );
+ $this->setOrder(new BigInteger('01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' .
+ 'FFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E9138' .
+ '6409', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect113r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect113r1.php
new file mode 100644
index 000000000..77ec7603a
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect113r1.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * sect113r1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect113r1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(113, 9, 0);
+ $this->setCoefficients(
+ '003088250CA6E7C7FE649CE85820F7',
+ '00E8BEE4D3E2260744188BE0E9C723'
+ );
+ $this->setBasePoint(
+ '009D73616F35F4AB1407D73562C10F',
+ '00A52830277958EE84D1315ED31886'
+ );
+ $this->setOrder(new BigInteger('0100000000000000D9CCEC8A39E56F', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect113r2.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect113r2.php
new file mode 100644
index 000000000..2185d60e3
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect113r2.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * sect113r2
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect113r2 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(113, 9, 0);
+ $this->setCoefficients(
+ '00689918DBEC7E5A0DD6DFC0AA55C7',
+ '0095E9A9EC9B297BD4BF36E059184F'
+ );
+ $this->setBasePoint(
+ '01A57A6A7B26CA5EF52FCDB8164797',
+ '00B3ADC94ED1FE674C06E695BABA1D'
+ );
+ $this->setOrder(new BigInteger('010000000000000108789B2496AF93', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect131r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect131r1.php
new file mode 100644
index 000000000..1365cb601
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect131r1.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * sect131r1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect131r1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(131, 8, 3, 2, 0);
+ $this->setCoefficients(
+ '07A11B09A76B562144418FF3FF8C2570B8',
+ '0217C05610884B63B9C6C7291678F9D341'
+ );
+ $this->setBasePoint(
+ '0081BAF91FDF9833C40F9C181343638399',
+ '078C6E7EA38C001F73C8134B1B4EF9E150'
+ );
+ $this->setOrder(new BigInteger('0400000000000000023123953A9464B54D', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect131r2.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect131r2.php
new file mode 100644
index 000000000..93c11b2a3
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect131r2.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * sect131r2
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect131r2 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(131, 8, 3, 2, 0);
+ $this->setCoefficients(
+ '03E5A88919D7CAFCBF415F07C2176573B2',
+ '04B8266A46C55657AC734CE38F018F2192'
+ );
+ $this->setBasePoint(
+ '0356DCD8F2F95031AD652D23951BB366A8',
+ '0648F06D867940A5366D9E265DE9EB240F'
+ );
+ $this->setOrder(new BigInteger('0400000000000000016954A233049BA98F', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163k1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163k1.php
new file mode 100644
index 000000000..3c8574bb3
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163k1.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * sect163k1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect163k1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(163, 7, 6, 3, 0);
+ $this->setCoefficients(
+ '000000000000000000000000000000000000000001',
+ '000000000000000000000000000000000000000001'
+ );
+ $this->setBasePoint(
+ '02FE13C0537BBC11ACAA07D793DE4E6D5E5C94EEE8',
+ '0289070FB05D38FF58321F2E800536D538CCDAA3D9'
+ );
+ $this->setOrder(new BigInteger('04000000000000000000020108A2E0CC0D99F8A5EF', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163r1.php
new file mode 100644
index 000000000..26afd87e4
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163r1.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * sect163r1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect163r1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(163, 7, 6, 3, 0);
+ $this->setCoefficients(
+ '07B6882CAAEFA84F9554FF8428BD88E246D2782AE2',
+ '0713612DCDDCB40AAB946BDA29CA91F73AF958AFD9'
+ );
+ $this->setBasePoint(
+ '0369979697AB43897789566789567F787A7876A654',
+ '00435EDB42EFAFB2989D51FEFCE3C80988F41FF883'
+ );
+ $this->setOrder(new BigInteger('03FFFFFFFFFFFFFFFFFFFF48AAB689C29CA710279B', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163r2.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163r2.php
new file mode 100644
index 000000000..38f94661c
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect163r2.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * sect163r2
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect163r2 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(163, 7, 6, 3, 0);
+ $this->setCoefficients(
+ '000000000000000000000000000000000000000001',
+ '020A601907B8C953CA1481EB10512F78744A3205FD'
+ );
+ $this->setBasePoint(
+ '03F0EBA16286A2D57EA0991168D4994637E8343E36',
+ '00D51FBC6C71A0094FA2CDD545B11C5C0C797324F1'
+ );
+ $this->setOrder(new BigInteger('040000000000000000000292FE77E70C12A4234C33', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect193r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect193r1.php
new file mode 100644
index 000000000..951f261eb
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect193r1.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * sect193r1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect193r1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(193, 15, 0);
+ $this->setCoefficients(
+ '0017858FEB7A98975169E171F77B4087DE098AC8A911DF7B01',
+ '00FDFB49BFE6C3A89FACADAA7A1E5BBC7CC1C2E5D831478814'
+ );
+ $this->setBasePoint(
+ '01F481BC5F0FF84A74AD6CDF6FDEF4BF6179625372D8C0C5E1',
+ '0025E399F2903712CCF3EA9E3A1AD17FB0B3201B6AF7CE1B05'
+ );
+ $this->setOrder(new BigInteger('01000000000000000000000000C7F34A778F443ACC920EBA49', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect193r2.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect193r2.php
new file mode 100644
index 000000000..e3ff47ac7
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect193r2.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * sect193r2
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect193r2 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(193, 15, 0);
+ $this->setCoefficients(
+ '0163F35A5137C2CE3EA6ED8667190B0BC43ECD69977702709B',
+ '00C9BB9E8927D4D64C377E2AB2856A5B16E3EFB7F61D4316AE'
+ );
+ $this->setBasePoint(
+ '00D9B67D192E0367C803F39E1A7E82CA14A651350AAE617E8F',
+ '01CE94335607C304AC29E7DEFBD9CA01F596F927224CDECF6C'
+ );
+ $this->setOrder(new BigInteger('010000000000000000000000015AAB561B005413CCD4EE99D5', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect233k1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect233k1.php
new file mode 100644
index 000000000..eea3f7ad5
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect233k1.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * sect233k1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect233k1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(233, 74, 0);
+ $this->setCoefficients(
+ '000000000000000000000000000000000000000000000000000000000000',
+ '000000000000000000000000000000000000000000000000000000000001'
+ );
+ $this->setBasePoint(
+ '017232BA853A7E731AF129F22FF4149563A419C26BF50A4C9D6EEFAD6126',
+ '01DB537DECE819B7F70F555A67C427A8CD9BF18AEB9B56E0C11056FAE6A3'
+ );
+ $this->setOrder(new BigInteger('8000000000000000000000000000069D5BB915BCD46EFB1AD5F173ABDF', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect233r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect233r1.php
new file mode 100644
index 000000000..68219f0ea
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect233r1.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * sect233r1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect233r1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(233, 74, 0);
+ $this->setCoefficients(
+ '000000000000000000000000000000000000000000000000000000000001',
+ '0066647EDE6C332C7F8C0923BB58213B333B20E9CE4281FE115F7D8F90AD'
+ );
+ $this->setBasePoint(
+ '00FAC9DFCBAC8313BB2139F1BB755FEF65BC391F8B36F8F8EB7371FD558B',
+ '01006A08A41903350678E58528BEBF8A0BEFF867A7CA36716F7E01F81052'
+ );
+ $this->setOrder(new BigInteger('01000000000000000000000000000013E974E72F8A6922031D2603CFE0D7', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect239k1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect239k1.php
new file mode 100644
index 000000000..0e6994ba3
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect239k1.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * sect239k1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wiggint on <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect239k1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(239, 158, 0);
+ $this->setCoefficients(
+ '000000000000000000000000000000000000000000000000000000000000',
+ '000000000000000000000000000000000000000000000000000000000001'
+ );
+ $this->setBasePoint(
+ '29A0B6A887A983E9730988A68727A8B2D126C44CC2CC7B2A6555193035DC',
+ '76310804F12E549BDB011C103089E73510ACB275FC312A5DC6B76553F0CA'
+ );
+ $this->setOrder(new BigInteger('2000000000000000000000000000005A79FEC67CB6E91F1C1DA800E478A5', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect283k1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect283k1.php
new file mode 100644
index 000000000..279c24aac
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect283k1.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * sect283k1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wiggint on <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect283k1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(283, 12, 7, 5, 0);
+ $this->setCoefficients(
+ '000000000000000000000000000000000000000000000000000000000000000000000000',
+ '000000000000000000000000000000000000000000000000000000000000000000000001'
+ );
+ $this->setBasePoint(
+ '0503213F78CA44883F1A3B8162F188E553CD265F23C1567A16876913B0C2AC2458492836',
+ '01CCDA380F1C9E318D90F95D07E5426FE87E45C0E8184698E45962364E34116177DD2259'
+ );
+ $this->setOrder(new BigInteger('01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE9AE2ED07577265DFF7F94451E061E163C61', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect283r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect283r1.php
new file mode 100644
index 000000000..e44a60765
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect283r1.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * sect283r1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wiggint on <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect283r1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(283, 12, 7, 5, 0);
+ $this->setCoefficients(
+ '000000000000000000000000000000000000000000000000000000000000000000000001',
+ '027B680AC8B8596DA5A4AF8A19A0303FCA97FD7645309FA2A581485AF6263E313B79A2F5'
+ );
+ $this->setBasePoint(
+ '05F939258DB7DD90E1934F8C70B0DFEC2EED25B8557EAC9C80E2E198F8CDBECD86B12053',
+ '03676854FE24141CB98FE6D4B20D02B4516FF702350EDDB0826779C813F0DF45BE8112F4'
+ );
+ $this->setOrder(new BigInteger('03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEF90399660FC938A90165B042A7CEFADB307', 16));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect409k1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect409k1.php
new file mode 100644
index 000000000..1fe329d8c
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect409k1.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * sect409k1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wiggint on <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect409k1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(409, 87, 0);
+ $this->setCoefficients(
+ '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
+ '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001'
+ );
+ $this->setBasePoint(
+ '0060F05F658F49C1AD3AB1890F7184210EFD0987E307C84C27ACCFB8F9F67CC2C460189EB5AAAA62EE222EB1B35540CFE9023746',
+ '01E369050B7C4E42ACBA1DACBF04299C3460782F918EA427E6325165E9EA10E3DA5F6C42E9C55215AA9CA27A5863EC48D8E0286B'
+ );
+ $this->setOrder(new BigInteger(
+ '7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE5F' .
+ '83B2D4EA20400EC4557D5ED3E3E7CA5B4B5C83B8E01E5FCF',
+ 16
+ ));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect409r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect409r1.php
new file mode 100644
index 000000000..3e209ef8f
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect409r1.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * sect409r1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wiggint on <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect409r1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(409, 87, 0);
+ $this->setCoefficients(
+ '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001',
+ '0021A5C2C8EE9FEB5C4B9A753B7B476B7FD6422EF1F3DD674761FA99D6AC27C8A9A197B272822F6CD57A55AA4F50AE317B13545F'
+ );
+ $this->setBasePoint(
+ '015D4860D088DDB3496B0C6064756260441CDE4AF1771D4DB01FFE5B34E59703DC255A868A1180515603AEAB60794E54BB7996A7',
+ '0061B1CFAB6BE5F32BBFA78324ED106A7636B9C5A7BD198D0158AA4F5488D08F38514F1FDF4B4F40D2181B3681C364BA0273C706'
+ );
+ $this->setOrder(new BigInteger(
+ '010000000000000000000000000000000000000000000000000001E2' .
+ 'AAD6A612F33307BE5FA47C3C9E052F838164CD37D9A21173',
+ 16
+ ));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect571k1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect571k1.php
new file mode 100644
index 000000000..3c54eabdb
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect571k1.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * sect571k1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wiggint on <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect571k1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(571, 10, 5, 2, 0);
+ $this->setCoefficients(
+ '000000000000000000000000000000000000000000000000000000000000000000000000' .
+ '000000000000000000000000000000000000000000000000000000000000000000000000',
+ '000000000000000000000000000000000000000000000000000000000000000000000000' .
+ '000000000000000000000000000000000000000000000000000000000000000000000001'
+ );
+ $this->setBasePoint(
+ '026EB7A859923FBC82189631F8103FE4AC9CA2970012D5D46024804801841CA443709584' .
+ '93B205E647DA304DB4CEB08CBBD1BA39494776FB988B47174DCA88C7E2945283A01C8972',
+ '0349DC807F4FBF374F4AEADE3BCA95314DD58CEC9F307A54FFC61EFC006D8A2C9D4979C0' .
+ 'AC44AEA74FBEBBB9F772AEDCB620B01A7BA7AF1B320430C8591984F601CD4C143EF1C7A3'
+ );
+ $this->setOrder(new BigInteger(
+ '020000000000000000000000000000000000000000000000000000000000000000000000' .
+ '131850E1F19A63E4B391A8DB917F4138B630D84BE5D639381E91DEB45CFE778F637C1001',
+ 16
+ ));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect571r1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect571r1.php
new file mode 100644
index 000000000..172c1af9c
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Curves/sect571r1.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * sect571r1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wiggint on <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Crypt\EC\Curves;
+
+use phpseclib3\Crypt\EC\BaseCurves\Binary;
+use phpseclib3\Math\BigInteger;
+
+class sect571r1 extends Binary
+{
+ public function __construct()
+ {
+ $this->setModulo(571, 10, 5, 2, 0);
+ $this->setCoefficients(
+ '000000000000000000000000000000000000000000000000000000000000000000000000' .
+ '000000000000000000000000000000000000000000000000000000000000000000000001',
+ '02F40E7E2221F295DE297117B7F3D62F5C6A97FFCB8CEFF1CD6BA8CE4A9A18AD84FFABBD' .
+ '8EFA59332BE7AD6756A66E294AFD185A78FF12AA520E4DE739BACA0C7FFEFF7F2955727A'
+ );
+ $this->setBasePoint(
+ '0303001D34B856296C16C0D40D3CD7750A93D1D2955FA80AA5F40FC8DB7B2ABDBDE53950' .
+ 'F4C0D293CDD711A35B67FB1499AE60038614F1394ABFA3B4C850D927E1E7769C8EEC2D19',
+ '037BF27342DA639B6DCCFFFEB73D69D78C6C27A6009CBBCA1980F8533921E8A684423E43' .
+ 'BAB08A576291AF8F461BB2A8B3531D2F0485C19B16E2F1516E23DD3C1A4827AF1B8AC15B'
+ );
+ $this->setOrder(new BigInteger(
+ '03FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' .
+ 'E661CE18FF55987308059B186823851EC7DD9CA1161DE93D5174D66E8382E9BB2FE84E47',
+ 16
+ ));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/Common.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/Common.php
new file mode 100644
index 000000000..743c07c3e
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/Common.php
@@ -0,0 +1,549 @@
+<?php
+
+/**
+ * Generic EC Key Parsing Helper functions
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve;
+use phpseclib3\Crypt\EC\BaseCurves\Binary as BinaryCurve;
+use phpseclib3\Crypt\EC\BaseCurves\Prime as PrimeCurve;
+use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
+use phpseclib3\Exception\UnsupportedCurveException;
+use phpseclib3\File\ASN1;
+use phpseclib3\File\ASN1\Maps;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Generic EC Key Parsing Helper functions
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+trait Common
+{
+ /**
+ * Curve OIDs
+ *
+ * @var array
+ */
+ private static $curveOIDs = [];
+
+ /**
+ * Child OIDs loaded
+ *
+ * @var bool
+ */
+ protected static $childOIDsLoaded = false;
+
+ /**
+ * Use Named Curves
+ *
+ * @var bool
+ */
+ private static $useNamedCurves = true;
+
+ /**
+ * Initialize static variables
+ */
+ private static function initialize_static_variables()
+ {
+ if (empty(self::$curveOIDs)) {
+ // the sec* curves are from the standards for efficient cryptography group
+ // sect* curves are curves over binary finite fields
+ // secp* curves are curves over prime finite fields
+ // sec*r* curves are regular curves; sec*k* curves are koblitz curves
+ // brainpool*r* curves are regular prime finite field curves
+ // brainpool*t* curves are twisted versions of the brainpool*r* curves
+ self::$curveOIDs = [
+ 'prime192v1' => '1.2.840.10045.3.1.1', // J.5.1, example 1 (aka secp192r1)
+ 'prime192v2' => '1.2.840.10045.3.1.2', // J.5.1, example 2
+ 'prime192v3' => '1.2.840.10045.3.1.3', // J.5.1, example 3
+ 'prime239v1' => '1.2.840.10045.3.1.4', // J.5.2, example 1
+ 'prime239v2' => '1.2.840.10045.3.1.5', // J.5.2, example 2
+ 'prime239v3' => '1.2.840.10045.3.1.6', // J.5.2, example 3
+ 'prime256v1' => '1.2.840.10045.3.1.7', // J.5.3, example 1 (aka secp256r1)
+
+ // https://tools.ietf.org/html/rfc5656#section-10
+ 'nistp256' => '1.2.840.10045.3.1.7', // aka secp256r1
+ 'nistp384' => '1.3.132.0.34', // aka secp384r1
+ 'nistp521' => '1.3.132.0.35', // aka secp521r1
+
+ 'nistk163' => '1.3.132.0.1', // aka sect163k1
+ 'nistp192' => '1.2.840.10045.3.1.1', // aka secp192r1
+ 'nistp224' => '1.3.132.0.33', // aka secp224r1
+ 'nistk233' => '1.3.132.0.26', // aka sect233k1
+ 'nistb233' => '1.3.132.0.27', // aka sect233r1
+ 'nistk283' => '1.3.132.0.16', // aka sect283k1
+ 'nistk409' => '1.3.132.0.36', // aka sect409k1
+ 'nistb409' => '1.3.132.0.37', // aka sect409r1
+ 'nistt571' => '1.3.132.0.38', // aka sect571k1
+
+ // from https://tools.ietf.org/html/rfc5915
+ 'secp192r1' => '1.2.840.10045.3.1.1', // aka prime192v1
+ 'sect163k1' => '1.3.132.0.1',
+ 'sect163r2' => '1.3.132.0.15',
+ 'secp224r1' => '1.3.132.0.33',
+ 'sect233k1' => '1.3.132.0.26',
+ 'sect233r1' => '1.3.132.0.27',
+ 'secp256r1' => '1.2.840.10045.3.1.7', // aka prime256v1
+ 'sect283k1' => '1.3.132.0.16',
+ 'sect283r1' => '1.3.132.0.17',
+ 'secp384r1' => '1.3.132.0.34',
+ 'sect409k1' => '1.3.132.0.36',
+ 'sect409r1' => '1.3.132.0.37',
+ 'secp521r1' => '1.3.132.0.35',
+ 'sect571k1' => '1.3.132.0.38',
+ 'sect571r1' => '1.3.132.0.39',
+ // from http://www.secg.org/SEC2-Ver-1.0.pdf
+ 'secp112r1' => '1.3.132.0.6',
+ 'secp112r2' => '1.3.132.0.7',
+ 'secp128r1' => '1.3.132.0.28',
+ 'secp128r2' => '1.3.132.0.29',
+ 'secp160k1' => '1.3.132.0.9',
+ 'secp160r1' => '1.3.132.0.8',
+ 'secp160r2' => '1.3.132.0.30',
+ 'secp192k1' => '1.3.132.0.31',
+ 'secp224k1' => '1.3.132.0.32',
+ 'secp256k1' => '1.3.132.0.10',
+
+ 'sect113r1' => '1.3.132.0.4',
+ 'sect113r2' => '1.3.132.0.5',
+ 'sect131r1' => '1.3.132.0.22',
+ 'sect131r2' => '1.3.132.0.23',
+ 'sect163r1' => '1.3.132.0.2',
+ 'sect193r1' => '1.3.132.0.24',
+ 'sect193r2' => '1.3.132.0.25',
+ 'sect239k1' => '1.3.132.0.3',
+
+ // from http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.202.2977&rep=rep1&type=pdf#page=36
+ /*
+ 'c2pnb163v1' => '1.2.840.10045.3.0.1', // J.4.1, example 1
+ 'c2pnb163v2' => '1.2.840.10045.3.0.2', // J.4.1, example 2
+ 'c2pnb163v3' => '1.2.840.10045.3.0.3', // J.4.1, example 3
+ 'c2pnb172w1' => '1.2.840.10045.3.0.4', // J.4.2, example 1
+ 'c2tnb191v1' => '1.2.840.10045.3.0.5', // J.4.3, example 1
+ 'c2tnb191v2' => '1.2.840.10045.3.0.6', // J.4.3, example 2
+ 'c2tnb191v3' => '1.2.840.10045.3.0.7', // J.4.3, example 3
+ 'c2onb191v4' => '1.2.840.10045.3.0.8', // J.4.3, example 4
+ 'c2onb191v5' => '1.2.840.10045.3.0.9', // J.4.3, example 5
+ 'c2pnb208w1' => '1.2.840.10045.3.0.10', // J.4.4, example 1
+ 'c2tnb239v1' => '1.2.840.10045.3.0.11', // J.4.5, example 1
+ 'c2tnb239v2' => '1.2.840.10045.3.0.12', // J.4.5, example 2
+ 'c2tnb239v3' => '1.2.840.10045.3.0.13', // J.4.5, example 3
+ 'c2onb239v4' => '1.2.840.10045.3.0.14', // J.4.5, example 4
+ 'c2onb239v5' => '1.2.840.10045.3.0.15', // J.4.5, example 5
+ 'c2pnb272w1' => '1.2.840.10045.3.0.16', // J.4.6, example 1
+ 'c2pnb304w1' => '1.2.840.10045.3.0.17', // J.4.7, example 1
+ 'c2tnb359v1' => '1.2.840.10045.3.0.18', // J.4.8, example 1
+ 'c2pnb368w1' => '1.2.840.10045.3.0.19', // J.4.9, example 1
+ 'c2tnb431r1' => '1.2.840.10045.3.0.20', // J.4.10, example 1
+ */
+
+ // http://www.ecc-brainpool.org/download/Domain-parameters.pdf
+ // https://tools.ietf.org/html/rfc5639
+ 'brainpoolP160r1' => '1.3.36.3.3.2.8.1.1.1',
+ 'brainpoolP160t1' => '1.3.36.3.3.2.8.1.1.2',
+ 'brainpoolP192r1' => '1.3.36.3.3.2.8.1.1.3',
+ 'brainpoolP192t1' => '1.3.36.3.3.2.8.1.1.4',
+ 'brainpoolP224r1' => '1.3.36.3.3.2.8.1.1.5',
+ 'brainpoolP224t1' => '1.3.36.3.3.2.8.1.1.6',
+ 'brainpoolP256r1' => '1.3.36.3.3.2.8.1.1.7',
+ 'brainpoolP256t1' => '1.3.36.3.3.2.8.1.1.8',
+ 'brainpoolP320r1' => '1.3.36.3.3.2.8.1.1.9',
+ 'brainpoolP320t1' => '1.3.36.3.3.2.8.1.1.10',
+ 'brainpoolP384r1' => '1.3.36.3.3.2.8.1.1.11',
+ 'brainpoolP384t1' => '1.3.36.3.3.2.8.1.1.12',
+ 'brainpoolP512r1' => '1.3.36.3.3.2.8.1.1.13',
+ 'brainpoolP512t1' => '1.3.36.3.3.2.8.1.1.14'
+ ];
+ ASN1::loadOIDs([
+ 'prime-field' => '1.2.840.10045.1.1',
+ 'characteristic-two-field' => '1.2.840.10045.1.2',
+ 'characteristic-two-basis' => '1.2.840.10045.1.2.3',
+ // per http://www.secg.org/SEC1-Ver-1.0.pdf#page=84, gnBasis "not used here"
+ 'gnBasis' => '1.2.840.10045.1.2.3.1', // NULL
+ 'tpBasis' => '1.2.840.10045.1.2.3.2', // Trinomial
+ 'ppBasis' => '1.2.840.10045.1.2.3.3' // Pentanomial
+ ] + self::$curveOIDs);
+ }
+ }
+
+ /**
+ * Explicitly set the curve
+ *
+ * If the key contains an implicit curve phpseclib needs the curve
+ * to be explicitly provided
+ *
+ * @param BaseCurve $curve
+ */
+ public static function setImplicitCurve(BaseCurve $curve)
+ {
+ self::$implicitCurve = $curve;
+ }
+
+ /**
+ * Returns an instance of \phpseclib3\Crypt\EC\BaseCurves\Base based
+ * on the curve parameters
+ *
+ * @param array $params
+ * @return BaseCurve|false
+ */
+ protected static function loadCurveByParam(array $params)
+ {
+ if (count($params) > 1) {
+ throw new \RuntimeException('No parameters are present');
+ }
+ if (isset($params['namedCurve'])) {
+ $curve = '\phpseclib3\Crypt\EC\Curves\\' . $params['namedCurve'];
+ if (!class_exists($curve)) {
+ throw new UnsupportedCurveException('Named Curve of ' . $params['namedCurve'] . ' is not supported');
+ }
+ return new $curve();
+ }
+ if (isset($params['implicitCurve'])) {
+ if (!isset(self::$implicitCurve)) {
+ throw new \RuntimeException('Implicit curves can be provided by calling setImplicitCurve');
+ }
+ return self::$implicitCurve;
+ }
+ if (isset($params['specifiedCurve'])) {
+ $data = $params['specifiedCurve'];
+ switch ($data['fieldID']['fieldType']) {
+ case 'prime-field':
+ $curve = new PrimeCurve();
+ $curve->setModulo($data['fieldID']['parameters']);
+ $curve->setCoefficients(
+ new BigInteger($data['curve']['a'], 256),
+ new BigInteger($data['curve']['b'], 256)
+ );
+ $point = self::extractPoint("\0" . $data['base'], $curve);
+ $curve->setBasePoint(...$point);
+ $curve->setOrder($data['order']);
+ return $curve;
+ case 'characteristic-two-field':
+ $curve = new BinaryCurve();
+ $params = ASN1::decodeBER($data['fieldID']['parameters']);
+ $params = ASN1::asn1map($params[0], Maps\Characteristic_two::MAP);
+ $modulo = [(int) $params['m']->toString()];
+ switch ($params['basis']) {
+ case 'tpBasis':
+ $modulo[] = (int) $params['parameters']->toString();
+ break;
+ case 'ppBasis':
+ $temp = ASN1::decodeBER($params['parameters']);
+ $temp = ASN1::asn1map($temp[0], Maps\Pentanomial::MAP);
+ $modulo[] = (int) $temp['k3']->toString();
+ $modulo[] = (int) $temp['k2']->toString();
+ $modulo[] = (int) $temp['k1']->toString();
+ }
+ $modulo[] = 0;
+ $curve->setModulo(...$modulo);
+ $len = ceil($modulo[0] / 8);
+ $curve->setCoefficients(
+ Strings::bin2hex($data['curve']['a']),
+ Strings::bin2hex($data['curve']['b'])
+ );
+ $point = self::extractPoint("\0" . $data['base'], $curve);
+ $curve->setBasePoint(...$point);
+ $curve->setOrder($data['order']);
+ return $curve;
+ default:
+ throw new UnsupportedCurveException('Field Type of ' . $data['fieldID']['fieldType'] . ' is not supported');
+ }
+ }
+ throw new \RuntimeException('No valid parameters are present');
+ }
+
+ /**
+ * Extract points from a string
+ *
+ * Supports both compressed and uncompressed points
+ *
+ * @param string $str
+ * @param BaseCurve $curve
+ * @return object[]
+ */
+ public static function extractPoint($str, BaseCurve $curve)
+ {
+ if ($curve instanceof TwistedEdwardsCurve) {
+ // first step of point deciding as discussed at the following URL's:
+ // https://tools.ietf.org/html/rfc8032#section-5.1.3
+ // https://tools.ietf.org/html/rfc8032#section-5.2.3
+ $y = $str;
+ $y = strrev($y);
+ $sign = (bool) (ord($y[0]) & 0x80);
+ $y[0] = $y[0] & chr(0x7F);
+ $y = new BigInteger($y, 256);
+ if ($y->compare($curve->getModulo()) >= 0) {
+ throw new \RuntimeException('The Y coordinate should not be >= the modulo');
+ }
+ $point = $curve->recoverX($y, $sign);
+ if (!$curve->verifyPoint($point)) {
+ throw new \RuntimeException('Unable to verify that point exists on curve');
+ }
+ return $point;
+ }
+
+ // the first byte of a bit string represents the number of bits in the last byte that are to be ignored but,
+ // currently, bit strings wanting a non-zero amount of bits trimmed are not supported
+ if (($val = Strings::shift($str)) != "\0") {
+ throw new \UnexpectedValueException('extractPoint expects the first byte to be null - not ' . Strings::bin2hex($val));
+ }
+ if ($str == "\0") {
+ return [];
+ }
+
+ $keylen = strlen($str);
+ $order = $curve->getLengthInBytes();
+ // point compression is being used
+ if ($keylen == $order + 1) {
+ return $curve->derivePoint($str);
+ }
+
+ // point compression is not being used
+ if ($keylen == 2 * $order + 1) {
+ preg_match("#(.)(.{{$order}})(.{{$order}})#s", $str, $matches);
+ list(, $w, $x, $y) = $matches;
+ if ($w != "\4") {
+ throw new \UnexpectedValueException('The first byte of an uncompressed point should be 04 - not ' . Strings::bin2hex($val));
+ }
+ $point = [
+ $curve->convertInteger(new BigInteger($x, 256)),
+ $curve->convertInteger(new BigInteger($y, 256))
+ ];
+
+ if (!$curve->verifyPoint($point)) {
+ throw new \RuntimeException('Unable to verify that point exists on curve');
+ }
+
+ return $point;
+ }
+
+ throw new \UnexpectedValueException('The string representation of the points is not of an appropriate length');
+ }
+
+ /**
+ * Encode Parameters
+ *
+ * @todo Maybe at some point this could be moved to __toString() for each of the curves?
+ * @param BaseCurve $curve
+ * @param bool $returnArray optional
+ * @param array $options optional
+ * @return string|false
+ */
+ private static function encodeParameters(BaseCurve $curve, $returnArray = false, array $options = [])
+ {
+ $useNamedCurves = isset($options['namedCurve']) ? $options['namedCurve'] : self::$useNamedCurves;
+
+ $reflect = new \ReflectionClass($curve);
+ $name = $reflect->getShortName();
+ if ($useNamedCurves) {
+ if (isset(self::$curveOIDs[$name])) {
+ if ($reflect->isFinal()) {
+ $reflect = $reflect->getParentClass();
+ $name = $reflect->getShortName();
+ }
+ return $returnArray ?
+ ['namedCurve' => $name] :
+ ASN1::encodeDER(['namedCurve' => $name], Maps\ECParameters::MAP);
+ }
+ foreach (new \DirectoryIterator(__DIR__ . '/../../Curves/') as $file) {
+ if ($file->getExtension() != 'php') {
+ continue;
+ }
+ $testName = $file->getBasename('.php');
+ $class = 'phpseclib3\Crypt\EC\Curves\\' . $testName;
+ $reflect = new \ReflectionClass($class);
+ if ($reflect->isFinal()) {
+ continue;
+ }
+ $candidate = new $class();
+ switch ($name) {
+ case 'Prime':
+ if (!$candidate instanceof PrimeCurve) {
+ break;
+ }
+ if (!$candidate->getModulo()->equals($curve->getModulo())) {
+ break;
+ }
+ if ($candidate->getA()->toBytes() != $curve->getA()->toBytes()) {
+ break;
+ }
+ if ($candidate->getB()->toBytes() != $curve->getB()->toBytes()) {
+ break;
+ }
+
+ list($candidateX, $candidateY) = $candidate->getBasePoint();
+ list($curveX, $curveY) = $curve->getBasePoint();
+ if ($candidateX->toBytes() != $curveX->toBytes()) {
+ break;
+ }
+ if ($candidateY->toBytes() != $curveY->toBytes()) {
+ break;
+ }
+
+ return $returnArray ?
+ ['namedCurve' => $testName] :
+ ASN1::encodeDER(['namedCurve' => $testName], Maps\ECParameters::MAP);
+ case 'Binary':
+ if (!$candidate instanceof BinaryCurve) {
+ break;
+ }
+ if ($candidate->getModulo() != $curve->getModulo()) {
+ break;
+ }
+ if ($candidate->getA()->toBytes() != $curve->getA()->toBytes()) {
+ break;
+ }
+ if ($candidate->getB()->toBytes() != $curve->getB()->toBytes()) {
+ break;
+ }
+
+ list($candidateX, $candidateY) = $candidate->getBasePoint();
+ list($curveX, $curveY) = $curve->getBasePoint();
+ if ($candidateX->toBytes() != $curveX->toBytes()) {
+ break;
+ }
+ if ($candidateY->toBytes() != $curveY->toBytes()) {
+ break;
+ }
+
+ return $returnArray ?
+ ['namedCurve' => $testName] :
+ ASN1::encodeDER(['namedCurve' => $testName], Maps\ECParameters::MAP);
+ }
+ }
+ }
+
+ $order = $curve->getOrder();
+ // we could try to calculate the order thusly:
+ // https://crypto.stackexchange.com/a/27914/4520
+ // https://en.wikipedia.org/wiki/Schoof%E2%80%93Elkies%E2%80%93Atkin_algorithm
+ if (!$order) {
+ throw new \RuntimeException('Specified Curves need the order to be specified');
+ }
+ $point = $curve->getBasePoint();
+ $x = $point[0]->toBytes();
+ $y = $point[1]->toBytes();
+
+ if ($curve instanceof PrimeCurve) {
+ /*
+ * valid versions are:
+ *
+ * ecdpVer1:
+ * - neither the curve or the base point are generated verifiably randomly.
+ * ecdpVer2:
+ * - curve and base point are generated verifiably at random and curve.seed is present
+ * ecdpVer3:
+ * - base point is generated verifiably at random but curve is not. curve.seed is present
+ */
+ // other (optional) parameters can be calculated using the methods discused at
+ // https://crypto.stackexchange.com/q/28947/4520
+ $data = [
+ 'version' => 'ecdpVer1',
+ 'fieldID' => [
+ 'fieldType' => 'prime-field',
+ 'parameters' => $curve->getModulo()
+ ],
+ 'curve' => [
+ 'a' => $curve->getA()->toBytes(),
+ 'b' => $curve->getB()->toBytes()
+ ],
+ 'base' => "\4" . $x . $y,
+ 'order' => $order
+ ];
+
+ return $returnArray ?
+ ['specifiedCurve' => $data] :
+ ASN1::encodeDER(['specifiedCurve' => $data], Maps\ECParameters::MAP);
+ }
+ if ($curve instanceof BinaryCurve) {
+ $modulo = $curve->getModulo();
+ $basis = count($modulo);
+ $m = array_shift($modulo);
+ array_pop($modulo); // the last parameter should always be 0
+ //rsort($modulo);
+ switch ($basis) {
+ case 3:
+ $basis = 'tpBasis';
+ $modulo = new BigInteger($modulo[0]);
+ break;
+ case 5:
+ $basis = 'ppBasis';
+ // these should be in strictly ascending order (hence the commented out rsort above)
+ $modulo = [
+ 'k1' => new BigInteger($modulo[2]),
+ 'k2' => new BigInteger($modulo[1]),
+ 'k3' => new BigInteger($modulo[0])
+ ];
+ $modulo = ASN1::encodeDER($modulo, Maps\Pentanomial::MAP);
+ $modulo = new ASN1\Element($modulo);
+ }
+ $params = ASN1::encodeDER([
+ 'm' => new BigInteger($m),
+ 'basis' => $basis,
+ 'parameters' => $modulo
+ ], Maps\Characteristic_two::MAP);
+ $params = new ASN1\Element($params);
+ $a = ltrim($curve->getA()->toBytes(), "\0");
+ if (!strlen($a)) {
+ $a = "\0";
+ }
+ $b = ltrim($curve->getB()->toBytes(), "\0");
+ if (!strlen($b)) {
+ $b = "\0";
+ }
+ $data = [
+ 'version' => 'ecdpVer1',
+ 'fieldID' => [
+ 'fieldType' => 'characteristic-two-field',
+ 'parameters' => $params
+ ],
+ 'curve' => [
+ 'a' => $a,
+ 'b' => $b
+ ],
+ 'base' => "\4" . $x . $y,
+ 'order' => $order
+ ];
+
+ return $returnArray ?
+ ['specifiedCurve' => $data] :
+ ASN1::encodeDER(['specifiedCurve' => $data], Maps\ECParameters::MAP);
+ }
+
+ throw new UnsupportedCurveException('Curve cannot be serialized');
+ }
+
+ /**
+ * Use Specified Curve
+ *
+ * A specified curve has all the coefficients, the base points, etc, explicitely included.
+ * A specified curve is a more verbose way of representing a curve
+ */
+ public static function useSpecifiedCurve()
+ {
+ self::$useNamedCurves = false;
+ }
+
+ /**
+ * Use Named Curve
+ *
+ * A named curve does not include any parameters. It is up to the EC parameters to
+ * know what the coefficients, the base points, etc, are from the name of the curve.
+ * A named curve is a more concise way of representing a curve
+ */
+ public static function useNamedCurve()
+ {
+ self::$useNamedCurves = true;
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/JWK.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/JWK.php
new file mode 100644
index 000000000..5bc5184f7
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/JWK.php
@@ -0,0 +1,189 @@
+<?php
+
+/**
+ * JSON Web Key (RFC7517 / RFC8037) Formatted EC Handler
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\Formats\Keys\JWK as Progenitor;
+use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve;
+use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
+use phpseclib3\Crypt\EC\Curves\Ed25519;
+use phpseclib3\Crypt\EC\Curves\secp256k1;
+use phpseclib3\Crypt\EC\Curves\secp256r1;
+use phpseclib3\Crypt\EC\Curves\secp384r1;
+use phpseclib3\Crypt\EC\Curves\secp521r1;
+use phpseclib3\Exception\UnsupportedCurveException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * JWK Formatted EC Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class JWK extends Progenitor
+{
+ use Common;
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ $key = parent::load($key, $password);
+
+ switch ($key->kty) {
+ case 'EC':
+ switch ($key->crv) {
+ case 'P-256':
+ case 'P-384':
+ case 'P-521':
+ case 'secp256k1':
+ break;
+ default:
+ throw new UnsupportedCurveException('Only P-256, P-384, P-521 and secp256k1 curves are accepted (' . $key->crv . ' provided)');
+ }
+ break;
+ case 'OKP':
+ switch ($key->crv) {
+ case 'Ed25519':
+ case 'Ed448':
+ break;
+ default:
+ throw new UnsupportedCurveException('Only Ed25519 and Ed448 curves are accepted (' . $key->crv . ' provided)');
+ }
+ break;
+ default:
+ throw new \Exception('Only EC and OKP JWK keys are supported');
+ }
+
+ $curve = '\phpseclib3\Crypt\EC\Curves\\' . str_replace('P-', 'nistp', $key->crv);
+ $curve = new $curve();
+
+ if ($curve instanceof TwistedEdwardsCurve) {
+ $QA = self::extractPoint(Strings::base64url_decode($key->x), $curve);
+ if (!isset($key->d)) {
+ return compact('curve', 'QA');
+ }
+ $arr = $curve->extractSecret(Strings::base64url_decode($key->d));
+ return compact('curve', 'QA') + $arr;
+ }
+
+ $QA = [
+ $curve->convertInteger(new BigInteger(Strings::base64url_decode($key->x), 256)),
+ $curve->convertInteger(new BigInteger(Strings::base64url_decode($key->y), 256))
+ ];
+
+ if (!$curve->verifyPoint($QA)) {
+ throw new \RuntimeException('Unable to verify that point exists on curve');
+ }
+
+ if (!isset($key->d)) {
+ return compact('curve', 'QA');
+ }
+
+ $dA = new BigInteger(Strings::base64url_decode($key->d), 256);
+
+ $curve->rangeCheck($dA);
+
+ return compact('curve', 'dA', 'QA');
+ }
+
+ /**
+ * Returns the alias that corresponds to a curve
+ *
+ * @return string
+ */
+ private static function getAlias(BaseCurve $curve)
+ {
+ switch (true) {
+ case $curve instanceof secp256r1:
+ return 'P-256';
+ case $curve instanceof secp384r1:
+ return 'P-384';
+ case $curve instanceof secp521r1:
+ return 'P-521';
+ case $curve instanceof secp256k1:
+ return 'secp256k1';
+ }
+
+ $reflect = new \ReflectionClass($curve);
+ $curveName = $reflect->isFinal() ?
+ $reflect->getParentClass()->getShortName() :
+ $reflect->getShortName();
+ throw new UnsupportedCurveException("$curveName is not a supported curve");
+ }
+
+ /**
+ * Return the array superstructure for an EC public key
+ *
+ * @param BaseCurve $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @return array
+ */
+ private static function savePublicKeyHelper(BaseCurve $curve, array $publicKey)
+ {
+ if ($curve instanceof TwistedEdwardsCurve) {
+ return [
+ 'kty' => 'OKP',
+ 'crv' => $curve instanceof Ed25519 ? 'Ed25519' : 'Ed448',
+ 'x' => Strings::base64url_encode($curve->encodePoint($publicKey))
+ ];
+ }
+
+ return [
+ 'kty' => 'EC',
+ 'crv' => self::getAlias($curve),
+ 'x' => Strings::base64url_encode($publicKey[0]->toBytes()),
+ 'y' => Strings::base64url_encode($publicKey[1]->toBytes())
+ ];
+ }
+
+ /**
+ * Convert an EC public key to the appropriate format
+ *
+ * @param BaseCurve $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePublicKey(BaseCurve $curve, array $publicKey, array $options = [])
+ {
+ $key = self::savePublicKeyHelper($curve, $publicKey);
+
+ return self::wrapKey($key, $options);
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $privateKey
+ * @param Ed25519 $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @param string $secret optional
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $privateKey, BaseCurve $curve, array $publicKey, $secret = null, $password = '', array $options = [])
+ {
+ $key = self::savePublicKeyHelper($curve, $publicKey);
+ $key['d'] = $curve instanceof TwistedEdwardsCurve ? $secret : $privateKey->toBytes();
+ $key['d'] = Strings::base64url_encode($key['d']);
+
+ return self::wrapKey($key, $options);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/MontgomeryPrivate.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/MontgomeryPrivate.php
new file mode 100644
index 000000000..aa64f79ab
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/MontgomeryPrivate.php
@@ -0,0 +1,101 @@
+<?php
+
+/**
+ * Montgomery Private Key Handler
+ *
+ * "Naked" Curve25519 private keys can pretty much be any sequence of random 32x bytes so unless
+ * we have a "hidden" key handler pretty much every 32 byte string will be loaded as a curve25519
+ * private key even if it probably isn't one by PublicKeyLoader.
+ *
+ * "Naked" Curve25519 public keys also a string of 32 bytes so distinguishing between a "naked"
+ * curve25519 private key and a public key is nigh impossible, hence separate plugins for each
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Keys;
+
+use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
+use phpseclib3\Crypt\EC\Curves\Curve25519;
+use phpseclib3\Crypt\EC\Curves\Curve448;
+use phpseclib3\Exception\UnsupportedFormatException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Montgomery Curve Private Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class MontgomeryPrivate
+{
+ /**
+ * Is invisible flag
+ *
+ */
+ const IS_INVISIBLE = true;
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ switch (strlen($key)) {
+ case 32:
+ $curve = new Curve25519();
+ break;
+ case 56:
+ $curve = new Curve448();
+ break;
+ default:
+ throw new \LengthException('The only supported lengths are 32 and 56');
+ }
+
+ $components = ['curve' => $curve];
+ $components['dA'] = new BigInteger($key, 256);
+ $curve->rangeCheck($components['dA']);
+ // note that EC::getEncodedCoordinates does some additional "magic" (it does strrev on the result)
+ $components['QA'] = $components['curve']->multiplyPoint($components['curve']->getBasePoint(), $components['dA']);
+
+ return $components;
+ }
+
+ /**
+ * Convert an EC public key to the appropriate format
+ *
+ * @param MontgomeryCurve $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @return string
+ */
+ public static function savePublicKey(MontgomeryCurve $curve, array $publicKey)
+ {
+ return strrev($publicKey[0]->toBytes());
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $privateKey
+ * @param MontgomeryCurve $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @param string $secret optional
+ * @param string $password optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $privateKey, MontgomeryCurve $curve, array $publicKey, $secret = null, $password = '')
+ {
+ if (!empty($password) && is_string($password)) {
+ throw new UnsupportedFormatException('MontgomeryPrivate private keys do not support encryption');
+ }
+
+ return $privateKey->toBytes();
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/MontgomeryPublic.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/MontgomeryPublic.php
new file mode 100644
index 000000000..257c26e87
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/MontgomeryPublic.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * Montgomery Public Key Handler
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Keys;
+
+use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
+use phpseclib3\Crypt\EC\Curves\Curve25519;
+use phpseclib3\Crypt\EC\Curves\Curve448;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Montgomery Public Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class MontgomeryPublic
+{
+ /**
+ * Is invisible flag
+ *
+ */
+ const IS_INVISIBLE = true;
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ switch (strlen($key)) {
+ case 32:
+ $curve = new Curve25519();
+ break;
+ case 56:
+ $curve = new Curve448();
+ break;
+ default:
+ throw new \LengthException('The only supported lengths are 32 and 56');
+ }
+
+ $components = ['curve' => $curve];
+ $components['QA'] = [$components['curve']->convertInteger(new BigInteger(strrev($key), 256))];
+
+ return $components;
+ }
+
+ /**
+ * Convert an EC public key to the appropriate format
+ *
+ * @param MontgomeryCurve $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @return string
+ */
+ public static function savePublicKey(MontgomeryCurve $curve, array $publicKey)
+ {
+ return strrev($publicKey[0]->toBytes());
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/OpenSSH.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/OpenSSH.php
new file mode 100644
index 000000000..0ef116044
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/OpenSSH.php
@@ -0,0 +1,209 @@
+<?php
+
+/**
+ * OpenSSH Formatted EC Key Handler
+ *
+ * PHP version 5
+ *
+ * Place in $HOME/.ssh/authorized_keys
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\Formats\Keys\OpenSSH as Progenitor;
+use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve;
+use phpseclib3\Crypt\EC\Curves\Ed25519;
+use phpseclib3\Exception\UnsupportedCurveException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * OpenSSH Formatted EC Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class OpenSSH extends Progenitor
+{
+ use Common;
+
+ /**
+ * Supported Key Types
+ *
+ * @var array
+ */
+ protected static $types = [
+ 'ecdsa-sha2-nistp256',
+ 'ecdsa-sha2-nistp384',
+ 'ecdsa-sha2-nistp521',
+ 'ssh-ed25519'
+ ];
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ $parsed = parent::load($key, $password);
+
+ if (isset($parsed['paddedKey'])) {
+ $paddedKey = $parsed['paddedKey'];
+ list($type) = Strings::unpackSSH2('s', $paddedKey);
+ if ($type != $parsed['type']) {
+ throw new \RuntimeException("The public and private keys are not of the same type ($type vs $parsed[type])");
+ }
+ if ($type == 'ssh-ed25519') {
+ list(, $key, $comment) = Strings::unpackSSH2('sss', $paddedKey);
+ $key = libsodium::load($key);
+ $key['comment'] = $comment;
+ return $key;
+ }
+ list($curveName, $publicKey, $privateKey, $comment) = Strings::unpackSSH2('ssis', $paddedKey);
+ $curve = self::loadCurveByParam(['namedCurve' => $curveName]);
+ $curve->rangeCheck($privateKey);
+ return [
+ 'curve' => $curve,
+ 'dA' => $privateKey,
+ 'QA' => self::extractPoint("\0$publicKey", $curve),
+ 'comment' => $comment
+ ];
+ }
+
+ if ($parsed['type'] == 'ssh-ed25519') {
+ if (Strings::shift($parsed['publicKey'], 4) != "\0\0\0\x20") {
+ throw new \RuntimeException('Length of ssh-ed25519 key should be 32');
+ }
+
+ $curve = new Ed25519();
+ $qa = self::extractPoint($parsed['publicKey'], $curve);
+ } else {
+ list($curveName, $publicKey) = Strings::unpackSSH2('ss', $parsed['publicKey']);
+ $curveName = '\phpseclib3\Crypt\EC\Curves\\' . $curveName;
+ $curve = new $curveName();
+
+ $qa = self::extractPoint("\0" . $publicKey, $curve);
+ }
+
+ return [
+ 'curve' => $curve,
+ 'QA' => $qa,
+ 'comment' => $parsed['comment']
+ ];
+ }
+
+ /**
+ * Returns the alias that corresponds to a curve
+ *
+ * @return string
+ */
+ private static function getAlias(BaseCurve $curve)
+ {
+ self::initialize_static_variables();
+
+ $reflect = new \ReflectionClass($curve);
+ $name = $reflect->getShortName();
+
+ $oid = self::$curveOIDs[$name];
+ $aliases = array_filter(self::$curveOIDs, function ($v) use ($oid) {
+ return $v == $oid;
+ });
+ $aliases = array_keys($aliases);
+
+ for ($i = 0; $i < count($aliases); $i++) {
+ if (in_array('ecdsa-sha2-' . $aliases[$i], self::$types)) {
+ $alias = $aliases[$i];
+ break;
+ }
+ }
+
+ if (!isset($alias)) {
+ throw new UnsupportedCurveException($name . ' is not a curve that the OpenSSH plugin supports');
+ }
+
+ return $alias;
+ }
+
+ /**
+ * Convert an EC public key to the appropriate format
+ *
+ * @param BaseCurve $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePublicKey(BaseCurve $curve, array $publicKey, array $options = [])
+ {
+ $comment = isset($options['comment']) ? $options['comment'] : self::$comment;
+
+ if ($curve instanceof Ed25519) {
+ $key = Strings::packSSH2('ss', 'ssh-ed25519', $curve->encodePoint($publicKey));
+
+ if (isset($options['binary']) ? $options['binary'] : self::$binary) {
+ return $key;
+ }
+
+ $key = 'ssh-ed25519 ' . base64_encode($key) . ' ' . $comment;
+ return $key;
+ }
+
+ $alias = self::getAlias($curve);
+
+ $points = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes();
+ $key = Strings::packSSH2('sss', 'ecdsa-sha2-' . $alias, $alias, $points);
+
+ if (isset($options['binary']) ? $options['binary'] : self::$binary) {
+ return $key;
+ }
+
+ $key = 'ecdsa-sha2-' . $alias . ' ' . base64_encode($key) . ' ' . $comment;
+
+ return $key;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $privateKey
+ * @param Ed25519 $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @param string $secret optional
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $privateKey, BaseCurve $curve, array $publicKey, $secret = null, $password = '', array $options = [])
+ {
+ if ($curve instanceof Ed25519) {
+ if (!isset($secret)) {
+ throw new \RuntimeException('Private Key does not have a secret set');
+ }
+ if (strlen($secret) != 32) {
+ throw new \RuntimeException('Private Key secret is not of the correct length');
+ }
+
+ $pubKey = $curve->encodePoint($publicKey);
+
+ $publicKey = Strings::packSSH2('ss', 'ssh-ed25519', $pubKey);
+ $privateKey = Strings::packSSH2('sss', 'ssh-ed25519', $pubKey, $secret . $pubKey);
+
+ return self::wrapPrivateKey($publicKey, $privateKey, $password, $options);
+ }
+
+ $alias = self::getAlias($curve);
+
+ $points = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes();
+ $publicKey = self::savePublicKey($curve, $publicKey, ['binary' => true]);
+
+ $privateKey = Strings::packSSH2('sssi', 'ecdsa-sha2-' . $alias, $alias, $points, $privateKey);
+
+ return self::wrapPrivateKey($publicKey, $privateKey, $password, $options);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PKCS1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PKCS1.php
new file mode 100644
index 000000000..756ffb957
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PKCS1.php
@@ -0,0 +1,194 @@
+<?php
+
+/**
+ * "PKCS1" (RFC5915) Formatted EC Key Handler
+ *
+ * PHP version 5
+ *
+ * Used by File/X509.php
+ *
+ * Processes keys with the following headers:
+ *
+ * -----BEGIN EC PRIVATE KEY-----
+ * -----BEGIN EC PARAMETERS-----
+ *
+ * Technically, PKCS1 is for RSA keys, only, but we're using PKCS1 to describe
+ * DSA, whose format isn't really formally described anywhere, so might as well
+ * use it to describe this, too. PKCS1 is easier to remember than RFC5915, after
+ * all. I suppose this could also be named IETF but idk
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\Formats\Keys\PKCS1 as Progenitor;
+use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve;
+use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
+use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
+use phpseclib3\Exception\UnsupportedCurveException;
+use phpseclib3\File\ASN1;
+use phpseclib3\File\ASN1\Maps;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * "PKCS1" (RFC5915) Formatted EC Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PKCS1 extends Progenitor
+{
+ use Common;
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ self::initialize_static_variables();
+
+ if (!Strings::is_stringable($key)) {
+ throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
+ }
+
+ if (strpos($key, 'BEGIN EC PARAMETERS') && strpos($key, 'BEGIN EC PRIVATE KEY')) {
+ $components = [];
+
+ preg_match('#-*BEGIN EC PRIVATE KEY-*[^-]*-*END EC PRIVATE KEY-*#s', $key, $matches);
+ $decoded = parent::load($matches[0], $password);
+ $decoded = ASN1::decodeBER($decoded);
+ if (!$decoded) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+
+ $ecPrivate = ASN1::asn1map($decoded[0], Maps\ECPrivateKey::MAP);
+ if (!is_array($ecPrivate)) {
+ throw new \RuntimeException('Unable to perform ASN1 mapping');
+ }
+
+ if (isset($ecPrivate['parameters'])) {
+ $components['curve'] = self::loadCurveByParam($ecPrivate['parameters']);
+ }
+
+ preg_match('#-*BEGIN EC PARAMETERS-*[^-]*-*END EC PARAMETERS-*#s', $key, $matches);
+ $decoded = parent::load($matches[0], '');
+ $decoded = ASN1::decodeBER($decoded);
+ if (!$decoded) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+ $ecParams = ASN1::asn1map($decoded[0], Maps\ECParameters::MAP);
+ if (!is_array($ecParams)) {
+ throw new \RuntimeException('Unable to perform ASN1 mapping');
+ }
+ $ecParams = self::loadCurveByParam($ecParams);
+
+ // comparing $ecParams and $components['curve'] directly won't work because they'll have different Math\Common\FiniteField classes
+ // even if the modulo is the same
+ if (isset($components['curve']) && self::encodeParameters($ecParams, false, []) != self::encodeParameters($components['curve'], false, [])) {
+ throw new \RuntimeException('EC PARAMETERS does not correspond to EC PRIVATE KEY');
+ }
+
+ if (!isset($components['curve'])) {
+ $components['curve'] = $ecParams;
+ }
+
+ $components['dA'] = new BigInteger($ecPrivate['privateKey'], 256);
+ $components['curve']->rangeCheck($components['dA']);
+ $components['QA'] = isset($ecPrivate['publicKey']) ?
+ self::extractPoint($ecPrivate['publicKey'], $components['curve']) :
+ $components['curve']->multiplyPoint($components['curve']->getBasePoint(), $components['dA']);
+
+ return $components;
+ }
+
+ $key = parent::load($key, $password);
+
+ $decoded = ASN1::decodeBER($key);
+ if (!$decoded) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+
+ $key = ASN1::asn1map($decoded[0], Maps\ECParameters::MAP);
+ if (is_array($key)) {
+ return ['curve' => self::loadCurveByParam($key)];
+ }
+
+ $key = ASN1::asn1map($decoded[0], Maps\ECPrivateKey::MAP);
+ if (!is_array($key)) {
+ throw new \RuntimeException('Unable to perform ASN1 mapping');
+ }
+ if (!isset($key['parameters'])) {
+ throw new \RuntimeException('Key cannot be loaded without parameters');
+ }
+
+ $components = [];
+ $components['curve'] = self::loadCurveByParam($key['parameters']);
+ $components['dA'] = new BigInteger($key['privateKey'], 256);
+ $components['QA'] = isset($ecPrivate['publicKey']) ?
+ self::extractPoint($ecPrivate['publicKey'], $components['curve']) :
+ $components['curve']->multiplyPoint($components['curve']->getBasePoint(), $components['dA']);
+
+ return $components;
+ }
+
+ /**
+ * Convert EC parameters to the appropriate format
+ *
+ * @return string
+ */
+ public static function saveParameters(BaseCurve $curve, array $options = [])
+ {
+ self::initialize_static_variables();
+
+ if ($curve instanceof TwistedEdwardsCurve || $curve instanceof MontgomeryCurve) {
+ throw new UnsupportedCurveException('TwistedEdwards and Montgomery Curves are not supported');
+ }
+
+ $key = self::encodeParameters($curve, false, $options);
+
+ return "-----BEGIN EC PARAMETERS-----\r\n" .
+ chunk_split(Strings::base64_encode($key), 64) .
+ "-----END EC PARAMETERS-----\r\n";
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $privateKey
+ * @param BaseCurve $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @param string $secret optional
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $privateKey, BaseCurve $curve, array $publicKey, $secret = null, $password = '', array $options = [])
+ {
+ self::initialize_static_variables();
+
+ if ($curve instanceof TwistedEdwardsCurve || $curve instanceof MontgomeryCurve) {
+ throw new UnsupportedCurveException('TwistedEdwards Curves are not supported');
+ }
+
+ $publicKey = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes();
+
+ $key = [
+ 'version' => 'ecPrivkeyVer1',
+ 'privateKey' => $privateKey->toBytes(),
+ 'parameters' => new ASN1\Element(self::encodeParameters($curve)),
+ 'publicKey' => "\0" . $publicKey
+ ];
+
+ $key = ASN1::encodeDER($key, Maps\ECPrivateKey::MAP);
+
+ return self::wrapPrivateKey($key, 'EC', $password, $options);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PKCS8.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PKCS8.php
new file mode 100644
index 000000000..9fc84054e
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PKCS8.php
@@ -0,0 +1,237 @@
+<?php
+
+/**
+ * PKCS#8 Formatted EC Key Handler
+ *
+ * PHP version 5
+ *
+ * Processes keys with the following headers:
+ *
+ * -----BEGIN ENCRYPTED PRIVATE KEY-----
+ * -----BEGIN PRIVATE KEY-----
+ * -----BEGIN PUBLIC KEY-----
+ *
+ * Analogous to ssh-keygen's pkcs8 format (as specified by -m). Although PKCS8
+ * is specific to private keys it's basically creating a DER-encoded wrapper
+ * for keys. This just extends that same concept to public keys (much like ssh-keygen)
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Keys;
+
+use phpseclib3\Crypt\Common\Formats\Keys\PKCS8 as Progenitor;
+use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve;
+use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
+use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
+use phpseclib3\Crypt\EC\Curves\Ed25519;
+use phpseclib3\Crypt\EC\Curves\Ed448;
+use phpseclib3\Exception\UnsupportedCurveException;
+use phpseclib3\File\ASN1;
+use phpseclib3\File\ASN1\Maps;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * PKCS#8 Formatted EC Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PKCS8 extends Progenitor
+{
+ use Common;
+
+ /**
+ * OID Name
+ *
+ * @var array
+ */
+ const OID_NAME = ['id-ecPublicKey', 'id-Ed25519', 'id-Ed448'];
+
+ /**
+ * OID Value
+ *
+ * @var string
+ */
+ const OID_VALUE = ['1.2.840.10045.2.1', '1.3.101.112', '1.3.101.113'];
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ // initialize_static_variables() is defined in both the trait and the parent class
+ // when it's defined in two places it's the traits one that's called
+ // the parent one is needed, as well, but the parent one is called by other methods
+ // in the parent class as needed and in the context of the parent it's the parent
+ // one that's called
+ self::initialize_static_variables();
+
+ $key = parent::load($key, $password);
+
+ $type = isset($key['privateKey']) ? 'privateKey' : 'publicKey';
+
+ switch ($key[$type . 'Algorithm']['algorithm']) {
+ case 'id-Ed25519':
+ case 'id-Ed448':
+ return self::loadEdDSA($key);
+ }
+
+ $decoded = ASN1::decodeBER($key[$type . 'Algorithm']['parameters']->element);
+ if (!$decoded) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+ $params = ASN1::asn1map($decoded[0], Maps\ECParameters::MAP);
+ if (!$params) {
+ throw new \RuntimeException('Unable to decode the parameters using Maps\ECParameters');
+ }
+
+ $components = [];
+ $components['curve'] = self::loadCurveByParam($params);
+
+ if ($type == 'publicKey') {
+ $components['QA'] = self::extractPoint("\0" . $key['publicKey'], $components['curve']);
+
+ return $components;
+ }
+
+ $decoded = ASN1::decodeBER($key['privateKey']);
+ if (!$decoded) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+ $key = ASN1::asn1map($decoded[0], Maps\ECPrivateKey::MAP);
+ if (isset($key['parameters']) && $params != $key['parameters']) {
+ throw new \RuntimeException('The PKCS8 parameter field does not match the private key parameter field');
+ }
+
+ $components['dA'] = new BigInteger($key['privateKey'], 256);
+ $components['curve']->rangeCheck($components['dA']);
+ $components['QA'] = isset($key['publicKey']) ?
+ self::extractPoint($key['publicKey'], $components['curve']) :
+ $components['curve']->multiplyPoint($components['curve']->getBasePoint(), $components['dA']);
+
+ return $components;
+ }
+
+ /**
+ * Break a public or private EdDSA key down into its constituent components
+ *
+ * @return array
+ */
+ private static function loadEdDSA(array $key)
+ {
+ $components = [];
+
+ if (isset($key['privateKey'])) {
+ $components['curve'] = $key['privateKeyAlgorithm']['algorithm'] == 'id-Ed25519' ? new Ed25519() : new Ed448();
+ $expected = chr(ASN1::TYPE_OCTET_STRING) . ASN1::encodeLength($components['curve']::SIZE);
+ if (substr($key['privateKey'], 0, 2) != $expected) {
+ throw new \RuntimeException(
+ 'The first two bytes of the ' .
+ $key['privateKeyAlgorithm']['algorithm'] .
+ ' private key field should be 0x' . bin2hex($expected)
+ );
+ }
+ $arr = $components['curve']->extractSecret(substr($key['privateKey'], 2));
+ $components['dA'] = $arr['dA'];
+ $components['secret'] = $arr['secret'];
+ }
+
+ if (isset($key['publicKey'])) {
+ if (!isset($components['curve'])) {
+ $components['curve'] = $key['publicKeyAlgorithm']['algorithm'] == 'id-Ed25519' ? new Ed25519() : new Ed448();
+ }
+
+ $components['QA'] = self::extractPoint($key['publicKey'], $components['curve']);
+ }
+
+ if (isset($key['privateKey']) && !isset($components['QA'])) {
+ $components['QA'] = $components['curve']->multiplyPoint($components['curve']->getBasePoint(), $components['dA']);
+ }
+
+ return $components;
+ }
+
+ /**
+ * Convert an EC public key to the appropriate format
+ *
+ * @param BaseCurve $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePublicKey(BaseCurve $curve, array $publicKey, array $options = [])
+ {
+ self::initialize_static_variables();
+
+ if ($curve instanceof MontgomeryCurve) {
+ throw new UnsupportedCurveException('Montgomery Curves are not supported');
+ }
+
+ if ($curve instanceof TwistedEdwardsCurve) {
+ return self::wrapPublicKey(
+ $curve->encodePoint($publicKey),
+ null,
+ $curve instanceof Ed25519 ? 'id-Ed25519' : 'id-Ed448',
+ $options
+ );
+ }
+
+ $params = new ASN1\Element(self::encodeParameters($curve, false, $options));
+
+ $key = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes();
+
+ return self::wrapPublicKey($key, $params, 'id-ecPublicKey', $options);
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $privateKey
+ * @param BaseCurve $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @param string $secret optional
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $privateKey, BaseCurve $curve, array $publicKey, $secret = null, $password = '', array $options = [])
+ {
+ self::initialize_static_variables();
+
+ if ($curve instanceof MontgomeryCurve) {
+ throw new UnsupportedCurveException('Montgomery Curves are not supported');
+ }
+
+ if ($curve instanceof TwistedEdwardsCurve) {
+ return self::wrapPrivateKey(
+ chr(ASN1::TYPE_OCTET_STRING) . ASN1::encodeLength($curve::SIZE) . $secret,
+ [],
+ null,
+ $password,
+ $curve instanceof Ed25519 ? 'id-Ed25519' : 'id-Ed448'
+ );
+ }
+
+ $publicKey = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes();
+
+ $params = new ASN1\Element(self::encodeParameters($curve, false, $options));
+
+ $key = [
+ 'version' => 'ecPrivkeyVer1',
+ 'privateKey' => $privateKey->toBytes(),
+ //'parameters' => $params,
+ 'publicKey' => "\0" . $publicKey
+ ];
+
+ $key = ASN1::encodeDER($key, Maps\ECPrivateKey::MAP);
+
+ return self::wrapPrivateKey($key, [], $params, $password, 'id-ecPublicKey', '', $options);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PuTTY.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PuTTY.php
new file mode 100644
index 000000000..7e1e9170f
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/PuTTY.php
@@ -0,0 +1,138 @@
+<?php
+
+/**
+ * PuTTY Formatted EC Key Handler
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\Formats\Keys\PuTTY as Progenitor;
+use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve;
+use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * PuTTY Formatted EC Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PuTTY extends Progenitor
+{
+ use Common;
+
+ /**
+ * Public Handler
+ *
+ * @var string
+ */
+ const PUBLIC_HANDLER = 'phpseclib3\Crypt\EC\Formats\Keys\OpenSSH';
+
+ /**
+ * Supported Key Types
+ *
+ * @var array
+ */
+ protected static $types = [
+ 'ecdsa-sha2-nistp256',
+ 'ecdsa-sha2-nistp384',
+ 'ecdsa-sha2-nistp521',
+ 'ssh-ed25519'
+ ];
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ $components = parent::load($key, $password);
+ if (!isset($components['private'])) {
+ return $components;
+ }
+
+ $private = $components['private'];
+
+ $temp = Strings::base64_encode(Strings::packSSH2('s', $components['type']) . $components['public']);
+ $components = OpenSSH::load($components['type'] . ' ' . $temp . ' ' . $components['comment']);
+
+ if ($components['curve'] instanceof TwistedEdwardsCurve) {
+ if (Strings::shift($private, 4) != "\0\0\0\x20") {
+ throw new \RuntimeException('Length of ssh-ed25519 key should be 32');
+ }
+ $arr = $components['curve']->extractSecret($private);
+ $components['dA'] = $arr['dA'];
+ $components['secret'] = $arr['secret'];
+ } else {
+ list($components['dA']) = Strings::unpackSSH2('i', $private);
+ $components['curve']->rangeCheck($components['dA']);
+ }
+
+ return $components;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $privateKey
+ * @param BaseCurve $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @param string $secret optional
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $privateKey, BaseCurve $curve, array $publicKey, $secret = null, $password = false, array $options = [])
+ {
+ self::initialize_static_variables();
+
+ $public = explode(' ', OpenSSH::savePublicKey($curve, $publicKey));
+ $name = $public[0];
+ $public = Strings::base64_decode($public[1]);
+ list(, $length) = unpack('N', Strings::shift($public, 4));
+ Strings::shift($public, $length);
+
+ // PuTTY pads private keys with a null byte per the following:
+ // https://github.com/github/putty/blob/a3d14d77f566a41fc61dfdc5c2e0e384c9e6ae8b/sshecc.c#L1926
+ if (!$curve instanceof TwistedEdwardsCurve) {
+ $private = $privateKey->toBytes();
+ if (!(strlen($privateKey->toBits()) & 7)) {
+ $private = "\0$private";
+ }
+ }
+
+ $private = $curve instanceof TwistedEdwardsCurve ?
+ Strings::packSSH2('s', $secret) :
+ Strings::packSSH2('s', $private);
+
+ return self::wrapPrivateKey($public, $private, $name, $password, $options);
+ }
+
+ /**
+ * Convert an EC public key to the appropriate format
+ *
+ * @param BaseCurve $curve
+ * @param \phpseclib3\Math\Common\FiniteField[] $publicKey
+ * @return string
+ */
+ public static function savePublicKey(BaseCurve $curve, array $publicKey)
+ {
+ $public = explode(' ', OpenSSH::savePublicKey($curve, $publicKey));
+ $type = $public[0];
+ $public = Strings::base64_decode($public[1]);
+ list(, $length) = unpack('N', Strings::shift($public, 4));
+ Strings::shift($public, $length);
+
+ return self::wrapPublicKey($public, $type);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/XML.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/XML.php
new file mode 100644
index 000000000..7f6cf6345
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/XML.php
@@ -0,0 +1,486 @@
+<?php
+
+/**
+ * XML Formatted EC Key Handler
+ *
+ * More info:
+ *
+ * https://www.w3.org/TR/xmldsig-core/#sec-ECKeyValue
+ * http://en.wikipedia.org/wiki/XML_Signature
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\EC\BaseCurves\Base as BaseCurve;
+use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
+use phpseclib3\Crypt\EC\BaseCurves\Prime as PrimeCurve;
+use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
+use phpseclib3\Exception\BadConfigurationException;
+use phpseclib3\Exception\UnsupportedCurveException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * XML Formatted EC Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class XML
+{
+ use Common;
+
+ /**
+ * Default namespace
+ *
+ * @var string
+ */
+ private static $namespace;
+
+ /**
+ * Flag for using RFC4050 syntax
+ *
+ * @var bool
+ */
+ private static $rfc4050 = false;
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ self::initialize_static_variables();
+
+ if (!Strings::is_stringable($key)) {
+ throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
+ }
+
+ if (!class_exists('DOMDocument')) {
+ throw new BadConfigurationException('The dom extension is not setup correctly on this system');
+ }
+
+ $use_errors = libxml_use_internal_errors(true);
+
+ if (substr($key, 0, 5) != '<?xml') {
+ $key = '<xml>' . $key . '</xml>';
+ }
+
+ $temp = self::isolateNamespace($key, 'http://www.w3.org/2009/xmldsig11#');
+ if ($temp) {
+ $key = $temp;
+ }
+
+ $temp = self::isolateNamespace($key, 'http://www.w3.org/2001/04/xmldsig-more#');
+ if ($temp) {
+ $key = $temp;
+ }
+
+ $dom = new \DOMDocument();
+
+ if (!$dom->loadXML($key)) {
+ libxml_use_internal_errors($use_errors);
+ throw new \UnexpectedValueException('Key does not appear to contain XML');
+ }
+ $xpath = new \DOMXPath($dom);
+ libxml_use_internal_errors($use_errors);
+ $curve = self::loadCurveByParam($xpath);
+
+ $pubkey = self::query($xpath, 'publickey', 'Public Key is not present');
+
+ $QA = self::query($xpath, 'ecdsakeyvalue')->length ?
+ self::extractPointRFC4050($xpath, $curve) :
+ self::extractPoint("\0" . $pubkey, $curve);
+
+ libxml_use_internal_errors($use_errors);
+
+ return compact('curve', 'QA');
+ }
+
+ /**
+ * Case-insensitive xpath query
+ *
+ * @param \DOMXPath $xpath
+ * @param string $name
+ * @param string $error optional
+ * @param bool $decode optional
+ * @return \DOMNodeList
+ */
+ private static function query(\DOMXPath $xpath, $name, $error = null, $decode = true)
+ {
+ $query = '/';
+ $names = explode('/', $name);
+ foreach ($names as $name) {
+ $query .= "/*[translate(local-name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='$name']";
+ }
+ $result = $xpath->query($query);
+ if (!isset($error)) {
+ return $result;
+ }
+
+ if (!$result->length) {
+ throw new \RuntimeException($error);
+ }
+ return $decode ? self::decodeValue($result->item(0)->textContent) : $result->item(0)->textContent;
+ }
+
+ /**
+ * Finds the first element in the relevant namespace, strips the namespacing and returns the XML for that element.
+ *
+ * @param string $xml
+ * @param string $ns
+ */
+ private static function isolateNamespace($xml, $ns)
+ {
+ $dom = new \DOMDocument();
+ if (!$dom->loadXML($xml)) {
+ return false;
+ }
+ $xpath = new \DOMXPath($dom);
+ $nodes = $xpath->query("//*[namespace::*[.='$ns'] and not(../namespace::*[.='$ns'])]");
+ if (!$nodes->length) {
+ return false;
+ }
+ $node = $nodes->item(0);
+ $ns_name = $node->lookupPrefix($ns);
+ if ($ns_name) {
+ $node->removeAttributeNS($ns, $ns_name);
+ }
+ return $dom->saveXML($node);
+ }
+
+ /**
+ * Decodes the value
+ *
+ * @param string $value
+ */
+ private static function decodeValue($value)
+ {
+ return Strings::base64_decode(str_replace(["\r", "\n", ' ', "\t"], '', $value));
+ }
+
+ /**
+ * Extract points from an XML document
+ *
+ * @param \DOMXPath $xpath
+ * @param BaseCurve $curve
+ * @return object[]
+ */
+ private static function extractPointRFC4050(\DOMXPath $xpath, BaseCurve $curve)
+ {
+ $x = self::query($xpath, 'publickey/x');
+ $y = self::query($xpath, 'publickey/y');
+ if (!$x->length || !$x->item(0)->hasAttribute('Value')) {
+ throw new \RuntimeException('Public Key / X coordinate not found');
+ }
+ if (!$y->length || !$y->item(0)->hasAttribute('Value')) {
+ throw new \RuntimeException('Public Key / Y coordinate not found');
+ }
+ $point = [
+ $curve->convertInteger(new BigInteger($x->item(0)->getAttribute('Value'))),
+ $curve->convertInteger(new BigInteger($y->item(0)->getAttribute('Value')))
+ ];
+ if (!$curve->verifyPoint($point)) {
+ throw new \RuntimeException('Unable to verify that point exists on curve');
+ }
+ return $point;
+ }
+
+ /**
+ * Returns an instance of \phpseclib3\Crypt\EC\BaseCurves\Base based
+ * on the curve parameters
+ *
+ * @param \DomXPath $xpath
+ * @return BaseCurve|false
+ */
+ private static function loadCurveByParam(\DOMXPath $xpath)
+ {
+ $namedCurve = self::query($xpath, 'namedcurve');
+ if ($namedCurve->length == 1) {
+ $oid = $namedCurve->item(0)->getAttribute('URN');
+ $oid = preg_replace('#[^\d.]#', '', $oid);
+ $name = array_search($oid, self::$curveOIDs);
+ if ($name === false) {
+ throw new UnsupportedCurveException('Curve with OID of ' . $oid . ' is not supported');
+ }
+
+ $curve = '\phpseclib3\Crypt\EC\Curves\\' . $name;
+ if (!class_exists($curve)) {
+ throw new UnsupportedCurveException('Named Curve of ' . $name . ' is not supported');
+ }
+ return new $curve();
+ }
+
+ $params = self::query($xpath, 'explicitparams');
+ if ($params->length) {
+ return self::loadCurveByParamRFC4050($xpath);
+ }
+
+ $params = self::query($xpath, 'ecparameters');
+ if (!$params->length) {
+ throw new \RuntimeException('No parameters are present');
+ }
+
+ $fieldTypes = [
+ 'prime-field' => ['fieldid/prime/p'],
+ 'gnb' => ['fieldid/gnb/m'],
+ 'tnb' => ['fieldid/tnb/k'],
+ 'pnb' => ['fieldid/pnb/k1', 'fieldid/pnb/k2', 'fieldid/pnb/k3'],
+ 'unknown' => []
+ ];
+
+ foreach ($fieldTypes as $type => $queries) {
+ foreach ($queries as $query) {
+ $result = self::query($xpath, $query);
+ if (!$result->length) {
+ continue 2;
+ }
+ $param = preg_replace('#.*/#', '', $query);
+ $$param = self::decodeValue($result->item(0)->textContent);
+ }
+ break;
+ }
+
+ $a = self::query($xpath, 'curve/a', 'A coefficient is not present');
+ $b = self::query($xpath, 'curve/b', 'B coefficient is not present');
+ $base = self::query($xpath, 'base', 'Base point is not present');
+ $order = self::query($xpath, 'order', 'Order is not present');
+
+ switch ($type) {
+ case 'prime-field':
+ $curve = new PrimeCurve();
+ $curve->setModulo(new BigInteger($p, 256));
+ $curve->setCoefficients(
+ new BigInteger($a, 256),
+ new BigInteger($b, 256)
+ );
+ $point = self::extractPoint("\0" . $base, $curve);
+ $curve->setBasePoint(...$point);
+ $curve->setOrder(new BigInteger($order, 256));
+ return $curve;
+ case 'gnb':
+ case 'tnb':
+ case 'pnb':
+ default:
+ throw new UnsupportedCurveException('Field Type of ' . $type . ' is not supported');
+ }
+ }
+
+ /**
+ * Returns an instance of \phpseclib3\Crypt\EC\BaseCurves\Base based
+ * on the curve parameters
+ *
+ * @param \DomXPath $xpath
+ * @return BaseCurve|false
+ */
+ private static function loadCurveByParamRFC4050(\DOMXPath $xpath)
+ {
+ $fieldTypes = [
+ 'prime-field' => ['primefieldparamstype/p'],
+ 'unknown' => []
+ ];
+
+ foreach ($fieldTypes as $type => $queries) {
+ foreach ($queries as $query) {
+ $result = self::query($xpath, $query);
+ if (!$result->length) {
+ continue 2;
+ }
+ $param = preg_replace('#.*/#', '', $query);
+ $$param = $result->item(0)->textContent;
+ }
+ break;
+ }
+
+ $a = self::query($xpath, 'curveparamstype/a', 'A coefficient is not present', false);
+ $b = self::query($xpath, 'curveparamstype/b', 'B coefficient is not present', false);
+ $x = self::query($xpath, 'basepointparams/basepoint/ecpointtype/x', 'Base Point X is not present', false);
+ $y = self::query($xpath, 'basepointparams/basepoint/ecpointtype/y', 'Base Point Y is not present', false);
+ $order = self::query($xpath, 'order', 'Order is not present', false);
+
+ switch ($type) {
+ case 'prime-field':
+ $curve = new PrimeCurve();
+
+ $p = str_replace(["\r", "\n", ' ', "\t"], '', $p);
+ $curve->setModulo(new BigInteger($p));
+
+ $a = str_replace(["\r", "\n", ' ', "\t"], '', $a);
+ $b = str_replace(["\r", "\n", ' ', "\t"], '', $b);
+ $curve->setCoefficients(
+ new BigInteger($a),
+ new BigInteger($b)
+ );
+
+ $x = str_replace(["\r", "\n", ' ', "\t"], '', $x);
+ $y = str_replace(["\r", "\n", ' ', "\t"], '', $y);
+ $curve->setBasePoint(
+ new BigInteger($x),
+ new BigInteger($y)
+ );
+
+ $order = str_replace(["\r", "\n", ' ', "\t"], '', $order);
+ $curve->setOrder(new BigInteger($order));
+ return $curve;
+ default:
+ throw new UnsupportedCurveException('Field Type of ' . $type . ' is not supported');
+ }
+ }
+
+ /**
+ * Sets the namespace. dsig11 is the most common one.
+ *
+ * Set to null to unset. Used only for creating public keys.
+ *
+ * @param string $namespace
+ */
+ public static function setNamespace($namespace)
+ {
+ self::$namespace = $namespace;
+ }
+
+ /**
+ * Uses the XML syntax specified in https://tools.ietf.org/html/rfc4050
+ */
+ public static function enableRFC4050Syntax()
+ {
+ self::$rfc4050 = true;
+ }
+
+ /**
+ * Uses the XML syntax specified in https://www.w3.org/TR/xmldsig-core/#sec-ECParameters
+ */
+ public static function disableRFC4050Syntax()
+ {
+ self::$rfc4050 = false;
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BaseCurve $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePublicKey(BaseCurve $curve, array $publicKey, array $options = [])
+ {
+ self::initialize_static_variables();
+
+ if ($curve instanceof TwistedEdwardsCurve || $curve instanceof MontgomeryCurve) {
+ throw new UnsupportedCurveException('TwistedEdwards and Montgomery Curves are not supported');
+ }
+
+ if (empty(static::$namespace)) {
+ $pre = $post = '';
+ } else {
+ $pre = static::$namespace . ':';
+ $post = ':' . static::$namespace;
+ }
+
+ if (self::$rfc4050) {
+ return '<' . $pre . 'ECDSAKeyValue xmlns' . $post . '="http://www.w3.org/2001/04/xmldsig-more#">' . "\r\n" .
+ self::encodeXMLParameters($curve, $pre, $options) . "\r\n" .
+ '<' . $pre . 'PublicKey>' . "\r\n" .
+ '<' . $pre . 'X Value="' . $publicKey[0] . '" />' . "\r\n" .
+ '<' . $pre . 'Y Value="' . $publicKey[1] . '" />' . "\r\n" .
+ '</' . $pre . 'PublicKey>' . "\r\n" .
+ '</' . $pre . 'ECDSAKeyValue>';
+ }
+
+ $publicKey = "\4" . $publicKey[0]->toBytes() . $publicKey[1]->toBytes();
+
+ return '<' . $pre . 'ECDSAKeyValue xmlns' . $post . '="http://www.w3.org/2009/xmldsig11#">' . "\r\n" .
+ self::encodeXMLParameters($curve, $pre, $options) . "\r\n" .
+ '<' . $pre . 'PublicKey>' . Strings::base64_encode($publicKey) . '</' . $pre . 'PublicKey>' . "\r\n" .
+ '</' . $pre . 'ECDSAKeyValue>';
+ }
+
+ /**
+ * Encode Parameters
+ *
+ * @param BaseCurve $curve
+ * @param string $pre
+ * @param array $options optional
+ * @return string|false
+ */
+ private static function encodeXMLParameters(BaseCurve $curve, $pre, array $options = [])
+ {
+ $result = self::encodeParameters($curve, true, $options);
+
+ if (isset($result['namedCurve'])) {
+ $namedCurve = '<' . $pre . 'NamedCurve URI="urn:oid:' . self::$curveOIDs[$result['namedCurve']] . '" />';
+ return self::$rfc4050 ?
+ '<DomainParameters>' . str_replace('URI', 'URN', $namedCurve) . '</DomainParameters>' :
+ $namedCurve;
+ }
+
+ if (self::$rfc4050) {
+ $xml = '<' . $pre . 'ExplicitParams>' . "\r\n" .
+ '<' . $pre . 'FieldParams>' . "\r\n";
+ $temp = $result['specifiedCurve'];
+ switch ($temp['fieldID']['fieldType']) {
+ case 'prime-field':
+ $xml .= '<' . $pre . 'PrimeFieldParamsType>' . "\r\n" .
+ '<' . $pre . 'P>' . $temp['fieldID']['parameters'] . '</' . $pre . 'P>' . "\r\n" .
+ '</' . $pre . 'PrimeFieldParamsType>' . "\r\n";
+ $a = $curve->getA();
+ $b = $curve->getB();
+ list($x, $y) = $curve->getBasePoint();
+ break;
+ default:
+ throw new UnsupportedCurveException('Field Type of ' . $temp['fieldID']['fieldType'] . ' is not supported');
+ }
+ $xml .= '</' . $pre . 'FieldParams>' . "\r\n" .
+ '<' . $pre . 'CurveParamsType>' . "\r\n" .
+ '<' . $pre . 'A>' . $a . '</' . $pre . 'A>' . "\r\n" .
+ '<' . $pre . 'B>' . $b . '</' . $pre . 'B>' . "\r\n" .
+ '</' . $pre . 'CurveParamsType>' . "\r\n" .
+ '<' . $pre . 'BasePointParams>' . "\r\n" .
+ '<' . $pre . 'BasePoint>' . "\r\n" .
+ '<' . $pre . 'ECPointType>' . "\r\n" .
+ '<' . $pre . 'X>' . $x . '</' . $pre . 'X>' . "\r\n" .
+ '<' . $pre . 'Y>' . $y . '</' . $pre . 'Y>' . "\r\n" .
+ '</' . $pre . 'ECPointType>' . "\r\n" .
+ '</' . $pre . 'BasePoint>' . "\r\n" .
+ '<' . $pre . 'Order>' . $curve->getOrder() . '</' . $pre . 'Order>' . "\r\n" .
+ '</' . $pre . 'BasePointParams>' . "\r\n" .
+ '</' . $pre . 'ExplicitParams>' . "\r\n";
+
+ return $xml;
+ }
+
+ if (isset($result['specifiedCurve'])) {
+ $xml = '<' . $pre . 'ECParameters>' . "\r\n" .
+ '<' . $pre . 'FieldID>' . "\r\n";
+ $temp = $result['specifiedCurve'];
+ switch ($temp['fieldID']['fieldType']) {
+ case 'prime-field':
+ $xml .= '<' . $pre . 'Prime>' . "\r\n" .
+ '<' . $pre . 'P>' . Strings::base64_encode($temp['fieldID']['parameters']->toBytes()) . '</' . $pre . 'P>' . "\r\n" .
+ '</' . $pre . 'Prime>' . "\r\n" ;
+ break;
+ default:
+ throw new UnsupportedCurveException('Field Type of ' . $temp['fieldID']['fieldType'] . ' is not supported');
+ }
+ $xml .= '</' . $pre . 'FieldID>' . "\r\n" .
+ '<' . $pre . 'Curve>' . "\r\n" .
+ '<' . $pre . 'A>' . Strings::base64_encode($temp['curve']['a']) . '</' . $pre . 'A>' . "\r\n" .
+ '<' . $pre . 'B>' . Strings::base64_encode($temp['curve']['b']) . '</' . $pre . 'B>' . "\r\n" .
+ '</' . $pre . 'Curve>' . "\r\n" .
+ '<' . $pre . 'Base>' . Strings::base64_encode($temp['base']) . '</' . $pre . 'Base>' . "\r\n" .
+ '<' . $pre . 'Order>' . Strings::base64_encode($temp['order']) . '</' . $pre . 'Order>' . "\r\n" .
+ '</' . $pre . 'ECParameters>';
+ return $xml;
+ }
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/libsodium.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/libsodium.php
new file mode 100644
index 000000000..cce37bab4
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Keys/libsodium.php
@@ -0,0 +1,116 @@
+<?php
+
+/**
+ * libsodium Key Handler
+ *
+ * Different NaCl implementations store the key differently.
+ * https://blog.mozilla.org/warner/2011/11/29/ed25519-keys/ elaborates.
+ * libsodium appears to use the same format as SUPERCOP.
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Keys;
+
+use phpseclib3\Crypt\EC\Curves\Ed25519;
+use phpseclib3\Exception\UnsupportedFormatException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * libsodium Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class libsodium
+{
+ use Common;
+
+ /**
+ * Is invisible flag
+ *
+ */
+ const IS_INVISIBLE = true;
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ switch (strlen($key)) {
+ case 32:
+ $public = $key;
+ break;
+ case 64:
+ $private = substr($key, 0, 32);
+ $public = substr($key, -32);
+ break;
+ case 96:
+ $public = substr($key, -32);
+ if (substr($key, 32, 32) != $public) {
+ throw new \RuntimeException('Keys with 96 bytes should have the 2nd and 3rd set of 32 bytes match');
+ }
+ $private = substr($key, 0, 32);
+ break;
+ default:
+ throw new \RuntimeException('libsodium keys need to either be 32 bytes long, 64 bytes long or 96 bytes long');
+ }
+
+ $curve = new Ed25519();
+ $components = ['curve' => $curve];
+ if (isset($private)) {
+ $arr = $curve->extractSecret($private);
+ $components['dA'] = $arr['dA'];
+ $components['secret'] = $arr['secret'];
+ }
+ $components['QA'] = isset($public) ?
+ self::extractPoint($public, $curve) :
+ $curve->multiplyPoint($curve->getBasePoint(), $components['dA']);
+
+ return $components;
+ }
+
+ /**
+ * Convert an EC public key to the appropriate format
+ *
+ * @param Ed25519 $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @return string
+ */
+ public static function savePublicKey(Ed25519 $curve, array $publicKey)
+ {
+ return $curve->encodePoint($publicKey);
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $privateKey
+ * @param Ed25519 $curve
+ * @param \phpseclib3\Math\Common\FiniteField\Integer[] $publicKey
+ * @param string $secret optional
+ * @param string $password optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $privateKey, Ed25519 $curve, array $publicKey, $secret = null, $password = '')
+ {
+ if (!isset($secret)) {
+ throw new \RuntimeException('Private Key does not have a secret set');
+ }
+ if (strlen($secret) != 32) {
+ throw new \RuntimeException('Private Key secret is not of the correct length');
+ }
+ if (!empty($password) && is_string($password)) {
+ throw new UnsupportedFormatException('libsodium private keys do not support encryption');
+ }
+ return $secret . $curve->encodePoint($publicKey);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/ASN1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/ASN1.php
new file mode 100644
index 000000000..385028b3a
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/ASN1.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * ASN1 Signature Handler
+ *
+ * PHP version 5
+ *
+ * Handles signatures in the format described in
+ * https://tools.ietf.org/html/rfc3279#section-2.2.3
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Signature;
+
+use phpseclib3\File\ASN1 as Encoder;
+use phpseclib3\File\ASN1\Maps\EcdsaSigValue;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * ASN1 Signature Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class ASN1
+{
+ /**
+ * Loads a signature
+ *
+ * @param string $sig
+ * @return array
+ */
+ public static function load($sig)
+ {
+ if (!is_string($sig)) {
+ return false;
+ }
+
+ $decoded = Encoder::decodeBER($sig);
+ if (empty($decoded)) {
+ return false;
+ }
+ $components = Encoder::asn1map($decoded[0], EcdsaSigValue::MAP);
+
+ return $components;
+ }
+
+ /**
+ * Returns a signature in the appropriate format
+ *
+ * @param BigInteger $r
+ * @param BigInteger $s
+ * @return string
+ */
+ public static function save(BigInteger $r, BigInteger $s)
+ {
+ return Encoder::encodeDER(compact('r', 's'), EcdsaSigValue::MAP);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/IEEE.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/IEEE.php
new file mode 100644
index 000000000..c5e622a12
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/IEEE.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * IEEE P1363 Signature Handler
+ *
+ * PHP version 5
+ *
+ * Handles signatures in the format described in
+ * https://standards.ieee.org/ieee/1363/2049/ and
+ * https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/sign#ecdsa
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Signature;
+
+use phpseclib3\Math\BigInteger;
+
+/**
+ * ASN1 Signature Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class IEEE
+{
+ /**
+ * Loads a signature
+ *
+ * @param string $sig
+ * @return array
+ */
+ public static function load($sig)
+ {
+ if (!is_string($sig)) {
+ return false;
+ }
+
+ $len = strlen($sig);
+ if ($len & 1) {
+ return false;
+ }
+
+ $r = new BigInteger(substr($sig, 0, $len >> 1), 256);
+ $s = new BigInteger(substr($sig, $len >> 1), 256);
+
+ return compact('r', 's');
+ }
+
+ /**
+ * Returns a signature in the appropriate format
+ *
+ * @param BigInteger $r
+ * @param BigInteger $s
+ * @param string $curve
+ * @param int $length
+ * @return string
+ */
+ public static function save(BigInteger $r, BigInteger $s, $curve, $length)
+ {
+ $r = $r->toBytes();
+ $s = $s->toBytes();
+ $length = (int) ceil($length / 8);
+ return str_pad($r, $length, "\0", STR_PAD_LEFT) . str_pad($s, $length, "\0", STR_PAD_LEFT);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/Raw.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/Raw.php
new file mode 100644
index 000000000..7e4b47fe6
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/Raw.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * Raw EC Signature Handler
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Signature;
+
+use phpseclib3\Crypt\Common\Formats\Signature\Raw as Progenitor;
+
+/**
+ * Raw DSA Signature Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Raw extends Progenitor
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/SSH2.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/SSH2.php
new file mode 100644
index 000000000..698c8e4ce
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Formats/Signature/SSH2.php
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * SSH2 Signature Handler
+ *
+ * PHP version 5
+ *
+ * Handles signatures in the format used by SSH2
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC\Formats\Signature;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * SSH2 Signature Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class SSH2
+{
+ /**
+ * Loads a signature
+ *
+ * @param string $sig
+ * @return mixed
+ */
+ public static function load($sig)
+ {
+ if (!is_string($sig)) {
+ return false;
+ }
+
+ $result = Strings::unpackSSH2('ss', $sig);
+ if ($result === false) {
+ return false;
+ }
+ list($type, $blob) = $result;
+ switch ($type) {
+ // see https://tools.ietf.org/html/rfc5656#section-3.1.2
+ case 'ecdsa-sha2-nistp256':
+ case 'ecdsa-sha2-nistp384':
+ case 'ecdsa-sha2-nistp521':
+ break;
+ default:
+ return false;
+ }
+
+ $result = Strings::unpackSSH2('ii', $blob);
+ if ($result === false) {
+ return false;
+ }
+
+ return [
+ 'r' => $result[0],
+ 's' => $result[1]
+ ];
+ }
+
+ /**
+ * Returns a signature in the appropriate format
+ *
+ * @param BigInteger $r
+ * @param BigInteger $s
+ * @param string $curve
+ * @return string
+ */
+ public static function save(BigInteger $r, BigInteger $s, $curve)
+ {
+ switch ($curve) {
+ case 'secp256r1':
+ $curve = 'nistp256';
+ break;
+ case 'secp384r1':
+ $curve = 'nistp384';
+ break;
+ case 'secp521r1':
+ $curve = 'nistp521';
+ break;
+ default:
+ return false;
+ }
+
+ $blob = Strings::packSSH2('ii', $r, $s);
+
+ return Strings::packSSH2('ss', 'ecdsa-sha2-' . $curve, $blob);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Parameters.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Parameters.php
new file mode 100644
index 000000000..c0ed64a84
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/Parameters.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * EC Parameters
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC;
+
+use phpseclib3\Crypt\EC;
+
+/**
+ * EC Parameters
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+final class Parameters extends EC
+{
+ /**
+ * Returns the parameters
+ *
+ * @param string $type
+ * @param array $options optional
+ * @return string
+ */
+ public function toString($type = 'PKCS1', array $options = [])
+ {
+ $type = self::validatePlugin('Keys', 'PKCS1', 'saveParameters');
+
+ return $type::saveParameters($this->curve, $options);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/PrivateKey.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/PrivateKey.php
new file mode 100644
index 000000000..9947bb7d5
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/PrivateKey.php
@@ -0,0 +1,283 @@
+<?php
+
+/**
+ * EC Private Key
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common;
+use phpseclib3\Crypt\EC;
+use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
+use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
+use phpseclib3\Crypt\EC\Curves\Curve25519;
+use phpseclib3\Crypt\EC\Curves\Ed25519;
+use phpseclib3\Crypt\EC\Formats\Keys\PKCS1;
+use phpseclib3\Crypt\EC\Formats\Signature\ASN1 as ASN1Signature;
+use phpseclib3\Crypt\Hash;
+use phpseclib3\Exception\UnsupportedOperationException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * EC Private Key
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+final class PrivateKey extends EC implements Common\PrivateKey
+{
+ use Common\Traits\PasswordProtected;
+
+ /**
+ * Private Key dA
+ *
+ * sign() converts this to a BigInteger so one might wonder why this is a FiniteFieldInteger instead of
+ * a BigInteger. That's because a FiniteFieldInteger, when converted to a byte string, is null padded by
+ * a certain amount whereas a BigInteger isn't.
+ *
+ * @var object
+ */
+ protected $dA;
+
+ /**
+ * @var string
+ */
+ protected $secret;
+
+ /**
+ * Multiplies an encoded point by the private key
+ *
+ * Used by ECDH
+ *
+ * @param string $coordinates
+ * @return string
+ */
+ public function multiply($coordinates)
+ {
+ if ($this->curve instanceof MontgomeryCurve) {
+ if ($this->curve instanceof Curve25519 && self::$engines['libsodium']) {
+ return sodium_crypto_scalarmult($this->dA->toBytes(), $coordinates);
+ }
+
+ $point = [$this->curve->convertInteger(new BigInteger(strrev($coordinates), 256))];
+ $point = $this->curve->multiplyPoint($point, $this->dA);
+ return strrev($point[0]->toBytes(true));
+ }
+ if (!$this->curve instanceof TwistedEdwardsCurve) {
+ $coordinates = "\0$coordinates";
+ }
+ $point = PKCS1::extractPoint($coordinates, $this->curve);
+ $point = $this->curve->multiplyPoint($point, $this->dA);
+ if ($this->curve instanceof TwistedEdwardsCurve) {
+ return $this->curve->encodePoint($point);
+ }
+ if (empty($point)) {
+ throw new \RuntimeException('The infinity point is invalid');
+ }
+ return "\4" . $point[0]->toBytes(true) . $point[1]->toBytes(true);
+ }
+
+ /**
+ * Create a signature
+ *
+ * @see self::verify()
+ * @param string $message
+ * @return mixed
+ */
+ public function sign($message)
+ {
+ if ($this->curve instanceof MontgomeryCurve) {
+ throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures');
+ }
+
+ $dA = $this->dA;
+ $order = $this->curve->getOrder();
+
+ $shortFormat = $this->shortFormat;
+ $format = $this->sigFormat;
+ if ($format === false) {
+ return false;
+ }
+
+ if ($this->curve instanceof TwistedEdwardsCurve) {
+ if ($this->curve instanceof Ed25519 && self::$engines['libsodium'] && !isset($this->context)) {
+ $result = sodium_crypto_sign_detached($message, $this->withPassword()->toString('libsodium'));
+ return $shortFormat == 'SSH2' ? Strings::packSSH2('ss', 'ssh-' . strtolower($this->getCurve()), $result) : $result;
+ }
+
+ // contexts (Ed25519ctx) are supported but prehashing (Ed25519ph) is not.
+ // quoting https://tools.ietf.org/html/rfc8032#section-8.5 ,
+ // "The Ed25519ph and Ed448ph variants ... SHOULD NOT be used"
+ $A = $this->curve->encodePoint($this->QA);
+ $curve = $this->curve;
+ $hash = new Hash($curve::HASH);
+
+ $secret = substr($hash->hash($this->secret), $curve::SIZE);
+
+ if ($curve instanceof Ed25519) {
+ $dom = !isset($this->context) ? '' :
+ 'SigEd25519 no Ed25519 collisions' . "\0" . chr(strlen($this->context)) . $this->context;
+ } else {
+ $context = isset($this->context) ? $this->context : '';
+ $dom = 'SigEd448' . "\0" . chr(strlen($context)) . $context;
+ }
+ // SHA-512(dom2(F, C) || prefix || PH(M))
+ $r = $hash->hash($dom . $secret . $message);
+ $r = strrev($r);
+ $r = new BigInteger($r, 256);
+ list(, $r) = $r->divide($order);
+ $R = $curve->multiplyPoint($curve->getBasePoint(), $r);
+ $R = $curve->encodePoint($R);
+ $k = $hash->hash($dom . $R . $A . $message);
+ $k = strrev($k);
+ $k = new BigInteger($k, 256);
+ list(, $k) = $k->divide($order);
+ $S = $k->multiply($dA)->add($r);
+ list(, $S) = $S->divide($order);
+ $S = str_pad(strrev($S->toBytes()), $curve::SIZE, "\0");
+ return $shortFormat == 'SSH2' ? Strings::packSSH2('ss', 'ssh-' . strtolower($this->getCurve()), $R . $S) : $R . $S;
+ }
+
+ if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) {
+ $signature = '';
+ // altho PHP's OpenSSL bindings only supported EC key creation in PHP 7.1 they've long
+ // supported signing / verification
+ // we use specified curves to avoid issues with OpenSSL possibly not supporting a given named curve;
+ // doing this may mean some curve-specific optimizations can't be used but idk if OpenSSL even
+ // has curve-specific optimizations
+ $result = openssl_sign($message, $signature, $this->withPassword()->toString('PKCS8', ['namedCurve' => false]), $this->hash->getHash());
+
+ if ($result) {
+ if ($shortFormat == 'ASN1') {
+ return $signature;
+ }
+
+ $loaded = ASN1Signature::load($signature);
+ $r = $loaded['r'];
+ $s = $loaded['s'];
+
+
+ return $this->formatSignature($r, $s);
+ }
+ }
+
+ $e = $this->hash->hash($message);
+ $e = new BigInteger($e, 256);
+
+ $Ln = $this->hash->getLength() - $order->getLength();
+ $z = $Ln > 0 ? $e->bitwise_rightShift($Ln) : $e;
+
+ while (true) {
+ $k = BigInteger::randomRange(self::$one, $order->subtract(self::$one));
+ list($x, $y) = $this->curve->multiplyPoint($this->curve->getBasePoint(), $k);
+ $x = $x->toBigInteger();
+ list(, $r) = $x->divide($order);
+ if ($r->equals(self::$zero)) {
+ continue;
+ }
+ $kinv = $k->modInverse($order);
+ $temp = $z->add($dA->multiply($r));
+ $temp = $kinv->multiply($temp);
+ list(, $s) = $temp->divide($order);
+ if (!$s->equals(self::$zero)) {
+ break;
+ }
+ }
+
+ // the following is an RFC6979 compliant implementation of deterministic ECDSA
+ // it's unused because it's mainly intended for use when a good CSPRNG isn't
+ // available. if phpseclib's CSPRNG isn't good then even key generation is
+ // suspect
+ /*
+ // if this were actually being used it'd probably be better if this lived in load() and createKey()
+ $this->q = $this->curve->getOrder();
+ $dA = $this->dA->toBigInteger();
+ $this->x = $dA;
+
+ $h1 = $this->hash->hash($message);
+ $k = $this->computek($h1);
+ list($x, $y) = $this->curve->multiplyPoint($this->curve->getBasePoint(), $k);
+ $x = $x->toBigInteger();
+ list(, $r) = $x->divide($this->q);
+ $kinv = $k->modInverse($this->q);
+ $h1 = $this->bits2int($h1);
+ $temp = $h1->add($dA->multiply($r));
+ $temp = $kinv->multiply($temp);
+ list(, $s) = $temp->divide($this->q);
+ */
+
+ return $this->formatSignature($r, $s);
+ }
+
+ /**
+ * Returns the private key
+ *
+ * @param string $type
+ * @param array $options optional
+ * @return string
+ */
+ public function toString($type, array $options = [])
+ {
+ $type = self::validatePlugin('Keys', $type, 'savePrivateKey');
+
+ return $type::savePrivateKey($this->dA, $this->curve, $this->QA, $this->secret, $this->password, $options);
+ }
+
+ /**
+ * Returns the public key
+ *
+ * @see self::getPrivateKey()
+ * @return mixed
+ */
+ public function getPublicKey()
+ {
+ $format = 'PKCS8';
+ if ($this->curve instanceof MontgomeryCurve) {
+ $format = 'MontgomeryPublic';
+ }
+
+ $type = self::validatePlugin('Keys', $format, 'savePublicKey');
+
+ $key = $type::savePublicKey($this->curve, $this->QA);
+ $key = EC::loadFormat($format, $key);
+ if ($this->curve instanceof MontgomeryCurve) {
+ return $key;
+ }
+ $key = $key
+ ->withHash($this->hash->getHash())
+ ->withSignatureFormat($this->shortFormat);
+ if ($this->curve instanceof TwistedEdwardsCurve) {
+ $key = $key->withContext($this->context);
+ }
+ return $key;
+ }
+
+ /**
+ * Returns a signature in the appropriate format
+ *
+ * @return string
+ */
+ private function formatSignature(BigInteger $r, BigInteger $s)
+ {
+ $format = $this->sigFormat;
+
+ $temp = new \ReflectionMethod($format, 'save');
+ $paramCount = $temp->getNumberOfRequiredParameters();
+
+ // @codingStandardsIgnoreStart
+ switch ($paramCount) {
+ case 2: return $format::save($r, $s);
+ case 3: return $format::save($r, $s, $this->getCurve());
+ case 4: return $format::save($r, $s, $this->getCurve(), $this->getLength());
+ }
+ // @codingStandardsIgnoreEnd
+
+ // presumably the only way you could get to this is if you were using a custom plugin
+ throw new UnsupportedOperationException("$format::save() has $paramCount parameters - the only valid parameter counts are 2 or 3");
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/PublicKey.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/PublicKey.php
new file mode 100644
index 000000000..d34c6c4dd
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/EC/PublicKey.php
@@ -0,0 +1,173 @@
+<?php
+
+/**
+ * EC Public Key
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\EC;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common;
+use phpseclib3\Crypt\EC;
+use phpseclib3\Crypt\EC\BaseCurves\Montgomery as MontgomeryCurve;
+use phpseclib3\Crypt\EC\BaseCurves\TwistedEdwards as TwistedEdwardsCurve;
+use phpseclib3\Crypt\EC\Curves\Ed25519;
+use phpseclib3\Crypt\EC\Formats\Keys\PKCS1;
+use phpseclib3\Crypt\EC\Formats\Signature\ASN1 as ASN1Signature;
+use phpseclib3\Crypt\Hash;
+use phpseclib3\Exception\UnsupportedOperationException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * EC Public Key
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+final class PublicKey extends EC implements Common\PublicKey
+{
+ use Common\Traits\Fingerprint;
+
+ /**
+ * Verify a signature
+ *
+ * @see self::verify()
+ * @param string $message
+ * @param string $signature
+ * @return mixed
+ */
+ public function verify($message, $signature)
+ {
+ if ($this->curve instanceof MontgomeryCurve) {
+ throw new UnsupportedOperationException('Montgomery Curves cannot be used to create signatures');
+ }
+
+ $shortFormat = $this->shortFormat;
+ $format = $this->sigFormat;
+ if ($format === false) {
+ return false;
+ }
+
+ $order = $this->curve->getOrder();
+
+ if ($this->curve instanceof TwistedEdwardsCurve) {
+ if ($shortFormat == 'SSH2') {
+ list(, $signature) = Strings::unpackSSH2('ss', $signature);
+ }
+
+ if ($this->curve instanceof Ed25519 && self::$engines['libsodium'] && !isset($this->context)) {
+ return sodium_crypto_sign_verify_detached($signature, $message, $this->toString('libsodium'));
+ }
+
+ $curve = $this->curve;
+ if (strlen($signature) != 2 * $curve::SIZE) {
+ return false;
+ }
+
+ $R = substr($signature, 0, $curve::SIZE);
+ $S = substr($signature, $curve::SIZE);
+
+ try {
+ $R = PKCS1::extractPoint($R, $curve);
+ $R = $this->curve->convertToInternal($R);
+ } catch (\Exception $e) {
+ return false;
+ }
+
+ $S = strrev($S);
+ $S = new BigInteger($S, 256);
+
+ if ($S->compare($order) >= 0) {
+ return false;
+ }
+
+ $A = $curve->encodePoint($this->QA);
+
+ if ($curve instanceof Ed25519) {
+ $dom2 = !isset($this->context) ? '' :
+ 'SigEd25519 no Ed25519 collisions' . "\0" . chr(strlen($this->context)) . $this->context;
+ } else {
+ $context = isset($this->context) ? $this->context : '';
+ $dom2 = 'SigEd448' . "\0" . chr(strlen($context)) . $context;
+ }
+
+ $hash = new Hash($curve::HASH);
+ $k = $hash->hash($dom2 . substr($signature, 0, $curve::SIZE) . $A . $message);
+ $k = strrev($k);
+ $k = new BigInteger($k, 256);
+ list(, $k) = $k->divide($order);
+
+ $qa = $curve->convertToInternal($this->QA);
+
+ $lhs = $curve->multiplyPoint($curve->getBasePoint(), $S);
+ $rhs = $curve->multiplyPoint($qa, $k);
+ $rhs = $curve->addPoint($rhs, $R);
+ $rhs = $curve->convertToAffine($rhs);
+
+ return $lhs[0]->equals($rhs[0]) && $lhs[1]->equals($rhs[1]);
+ }
+
+ $params = $format::load($signature);
+ if ($params === false || count($params) != 2) {
+ return false;
+ }
+ $r = $params['r'];
+ $s = $params['s'];
+
+ if (self::$engines['OpenSSL'] && in_array($this->hash->getHash(), openssl_get_md_methods())) {
+ $sig = $format != 'ASN1' ? ASN1Signature::save($r, $s) : $signature;
+
+ $result = openssl_verify($message, $sig, $this->toString('PKCS8', ['namedCurve' => false]), $this->hash->getHash());
+
+ if ($result != -1) {
+ return (bool) $result;
+ }
+ }
+
+ $n_1 = $order->subtract(self::$one);
+ if (!$r->between(self::$one, $n_1) || !$s->between(self::$one, $n_1)) {
+ return false;
+ }
+
+ $e = $this->hash->hash($message);
+ $e = new BigInteger($e, 256);
+
+ $Ln = $this->hash->getLength() - $order->getLength();
+ $z = $Ln > 0 ? $e->bitwise_rightShift($Ln) : $e;
+
+ $w = $s->modInverse($order);
+ list(, $u1) = $z->multiply($w)->divide($order);
+ list(, $u2) = $r->multiply($w)->divide($order);
+
+ $u1 = $this->curve->convertInteger($u1);
+ $u2 = $this->curve->convertInteger($u2);
+
+ list($x1, $y1) = $this->curve->multiplyAddPoints(
+ [$this->curve->getBasePoint(), $this->QA],
+ [$u1, $u2]
+ );
+
+ $x1 = $x1->toBigInteger();
+ list(, $x1) = $x1->divide($order);
+
+ return $x1->equals($r);
+ }
+
+ /**
+ * Returns the public key
+ *
+ * @param string $type
+ * @param array $options optional
+ * @return string
+ */
+ public function toString($type, array $options = [])
+ {
+ $type = self::validatePlugin('Keys', $type, 'savePublicKey');
+
+ return $type::savePublicKey($this->curve, $this->QA, $options);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Hash.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Hash.php
index 5e5d13d4c..cc5b42c08 100644
--- a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Hash.php
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Hash.php
@@ -1,26 +1,19 @@
<?php
/**
- * Pure-PHP implementations of keyed-hash message authentication codes (HMACs) and various cryptographic hashing functions.
+ * Wrapper around hash() and hash_hmac() functions supporting truncated hashes
+ * such as sha256-96. Any hash algorithm returned by hash_algos() (and
+ * truncated versions thereof) are supported.
*
- * Uses hash() or mhash() if available and an internal implementation, otherwise. Currently supports the following:
- *
- * md2, md5, md5-96, sha1, sha1-96, sha256, sha256-96, sha384, and sha512, sha512-96
- *
- * If {@link self::setKey() setKey()} is called, {@link self::hash() hash()} will return the HMAC as opposed to
- * the hash. If no valid algorithm is provided, sha1 will be used.
- *
- * PHP version 5
- *
- * {@internal The variable names are the same as those in
- * {@link http://tools.ietf.org/html/rfc2104#section-2 RFC2104}.}}
+ * If {@link self::setKey() setKey()} is called, {@link self::hash() hash()} will
+ * return the HMAC as opposed to the hash.
*
* Here's a short example of how to use this library:
* <code>
* <?php
* include 'vendor/autoload.php';
*
- * $hash = new \phpseclib\Crypt\Hash('sha1');
+ * $hash = new \phpseclib3\Crypt\Hash('sha512');
*
* $hash->setKey('abcdefg');
*
@@ -28,148 +21,195 @@
* ?>
* </code>
*
- * @category Crypt
- * @package Hash
* @author Jim Wigginton <terrafrost@php.net>
- * @copyright 2007 Jim Wigginton
+ * @copyright 2015 Jim Wigginton
+ * @author Andreas Fischer <bantu@phpbb.com>
+ * @copyright 2015 Andreas Fischer
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
-namespace phpseclib\Crypt;
+namespace phpseclib3\Crypt;
-use phpseclib\Math\BigInteger;
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Exception\InsufficientSetupException;
+use phpseclib3\Exception\UnsupportedAlgorithmException;
+use phpseclib3\Math\BigInteger;
+use phpseclib3\Math\PrimeField;
/**
- * Pure-PHP implementations of keyed-hash message authentication codes (HMACs) and various cryptographic hashing functions.
- *
- * @package Hash
* @author Jim Wigginton <terrafrost@php.net>
- * @access public
+ * @author Andreas Fischer <bantu@phpbb.com>
*/
class Hash
{
- /**#@+
- * @access private
- * @see \phpseclib\Crypt\Hash::__construct()
- */
/**
- * Toggles the internal implementation
+ * Padding Types
+ *
*/
- const MODE_INTERNAL = 1;
+ const PADDING_KECCAK = 1;
+
/**
- * Toggles the mhash() implementation, which has been deprecated on PHP 5.3.0+.
+ * Padding Types
+ *
*/
- const MODE_MHASH = 2;
+ const PADDING_SHA3 = 2;
+
/**
- * Toggles the hash() implementation, which works on PHP 5.1.2+.
+ * Padding Types
+ *
*/
- const MODE_HASH = 3;
- /**#@-*/
+ const PADDING_SHAKE = 3;
/**
- * Hash Parameter
+ * Padding Type
+ *
+ * Only used by SHA3
*
- * @see self::setHash()
* @var int
- * @access private
*/
- var $hashParam;
+ private $paddingType = 0;
/**
- * Byte-length of compression blocks / key (Internal HMAC)
+ * Hash Parameter
*
- * @see self::setAlgorithm()
+ * @see self::setHash()
* @var int
- * @access private
*/
- var $b;
+ private $hashParam;
/**
* Byte-length of hash output (Internal HMAC)
*
* @see self::setHash()
* @var int
- * @access private
*/
- var $l = false;
+ private $length;
/**
* Hash Algorithm
*
* @see self::setHash()
* @var string
- * @access private
*/
- var $hash;
+ private $algo;
/**
* Key
*
* @see self::setKey()
* @var string
- * @access private
*/
- var $key = false;
+ private $key = false;
+
+ /**
+ * Nonce
+ *
+ * @see self::setNonce()
+ * @var string
+ */
+ private $nonce = false;
+
+ /**
+ * Hash Parameters
+ *
+ * @var array
+ */
+ private $parameters = [];
/**
* Computed Key
*
* @see self::_computeKey()
* @var string
- * @access private
*/
- var $computedKey = false;
+ private $computedKey = false;
/**
* Outer XOR (Internal HMAC)
*
- * @see self::setKey()
+ * Used only for sha512
+ *
+ * @see self::hash()
* @var string
- * @access private
*/
- var $opad;
+ private $opad;
/**
* Inner XOR (Internal HMAC)
*
- * @see self::setKey()
+ * Used only for sha512
+ *
+ * @see self::hash()
* @var string
- * @access private
*/
- var $ipad;
+ private $ipad;
/**
- * Engine
+ * Recompute AES Key
*
- * @see self::setHash()
+ * Used only for umac
+ *
+ * @see self::hash()
+ * @var boolean
+ */
+ private $recomputeAESKey;
+
+ /**
+ * umac cipher object
+ *
+ * @see self::hash()
+ * @var AES
+ */
+ private $c;
+
+ /**
+ * umac pad
+ *
+ * @see self::hash()
* @var string
- * @access private
*/
- var $engine;
+ private $pad;
+
+ /**
+ * Block Size
+ *
+ * @var int
+ */
+ private $blockSize;
+
+ /**#@+
+ * UMAC variables
+ *
+ * @var PrimeField
+ */
+ private static $factory36;
+ private static $factory64;
+ private static $factory128;
+ private static $offset64;
+ private static $offset128;
+ private static $marker64;
+ private static $marker128;
+ private static $maxwordrange64;
+ private static $maxwordrange128;
+ /**#@-*/
+
+ /**#@+
+ * AES_CMAC variables
+ *
+ * @var string
+ */
+ private $k1;
+ private $k2;
+ /**#@-*/
/**
* Default Constructor.
*
* @param string $hash
- * @return \phpseclib\Crypt\Hash
- * @access public
- */
- function __construct($hash = 'sha1')
- {
- if (!defined('CRYPT_HASH_MODE')) {
- switch (true) {
- case extension_loaded('hash'):
- define('CRYPT_HASH_MODE', self::MODE_HASH);
- break;
- case extension_loaded('mhash'):
- define('CRYPT_HASH_MODE', self::MODE_MHASH);
- break;
- default:
- define('CRYPT_HASH_MODE', self::MODE_INTERNAL);
- }
- }
-
+ */
+ public function __construct($hash = 'sha256')
+ {
$this->setHash($hash);
}
@@ -178,13 +218,33 @@ class Hash
*
* Keys can be of any length.
*
- * @access public
* @param string $key
*/
- function setKey($key = false)
+ public function setKey($key = false)
{
$this->key = $key;
- $this->_computeKey();
+ $this->computeKey();
+ $this->recomputeAESKey = true;
+ }
+
+ /**
+ * Sets the nonce for UMACs
+ *
+ * Keys can be of any length.
+ *
+ * @param string $nonce
+ */
+ public function setNonce($nonce = false)
+ {
+ switch (true) {
+ case !is_string($nonce):
+ case strlen($nonce) > 0 && strlen($nonce) <= 16:
+ $this->recomputeAESKey = true;
+ $this->nonce = $nonce;
+ return;
+ }
+
+ throw new \LengthException('The nonce length must be between 1 and 16 bytes, inclusive');
}
/**
@@ -197,30 +257,22 @@ class Hash
* when doing an HMAC multiple times it's faster to compute the hash once instead of computing it during
* every call
*
- * @access private
*/
- function _computeKey()
+ private function computeKey()
{
if ($this->key === false) {
$this->computedKey = false;
return;
}
- if (strlen($this->key) <= $this->b) {
+ if (strlen($this->key) <= $this->getBlockLengthInBytes()) {
$this->computedKey = $this->key;
return;
}
- switch ($this->engine) {
- case self::MODE_MHASH:
- $this->computedKey = mhash($this->hash, $this->key);
- break;
- case self::MODE_HASH:
- $this->computedKey = hash($this->hash, $this->key, true);
- break;
- case self::MODE_INTERNAL:
- $this->computedKey = call_user_func($this->hash, $this->key);
- }
+ $this->computedKey = is_array($this->algo) ?
+ call_user_func($this->algo, $this->key) :
+ hash($this->algo, $this->key, true);
}
/**
@@ -228,10 +280,9 @@ class Hash
*
* As set by the constructor or by the setHash() method.
*
- * @access public
* @return string
*/
- function getHash()
+ public function getHash()
{
return $this->hashParam;
}
@@ -239,418 +290,1309 @@ class Hash
/**
* Sets the hash function.
*
- * @access public
* @param string $hash
*/
- function setHash($hash)
+ public function setHash($hash)
{
+ $oldHash = $this->hashParam;
$this->hashParam = $hash = strtolower($hash);
switch ($hash) {
+ case 'umac-32':
+ case 'umac-64':
+ case 'umac-96':
+ case 'umac-128':
+ if ($oldHash != $this->hashParam) {
+ $this->recomputeAESKey = true;
+ }
+ $this->blockSize = 128;
+ $this->length = abs(substr($hash, -3)) >> 3;
+ $this->algo = 'umac';
+ return;
+ case 'aes_cmac':
+ if ($oldHash != $this->hashParam) {
+ $this->recomputeAESKey = true;
+ }
+ $this->blockSize = 128;
+ $this->length = 16;
+ $this->algo = 'aes_cmac';
+ return;
+ case 'md2-96':
case 'md5-96':
case 'sha1-96':
+ case 'sha224-96':
case 'sha256-96':
+ case 'sha384-96':
case 'sha512-96':
+ case 'sha512/224-96':
+ case 'sha512/256-96':
$hash = substr($hash, 0, -3);
- $this->l = 12; // 96 / 8 = 12
+ $this->length = 12; // 96 / 8 = 12
break;
case 'md2':
case 'md5':
- $this->l = 16;
+ $this->length = 16;
break;
case 'sha1':
- $this->l = 20;
+ $this->length = 20;
+ break;
+ case 'sha224':
+ case 'sha512/224':
+ case 'sha3-224':
+ $this->length = 28;
break;
+ case 'keccak256':
+ $this->paddingType = self::PADDING_KECCAK;
+ // fall-through
case 'sha256':
- $this->l = 32;
+ case 'sha512/256':
+ case 'sha3-256':
+ $this->length = 32;
break;
case 'sha384':
- $this->l = 48;
+ case 'sha3-384':
+ $this->length = 48;
break;
case 'sha512':
- $this->l = 64;
+ case 'sha3-512':
+ $this->length = 64;
+ break;
+ default:
+ if (preg_match('#^(shake(?:128|256))-(\d+)$#', $hash, $matches)) {
+ $this->paddingType = self::PADDING_SHAKE;
+ $hash = $matches[1];
+ $this->length = $matches[2] >> 3;
+ } else {
+ throw new UnsupportedAlgorithmException(
+ "$hash is not a supported algorithm"
+ );
+ }
}
switch ($hash) {
- case 'md2-96':
case 'md2':
- $this->b = 16;
+ case 'md2-96':
+ $this->blockSize = 128;
+ break;
case 'md5-96':
case 'sha1-96':
case 'sha224-96':
case 'sha256-96':
- case 'md2':
case 'md5':
case 'sha1':
case 'sha224':
case 'sha256':
- $this->b = 64;
+ $this->blockSize = 512;
break;
- default:
- $this->b = 128;
- }
-
- switch ($hash) {
- case 'md2':
- $this->engine = CRYPT_HASH_MODE == self::MODE_HASH && in_array('md2', hash_algos()) ?
- self::MODE_HASH : self::MODE_INTERNAL;
+ case 'sha3-224':
+ $this->blockSize = 1152; // 1600 - 2*224
break;
- case 'sha384':
- case 'sha512':
- $this->engine = CRYPT_HASH_MODE == self::MODE_MHASH ? self::MODE_INTERNAL : CRYPT_HASH_MODE;
+ case 'sha3-256':
+ case 'shake256':
+ case 'keccak256':
+ $this->blockSize = 1088; // 1600 - 2*256
+ break;
+ case 'sha3-384':
+ $this->blockSize = 832; // 1600 - 2*384
+ break;
+ case 'sha3-512':
+ $this->blockSize = 576; // 1600 - 2*512
+ break;
+ case 'shake128':
+ $this->blockSize = 1344; // 1600 - 2*128
break;
default:
- $this->engine = CRYPT_HASH_MODE;
- }
-
- switch ($this->engine) {
- case self::MODE_MHASH:
- switch ($hash) {
- case 'md5':
- $this->hash = MHASH_MD5;
- break;
- case 'sha256':
- $this->hash = MHASH_SHA256;
- break;
- case 'sha1':
- default:
- $this->hash = MHASH_SHA1;
+ $this->blockSize = 1024;
+ }
+
+ if (in_array(substr($hash, 0, 5), ['sha3-', 'shake', 'kecca'])) {
+ // PHP 7.1.0 introduced support for "SHA3 fixed mode algorithms":
+ // http://php.net/ChangeLog-7.php#7.1.0
+ if (version_compare(PHP_VERSION, '7.1.0') < 0 || substr($hash, 0, 5) != 'sha3-') {
+ //preg_match('#(\d+)$#', $hash, $matches);
+ //$this->parameters['capacity'] = 2 * $matches[1]; // 1600 - $this->blockSize
+ //$this->parameters['rate'] = 1600 - $this->parameters['capacity']; // == $this->blockSize
+ if (!$this->paddingType) {
+ $this->paddingType = self::PADDING_SHA3;
}
- $this->_computeKey(self::MODE_MHASH);
- return;
- case self::MODE_HASH:
- switch ($hash) {
- case 'md5':
- $this->hash = 'md5';
- return;
- case 'md2':
- case 'sha256':
- case 'sha384':
- case 'sha512':
- $this->hash = $hash;
- return;
- case 'sha1':
- default:
- $this->hash = 'sha1';
+ $this->parameters = [
+ 'capacity' => 1600 - $this->blockSize,
+ 'rate' => $this->blockSize,
+ 'length' => $this->length,
+ 'padding' => $this->paddingType
+ ];
+ $hash = ['phpseclib3\Crypt\Hash', PHP_INT_SIZE == 8 ? 'sha3_64' : 'sha3_32'];
+ }
+ }
+
+ if ($hash == 'sha512/224' || $hash == 'sha512/256') {
+ // PHP 7.1.0 introduced sha512/224 and sha512/256 support:
+ // http://php.net/ChangeLog-7.php#7.1.0
+ if (version_compare(PHP_VERSION, '7.1.0') < 0) {
+ // from http://csrc.nist.gov/publications/fips/fips180-4/fips-180-4.pdf#page=24
+ $initial = $hash == 'sha512/256' ?
+ [
+ '22312194FC2BF72C', '9F555FA3C84C64C2', '2393B86B6F53B151', '963877195940EABD',
+ '96283EE2A88EFFE3', 'BE5E1E2553863992', '2B0199FC2C85B8AA', '0EB72DDC81C52CA2'
+ ] :
+ [
+ '8C3D37C819544DA2', '73E1996689DCD4D6', '1DFAB7AE32FF9C82', '679DD514582F9FCF',
+ '0F6D2B697BD44DA8', '77E36F7304C48942', '3F9D85A86A1D36C8', '1112E6AD91D692A1'
+ ];
+ for ($i = 0; $i < 8; $i++) {
+ if (PHP_INT_SIZE == 8) {
+ list(, $initial[$i]) = unpack('J', pack('H*', $initial[$i]));
+ } else {
+ $initial[$i] = new BigInteger($initial[$i], 16);
+ $initial[$i]->setPrecision(64);
+ }
}
- $this->_computeKey(self::MODE_HASH);
- return;
+
+ $this->parameters = compact('initial');
+
+ $hash = ['phpseclib3\Crypt\Hash', PHP_INT_SIZE == 8 ? 'sha512_64' : 'sha512'];
+ }
}
- switch ($hash) {
- case 'md2':
- $this->hash = array($this, '_md2');
- break;
- case 'md5':
- $this->hash = array($this, '_md5');
- break;
- case 'sha256':
- $this->hash = array($this, '_sha256');
- break;
- case 'sha384':
- case 'sha512':
- $this->hash = array($this, '_sha512');
- break;
- case 'sha1':
- default:
- $this->hash = array($this, '_sha1');
+ if (is_array($hash)) {
+ $b = $this->blockSize >> 3;
+ $this->ipad = str_repeat(chr(0x36), $b);
+ $this->opad = str_repeat(chr(0x5C), $b);
+ }
+
+ $this->algo = $hash;
+
+ $this->computeKey();
+ }
+
+ /**
+ * KDF: Key-Derivation Function
+ *
+ * The key-derivation function generates pseudorandom bits used to key the hash functions.
+ *
+ * @param int $index a non-negative integer less than 2^64
+ * @param int $numbytes a non-negative integer less than 2^64
+ * @return string string of length numbytes bytes
+ */
+ private function kdf($index, $numbytes)
+ {
+ $this->c->setIV(pack('N4', 0, $index, 0, 1));
+
+ return $this->c->encrypt(str_repeat("\0", $numbytes));
+ }
+
+ /**
+ * PDF Algorithm
+ *
+ * @return string string of length taglen bytes.
+ */
+ private function pdf()
+ {
+ $k = $this->key;
+ $nonce = $this->nonce;
+ $taglen = $this->length;
+
+ //
+ // Extract and zero low bit(s) of Nonce if needed
+ //
+ if ($taglen <= 8) {
+ $last = strlen($nonce) - 1;
+ $mask = $taglen == 4 ? "\3" : "\1";
+ $index = $nonce[$last] & $mask;
+ $nonce[$last] = $nonce[$last] ^ $index;
+ }
+
+ //
+ // Make Nonce BLOCKLEN bytes by appending zeroes if needed
+ //
+ $nonce = str_pad($nonce, 16, "\0");
+
+ //
+ // Generate subkey, encipher and extract indexed substring
+ //
+ $kp = $this->kdf(0, 16);
+ $c = new AES('ctr');
+ $c->disablePadding();
+ $c->setKey($kp);
+ $c->setIV($nonce);
+ $t = $c->encrypt("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
+
+ // we could use ord() but per https://paragonie.com/blog/2016/06/constant-time-encoding-boring-cryptography-rfc-4648-and-you
+ // unpack() doesn't leak timing info
+ return $taglen <= 8 ?
+ substr($t, unpack('C', $index)[1] * $taglen, $taglen) :
+ substr($t, 0, $taglen);
+ }
+
+ /**
+ * UHASH Algorithm
+ *
+ * @param string $m string of length less than 2^67 bits.
+ * @param int $taglen the integer 4, 8, 12 or 16.
+ * @return string string of length taglen bytes.
+ */
+ private function uhash($m, $taglen)
+ {
+ //
+ // One internal iteration per 4 bytes of output
+ //
+ $iters = $taglen >> 2;
+
+ //
+ // Define total key needed for all iterations using KDF.
+ // L1Key reuses most key material between iterations.
+ //
+ //$L1Key = $this->kdf(1, 1024 + ($iters - 1) * 16);
+ $L1Key = $this->kdf(1, (1024 + ($iters - 1)) * 16);
+ $L2Key = $this->kdf(2, $iters * 24);
+ $L3Key1 = $this->kdf(3, $iters * 64);
+ $L3Key2 = $this->kdf(4, $iters * 4);
+
+ //
+ // For each iteration, extract key and do three-layer hash.
+ // If bytelength(M) <= 1024, then skip L2-HASH.
+ //
+ $y = '';
+ for ($i = 0; $i < $iters; $i++) {
+ $L1Key_i = substr($L1Key, $i * 16, 1024);
+ $L2Key_i = substr($L2Key, $i * 24, 24);
+ $L3Key1_i = substr($L3Key1, $i * 64, 64);
+ $L3Key2_i = substr($L3Key2, $i * 4, 4);
+
+ $a = self::L1Hash($L1Key_i, $m);
+ $b = strlen($m) <= 1024 ? "\0\0\0\0\0\0\0\0$a" : self::L2Hash($L2Key_i, $a);
+ $c = self::L3Hash($L3Key1_i, $L3Key2_i, $b);
+ $y .= $c;
+ }
+
+ return $y;
+ }
+
+ /**
+ * L1-HASH Algorithm
+ *
+ * The first-layer hash breaks the message into 1024-byte chunks and
+ * hashes each with a function called NH. Concatenating the results
+ * forms a string, which is up to 128 times shorter than the original.
+ *
+ * @param string $k string of length 1024 bytes.
+ * @param string $m string of length less than 2^67 bits.
+ * @return string string of length (8 * ceil(bitlength(M)/8192)) bytes.
+ */
+ private static function L1Hash($k, $m)
+ {
+ //
+ // Break M into 1024 byte chunks (final chunk may be shorter)
+ //
+ $m = str_split($m, 1024);
+
+ //
+ // For each chunk, except the last: endian-adjust, NH hash
+ // and add bit-length. Use results to build Y.
+ //
+ $length = 1024 * 8;
+ $y = '';
+
+ for ($i = 0; $i < count($m) - 1; $i++) {
+ $m[$i] = pack('N*', ...unpack('V*', $m[$i])); // ENDIAN-SWAP
+ $y .= PHP_INT_SIZE == 8 ?
+ static::nh64($k, $m[$i], $length) :
+ static::nh32($k, $m[$i], $length);
+ }
+
+ //
+ // For the last chunk: pad to 32-byte boundary, endian-adjust,
+ // NH hash and add bit-length. Concatenate the result to Y.
+ //
+ $length = count($m) ? strlen($m[$i]) : 0;
+ $pad = 32 - ($length % 32);
+ $pad = max(32, $length + $pad % 32);
+ $m[$i] = str_pad(isset($m[$i]) ? $m[$i] : '', $pad, "\0"); // zeropad
+ $m[$i] = pack('N*', ...unpack('V*', $m[$i])); // ENDIAN-SWAP
+
+ $y .= PHP_INT_SIZE == 8 ?
+ static::nh64($k, $m[$i], $length * 8) :
+ static::nh32($k, $m[$i], $length * 8);
+
+ return $y;
+ }
+
+ /**
+ * 32-bit safe 64-bit Multiply with 2x 32-bit ints
+ *
+ * @param int $x
+ * @param int $y
+ * @return string $x * $y
+ */
+ private static function mul32_64($x, $y)
+ {
+ // see mul64() for a more detailed explanation of how this works
+
+ $x1 = ($x >> 16) & 0xFFFF;
+ $x0 = $x & 0xFFFF;
+
+ $y1 = ($y >> 16) & 0xFFFF;
+ $y0 = $y & 0xFFFF;
+
+ // the following 3x lines will possibly yield floats
+ $z2 = $x1 * $y1;
+ $z0 = $x0 * $y0;
+ $z1 = $x1 * $y0 + $x0 * $y1;
+
+ $a = intval(fmod($z0, 65536));
+ $b = intval($z0 / 65536) + intval(fmod($z1, 65536));
+ $c = intval($z1 / 65536) + intval(fmod($z2, 65536)) + intval($b / 65536);
+ $b = intval(fmod($b, 65536));
+ $d = intval($z2 / 65536) + intval($c / 65536);
+ $c = intval(fmod($c, 65536));
+ $d = intval(fmod($d, 65536));
+
+ return pack('n4', $d, $c, $b, $a);
+ }
+
+ /**
+ * 32-bit safe 64-bit Addition with 2x 64-bit strings
+ *
+ * @param int $x
+ * @param int $y
+ * @return int $x * $y
+ */
+ private static function add32_64($x, $y)
+ {
+ list(, $x1, $x2, $x3, $x4) = unpack('n4', $x);
+ list(, $y1, $y2, $y3, $y4) = unpack('n4', $y);
+ $a = $x4 + $y4;
+ $b = $x3 + $y3 + ($a >> 16);
+ $c = $x2 + $y2 + ($b >> 16);
+ $d = $x1 + $y1 + ($c >> 16);
+ return pack('n4', $d, $c, $b, $a);
+ }
+
+ /**
+ * 32-bit safe 32-bit Addition with 2x 32-bit strings
+ *
+ * @param int $x
+ * @param int $y
+ * @return int $x * $y
+ */
+ private static function add32($x, $y)
+ {
+ // see add64() for a more detailed explanation of how this works
+
+ $x1 = $x & 0xFFFF;
+ $x2 = ($x >> 16) & 0xFFFF;
+ $y1 = $y & 0xFFFF;
+ $y2 = ($y >> 16) & 0xFFFF;
+
+ $a = $x1 + $y1;
+ $b = ($x2 + $y2 + ($a >> 16)) << 16;
+ $a &= 0xFFFF;
+
+ return $a | $b;
+ }
+
+ /**
+ * NH Algorithm / 32-bit safe
+ *
+ * @param string $k string of length 1024 bytes.
+ * @param string $m string with length divisible by 32 bytes.
+ * @return string string of length 8 bytes.
+ */
+ private static function nh32($k, $m, $length)
+ {
+ //
+ // Break M and K into 4-byte chunks
+ //
+ $k = unpack('N*', $k);
+ $m = unpack('N*', $m);
+ $t = count($m);
+
+ //
+ // Perform NH hash on the chunks, pairing words for multiplication
+ // which are 4 apart to accommodate vector-parallelism.
+ //
+ $i = 1;
+ $y = "\0\0\0\0\0\0\0\0";
+ while ($i <= $t) {
+ $temp = self::add32($m[$i], $k[$i]);
+ $temp2 = self::add32($m[$i + 4], $k[$i + 4]);
+ $y = self::add32_64($y, self::mul32_64($temp, $temp2));
+
+ $temp = self::add32($m[$i + 1], $k[$i + 1]);
+ $temp2 = self::add32($m[$i + 5], $k[$i + 5]);
+ $y = self::add32_64($y, self::mul32_64($temp, $temp2));
+
+ $temp = self::add32($m[$i + 2], $k[$i + 2]);
+ $temp2 = self::add32($m[$i + 6], $k[$i + 6]);
+ $y = self::add32_64($y, self::mul32_64($temp, $temp2));
+
+ $temp = self::add32($m[$i + 3], $k[$i + 3]);
+ $temp2 = self::add32($m[$i + 7], $k[$i + 7]);
+ $y = self::add32_64($y, self::mul32_64($temp, $temp2));
+
+ $i += 8;
+ }
+
+ return self::add32_64($y, pack('N2', 0, $length));
+ }
+
+ /**
+ * 64-bit Multiply with 2x 32-bit ints
+ *
+ * @param int $x
+ * @param int $y
+ * @return int $x * $y
+ */
+ private static function mul64($x, $y)
+ {
+ // since PHP doesn't implement unsigned integers we'll implement them with signed integers
+ // to do this we'll use karatsuba multiplication
+
+ $x1 = $x >> 16;
+ $x0 = $x & 0xFFFF;
+
+ $y1 = $y >> 16;
+ $y0 = $y & 0xFFFF;
+
+ $z2 = $x1 * $y1; // up to 32 bits long
+ $z0 = $x0 * $y0; // up to 32 bits long
+ $z1 = $x1 * $y0 + $x0 * $y1; // up to 33 bit long
+ // normally karatsuba multiplication calculates $z1 thusly:
+ //$z1 = ($x1 + $x0) * ($y0 + $y1) - $z2 - $z0;
+ // the idea being to eliminate one extra multiplication. for arbitrary precision math that makes sense
+ // but not for this purpose
+
+ // at this point karatsuba would normally return this:
+ //return ($z2 << 64) + ($z1 << 32) + $z0;
+ // the problem is that the output could be out of range for signed 64-bit ints,
+ // which would cause PHP to switch to floats, which would risk losing the lower few bits
+ // as such we'll OR 4x 16-bit blocks together like so:
+ /*
+ ........ | ........ | ........ | ........
+ upper $z2 | lower $z2 | lower $z1 | lower $z0
+ | +upper $z1 | +upper $z0 |
+ + $carry | + $carry | |
+ */
+ // technically upper $z1 is 17 bit - not 16 - but the most significant digit of that will
+ // just get added to $carry
+
+ $a = $z0 & 0xFFFF;
+ $b = ($z0 >> 16) + ($z1 & 0xFFFF);
+ $c = ($z1 >> 16) + ($z2 & 0xFFFF) + ($b >> 16);
+ $b = ($b & 0xFFFF) << 16;
+ $d = ($z2 >> 16) + ($c >> 16);
+ $c = ($c & 0xFFFF) << 32;
+ $d = ($d & 0xFFFF) << 48;
+
+ return $a | $b | $c | $d;
+ }
+
+ /**
+ * 64-bit Addition with 2x 64-bit ints
+ *
+ * @param int $x
+ * @param int $y
+ * @return int $x + $y
+ */
+ private static function add64($x, $y)
+ {
+ // doing $x + $y risks returning a result that's out of range for signed 64-bit ints
+ // in that event PHP would convert the result to a float and precision would be lost
+ // so we'll just add 2x 32-bit ints together like so:
+ /*
+ ........ | ........
+ upper $x | lower $x
+ +upper $y |+lower $y
+ + $carry |
+ */
+ $x1 = $x & 0xFFFFFFFF;
+ $x2 = ($x >> 32) & 0xFFFFFFFF;
+ $y1 = $y & 0xFFFFFFFF;
+ $y2 = ($y >> 32) & 0xFFFFFFFF;
+
+ $a = $x1 + $y1;
+ $b = ($x2 + $y2 + ($a >> 32)) << 32;
+ $a &= 0xFFFFFFFF;
+
+ return $a | $b;
+ }
+
+ /**
+ * NH Algorithm / 64-bit safe
+ *
+ * @param string $k string of length 1024 bytes.
+ * @param string $m string with length divisible by 32 bytes.
+ * @return string string of length 8 bytes.
+ */
+ private static function nh64($k, $m, $length)
+ {
+ //
+ // Break M and K into 4-byte chunks
+ //
+ $k = unpack('N*', $k);
+ $m = unpack('N*', $m);
+ $t = count($m);
+
+ //
+ // Perform NH hash on the chunks, pairing words for multiplication
+ // which are 4 apart to accommodate vector-parallelism.
+ //
+ $i = 1;
+ $y = 0;
+ while ($i <= $t) {
+ $temp = ($m[$i] + $k[$i]) & 0xFFFFFFFF;
+ $temp2 = ($m[$i + 4] + $k[$i + 4]) & 0xFFFFFFFF;
+ $y = self::add64($y, self::mul64($temp, $temp2));
+
+ $temp = ($m[$i + 1] + $k[$i + 1]) & 0xFFFFFFFF;
+ $temp2 = ($m[$i + 5] + $k[$i + 5]) & 0xFFFFFFFF;
+ $y = self::add64($y, self::mul64($temp, $temp2));
+
+ $temp = ($m[$i + 2] + $k[$i + 2]) & 0xFFFFFFFF;
+ $temp2 = ($m[$i + 6] + $k[$i + 6]) & 0xFFFFFFFF;
+ $y = self::add64($y, self::mul64($temp, $temp2));
+
+ $temp = ($m[$i + 3] + $k[$i + 3]) & 0xFFFFFFFF;
+ $temp2 = ($m[$i + 7] + $k[$i + 7]) & 0xFFFFFFFF;
+ $y = self::add64($y, self::mul64($temp, $temp2));
+
+ $i += 8;
}
- $this->ipad = str_repeat(chr(0x36), $this->b);
- $this->opad = str_repeat(chr(0x5C), $this->b);
+ return pack('J', self::add64($y, $length));
+ }
+
+ /**
+ * L2-HASH: Second-Layer Hash
+ *
+ * The second-layer rehashes the L1-HASH output using a polynomial hash
+ * called POLY. If the L1-HASH output is long, then POLY is called once
+ * on a prefix of the L1-HASH output and called using different settings
+ * on the remainder. (This two-step hashing of the L1-HASH output is
+ * needed only if the message length is greater than 16 megabytes.)
+ * Careful implementation of POLY is necessary to avoid a possible
+ * timing attack (see Section 6.6 for more information).
+ *
+ * @param string $k string of length 24 bytes.
+ * @param string $m string of length less than 2^64 bytes.
+ * @return string string of length 16 bytes.
+ */
+ private static function L2Hash($k, $m)
+ {
+ //
+ // Extract keys and restrict to special key-sets
+ //
+ $k64 = $k & "\x01\xFF\xFF\xFF\x01\xFF\xFF\xFF";
+ $k64 = new BigInteger($k64, 256);
+ $k128 = substr($k, 8) & "\x01\xFF\xFF\xFF\x01\xFF\xFF\xFF\x01\xFF\xFF\xFF\x01\xFF\xFF\xFF";
+ $k128 = new BigInteger($k128, 256);
+
+ //
+ // If M is no more than 2^17 bytes, hash under 64-bit prime,
+ // otherwise, hash first 2^17 bytes under 64-bit prime and
+ // remainder under 128-bit prime.
+ //
+ if (strlen($m) <= 0x20000) { // 2^14 64-bit words
+ $y = self::poly(64, self::$maxwordrange64, $k64, $m);
+ } else {
+ $m_1 = substr($m, 0, 0x20000); // 1 << 17
+ $m_2 = substr($m, 0x20000) . "\x80";
+ $length = strlen($m_2);
+ $pad = 16 - ($length % 16);
+ $pad %= 16;
+ $m_2 = str_pad($m_2, $length + $pad, "\0"); // zeropad
+ $y = self::poly(64, self::$maxwordrange64, $k64, $m_1);
+ $y = str_pad($y, 16, "\0", STR_PAD_LEFT);
+ $y = self::poly(128, self::$maxwordrange128, $k128, $y . $m_2);
+ }
+
+ return str_pad($y, 16, "\0", STR_PAD_LEFT);
+ }
+
+ /**
+ * POLY Algorithm
+ *
+ * @param int $wordbits the integer 64 or 128.
+ * @param BigInteger $maxwordrange positive integer less than 2^wordbits.
+ * @param BigInteger $k integer in the range 0 ... prime(wordbits) - 1.
+ * @param string $m string with length divisible by (wordbits / 8) bytes.
+ * @return integer in the range 0 ... prime(wordbits) - 1.
+ */
+ private static function poly($wordbits, $maxwordrange, $k, $m)
+ {
+ //
+ // Define constants used for fixing out-of-range words
+ //
+ $wordbytes = $wordbits >> 3;
+ if ($wordbits == 128) {
+ $factory = self::$factory128;
+ $offset = self::$offset128;
+ $marker = self::$marker128;
+ } else {
+ $factory = self::$factory64;
+ $offset = self::$offset64;
+ $marker = self::$marker64;
+ }
+
+ $k = $factory->newInteger($k);
+
+ //
+ // Break M into chunks of length wordbytes bytes
+ //
+ $m_i = str_split($m, $wordbytes);
+
+ //
+ // Each input word m is compared with maxwordrange. If not smaller
+ // then 'marker' and (m - offset), both in range, are hashed.
+ //
+ $y = $factory->newInteger(new BigInteger(1));
+ foreach ($m_i as $m) {
+ $m = $factory->newInteger(new BigInteger($m, 256));
+ if ($m->compare($maxwordrange) >= 0) {
+ $y = $k->multiply($y)->add($marker);
+ $y = $k->multiply($y)->add($m->subtract($offset));
+ } else {
+ $y = $k->multiply($y)->add($m);
+ }
+ }
- $this->_computeKey(self::MODE_INTERNAL);
+ return $y->toBytes();
}
/**
- * Compute the HMAC.
+ * L3-HASH: Third-Layer Hash
+ *
+ * The output from L2-HASH is 16 bytes long. This final hash function
+ * hashes the 16-byte string to a fixed length of 4 bytes.
+ *
+ * @param string $k1 string of length 64 bytes.
+ * @param string $k2 string of length 4 bytes.
+ * @param string $m string of length 16 bytes.
+ * @return string string of length 4 bytes.
+ */
+ private static function L3Hash($k1, $k2, $m)
+ {
+ $factory = self::$factory36;
+
+ $y = $factory->newInteger(new BigInteger());
+ for ($i = 0; $i < 8; $i++) {
+ $m_i = $factory->newInteger(new BigInteger(substr($m, 2 * $i, 2), 256));
+ $k_i = $factory->newInteger(new BigInteger(substr($k1, 8 * $i, 8), 256));
+ $y = $y->add($m_i->multiply($k_i));
+ }
+ $y = str_pad(substr($y->toBytes(), -4), 4, "\0", STR_PAD_LEFT);
+ $y = $y ^ $k2;
+
+ return $y;
+ }
+
+ /**
+ * Compute the Hash / HMAC / UMAC.
*
- * @access public
* @param string $text
* @return string
*/
- function hash($text)
- {
- if (!empty($this->key) || is_string($this->key)) {
- switch ($this->engine) {
- case self::MODE_MHASH:
- $output = mhash($this->hash, $text, $this->computedKey);
- break;
- case self::MODE_HASH:
- $output = hash_hmac($this->hash, $text, $this->computedKey, true);
- break;
- case self::MODE_INTERNAL:
- $key = str_pad($this->computedKey, $this->b, chr(0)); // step 1
- $temp = $this->ipad ^ $key; // step 2
- $temp .= $text; // step 3
- $temp = call_user_func($this->hash, $temp); // step 4
- $output = $this->opad ^ $key; // step 5
- $output.= $temp; // step 6
- $output = call_user_func($this->hash, $output); // step 7
+ public function hash($text)
+ {
+ $algo = $this->algo;
+ // https://www.rfc-editor.org/rfc/rfc4493.html
+ // https://en.wikipedia.org/wiki/One-key_MAC
+ if ($algo == 'aes_cmac') {
+ $constZero = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
+ if ($this->recomputeAESKey) {
+ if (!is_string($this->key)) {
+ throw new InsufficientSetupException('No key has been set');
+ }
+ if (strlen($this->key) != 16) {
+ throw new \LengthException('Key must be 16 bytes long');
+ }
+ // Algorithm Generate_Subkey
+ $constRb = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\x87";
+ $this->c = new AES('ecb');
+ $this->c->setKey($this->key);
+ $this->c->disablePadding();
+ $l = $this->c->encrypt($constZero);
+ $msb = ($l & "\x80") == "\x80";
+ $l = new BigInteger($l, 256);
+ $l->setPrecision(128);
+ $l = $l->bitwise_leftShift(1)->toBytes();
+ // make it constant time
+ $k1 = $msb ? $l ^ $constRb : $l | $constZero;
+
+ $msb = ($k1 & "\x80") == "\x80";
+ $k2 = new BigInteger($k1, 256);
+ $k2->setPrecision(128);
+ $k2 = $k2->bitwise_leftShift(1)->toBytes();
+ // make it constant time
+ $k2 = $msb ? $k2 ^ $constRb : $k2 | $constZero;
+
+ $this->k1 = $k1;
+ $this->k2 = $k2;
}
- } else {
- switch ($this->engine) {
- case self::MODE_MHASH:
- $output = mhash($this->hash, $text);
- break;
- case self::MODE_HASH:
- $output = hash($this->hash, $text, true);
- break;
- case self::MODE_INTERNAL:
- $output = call_user_func($this->hash, $text);
+
+ $len = strlen($text);
+ $const_Bsize = 16;
+ $M = strlen($text) ? str_split($text, $const_Bsize) : [''];
+
+ // Step 2
+ $n = ceil($len / $const_Bsize);
+ // Step 3
+ if ($n == 0) {
+ $n = 1;
+ $flag = false;
+ } else {
+ $flag = $len % $const_Bsize == 0;
+ }
+ // Step 4
+ $M_last = $flag ?
+ $M[$n - 1] ^ $k1 :
+ self::OMAC_padding($M[$n - 1], $const_Bsize) ^ $k2;
+ // Step 5
+ $x = $constZero;
+ // Step 6
+ $c = &$this->c;
+ for ($i = 0; $i < $n - 1; $i++) {
+ $y = $x ^ $M[$i];
+ $x = $c->encrypt($y);
+ }
+ $y = $M_last ^ $x;
+ return $c->encrypt($y);
+ }
+ if ($algo == 'umac') {
+ if ($this->recomputeAESKey) {
+ if (!is_string($this->nonce)) {
+ throw new InsufficientSetupException('No nonce has been set');
+ }
+ if (!is_string($this->key)) {
+ throw new InsufficientSetupException('No key has been set');
+ }
+ if (strlen($this->key) != 16) {
+ throw new \LengthException('Key must be 16 bytes long');
+ }
+
+ if (!isset(self::$maxwordrange64)) {
+ $one = new BigInteger(1);
+
+ $prime36 = new BigInteger("\x00\x00\x00\x0F\xFF\xFF\xFF\xFB", 256);
+ self::$factory36 = new PrimeField($prime36);
+
+ $prime64 = new BigInteger("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC5", 256);
+ self::$factory64 = new PrimeField($prime64);
+
+ $prime128 = new BigInteger("\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x61", 256);
+ self::$factory128 = new PrimeField($prime128);
+
+ self::$offset64 = new BigInteger("\1\0\0\0\0\0\0\0\0", 256);
+ self::$offset64 = self::$factory64->newInteger(self::$offset64->subtract($prime64));
+ self::$offset128 = new BigInteger("\1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 256);
+ self::$offset128 = self::$factory128->newInteger(self::$offset128->subtract($prime128));
+
+ self::$marker64 = self::$factory64->newInteger($prime64->subtract($one));
+ self::$marker128 = self::$factory128->newInteger($prime128->subtract($one));
+
+ $maxwordrange64 = $one->bitwise_leftShift(64)->subtract($one->bitwise_leftShift(32));
+ self::$maxwordrange64 = self::$factory64->newInteger($maxwordrange64);
+
+ $maxwordrange128 = $one->bitwise_leftShift(128)->subtract($one->bitwise_leftShift(96));
+ self::$maxwordrange128 = self::$factory128->newInteger($maxwordrange128);
+ }
+
+ $this->c = new AES('ctr');
+ $this->c->disablePadding();
+ $this->c->setKey($this->key);
+
+ $this->pad = $this->pdf();
+
+ $this->recomputeAESKey = false;
}
+
+ $hashedmessage = $this->uhash($text, $this->length);
+ return $hashedmessage ^ $this->pad;
}
- return substr($output, 0, $this->l);
+ if (is_array($algo)) {
+ if (empty($this->key) || !is_string($this->key)) {
+ return substr($algo($text, ...array_values($this->parameters)), 0, $this->length);
+ }
+
+ // SHA3 HMACs are discussed at https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf#page=30
+
+ $key = str_pad($this->computedKey, $b, chr(0));
+ $temp = $this->ipad ^ $key;
+ $temp .= $text;
+ $temp = substr($algo($temp, ...array_values($this->parameters)), 0, $this->length);
+ $output = $this->opad ^ $key;
+ $output .= $temp;
+ $output = $algo($output, ...array_values($this->parameters));
+
+ return substr($output, 0, $this->length);
+ }
+
+ $output = !empty($this->key) || is_string($this->key) ?
+ hash_hmac($algo, $text, $this->computedKey, true) :
+ hash($algo, $text, true);
+
+ return strlen($output) > $this->length
+ ? substr($output, 0, $this->length)
+ : $output;
}
/**
- * Returns the hash length (in bytes)
+ * Returns the hash length (in bits)
*
- * @access public
* @return int
*/
- function getLength()
+ public function getLength()
{
- return $this->l;
+ return $this->length << 3;
}
/**
- * Wrapper for MD5
+ * Returns the hash length (in bytes)
*
- * @access private
- * @param string $m
+ * @return int
*/
- function _md5($m)
+ public function getLengthInBytes()
{
- return pack('H*', md5($m));
+ return $this->length;
}
/**
- * Wrapper for SHA1
+ * Returns the block length (in bits)
*
- * @access private
- * @param string $m
+ * @return int
*/
- function _sha1($m)
+ public function getBlockLength()
{
- return pack('H*', sha1($m));
+ return $this->blockSize;
}
/**
- * Pure-PHP implementation of MD2
+ * Returns the block length (in bytes)
*
- * See {@link http://tools.ietf.org/html/rfc1319 RFC1319}.
+ * @return int
+ */
+ public function getBlockLengthInBytes()
+ {
+ return $this->blockSize >> 3;
+ }
+
+ /**
+ * Pads SHA3 based on the mode
*
- * @access private
- * @param string $m
+ * @param int $padLength
+ * @param int $padType
+ * @return string
*/
- function _md2($m)
- {
- static $s = array(
- 41, 46, 67, 201, 162, 216, 124, 1, 61, 54, 84, 161, 236, 240, 6,
- 19, 98, 167, 5, 243, 192, 199, 115, 140, 152, 147, 43, 217, 188,
- 76, 130, 202, 30, 155, 87, 60, 253, 212, 224, 22, 103, 66, 111, 24,
- 138, 23, 229, 18, 190, 78, 196, 214, 218, 158, 222, 73, 160, 251,
- 245, 142, 187, 47, 238, 122, 169, 104, 121, 145, 21, 178, 7, 63,
- 148, 194, 16, 137, 11, 34, 95, 33, 128, 127, 93, 154, 90, 144, 50,
- 39, 53, 62, 204, 231, 191, 247, 151, 3, 255, 25, 48, 179, 72, 165,
- 181, 209, 215, 94, 146, 42, 172, 86, 170, 198, 79, 184, 56, 210,
- 150, 164, 125, 182, 118, 252, 107, 226, 156, 116, 4, 241, 69, 157,
- 112, 89, 100, 113, 135, 32, 134, 91, 207, 101, 230, 45, 168, 2, 27,
- 96, 37, 173, 174, 176, 185, 246, 28, 70, 97, 105, 52, 64, 126, 15,
- 85, 71, 163, 35, 221, 81, 175, 58, 195, 92, 249, 206, 186, 197,
- 234, 38, 44, 83, 13, 110, 133, 40, 132, 9, 211, 223, 205, 244, 65,
- 129, 77, 82, 106, 220, 55, 200, 108, 193, 171, 250, 36, 225, 123,
- 8, 12, 189, 177, 74, 120, 136, 149, 139, 227, 99, 232, 109, 233,
- 203, 213, 254, 59, 0, 29, 57, 242, 239, 183, 14, 102, 88, 208, 228,
- 166, 119, 114, 248, 235, 117, 75, 10, 49, 68, 80, 180, 143, 237,
- 31, 26, 219, 153, 141, 51, 159, 17, 131, 20
- );
-
- // Step 1. Append Padding Bytes
- $pad = 16 - (strlen($m) & 0xF);
- $m.= str_repeat(chr($pad), $pad);
+ private static function sha3_pad($padLength, $padType)
+ {
+ switch ($padType) {
+ case self::PADDING_KECCAK:
+ $temp = chr(0x01) . str_repeat("\0", $padLength - 1);
+ $temp[$padLength - 1] = $temp[$padLength - 1] | chr(0x80);
+ return $temp;
+ case self::PADDING_SHAKE:
+ $temp = chr(0x1F) . str_repeat("\0", $padLength - 1);
+ $temp[$padLength - 1] = $temp[$padLength - 1] | chr(0x80);
+ return $temp;
+ //case self::PADDING_SHA3:
+ default:
+ // from https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf#page=36
+ return $padLength == 1 ? chr(0x86) : chr(0x06) . str_repeat("\0", $padLength - 2) . chr(0x80);
+ }
+ }
- $length = strlen($m);
+ /**
+ * Pure-PHP 32-bit implementation of SHA3
+ *
+ * Whereas BigInteger.php's 32-bit engine works on PHP 64-bit this 32-bit implementation
+ * of SHA3 will *not* work on PHP 64-bit. This is because this implementation
+ * employees bitwise NOTs and bitwise left shifts. And the round constants only work
+ * on 32-bit PHP. eg. dechex(-2147483648) returns 80000000 on 32-bit PHP and
+ * FFFFFFFF80000000 on 64-bit PHP. Sure, we could do bitwise ANDs but that would slow
+ * things down.
+ *
+ * SHA512 requires BigInteger to simulate 64-bit unsigned integers because SHA2 employees
+ * addition whereas SHA3 just employees bitwise operators. PHP64 only supports signed
+ * 64-bit integers, which complicates addition, whereas that limitation isn't an issue
+ * for SHA3.
+ *
+ * In https://ws680.nist.gov/publication/get_pdf.cfm?pub_id=919061#page=16 KECCAK[C] is
+ * defined as "the KECCAK instance with KECCAK-f[1600] as the underlying permutation and
+ * capacity c". This is relevant because, altho the KECCAK standard defines a mode
+ * (KECCAK-f[800]) designed for 32-bit machines that mode is incompatible with SHA3
+ *
+ * @param string $p
+ * @param int $c
+ * @param int $r
+ * @param int $d
+ * @param int $padType
+ */
+ private static function sha3_32($p, $c, $r, $d, $padType)
+ {
+ $block_size = $r >> 3;
+ $padLength = $block_size - (strlen($p) % $block_size);
+ $num_ints = $block_size >> 2;
+
+ $p .= static::sha3_pad($padLength, $padType);
+
+ $n = strlen($p) / $r; // number of blocks
+
+ $s = [
+ [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]],
+ [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]],
+ [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]],
+ [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]],
+ [[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]
+ ];
+
+ $p = str_split($p, $block_size);
+
+ foreach ($p as $pi) {
+ $pi = unpack('V*', $pi);
+ $x = $y = 0;
+ for ($i = 1; $i <= $num_ints; $i += 2) {
+ $s[$x][$y][0] ^= $pi[$i + 1];
+ $s[$x][$y][1] ^= $pi[$i];
+ if (++$y == 5) {
+ $y = 0;
+ $x++;
+ }
+ }
+ static::processSHA3Block32($s);
+ }
- // Step 2. Append Checksum
- $c = str_repeat(chr(0), 16);
- $l = chr(0);
- for ($i = 0; $i < $length; $i+= 16) {
- for ($j = 0; $j < 16; $j++) {
- // RFC1319 incorrectly states that C[j] should be set to S[c xor L]
- //$c[$j] = chr($s[ord($m[$i + $j] ^ $l)]);
- // per <http://www.rfc-editor.org/errata_search.php?rfc=1319>, however, C[j] should be set to S[c xor L] xor C[j]
- $c[$j] = chr($s[ord($m[$i + $j] ^ $l)] ^ ord($c[$j]));
- $l = $c[$j];
+ $z = '';
+ $i = $j = 0;
+ while (strlen($z) < $d) {
+ $z .= pack('V2', $s[$i][$j][1], $s[$i][$j++][0]);
+ if ($j == 5) {
+ $j = 0;
+ $i++;
+ if ($i == 5) {
+ $i = 0;
+ static::processSHA3Block32($s);
+ }
}
}
- $m.= $c;
- $length+= 16;
+ return $z;
+ }
- // Step 3. Initialize MD Buffer
- $x = str_repeat(chr(0), 48);
+ /**
+ * 32-bit block processing method for SHA3
+ *
+ * @param array $s
+ */
+ private static function processSHA3Block32(&$s)
+ {
+ static $rotationOffsets = [
+ [ 0, 1, 62, 28, 27],
+ [36, 44, 6, 55, 20],
+ [ 3, 10, 43, 25, 39],
+ [41, 45, 15, 21, 8],
+ [18, 2, 61, 56, 14]
+ ];
+
+ // the standards give these constants in hexadecimal notation. it's tempting to want to use
+ // that same notation, here, however, we can't, because 0x80000000, on PHP32, is a positive
+ // float - not the negative int that we need to be in PHP32. so we use -2147483648 instead
+ static $roundConstants = [
+ [0, 1],
+ [0, 32898],
+ [-2147483648, 32906],
+ [-2147483648, -2147450880],
+ [0, 32907],
+ [0, -2147483647],
+ [-2147483648, -2147450751],
+ [-2147483648, 32777],
+ [0, 138],
+ [0, 136],
+ [0, -2147450871],
+ [0, -2147483638],
+ [0, -2147450741],
+ [-2147483648, 139],
+ [-2147483648, 32905],
+ [-2147483648, 32771],
+ [-2147483648, 32770],
+ [-2147483648, 128],
+ [0, 32778],
+ [-2147483648, -2147483638],
+ [-2147483648, -2147450751],
+ [-2147483648, 32896],
+ [0, -2147483647],
+ [-2147483648, -2147450872]
+ ];
+
+ for ($round = 0; $round < 24; $round++) {
+ // theta step
+ $parity = $rotated = [];
+ for ($i = 0; $i < 5; $i++) {
+ $parity[] = [
+ $s[0][$i][0] ^ $s[1][$i][0] ^ $s[2][$i][0] ^ $s[3][$i][0] ^ $s[4][$i][0],
+ $s[0][$i][1] ^ $s[1][$i][1] ^ $s[2][$i][1] ^ $s[3][$i][1] ^ $s[4][$i][1]
+ ];
+ $rotated[] = static::rotateLeft32($parity[$i], 1);
+ }
- // Step 4. Process Message in 16-Byte Blocks
- for ($i = 0; $i < $length; $i+= 16) {
- for ($j = 0; $j < 16; $j++) {
- $x[$j + 16] = $m[$i + $j];
- $x[$j + 32] = $x[$j + 16] ^ $x[$j];
+ $temp = [
+ [$parity[4][0] ^ $rotated[1][0], $parity[4][1] ^ $rotated[1][1]],
+ [$parity[0][0] ^ $rotated[2][0], $parity[0][1] ^ $rotated[2][1]],
+ [$parity[1][0] ^ $rotated[3][0], $parity[1][1] ^ $rotated[3][1]],
+ [$parity[2][0] ^ $rotated[4][0], $parity[2][1] ^ $rotated[4][1]],
+ [$parity[3][0] ^ $rotated[0][0], $parity[3][1] ^ $rotated[0][1]]
+ ];
+ for ($i = 0; $i < 5; $i++) {
+ for ($j = 0; $j < 5; $j++) {
+ $s[$i][$j][0] ^= $temp[$j][0];
+ $s[$i][$j][1] ^= $temp[$j][1];
+ }
}
- $t = chr(0);
- for ($j = 0; $j < 18; $j++) {
- for ($k = 0; $k < 48; $k++) {
- $x[$k] = $t = $x[$k] ^ chr($s[ord($t)]);
- //$t = $x[$k] = $x[$k] ^ chr($s[ord($t)]);
+
+ $st = $s;
+
+ // rho and pi steps
+ for ($i = 0; $i < 5; $i++) {
+ for ($j = 0; $j < 5; $j++) {
+ $st[(2 * $i + 3 * $j) % 5][$j] = static::rotateLeft32($s[$j][$i], $rotationOffsets[$j][$i]);
}
- $t = chr(ord($t) + $j);
}
- }
- // Step 5. Output
- return substr($x, 0, 16);
+ // chi step
+ for ($i = 0; $i < 5; $i++) {
+ $s[$i][0] = [
+ $st[$i][0][0] ^ (~$st[$i][1][0] & $st[$i][2][0]),
+ $st[$i][0][1] ^ (~$st[$i][1][1] & $st[$i][2][1])
+ ];
+ $s[$i][1] = [
+ $st[$i][1][0] ^ (~$st[$i][2][0] & $st[$i][3][0]),
+ $st[$i][1][1] ^ (~$st[$i][2][1] & $st[$i][3][1])
+ ];
+ $s[$i][2] = [
+ $st[$i][2][0] ^ (~$st[$i][3][0] & $st[$i][4][0]),
+ $st[$i][2][1] ^ (~$st[$i][3][1] & $st[$i][4][1])
+ ];
+ $s[$i][3] = [
+ $st[$i][3][0] ^ (~$st[$i][4][0] & $st[$i][0][0]),
+ $st[$i][3][1] ^ (~$st[$i][4][1] & $st[$i][0][1])
+ ];
+ $s[$i][4] = [
+ $st[$i][4][0] ^ (~$st[$i][0][0] & $st[$i][1][0]),
+ $st[$i][4][1] ^ (~$st[$i][0][1] & $st[$i][1][1])
+ ];
+ }
+
+ // iota step
+ $s[0][0][0] ^= $roundConstants[$round][0];
+ $s[0][0][1] ^= $roundConstants[$round][1];
+ }
}
/**
- * Pure-PHP implementation of SHA256
+ * Rotate 32-bit int
*
- * See {@link http://en.wikipedia.org/wiki/SHA_hash_functions#SHA-256_.28a_SHA-2_variant.29_pseudocode SHA-256 (a SHA-2 variant) pseudocode - Wikipedia}.
- *
- * @access private
- * @param string $m
+ * @param array $x
+ * @param int $shift
*/
- function _sha256($m)
- {
- if (extension_loaded('suhosin')) {
- return pack('H*', sha256($m));
- }
-
- // Initialize variables
- $hash = array(
- 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
- );
- // Initialize table of round constants
- // (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311)
- static $k = array(
- 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
- 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
- 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
- 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
- 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
- 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
- 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
- 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
- );
+ private static function rotateLeft32($x, $shift)
+ {
+ if ($shift < 32) {
+ list($hi, $lo) = $x;
+ } else {
+ $shift -= 32;
+ list($lo, $hi) = $x;
+ }
- // Pre-processing
- $length = strlen($m);
- // to round to nearest 56 mod 64, we'll add 64 - (length + (64 - 56)) % 64
- $m.= str_repeat(chr(0), 64 - (($length + 8) & 0x3F));
- $m[$length] = chr(0x80);
- // we don't support hashing strings 512MB long
- $m.= pack('N2', 0, $length << 3);
+ $mask = -1 ^ (-1 << $shift);
+ return [
+ ($hi << $shift) | (($lo >> (32 - $shift)) & $mask),
+ ($lo << $shift) | (($hi >> (32 - $shift)) & $mask)
+ ];
+ }
- // Process the message in successive 512-bit chunks
- $chunks = str_split($m, 64);
- foreach ($chunks as $chunk) {
- $w = array();
- for ($i = 0; $i < 16; $i++) {
- extract(unpack('Ntemp', $this->_string_shift($chunk, 4)));
- $w[] = $temp;
+ /**
+ * Pure-PHP 64-bit implementation of SHA3
+ *
+ * @param string $p
+ * @param int $c
+ * @param int $r
+ * @param int $d
+ * @param int $padType
+ */
+ private static function sha3_64($p, $c, $r, $d, $padType)
+ {
+ $block_size = $r >> 3;
+ $padLength = $block_size - (strlen($p) % $block_size);
+ $num_ints = $block_size >> 2;
+
+ $p .= static::sha3_pad($padLength, $padType);
+
+ $n = strlen($p) / $r; // number of blocks
+
+ $s = [
+ [0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0]
+ ];
+
+ $p = str_split($p, $block_size);
+
+ foreach ($p as $pi) {
+ $pi = unpack('P*', $pi);
+ $x = $y = 0;
+ foreach ($pi as $subpi) {
+ $s[$x][$y++] ^= $subpi;
+ if ($y == 5) {
+ $y = 0;
+ $x++;
+ }
}
+ static::processSHA3Block64($s);
+ }
- // Extend the sixteen 32-bit words into sixty-four 32-bit words
- for ($i = 16; $i < 64; $i++) {
- // @codingStandardsIgnoreStart
- $s0 = $this->_rightRotate($w[$i - 15], 7) ^
- $this->_rightRotate($w[$i - 15], 18) ^
- $this->_rightShift( $w[$i - 15], 3);
- $s1 = $this->_rightRotate($w[$i - 2], 17) ^
- $this->_rightRotate($w[$i - 2], 19) ^
- $this->_rightShift( $w[$i - 2], 10);
- // @codingStandardsIgnoreEnd
- $w[$i] = $this->_add($w[$i - 16], $s0, $w[$i - 7], $s1);
+ $z = '';
+ $i = $j = 0;
+ while (strlen($z) < $d) {
+ $z .= pack('P', $s[$i][$j++]);
+ if ($j == 5) {
+ $j = 0;
+ $i++;
+ if ($i == 5) {
+ $i = 0;
+ static::processSHA3Block64($s);
+ }
}
+ }
- // Initialize hash value for this chunk
- list($a, $b, $c, $d, $e, $f, $g, $h) = $hash;
+ return $z;
+ }
- // Main loop
- for ($i = 0; $i < 64; $i++) {
- $s0 = $this->_rightRotate($a, 2) ^
- $this->_rightRotate($a, 13) ^
- $this->_rightRotate($a, 22);
- $maj = ($a & $b) ^
- ($a & $c) ^
- ($b & $c);
- $t2 = $this->_add($s0, $maj);
-
- $s1 = $this->_rightRotate($e, 6) ^
- $this->_rightRotate($e, 11) ^
- $this->_rightRotate($e, 25);
- $ch = ($e & $f) ^
- ($this->_not($e) & $g);
- $t1 = $this->_add($h, $s1, $ch, $k[$i], $w[$i]);
+ /**
+ * 64-bit block processing method for SHA3
+ *
+ * @param array $s
+ */
+ private static function processSHA3Block64(&$s)
+ {
+ static $rotationOffsets = [
+ [ 0, 1, 62, 28, 27],
+ [36, 44, 6, 55, 20],
+ [ 3, 10, 43, 25, 39],
+ [41, 45, 15, 21, 8],
+ [18, 2, 61, 56, 14]
+ ];
+
+ static $roundConstants = [
+ 1,
+ 32898,
+ -9223372036854742902,
+ -9223372034707259392,
+ 32907,
+ 2147483649,
+ -9223372034707259263,
+ -9223372036854743031,
+ 138,
+ 136,
+ 2147516425,
+ 2147483658,
+ 2147516555,
+ -9223372036854775669,
+ -9223372036854742903,
+ -9223372036854743037,
+ -9223372036854743038,
+ -9223372036854775680,
+ 32778,
+ -9223372034707292150,
+ -9223372034707259263,
+ -9223372036854742912,
+ 2147483649,
+ -9223372034707259384
+ ];
+
+ for ($round = 0; $round < 24; $round++) {
+ // theta step
+ $parity = [];
+ for ($i = 0; $i < 5; $i++) {
+ $parity[] = $s[0][$i] ^ $s[1][$i] ^ $s[2][$i] ^ $s[3][$i] ^ $s[4][$i];
+ }
+ $temp = [
+ $parity[4] ^ static::rotateLeft64($parity[1], 1),
+ $parity[0] ^ static::rotateLeft64($parity[2], 1),
+ $parity[1] ^ static::rotateLeft64($parity[3], 1),
+ $parity[2] ^ static::rotateLeft64($parity[4], 1),
+ $parity[3] ^ static::rotateLeft64($parity[0], 1)
+ ];
+ for ($i = 0; $i < 5; $i++) {
+ for ($j = 0; $j < 5; $j++) {
+ $s[$i][$j] ^= $temp[$j];
+ }
+ }
- $h = $g;
- $g = $f;
- $f = $e;
- $e = $this->_add($d, $t1);
- $d = $c;
- $c = $b;
- $b = $a;
- $a = $this->_add($t1, $t2);
+ $st = $s;
+
+ // rho and pi steps
+ for ($i = 0; $i < 5; $i++) {
+ for ($j = 0; $j < 5; $j++) {
+ $st[(2 * $i + 3 * $j) % 5][$j] = static::rotateLeft64($s[$j][$i], $rotationOffsets[$j][$i]);
+ }
}
- // Add this chunk's hash to result so far
- $hash = array(
- $this->_add($hash[0], $a),
- $this->_add($hash[1], $b),
- $this->_add($hash[2], $c),
- $this->_add($hash[3], $d),
- $this->_add($hash[4], $e),
- $this->_add($hash[5], $f),
- $this->_add($hash[6], $g),
- $this->_add($hash[7], $h)
- );
+ // chi step
+ for ($i = 0; $i < 5; $i++) {
+ $s[$i] = [
+ $st[$i][0] ^ (~$st[$i][1] & $st[$i][2]),
+ $st[$i][1] ^ (~$st[$i][2] & $st[$i][3]),
+ $st[$i][2] ^ (~$st[$i][3] & $st[$i][4]),
+ $st[$i][3] ^ (~$st[$i][4] & $st[$i][0]),
+ $st[$i][4] ^ (~$st[$i][0] & $st[$i][1])
+ ];
+ }
+
+ // iota step
+ $s[0][0] ^= $roundConstants[$round];
}
+ }
- // Produce the final hash value (big-endian)
- return pack('N8', $hash[0], $hash[1], $hash[2], $hash[3], $hash[4], $hash[5], $hash[6], $hash[7]);
+ /**
+ * Left rotate 64-bit int
+ *
+ * @param int $x
+ * @param int $shift
+ */
+ private static function rotateLeft64($x, $shift)
+ {
+ $mask = -1 ^ (-1 << $shift);
+ return ($x << $shift) | (($x >> (64 - $shift)) & $mask);
+ }
+
+ /**
+ * Right rotate 64-bit int
+ *
+ * @param int $x
+ * @param int $shift
+ */
+ private static function rotateRight64($x, $shift)
+ {
+ $mask = -1 ^ (-1 << (64 - $shift));
+ return (($x >> $shift) & $mask) | ($x << (64 - $shift));
}
/**
- * Pure-PHP implementation of SHA384 and SHA512
+ * Pure-PHP implementation of SHA512
*
- * @access private
* @param string $m
+ * @param array $hash
+ * @return string
*/
- function _sha512($m)
+ private static function sha512($m, $hash)
{
- static $init384, $init512, $k;
+ static $k;
if (!isset($k)) {
- // Initialize variables
- $init384 = array( // initial values for SHA384
- 'cbbb9d5dc1059ed8', '629a292a367cd507', '9159015a3070dd17', '152fecd8f70e5939',
- '67332667ffc00b31', '8eb44a8768581511', 'db0c2e0d64f98fa7', '47b5481dbefa4fa4'
- );
- $init512 = array( // initial values for SHA512
- '6a09e667f3bcc908', 'bb67ae8584caa73b', '3c6ef372fe94f82b', 'a54ff53a5f1d36f1',
- '510e527fade682d1', '9b05688c2b3e6c1f', '1f83d9abfb41bd6b', '5be0cd19137e2179'
- );
-
- for ($i = 0; $i < 8; $i++) {
- $init384[$i] = new BigInteger($init384[$i], 16);
- $init384[$i]->setPrecision(64);
- $init512[$i] = new BigInteger($init512[$i], 16);
- $init512[$i]->setPrecision(64);
- }
-
// Initialize table of round constants
// (first 64 bits of the fractional parts of the cube roots of the first 80 primes 2..409)
- $k = array(
+ $k = [
'428a2f98d728ae22', '7137449123ef65cd', 'b5c0fbcfec4d3b2f', 'e9b5dba58189dbbc',
'3956c25bf348b538', '59f111f1b605d019', '923f82a4af194f9b', 'ab1c5ed5da6d8118',
'd807aa98a3030242', '12835b0145706fbe', '243185be4ee4b28c', '550c7dc3d5ffb4e2',
@@ -671,112 +1613,110 @@ class Hash
'06f067aa72176fba', '0a637dc5a2c898a6', '113f9804bef90dae', '1b710b35131c471b',
'28db77f523047d84', '32caab7b40c72493', '3c9ebe0a15c9bebc', '431d67c49c100d4c',
'4cc5d4becb3e42b6', '597f299cfc657e2a', '5fcb6fab3ad6faec', '6c44198c4a475817'
- );
+ ];
for ($i = 0; $i < 80; $i++) {
$k[$i] = new BigInteger($k[$i], 16);
}
}
- $hash = $this->l == 48 ? $init384 : $init512;
-
// Pre-processing
$length = strlen($m);
// to round to nearest 112 mod 128, we'll add 128 - (length + (128 - 112)) % 128
- $m.= str_repeat(chr(0), 128 - (($length + 16) & 0x7F));
+ $m .= str_repeat(chr(0), 128 - (($length + 16) & 0x7F));
$m[$length] = chr(0x80);
// we don't support hashing strings 512MB long
- $m.= pack('N4', 0, 0, 0, $length << 3);
+ $m .= pack('N4', 0, 0, 0, $length << 3);
// Process the message in successive 1024-bit chunks
$chunks = str_split($m, 128);
foreach ($chunks as $chunk) {
- $w = array();
+ $w = [];
for ($i = 0; $i < 16; $i++) {
- $temp = new BigInteger($this->_string_shift($chunk, 8), 256);
+ $temp = new BigInteger(Strings::shift($chunk, 8), 256);
$temp->setPrecision(64);
$w[] = $temp;
}
// Extend the sixteen 32-bit words into eighty 32-bit words
for ($i = 16; $i < 80; $i++) {
- $temp = array(
+ $temp = [
$w[$i - 15]->bitwise_rightRotate(1),
$w[$i - 15]->bitwise_rightRotate(8),
$w[$i - 15]->bitwise_rightShift(7)
- );
+ ];
$s0 = $temp[0]->bitwise_xor($temp[1]);
$s0 = $s0->bitwise_xor($temp[2]);
- $temp = array(
+ $temp = [
$w[$i - 2]->bitwise_rightRotate(19),
$w[$i - 2]->bitwise_rightRotate(61),
$w[$i - 2]->bitwise_rightShift(6)
- );
+ ];
$s1 = $temp[0]->bitwise_xor($temp[1]);
$s1 = $s1->bitwise_xor($temp[2]);
- $w[$i] = $w[$i - 16]->copy();
+ $w[$i] = clone $w[$i - 16];
$w[$i] = $w[$i]->add($s0);
$w[$i] = $w[$i]->add($w[$i - 7]);
$w[$i] = $w[$i]->add($s1);
}
// Initialize hash value for this chunk
- $a = $hash[0]->copy();
- $b = $hash[1]->copy();
- $c = $hash[2]->copy();
- $d = $hash[3]->copy();
- $e = $hash[4]->copy();
- $f = $hash[5]->copy();
- $g = $hash[6]->copy();
- $h = $hash[7]->copy();
+ $a = clone $hash[0];
+ $b = clone $hash[1];
+ $c = clone $hash[2];
+ $d = clone $hash[3];
+ $e = clone $hash[4];
+ $f = clone $hash[5];
+ $g = clone $hash[6];
+ $h = clone $hash[7];
// Main loop
for ($i = 0; $i < 80; $i++) {
- $temp = array(
+ $temp = [
$a->bitwise_rightRotate(28),
$a->bitwise_rightRotate(34),
$a->bitwise_rightRotate(39)
- );
+ ];
$s0 = $temp[0]->bitwise_xor($temp[1]);
$s0 = $s0->bitwise_xor($temp[2]);
- $temp = array(
+ $temp = [
$a->bitwise_and($b),
$a->bitwise_and($c),
$b->bitwise_and($c)
- );
+ ];
$maj = $temp[0]->bitwise_xor($temp[1]);
$maj = $maj->bitwise_xor($temp[2]);
$t2 = $s0->add($maj);
- $temp = array(
+ $temp = [
$e->bitwise_rightRotate(14),
$e->bitwise_rightRotate(18),
$e->bitwise_rightRotate(41)
- );
+ ];
$s1 = $temp[0]->bitwise_xor($temp[1]);
$s1 = $s1->bitwise_xor($temp[2]);
- $temp = array(
+ $temp = [
$e->bitwise_and($f),
$g->bitwise_and($e->bitwise_not())
- );
+ ];
$ch = $temp[0]->bitwise_xor($temp[1]);
$t1 = $h->add($s1);
$t1 = $t1->add($ch);
$t1 = $t1->add($k[$i]);
$t1 = $t1->add($w[$i]);
- $h = $g->copy();
- $g = $f->copy();
- $f = $e->copy();
+ $h = clone $g;
+ $g = clone $f;
+ $f = clone $e;
$e = $d->add($t1);
- $d = $c->copy();
- $c = $b->copy();
- $b = $a->copy();
+ $d = clone $c;
+ $c = clone $b;
+ $b = clone $a;
$a = $t1->add($t2);
}
// Add this chunk's hash to result so far
- $hash = array(
+ $hash = [
$hash[0]->add($a),
$hash[1]->add($b),
$hash[2]->add($c),
@@ -785,109 +1725,167 @@ class Hash
$hash[5]->add($f),
$hash[6]->add($g),
$hash[7]->add($h)
- );
+ ];
}
// Produce the final hash value (big-endian)
- // (\phpseclib\Crypt\Hash::hash() trims the output for hashes but not for HMACs. as such, we trim the output here)
+ // (\phpseclib3\Crypt\Hash::hash() trims the output for hashes but not for HMACs. as such, we trim the output here)
$temp = $hash[0]->toBytes() . $hash[1]->toBytes() . $hash[2]->toBytes() . $hash[3]->toBytes() .
- $hash[4]->toBytes() . $hash[5]->toBytes();
- if ($this->l != 48) {
- $temp.= $hash[6]->toBytes() . $hash[7]->toBytes();
- }
+ $hash[4]->toBytes() . $hash[5]->toBytes() . $hash[6]->toBytes() . $hash[7]->toBytes();
return $temp;
}
/**
- * Right Rotate
- *
- * @access private
- * @param int $int
- * @param int $amt
- * @see self::_sha256()
- * @return int
- */
- function _rightRotate($int, $amt)
- {
- $invamt = 32 - $amt;
- $mask = (1 << $invamt) - 1;
- return (($int << $invamt) & 0xFFFFFFFF) | (($int >> $amt) & $mask);
- }
-
- /**
- * Right Shift
+ * Pure-PHP implementation of SHA512
*
- * @access private
- * @param int $int
- * @param int $amt
- * @see self::_sha256()
- * @return int
+ * @param string $m
+ * @param array $hash
+ * @return string
*/
- function _rightShift($int, $amt)
+ private static function sha512_64($m, $hash)
{
- $mask = (1 << (32 - $amt)) - 1;
- return ($int >> $amt) & $mask;
- }
+ static $k;
- /**
- * Not
- *
- * @access private
- * @param int $int
- * @see self::_sha256()
- * @return int
- */
- function _not($int)
- {
- return ~$int & 0xFFFFFFFF;
- }
+ if (!isset($k)) {
+ // Initialize table of round constants
+ // (first 64 bits of the fractional parts of the cube roots of the first 80 primes 2..409)
+ $k = [
+ '428a2f98d728ae22', '7137449123ef65cd', 'b5c0fbcfec4d3b2f', 'e9b5dba58189dbbc',
+ '3956c25bf348b538', '59f111f1b605d019', '923f82a4af194f9b', 'ab1c5ed5da6d8118',
+ 'd807aa98a3030242', '12835b0145706fbe', '243185be4ee4b28c', '550c7dc3d5ffb4e2',
+ '72be5d74f27b896f', '80deb1fe3b1696b1', '9bdc06a725c71235', 'c19bf174cf692694',
+ 'e49b69c19ef14ad2', 'efbe4786384f25e3', '0fc19dc68b8cd5b5', '240ca1cc77ac9c65',
+ '2de92c6f592b0275', '4a7484aa6ea6e483', '5cb0a9dcbd41fbd4', '76f988da831153b5',
+ '983e5152ee66dfab', 'a831c66d2db43210', 'b00327c898fb213f', 'bf597fc7beef0ee4',
+ 'c6e00bf33da88fc2', 'd5a79147930aa725', '06ca6351e003826f', '142929670a0e6e70',
+ '27b70a8546d22ffc', '2e1b21385c26c926', '4d2c6dfc5ac42aed', '53380d139d95b3df',
+ '650a73548baf63de', '766a0abb3c77b2a8', '81c2c92e47edaee6', '92722c851482353b',
+ 'a2bfe8a14cf10364', 'a81a664bbc423001', 'c24b8b70d0f89791', 'c76c51a30654be30',
+ 'd192e819d6ef5218', 'd69906245565a910', 'f40e35855771202a', '106aa07032bbd1b8',
+ '19a4c116b8d2d0c8', '1e376c085141ab53', '2748774cdf8eeb99', '34b0bcb5e19b48a8',
+ '391c0cb3c5c95a63', '4ed8aa4ae3418acb', '5b9cca4f7763e373', '682e6ff3d6b2b8a3',
+ '748f82ee5defb2fc', '78a5636f43172f60', '84c87814a1f0ab72', '8cc702081a6439ec',
+ '90befffa23631e28', 'a4506cebde82bde9', 'bef9a3f7b2c67915', 'c67178f2e372532b',
+ 'ca273eceea26619c', 'd186b8c721c0c207', 'eada7dd6cde0eb1e', 'f57d4f7fee6ed178',
+ '06f067aa72176fba', '0a637dc5a2c898a6', '113f9804bef90dae', '1b710b35131c471b',
+ '28db77f523047d84', '32caab7b40c72493', '3c9ebe0a15c9bebc', '431d67c49c100d4c',
+ '4cc5d4becb3e42b6', '597f299cfc657e2a', '5fcb6fab3ad6faec', '6c44198c4a475817'
+ ];
- /**
- * Add
- *
- * _sha256() adds multiple unsigned 32-bit integers. Since PHP doesn't support unsigned integers and since the
- * possibility of overflow exists, care has to be taken. BigInteger could be used but this should be faster.
- *
- * @return int
- * @see self::_sha256()
- * @access private
- */
- function _add()
- {
- static $mod;
- if (!isset($mod)) {
- $mod = pow(2, 32);
+ for ($i = 0; $i < 80; $i++) {
+ list(, $k[$i]) = unpack('J', pack('H*', $k[$i]));
+ }
}
- $result = 0;
- $arguments = func_get_args();
- foreach ($arguments as $argument) {
- $result+= $argument < 0 ? ($argument & 0x7FFFFFFF) + 0x80000000 : $argument;
- }
+ // Pre-processing
+ $length = strlen($m);
+ // to round to nearest 112 mod 128, we'll add 128 - (length + (128 - 112)) % 128
+ $m .= str_repeat(chr(0), 128 - (($length + 16) & 0x7F));
+ $m[$length] = chr(0x80);
+ // we don't support hashing strings 512MB long
+ $m .= pack('N4', 0, 0, 0, $length << 3);
- if (function_exists('php_uname') && is_string(php_uname('m')) && (php_uname('m') & "\xDF\xDF\xDF") != 'ARM') {
- return fmod($result, $mod);
+ // Process the message in successive 1024-bit chunks
+ $chunks = str_split($m, 128);
+ foreach ($chunks as $chunk) {
+ $w = [];
+ for ($i = 0; $i < 16; $i++) {
+ list(, $w[]) = unpack('J', Strings::shift($chunk, 8));
+ }
+
+ // Extend the sixteen 32-bit words into eighty 32-bit words
+ for ($i = 16; $i < 80; $i++) {
+ $temp = [
+ self::rotateRight64($w[$i - 15], 1),
+ self::rotateRight64($w[$i - 15], 8),
+ ($w[$i - 15] >> 7) & 0x01FFFFFFFFFFFFFF,
+ ];
+ $s0 = $temp[0] ^ $temp[1] ^ $temp[2];
+ $temp = [
+ self::rotateRight64($w[$i - 2], 19),
+ self::rotateRight64($w[$i - 2], 61),
+ ($w[$i - 2] >> 6) & 0x03FFFFFFFFFFFFFF,
+ ];
+ $s1 = $temp[0] ^ $temp[1] ^ $temp[2];
+
+ $w[$i] = $w[$i - 16];
+ $w[$i] = self::add64($w[$i], $s0);
+ $w[$i] = self::add64($w[$i], $w[$i - 7]);
+ $w[$i] = self::add64($w[$i], $s1);
+ }
+
+ // Initialize hash value for this chunk
+ list($a, $b, $c, $d, $e, $f, $g, $h) = $hash;
+
+ // Main loop
+ for ($i = 0; $i < 80; $i++) {
+ $temp = [
+ self::rotateRight64($a, 28),
+ self::rotateRight64($a, 34),
+ self::rotateRight64($a, 39),
+ ];
+ $s0 = $temp[0] ^ $temp[1] ^ $temp[2];
+ $temp = [$a & $b, $a & $c, $b & $c];
+ $maj = $temp[0] ^ $temp[1] ^ $temp[2];
+ $t2 = self::add64($s0, $maj);
+
+ $temp = [
+ self::rotateRight64($e, 14),
+ self::rotateRight64($e, 18),
+ self::rotateRight64($e, 41),
+ ];
+ $s1 = $temp[0] ^ $temp[1] ^ $temp[2];
+ $ch = ($e & $f) ^ ($g & ~$e);
+ $t1 = self::add64($h, $s1);
+ $t1 = self::add64($t1, $ch);
+ $t1 = self::add64($t1, $k[$i]);
+ $t1 = self::add64($t1, $w[$i]);
+
+ $h = $g;
+ $g = $f;
+ $f = $e;
+ $e = self::add64($d, $t1);
+ $d = $c;
+ $c = $b;
+ $b = $a;
+ $a = self::add64($t1, $t2);
+ }
+
+ // Add this chunk's hash to result so far
+ $hash = [
+ self::add64($hash[0], $a),
+ self::add64($hash[1], $b),
+ self::add64($hash[2], $c),
+ self::add64($hash[3], $d),
+ self::add64($hash[4], $e),
+ self::add64($hash[5], $f),
+ self::add64($hash[6], $g),
+ self::add64($hash[7], $h),
+ ];
}
- return (fmod($result, 0x80000000) & 0x7FFFFFFF) |
- ((fmod(floor($result / 0x80000000), 2) & 1) << 31);
+ // Produce the final hash value (big-endian)
+ // (\phpseclib3\Crypt\Hash::hash() trims the output for hashes but not for HMACs. as such, we trim the output here)
+ return pack('J*', ...$hash);
}
/**
- * String Shift
+ * OMAC Padding
*
- * Inspired by array_shift
- *
- * @param string $string
- * @param int $index
- * @return string
- * @access private
+ * @link https://www.rfc-editor.org/rfc/rfc4493.html#section-2.4
+ */
+ private static function OMAC_padding($m, $length)
+ {
+ $count = $length - strlen($m) - 1;
+ return "$m\x80" . str_repeat("\0", $count);
+ }
+
+ /**
+ * __toString() magic method
*/
- function _string_shift(&$string, $index = 1)
+ public function __toString()
{
- $substr = substr($string, 0, $index);
- $string = substr($string, $index);
- return $substr;
+ return $this->getHash();
}
}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/PublicKeyLoader.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/PublicKeyLoader.php
new file mode 100644
index 000000000..36264080c
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/PublicKeyLoader.php
@@ -0,0 +1,112 @@
+<?php
+
+/**
+ * PublicKeyLoader
+ *
+ * Returns a PublicKey or PrivateKey object.
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2009 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt;
+
+use phpseclib3\Crypt\Common\AsymmetricKey;
+use phpseclib3\Crypt\Common\PrivateKey;
+use phpseclib3\Crypt\Common\PublicKey;
+use phpseclib3\Exception\NoKeyLoadedException;
+use phpseclib3\File\X509;
+
+/**
+ * PublicKeyLoader
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PublicKeyLoader
+{
+ /**
+ * Loads a public or private key
+ *
+ * @return AsymmetricKey
+ * @param string|array $key
+ * @param string $password optional
+ * @throws NoKeyLoadedException if key is not valid
+ */
+ public static function load($key, $password = false)
+ {
+ try {
+ return EC::load($key, $password);
+ } catch (NoKeyLoadedException $e) {
+ }
+
+ try {
+ return RSA::load($key, $password);
+ } catch (NoKeyLoadedException $e) {
+ }
+
+ try {
+ return DSA::load($key, $password);
+ } catch (NoKeyLoadedException $e) {
+ }
+
+ try {
+ $x509 = new X509();
+ $x509->loadX509($key);
+ $key = $x509->getPublicKey();
+ if ($key) {
+ return $key;
+ }
+ } catch (\Exception $e) {
+ }
+
+ throw new NoKeyLoadedException('Unable to read key');
+ }
+
+ /**
+ * Loads a private key
+ *
+ * @return PrivateKey
+ * @param string|array $key
+ * @param string $password optional
+ */
+ public static function loadPrivateKey($key, $password = false)
+ {
+ $key = self::load($key, $password);
+ if (!$key instanceof PrivateKey) {
+ throw new NoKeyLoadedException('The key that was loaded was not a private key');
+ }
+ return $key;
+ }
+
+ /**
+ * Loads a public key
+ *
+ * @return PublicKey
+ * @param string|array $key
+ */
+ public static function loadPublicKey($key)
+ {
+ $key = self::load($key);
+ if (!$key instanceof PublicKey) {
+ throw new NoKeyLoadedException('The key that was loaded was not a public key');
+ }
+ return $key;
+ }
+
+ /**
+ * Loads parameters
+ *
+ * @return AsymmetricKey
+ * @param string|array $key
+ */
+ public static function loadParameters($key)
+ {
+ $key = self::load($key);
+ if (!$key instanceof PrivateKey && !$key instanceof PublicKey) {
+ throw new NoKeyLoadedException('The key that was loaded was not a parameter');
+ }
+ return $key;
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RC2.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RC2.php
index e0511b32f..175c52e7b 100644
--- a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RC2.php
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RC2.php
@@ -16,7 +16,7 @@
* <?php
* include 'vendor/autoload.php';
*
- * $rc2 = new \phpseclib\Crypt\RC2();
+ * $rc2 = new \phpseclib3\Crypt\RC2('ctr');
*
* $rc2->setKey('abcdefgh');
*
@@ -26,121 +26,105 @@
* ?>
* </code>
*
- * @category Crypt
- * @package RC2
* @author Patrick Monnerat <pm@datasphere.ch>
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
-namespace phpseclib\Crypt;
+namespace phpseclib3\Crypt;
+
+use phpseclib3\Crypt\Common\BlockCipher;
+use phpseclib3\Exception\BadModeException;
/**
* Pure-PHP implementation of RC2.
*
- * @package RC2
- * @access public
*/
-class RC2 extends Base
+class RC2 extends BlockCipher
{
/**
* Block Length of the cipher
*
- * @see \phpseclib\Crypt\Base::block_size
+ * @see Common\SymmetricKey::block_size
* @var int
- * @access private
*/
- var $block_size = 8;
+ protected $block_size = 8;
/**
* The Key
*
- * @see \phpseclib\Crypt\Base::key
+ * @see Common\SymmetricKey::key
* @see self::setKey()
* @var string
- * @access private
*/
- var $key;
+ protected $key;
/**
* The Original (unpadded) Key
*
- * @see \phpseclib\Crypt\Base::key
+ * @see Common\SymmetricKey::key
* @see self::setKey()
* @see self::encrypt()
* @see self::decrypt()
* @var string
- * @access private
- */
- var $orig_key = '';
-
- /**
- * Don't truncate / null pad key
- *
- * @see \phpseclib\Crypt\Base::_clearBuffers()
- * @var bool
- * @access private
*/
- var $skip_key_adjustment = true;
+ private $orig_key;
/**
* Key Length (in bytes)
*
- * @see \phpseclib\Crypt\RC2::setKeyLength()
+ * @see \phpseclib3\Crypt\RC2::setKeyLength()
* @var int
- * @access private
*/
- var $key_length = 16; // = 128 bits
+ protected $key_length = 16; // = 128 bits
/**
* The mcrypt specific name of the cipher
*
- * @see \phpseclib\Crypt\Base::cipher_name_mcrypt
+ * @see Common\SymmetricKey::cipher_name_mcrypt
* @var string
- * @access private
*/
- var $cipher_name_mcrypt = 'rc2';
+ protected $cipher_name_mcrypt = 'rc2';
/**
* Optimizing value while CFB-encrypting
*
- * @see \phpseclib\Crypt\Base::cfb_init_len
+ * @see Common\SymmetricKey::cfb_init_len
* @var int
- * @access private
*/
- var $cfb_init_len = 500;
+ protected $cfb_init_len = 500;
/**
* The key length in bits.
*
+ * {@internal Should be in range [1..1024].}
+ *
+ * {@internal Changing this value after setting the key has no effect.}
+ *
* @see self::setKeyLength()
* @see self::setKey()
* @var int
- * @access private
- * @internal Should be in range [1..1024].
- * @internal Changing this value after setting the key has no effect.
*/
- var $default_key_length = 1024;
+ private $default_key_length = 1024;
/**
* The key length in bits.
*
+ * {@internal Should be in range [1..1024].}
+ *
* @see self::isValidEnine()
* @see self::setKey()
* @var int
- * @access private
- * @internal Should be in range [1..1024].
*/
- var $current_key_length;
+ private $current_key_length;
/**
* The Key Schedule
*
- * @see self::_setupKey()
+ * @see self::setupKey()
* @var array
- * @access private
*/
- var $keys;
+ private $keys;
/**
* Key expansion randomization table.
@@ -148,9 +132,8 @@ class RC2 extends Base
*
* @see self::setKey()
* @var array
- * @access private
*/
- var $pitable = array(
+ private static $pitable = [
0xD9, 0x78, 0xF9, 0xC4, 0x19, 0xDD, 0xB5, 0xED,
0x28, 0xE9, 0xFD, 0x79, 0x4A, 0xA0, 0xD8, 0x9D,
0xC6, 0x7E, 0x37, 0x83, 0x2B, 0x76, 0x53, 0x8E,
@@ -215,16 +198,15 @@ class RC2 extends Base
0xBB, 0x48, 0x0C, 0x5F, 0xB9, 0xB1, 0xCD, 0x2E,
0xC5, 0xF3, 0xDB, 0x47, 0xE5, 0xA5, 0x9C, 0x77,
0x0A, 0xA6, 0x20, 0x68, 0xFE, 0x7F, 0xC1, 0xAD
- );
+ ];
/**
* Inverse key expansion randomization table.
*
* @see self::setKey()
* @var array
- * @access private
*/
- var $invpitable = array(
+ private static $invpitable = [
0xD1, 0xDA, 0xB9, 0x6F, 0x9C, 0xC8, 0x78, 0x66,
0x80, 0x2C, 0xF8, 0x37, 0xEA, 0xE0, 0x62, 0xA4,
0xCB, 0x71, 0x50, 0x27, 0x4B, 0x95, 0xD9, 0x20,
@@ -257,36 +239,50 @@ class RC2 extends Base
0x81, 0x09, 0x82, 0x33, 0x9F, 0x07, 0x86, 0x75,
0x38, 0x4E, 0x69, 0xF1, 0xAD, 0x23, 0x73, 0x87,
0x70, 0x02, 0xC2, 0x1E, 0xB8, 0x0A, 0xFC, 0xE6
- );
+ ];
+
+ /**
+ * Default Constructor.
+ *
+ * @param string $mode
+ * @throws \InvalidArgumentException if an invalid / unsupported mode is provided
+ */
+ public function __construct($mode)
+ {
+ parent::__construct($mode);
+
+ if ($this->mode == self::MODE_STREAM) {
+ throw new BadModeException('Block ciphers cannot be ran in stream mode');
+ }
+ }
/**
* Test for engine validity
*
- * This is mainly just a wrapper to set things up for \phpseclib\Crypt\Base::isValidEngine()
+ * This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine()
*
- * @see \phpseclib\Crypt\Base::__construct()
+ * @see Common\SymmetricKey::__construct()
* @param int $engine
- * @access public
* @return bool
*/
- function isValidEngine($engine)
+ protected function isValidEngineHelper($engine)
{
switch ($engine) {
case self::ENGINE_OPENSSL:
+ if ($this->current_key_length != 128 || strlen($this->orig_key) < 16) {
+ return false;
+ }
// quoting https://www.openssl.org/news/openssl-3.0-notes.html, OpenSSL 3.0.1
// "Moved all variations of the EVP ciphers CAST5, BF, IDEA, SEED, RC2, RC4, RC5, and DES to the legacy provider"
// in theory openssl_get_cipher_methods() should catch this but, on GitHub Actions, at least, it does not
if (defined('OPENSSL_VERSION_TEXT') && version_compare(preg_replace('#OpenSSL (\d+\.\d+\.\d+) .*#', '$1', OPENSSL_VERSION_TEXT), '3.0.1', '>=')) {
return false;
}
- if ($this->current_key_length != 128 || strlen($this->orig_key) < 16) {
- return false;
- }
$this->cipher_name_openssl_ecb = 'rc2-ecb';
- $this->cipher_name_openssl = 'rc2-' . $this->_openssl_translate_mode();
+ $this->cipher_name_openssl = 'rc2-' . $this->openssl_translate_mode();
}
- return parent::isValidEngine($engine);
+ return parent::isValidEngineHelper($engine);
}
/**
@@ -294,32 +290,27 @@ class RC2 extends Base
*
* Valid key lengths are 8 to 1024.
* Calling this function after setting the key has no effect until the next
- * \phpseclib\Crypt\RC2::setKey() call.
+ * \phpseclib3\Crypt\RC2::setKey() call.
*
- * @access public
* @param int $length in bits
+ * @throws \LengthException if the key length isn't supported
*/
- function setKeyLength($length)
+ public function setKeyLength($length)
{
- if ($length < 8) {
- $this->default_key_length = 1;
- } elseif ($length > 1024) {
- $this->default_key_length = 128;
- } else {
- $this->default_key_length = $length;
+ if ($length < 8 || $length > 1024) {
+ throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys between 1 and 1024 bits, inclusive, are supported');
}
- $this->current_key_length = $this->default_key_length;
- parent::setKeyLength($length);
+ $this->default_key_length = $this->current_key_length = $length;
+ $this->explicit_key_length = $length >> 3;
}
/**
* Returns the current key length
*
- * @access public
* @return int
*/
- function getKeyLength()
+ public function getKeyLength()
{
return $this->current_key_length;
}
@@ -332,26 +323,28 @@ class RC2 extends Base
* has more then 128 bytes in it, and set $key to a single null byte if
* it is empty.
*
- * If the key is not explicitly set, it'll be assumed to be a single
- * null byte.
- *
- * @see \phpseclib\Crypt\Base::setKey()
- * @access public
+ * @see Common\SymmetricKey::setKey()
* @param string $key
- * @param int $t1 optional Effective key length in bits.
+ * @param int|boolean $t1 optional Effective key length in bits.
+ * @throws \LengthException if the key length isn't supported
*/
- function setKey($key, $t1 = 0)
+ public function setKey($key, $t1 = false)
{
$this->orig_key = $key;
- if ($t1 <= 0) {
+ if ($t1 === false) {
$t1 = $this->default_key_length;
- } elseif ($t1 > 1024) {
- $t1 = 1024;
}
+
+ if ($t1 < 1 || $t1 > 1024) {
+ throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys between 1 and 1024 bits, inclusive, are supported');
+ }
+
$this->current_key_length = $t1;
- // Key byte count should be 1..128.
- $key = strlen($key) ? substr($key, 0, 128) : "\x00";
+ if (strlen($key) < 1 || strlen($key) > 128) {
+ throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes between 8 and 1024 bits, inclusive, are supported');
+ }
+
$t = strlen($key);
// The mcrypt RC2 implementation only supports effective key length
@@ -366,7 +359,7 @@ class RC2 extends Base
$tm = 0xFF >> (8 * $t8 - $t1);
// Expand key.
- $pitable = $this->pitable;
+ $pitable = self::$pitable;
for ($i = $t; $i < 128; $i++) {
$l[$i] = $pitable[$l[$i - 1] + $l[$i - $t]];
}
@@ -377,23 +370,25 @@ class RC2 extends Base
}
// Prepare the key for mcrypt.
- $l[0] = $this->invpitable[$l[0]];
+ $l[0] = self::$invpitable[$l[0]];
array_unshift($l, 'C*');
- parent::setKey(call_user_func_array('pack', $l));
+ $this->key = pack(...$l);
+ $this->key_length = strlen($this->key);
+ $this->changed = $this->nonIVChanged = true;
+ $this->setEngine();
}
/**
* Encrypts a message.
*
- * Mostly a wrapper for \phpseclib\Crypt\Base::encrypt, with some additional OpenSSL handling code
+ * Mostly a wrapper for \phpseclib3\Crypt\Common\SymmetricKey::encrypt, with some additional OpenSSL handling code
*
* @see self::decrypt()
- * @access public
* @param string $plaintext
* @return string $ciphertext
*/
- function encrypt($plaintext)
+ public function encrypt($plaintext)
{
if ($this->engine == self::ENGINE_OPENSSL) {
$temp = $this->key;
@@ -409,14 +404,13 @@ class RC2 extends Base
/**
* Decrypts a message.
*
- * Mostly a wrapper for \phpseclib\Crypt\Base::decrypt, with some additional OpenSSL handling code
+ * Mostly a wrapper for \phpseclib3\Crypt\Common\SymmetricKey::decrypt, with some additional OpenSSL handling code
*
* @see self::encrypt()
- * @access public
* @param string $ciphertext
* @return string $plaintext
*/
- function decrypt($ciphertext)
+ public function decrypt($ciphertext)
{
if ($this->engine == self::ENGINE_OPENSSL) {
$temp = $this->key;
@@ -432,18 +426,17 @@ class RC2 extends Base
/**
* Encrypts a block
*
- * @see \phpseclib\Crypt\Base::_encryptBlock()
- * @see \phpseclib\Crypt\Base::encrypt()
- * @access private
+ * @see Common\SymmetricKey::encryptBlock()
+ * @see Common\SymmetricKey::encrypt()
* @param string $in
* @return string
*/
- function _encryptBlock($in)
+ protected function encryptBlock($in)
{
list($r0, $r1, $r2, $r3) = array_values(unpack('v*', $in));
$keys = $this->keys;
$limit = 20;
- $actions = array($limit => 44, 44 => 64);
+ $actions = [$limit => 44, 44 => 64];
$j = 0;
for (;;) {
@@ -477,18 +470,17 @@ class RC2 extends Base
/**
* Decrypts a block
*
- * @see \phpseclib\Crypt\Base::_decryptBlock()
- * @see \phpseclib\Crypt\Base::decrypt()
- * @access private
+ * @see Common\SymmetricKey::decryptBlock()
+ * @see Common\SymmetricKey::decrypt()
* @param string $in
* @return string
*/
- function _decryptBlock($in)
+ protected function decryptBlock($in)
{
list($r0, $r1, $r2, $r3) = array_values(unpack('v*', $in));
$keys = $this->keys;
$limit = 44;
- $actions = array($limit => 20, 20 => 0);
+ $actions = [$limit => 20, 20 => 0];
$j = 64;
for (;;) {
@@ -520,36 +512,20 @@ class RC2 extends Base
}
/**
- * Setup the \phpseclib\Crypt\Base::ENGINE_MCRYPT $engine
- *
- * @see \phpseclib\Crypt\Base::_setupMcrypt()
- * @access private
- */
- function _setupMcrypt()
- {
- if (!isset($this->key)) {
- $this->setKey('');
- }
-
- parent::_setupMcrypt();
- }
-
- /**
* Creates the key schedule
*
- * @see \phpseclib\Crypt\Base::_setupKey()
- * @access private
+ * @see Common\SymmetricKey::setupKey()
*/
- function _setupKey()
+ protected function setupKey()
{
if (!isset($this->key)) {
$this->setKey('');
}
- // Key has already been expanded in \phpseclib\Crypt\RC2::setKey():
+ // Key has already been expanded in \phpseclib3\Crypt\RC2::setKey():
// Only the first value must be altered.
$l = unpack('Ca/Cb/v*', $this->key);
- array_unshift($l, $this->pitable[$l['a']] | ($l['b'] << 8));
+ array_unshift($l, self::$pitable[$l['a']] | ($l['b'] << 8));
unset($l['a']);
unset($l['b']);
$this->keys = $l;
@@ -558,137 +534,107 @@ class RC2 extends Base
/**
* Setup the performance-optimized function for de/encrypt()
*
- * @see \phpseclib\Crypt\Base::_setupInlineCrypt()
- * @access private
+ * @see Common\SymmetricKey::setupInlineCrypt()
*/
- function _setupInlineCrypt()
+ protected function setupInlineCrypt()
{
- $lambda_functions =& self::_getLambdaFunctions();
-
- // The first 10 generated $lambda_functions will use the $keys hardcoded as integers
- // for the mixing rounds, for better inline crypt performance [~20% faster].
- // But for memory reason we have to limit those ultra-optimized $lambda_functions to an amount of 10.
- // (Currently, for Crypt_RC2, one generated $lambda_function cost on php5.5@32bit ~60kb unfreeable mem and ~100kb on php5.5@64bit)
- $gen_hi_opt_code = (bool)(count($lambda_functions) < 10);
-
- // Generation of a unique hash for our generated code
- $code_hash = "Crypt_RC2, {$this->mode}";
- if ($gen_hi_opt_code) {
- $code_hash = str_pad($code_hash, 32) . $this->_hashInlineCryptFunction($this->key);
- }
+ // Init code for both, encrypt and decrypt.
+ $init_crypt = '$keys = $this->keys;';
- // Is there a re-usable $lambda_functions in there?
- // If not, we have to create it.
- if (!isset($lambda_functions[$code_hash])) {
- // Init code for both, encrypt and decrypt.
- $init_crypt = '$keys = $self->keys;';
-
- switch (true) {
- case $gen_hi_opt_code:
- $keys = $this->keys;
- default:
- $keys = array();
- foreach ($this->keys as $k => $v) {
- $keys[$k] = '$keys[' . $k . ']';
- }
- }
+ $keys = $this->keys;
- // $in is the current 8 bytes block which has to be en/decrypt
- $encrypt_block = $decrypt_block = '
- $in = unpack("v4", $in);
- $r0 = $in[1];
- $r1 = $in[2];
- $r2 = $in[3];
- $r3 = $in[4];
- ';
-
- // Create code for encryption.
- $limit = 20;
- $actions = array($limit => 44, 44 => 64);
- $j = 0;
-
- for (;;) {
- // Mixing round.
- $encrypt_block .= '
- $r0 = (($r0 + ' . $keys[$j++] . ' +
- ((($r1 ^ $r2) & $r3) ^ $r1)) & 0xFFFF) << 1;
- $r0 |= $r0 >> 16;
- $r1 = (($r1 + ' . $keys[$j++] . ' +
- ((($r2 ^ $r3) & $r0) ^ $r2)) & 0xFFFF) << 2;
- $r1 |= $r1 >> 16;
- $r2 = (($r2 + ' . $keys[$j++] . ' +
- ((($r3 ^ $r0) & $r1) ^ $r3)) & 0xFFFF) << 3;
- $r2 |= $r2 >> 16;
- $r3 = (($r3 + ' . $keys[$j++] . ' +
- ((($r0 ^ $r1) & $r2) ^ $r0)) & 0xFFFF) << 5;
- $r3 |= $r3 >> 16;';
-
- if ($j === $limit) {
- if ($limit === 64) {
- break;
- }
-
- // Mashing round.
- $encrypt_block .= '
- $r0 += $keys[$r3 & 0x3F];
- $r1 += $keys[$r0 & 0x3F];
- $r2 += $keys[$r1 & 0x3F];
- $r3 += $keys[$r2 & 0x3F];';
- $limit = $actions[$limit];
- }
- }
+ // $in is the current 8 bytes block which has to be en/decrypt
+ $encrypt_block = $decrypt_block = '
+ $in = unpack("v4", $in);
+ $r0 = $in[1];
+ $r1 = $in[2];
+ $r2 = $in[3];
+ $r3 = $in[4];
+ ';
- $encrypt_block .= '$in = pack("v4", $r0, $r1, $r2, $r3);';
+ // Create code for encryption.
+ $limit = 20;
+ $actions = [$limit => 44, 44 => 64];
+ $j = 0;
- // Create code for decryption.
- $limit = 44;
- $actions = array($limit => 20, 20 => 0);
- $j = 64;
+ for (;;) {
+ // Mixing round.
+ $encrypt_block .= '
+ $r0 = (($r0 + ' . $keys[$j++] . ' +
+ ((($r1 ^ $r2) & $r3) ^ $r1)) & 0xFFFF) << 1;
+ $r0 |= $r0 >> 16;
+ $r1 = (($r1 + ' . $keys[$j++] . ' +
+ ((($r2 ^ $r3) & $r0) ^ $r2)) & 0xFFFF) << 2;
+ $r1 |= $r1 >> 16;
+ $r2 = (($r2 + ' . $keys[$j++] . ' +
+ ((($r3 ^ $r0) & $r1) ^ $r3)) & 0xFFFF) << 3;
+ $r2 |= $r2 >> 16;
+ $r3 = (($r3 + ' . $keys[$j++] . ' +
+ ((($r0 ^ $r1) & $r2) ^ $r0)) & 0xFFFF) << 5;
+ $r3 |= $r3 >> 16;';
- for (;;) {
- // R-mixing round.
- $decrypt_block .= '
- $r3 = ($r3 | ($r3 << 16)) >> 5;
- $r3 = ($r3 - ' . $keys[--$j] . ' -
- ((($r0 ^ $r1) & $r2) ^ $r0)) & 0xFFFF;
- $r2 = ($r2 | ($r2 << 16)) >> 3;
- $r2 = ($r2 - ' . $keys[--$j] . ' -
- ((($r3 ^ $r0) & $r1) ^ $r3)) & 0xFFFF;
- $r1 = ($r1 | ($r1 << 16)) >> 2;
- $r1 = ($r1 - ' . $keys[--$j] . ' -
- ((($r2 ^ $r3) & $r0) ^ $r2)) & 0xFFFF;
- $r0 = ($r0 | ($r0 << 16)) >> 1;
- $r0 = ($r0 - ' . $keys[--$j] . ' -
- ((($r1 ^ $r2) & $r3) ^ $r1)) & 0xFFFF;';
-
- if ($j === $limit) {
- if ($limit === 0) {
- break;
- }
-
- // R-mashing round.
- $decrypt_block .= '
- $r3 = ($r3 - $keys[$r2 & 0x3F]) & 0xFFFF;
- $r2 = ($r2 - $keys[$r1 & 0x3F]) & 0xFFFF;
- $r1 = ($r1 - $keys[$r0 & 0x3F]) & 0xFFFF;
- $r0 = ($r0 - $keys[$r3 & 0x3F]) & 0xFFFF;';
- $limit = $actions[$limit];
+ if ($j === $limit) {
+ if ($limit === 64) {
+ break;
}
+
+ // Mashing round.
+ $encrypt_block .= '
+ $r0 += $keys[$r3 & 0x3F];
+ $r1 += $keys[$r0 & 0x3F];
+ $r2 += $keys[$r1 & 0x3F];
+ $r3 += $keys[$r2 & 0x3F];';
+ $limit = $actions[$limit];
}
+ }
+
+ $encrypt_block .= '$in = pack("v4", $r0, $r1, $r2, $r3);';
+
+ // Create code for decryption.
+ $limit = 44;
+ $actions = [$limit => 20, 20 => 0];
+ $j = 64;
+
+ for (;;) {
+ // R-mixing round.
+ $decrypt_block .= '
+ $r3 = ($r3 | ($r3 << 16)) >> 5;
+ $r3 = ($r3 - ' . $keys[--$j] . ' -
+ ((($r0 ^ $r1) & $r2) ^ $r0)) & 0xFFFF;
+ $r2 = ($r2 | ($r2 << 16)) >> 3;
+ $r2 = ($r2 - ' . $keys[--$j] . ' -
+ ((($r3 ^ $r0) & $r1) ^ $r3)) & 0xFFFF;
+ $r1 = ($r1 | ($r1 << 16)) >> 2;
+ $r1 = ($r1 - ' . $keys[--$j] . ' -
+ ((($r2 ^ $r3) & $r0) ^ $r2)) & 0xFFFF;
+ $r0 = ($r0 | ($r0 << 16)) >> 1;
+ $r0 = ($r0 - ' . $keys[--$j] . ' -
+ ((($r1 ^ $r2) & $r3) ^ $r1)) & 0xFFFF;';
- $decrypt_block .= '$in = pack("v4", $r0, $r1, $r2, $r3);';
+ if ($j === $limit) {
+ if ($limit === 0) {
+ break;
+ }
- // Creates the inline-crypt function
- $lambda_functions[$code_hash] = $this->_createInlineCryptFunction(
- array(
- 'init_crypt' => $init_crypt,
- 'encrypt_block' => $encrypt_block,
- 'decrypt_block' => $decrypt_block
- )
- );
+ // R-mashing round.
+ $decrypt_block .= '
+ $r3 = ($r3 - $keys[$r2 & 0x3F]) & 0xFFFF;
+ $r2 = ($r2 - $keys[$r1 & 0x3F]) & 0xFFFF;
+ $r1 = ($r1 - $keys[$r0 & 0x3F]) & 0xFFFF;
+ $r0 = ($r0 - $keys[$r3 & 0x3F]) & 0xFFFF;';
+ $limit = $actions[$limit];
+ }
}
- // Set the inline-crypt function as callback in: $this->inline_crypt
- $this->inline_crypt = $lambda_functions[$code_hash];
+ $decrypt_block .= '$in = pack("v4", $r0, $r1, $r2, $r3);';
+
+ // Creates the inline-crypt function
+ $this->inline_crypt = $this->createInlineCryptFunction(
+ [
+ 'init_crypt' => $init_crypt,
+ 'encrypt_block' => $encrypt_block,
+ 'decrypt_block' => $decrypt_block
+ ]
+ );
}
}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RC4.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RC4.php
index 2e5c05567..98cf01165 100644
--- a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RC4.php
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RC4.php
@@ -20,7 +20,7 @@
* <?php
* include 'vendor/autoload.php';
*
- * $rc4 = new \phpseclib\Crypt\RC4();
+ * $rc4 = new \phpseclib3\Crypt\RC4();
*
* $rc4->setKey('abcdefgh');
*
@@ -34,166 +34,109 @@
* ?>
* </code>
*
- * @category Crypt
- * @package RC4
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
-namespace phpseclib\Crypt;
+namespace phpseclib3\Crypt;
+
+use phpseclib3\Crypt\Common\StreamCipher;
/**
* Pure-PHP implementation of RC4.
*
- * @package RC4
* @author Jim Wigginton <terrafrost@php.net>
- * @access public
*/
-class RC4 extends Base
+class RC4 extends StreamCipher
{
- /**#@+
- * @access private
- * @see \phpseclib\Crypt\RC4::_crypt()
- */
+ /**
+ * @see \phpseclib3\Crypt\RC4::_crypt()
+ */
const ENCRYPT = 0;
- const DECRYPT = 1;
- /**#@-*/
/**
- * Block Length of the cipher
- *
- * RC4 is a stream cipher
- * so we the block_size to 0
- *
- * @see \phpseclib\Crypt\Base::block_size
- * @var int
- * @access private
+ * @see \phpseclib3\Crypt\RC4::_crypt()
*/
- var $block_size = 0;
+ const DECRYPT = 1;
/**
* Key Length (in bytes)
*
- * @see \phpseclib\Crypt\RC4::setKeyLength()
+ * @see \phpseclib3\Crypt\RC4::setKeyLength()
* @var int
- * @access private
*/
- var $key_length = 128; // = 1024 bits
+ protected $key_length = 128; // = 1024 bits
/**
* The mcrypt specific name of the cipher
*
- * @see \phpseclib\Crypt\Base::cipher_name_mcrypt
+ * @see Common\SymmetricKey::cipher_name_mcrypt
* @var string
- * @access private
- */
- var $cipher_name_mcrypt = 'arcfour';
-
- /**
- * Holds whether performance-optimized $inline_crypt() can/should be used.
- *
- * @see \phpseclib\Crypt\Base::inline_crypt
- * @var mixed
- * @access private
*/
- var $use_inline_crypt = false; // currently not available
+ protected $cipher_name_mcrypt = 'arcfour';
/**
* The Key
*
* @see self::setKey()
* @var string
- * @access private
*/
- var $key;
+ protected $key;
/**
* The Key Stream for decryption and encryption
*
* @see self::setKey()
* @var array
- * @access private
*/
- var $stream;
-
- /**
- * Default Constructor.
- *
- * Determines whether or not the mcrypt extension should be used.
- *
- * @see \phpseclib\Crypt\Base::__construct()
- * @return \phpseclib\Crypt\RC4
- * @access public
- */
- function __construct()
- {
- parent::__construct(Base::MODE_STREAM);
- }
+ private $stream;
/**
* Test for engine validity
*
- * This is mainly just a wrapper to set things up for \phpseclib\Crypt\Base::isValidEngine()
+ * This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine()
*
- * @see \phpseclib\Crypt\Base::__construct()
+ * @see Common\SymmetricKey::__construct()
* @param int $engine
- * @access public
* @return bool
*/
- function isValidEngine($engine)
+ protected function isValidEngineHelper($engine)
{
if ($engine == self::ENGINE_OPENSSL) {
+ if ($this->continuousBuffer) {
+ return false;
+ }
// quoting https://www.openssl.org/news/openssl-3.0-notes.html, OpenSSL 3.0.1
// "Moved all variations of the EVP ciphers CAST5, BF, IDEA, SEED, RC2, RC4, RC5, and DES to the legacy provider"
// in theory openssl_get_cipher_methods() should catch this but, on GitHub Actions, at least, it does not
if (defined('OPENSSL_VERSION_TEXT') && version_compare(preg_replace('#OpenSSL (\d+\.\d+\.\d+) .*#', '$1', OPENSSL_VERSION_TEXT), '3.0.1', '>=')) {
return false;
}
- if (version_compare(PHP_VERSION, '5.3.7') >= 0) {
- $this->cipher_name_openssl = 'rc4-40';
- } else {
- switch (strlen($this->key)) {
- case 5:
- $this->cipher_name_openssl = 'rc4-40';
- break;
- case 8:
- $this->cipher_name_openssl = 'rc4-64';
- break;
- case 16:
- $this->cipher_name_openssl = 'rc4';
- break;
- default:
- return false;
- }
- }
+ $this->cipher_name_openssl = 'rc4-40';
}
- return parent::isValidEngine($engine);
+ return parent::isValidEngineHelper($engine);
}
/**
- * Dummy function.
- *
- * Some protocols, such as WEP, prepend an "initialization vector" to the key, effectively creating a new key [1].
- * If you need to use an initialization vector in this manner, feel free to prepend it to the key, yourself, before
- * calling setKey().
- *
- * [1] WEP's initialization vectors (IV's) are used in a somewhat insecure way. Since, in that protocol,
- * the IV's are relatively easy to predict, an attack described by
- * {@link http://www.drizzle.com/~aboba/IEEE/rc4_ksaproc.pdf Scott Fluhrer, Itsik Mantin, and Adi Shamir}
- * can be used to quickly guess at the rest of the key. The following links elaborate:
+ * Sets the key length
*
- * {@link http://www.rsa.com/rsalabs/node.asp?id=2009 http://www.rsa.com/rsalabs/node.asp?id=2009}
- * {@link http://en.wikipedia.org/wiki/Related_key_attack http://en.wikipedia.org/wiki/Related_key_attack}
+ * Keys can be between 1 and 256 bytes long.
*
- * @param string $iv
- * @see self::setKey()
- * @access public
+ * @param int $length
+ * @throws \LengthException if the key length is invalid
*/
- function setIV($iv)
+ public function setKeyLength($length)
{
+ if ($length < 8 || $length > 2048) {
+ throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys between 1 and 256 bytes are supported');
+ }
+
+ $this->key_length = $length >> 3;
+
+ parent::setKeyLength($length);
}
/**
@@ -201,37 +144,32 @@ class RC4 extends Base
*
* Keys can be between 1 and 256 bytes long.
*
- * @access public
- * @param int $length
+ * @param string $key
*/
- function setKeyLength($length)
+ public function setKey($key)
{
- if ($length < 8) {
- $this->key_length = 1;
- } elseif ($length > 2048) {
- $this->key_length = 256;
- } else {
- $this->key_length = $length >> 3;
+ $length = strlen($key);
+ if ($length < 1 || $length > 256) {
+ throw new \LengthException('Key size of ' . $length . ' bytes is not supported by RC4. Keys must be between 1 and 256 bytes long');
}
- parent::setKeyLength($length);
+ parent::setKey($key);
}
/**
* Encrypts a message.
*
- * @see \phpseclib\Crypt\Base::decrypt()
- * @see self::_crypt()
- * @access public
+ * @see Common\SymmetricKey::decrypt()
+ * @see self::crypt()
* @param string $plaintext
* @return string $ciphertext
*/
- function encrypt($plaintext)
+ public function encrypt($plaintext)
{
if ($this->engine != self::ENGINE_INTERNAL) {
return parent::encrypt($plaintext);
}
- return $this->_crypt($plaintext, self::ENCRYPT);
+ return $this->crypt($plaintext, self::ENCRYPT);
}
/**
@@ -240,27 +178,25 @@ class RC4 extends Base
* $this->decrypt($this->encrypt($plaintext)) == $this->encrypt($this->encrypt($plaintext)).
* At least if the continuous buffer is disabled.
*
- * @see \phpseclib\Crypt\Base::encrypt()
- * @see self::_crypt()
- * @access public
+ * @see Common\SymmetricKey::encrypt()
+ * @see self::crypt()
* @param string $ciphertext
* @return string $plaintext
*/
- function decrypt($ciphertext)
+ public function decrypt($ciphertext)
{
if ($this->engine != self::ENGINE_INTERNAL) {
return parent::decrypt($ciphertext);
}
- return $this->_crypt($ciphertext, self::DECRYPT);
+ return $this->crypt($ciphertext, self::DECRYPT);
}
/**
* Encrypts a block
*
- * @access private
* @param string $in
*/
- function _encryptBlock($in)
+ protected function encryptBlock($in)
{
// RC4 does not utilize this method
}
@@ -268,10 +204,9 @@ class RC4 extends Base
/**
* Decrypts a block
*
- * @access private
* @param string $in
*/
- function _decryptBlock($in)
+ protected function decryptBlock($in)
{
// RC4 does not utilize this method
}
@@ -279,10 +214,9 @@ class RC4 extends Base
/**
* Setup the key (expansion)
*
- * @see \phpseclib\Crypt\Base::_setupKey()
- * @access private
+ * @see Common\SymmetricKey::_setupKey()
*/
- function _setupKey()
+ protected function setupKey()
{
$key = $this->key;
$keyLength = strlen($key);
@@ -295,12 +229,12 @@ class RC4 extends Base
$keyStream[$j] = $temp;
}
- $this->stream = array();
- $this->stream[self::DECRYPT] = $this->stream[self::ENCRYPT] = array(
+ $this->stream = [];
+ $this->stream[self::DECRYPT] = $this->stream[self::ENCRYPT] = [
0, // index $i
0, // index $j
$keyStream
- );
+ ];
}
/**
@@ -308,16 +242,14 @@ class RC4 extends Base
*
* @see self::encrypt()
* @see self::decrypt()
- * @access private
* @param string $text
* @param int $mode
* @return string $text
*/
- function _crypt($text, $mode)
+ private function crypt($text, $mode)
{
if ($this->changed) {
- $this->_setup();
- $this->changed = false;
+ $this->setup();
}
$stream = &$this->stream[$mode];
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php
index 7f5df1d5f..0a11957b0 100644
--- a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA.php
@@ -8,660 +8,362 @@
* Here's an example of how to encrypt and decrypt text with this library:
* <code>
* <?php
- * include 'vendor/autoload.php';
+ * include 'vendor/autoload.php';
*
- * $rsa = new \phpseclib\Crypt\RSA();
- * extract($rsa->createKey());
+ * $private = Crypt\RSA::createKey();
+ * $public = $private->getPublicKey();
*
- * $plaintext = 'terrafrost';
+ * $plaintext = 'terrafrost';
*
- * $rsa->loadKey($privatekey);
- * $ciphertext = $rsa->encrypt($plaintext);
+ * $ciphertext = $public->encrypt($plaintext);
*
- * $rsa->loadKey($publickey);
- * echo $rsa->decrypt($ciphertext);
+ * echo $private->decrypt($ciphertext);
* ?>
* </code>
*
* Here's an example of how to create signatures and verify signatures with this library:
* <code>
* <?php
- * include 'vendor/autoload.php';
+ * include 'vendor/autoload.php';
*
- * $rsa = new \phpseclib\Crypt\RSA();
- * extract($rsa->createKey());
+ * $private = Crypt\RSA::createKey();
+ * $public = $private->getPublicKey();
*
- * $plaintext = 'terrafrost';
+ * $plaintext = 'terrafrost';
*
- * $rsa->loadKey($privatekey);
- * $signature = $rsa->sign($plaintext);
+ * $signature = $private->sign($plaintext);
*
- * $rsa->loadKey($publickey);
- * echo $rsa->verify($plaintext, $signature) ? 'verified' : 'unverified';
+ * echo $public->verify($plaintext, $signature) ? 'verified' : 'unverified';
* ?>
* </code>
*
- * @category Crypt
- * @package RSA
+ * One thing to consider when using this: so phpseclib uses PSS mode by default.
+ * Technically, id-RSASSA-PSS has a different key format than rsaEncryption. So
+ * should phpseclib save to the id-RSASSA-PSS format by default or the
+ * rsaEncryption format? For stand-alone keys I figure rsaEncryption is better
+ * because SSH doesn't use PSS and idk how many SSH servers would be able to
+ * decode an id-RSASSA-PSS key. For X.509 certificates the id-RSASSA-PSS
+ * format is used by default (unless you change it up to use PKCS1 instead)
+ *
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2009 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
-namespace phpseclib\Crypt;
+namespace phpseclib3\Crypt;
-use phpseclib\Math\BigInteger;
+use phpseclib3\Crypt\Common\AsymmetricKey;
+use phpseclib3\Crypt\RSA\Formats\Keys\PSS;
+use phpseclib3\Crypt\RSA\PrivateKey;
+use phpseclib3\Crypt\RSA\PublicKey;
+use phpseclib3\Exception\InconsistentSetupException;
+use phpseclib3\Exception\UnsupportedAlgorithmException;
+use phpseclib3\Math\BigInteger;
/**
* Pure-PHP PKCS#1 compliant implementation of RSA.
*
- * @package RSA
* @author Jim Wigginton <terrafrost@php.net>
- * @access public
*/
-class RSA
+abstract class RSA extends AsymmetricKey
{
- /**#@+
- * @access public
- * @see self::encrypt()
- * @see self::decrypt()
+ /**
+ * Algorithm Name
+ *
+ * @var string
*/
+ const ALGORITHM = 'RSA';
+
/**
* Use {@link http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding Optimal Asymmetric Encryption Padding}
* (OAEP) for encryption / decryption.
*
- * Uses sha1 by default.
+ * Uses sha256 by default
*
* @see self::setHash()
* @see self::setMGFHash()
+ * @see self::encrypt()
+ * @see self::decrypt()
*/
const ENCRYPTION_OAEP = 1;
+
/**
* Use PKCS#1 padding.
*
- * Although self::ENCRYPTION_OAEP offers more security, including PKCS#1 padding is necessary for purposes of backwards
+ * Although self::PADDING_OAEP / self::PADDING_PSS offers more security, including PKCS#1 padding is necessary for purposes of backwards
* compatibility with protocols (like SSH-1) written before OAEP's introduction.
+ *
+ * @see self::encrypt()
+ * @see self::decrypt()
*/
const ENCRYPTION_PKCS1 = 2;
+
/**
* Do not use any padding
*
* Although this method is not recommended it can none-the-less sometimes be useful if you're trying to decrypt some legacy
* stuff, if you're trying to diagnose why an encrypted message isn't decrypting, etc.
+ *
+ * @see self::encrypt()
+ * @see self::decrypt()
*/
- const ENCRYPTION_NONE = 3;
- /**#@-*/
+ const ENCRYPTION_NONE = 4;
- /**#@+
- * @access public
- * @see self::sign()
- * @see self::verify()
- * @see self::setHash()
- */
/**
* Use the Probabilistic Signature Scheme for signing
*
- * Uses sha1 by default.
+ * Uses sha256 and 0 as the salt length
*
* @see self::setSaltLength()
* @see self::setMGFHash()
+ * @see self::setHash()
+ * @see self::sign()
+ * @see self::verify()
+ * @see self::setHash()
*/
- const SIGNATURE_PSS = 1;
- /**
- * Use the PKCS#1 scheme by default.
- *
- * Although self::SIGNATURE_PSS offers more security, including PKCS#1 signing is necessary for purposes of backwards
- * compatibility with protocols (like SSH-2) written before PSS's introduction.
- */
- const SIGNATURE_PKCS1 = 2;
- /**#@-*/
-
- /**#@+
- * @access private
- * @see \phpseclib\Crypt\RSA::createKey()
- */
- /**
- * ASN1 Integer
- */
- const ASN1_INTEGER = 2;
- /**
- * ASN1 Bit String
- */
- const ASN1_BITSTRING = 3;
- /**
- * ASN1 Octet String
- */
- const ASN1_OCTETSTRING = 4;
- /**
- * ASN1 Object Identifier
- */
- const ASN1_OBJECT = 6;
- /**
- * ASN1 Sequence (with the constucted bit set)
- */
- const ASN1_SEQUENCE = 48;
- /**#@-*/
-
- /**#@+
- * @access private
- * @see \phpseclib\Crypt\RSA::__construct()
- */
- /**
- * To use the pure-PHP implementation
- */
- const MODE_INTERNAL = 1;
- /**
- * To use the OpenSSL library
- *
- * (if enabled; otherwise, the internal implementation will be used)
- */
- const MODE_OPENSSL = 2;
- /**#@-*/
-
- /**#@+
- * @access public
- * @see \phpseclib\Crypt\RSA::createKey()
- * @see \phpseclib\Crypt\RSA::setPrivateKeyFormat()
- */
- /**
- * PKCS#1 formatted private key
- *
- * Used by OpenSSH
- */
- const PRIVATE_FORMAT_PKCS1 = 0;
- /**
- * PuTTY formatted private key
- */
- const PRIVATE_FORMAT_PUTTY = 1;
- /**
- * XML formatted private key
- */
- const PRIVATE_FORMAT_XML = 2;
- /**
- * PKCS#8 formatted private key
- */
- const PRIVATE_FORMAT_PKCS8 = 8;
- /**
- * OpenSSH formatted private key
- */
- const PRIVATE_FORMAT_OPENSSH = 9;
- /**#@-*/
-
- /**#@+
- * @access public
- * @see \phpseclib\Crypt\RSA::createKey()
- * @see \phpseclib\Crypt\RSA::setPublicKeyFormat()
- */
- /**
- * Raw public key
- *
- * An array containing two \phpseclib\Math\BigInteger objects.
- *
- * The exponent can be indexed with any of the following:
- *
- * 0, e, exponent, publicExponent
- *
- * The modulus can be indexed with any of the following:
- *
- * 1, n, modulo, modulus
- */
- const PUBLIC_FORMAT_RAW = 3;
- /**
- * PKCS#1 formatted public key (raw)
- *
- * Used by File/X509.php
- *
- * Has the following header:
- *
- * -----BEGIN RSA PUBLIC KEY-----
- *
- * Analogous to ssh-keygen's pem format (as specified by -m)
- */
- const PUBLIC_FORMAT_PKCS1 = 4;
- const PUBLIC_FORMAT_PKCS1_RAW = 4;
- /**
- * XML formatted public key
- */
- const PUBLIC_FORMAT_XML = 5;
- /**
- * OpenSSH formatted public key
- *
- * Place in $HOME/.ssh/authorized_keys
- */
- const PUBLIC_FORMAT_OPENSSH = 6;
- /**
- * PKCS#1 formatted public key (encapsulated)
- *
- * Used by PHP's openssl_public_encrypt() and openssl's rsautl (when -pubin is set)
- *
- * Has the following header:
- *
- * -----BEGIN PUBLIC KEY-----
- *
- * Analogous to ssh-keygen's pkcs8 format (as specified by -m). Although PKCS8
- * is specific to private keys it's basically creating a DER-encoded wrapper
- * for keys. This just extends that same concept to public keys (much like ssh-keygen)
- */
- const PUBLIC_FORMAT_PKCS8 = 7;
- /**#@-*/
+ const SIGNATURE_PSS = 16;
/**
- * Precomputed Zero
+ * Use a relaxed version of PKCS#1 padding for signature verification
*
- * @var \phpseclib\Math\BigInteger
- * @access private
+ * @see self::sign()
+ * @see self::verify()
+ * @see self::setHash()
*/
- var $zero;
+ const SIGNATURE_RELAXED_PKCS1 = 32;
/**
- * Precomputed One
+ * Use PKCS#1 padding for signature verification
*
- * @var \phpseclib\Math\BigInteger
- * @access private
+ * @see self::sign()
+ * @see self::verify()
+ * @see self::setHash()
*/
- var $one;
+ const SIGNATURE_PKCS1 = 64;
/**
- * Private Key Format
+ * Encryption padding mode
*
* @var int
- * @access private
*/
- var $privateKeyFormat = self::PRIVATE_FORMAT_PKCS1;
+ protected $encryptionPadding = self::ENCRYPTION_OAEP;
/**
- * Public Key Format
+ * Signature padding mode
*
* @var int
- * @access public
*/
- var $publicKeyFormat = self::PUBLIC_FORMAT_PKCS8;
+ protected $signaturePadding = self::SIGNATURE_PSS;
/**
- * Modulus (ie. n)
- *
- * @var \phpseclib\Math\BigInteger
- * @access private
- */
- var $modulus;
-
- /**
- * Modulus length
+ * Length of hash function output
*
- * @var \phpseclib\Math\BigInteger
- * @access private
+ * @var int
*/
- var $k;
+ protected $hLen;
/**
- * Exponent (ie. e or d)
+ * Length of salt
*
- * @var \phpseclib\Math\BigInteger
- * @access private
+ * @var int
*/
- var $exponent;
+ protected $sLen;
/**
- * Primes for Chinese Remainder Theorem (ie. p and q)
+ * Label
*
- * @var array
- * @access private
+ * @var string
*/
- var $primes;
+ protected $label = '';
/**
- * Exponents for Chinese Remainder Theorem (ie. dP and dQ)
+ * Hash function for the Mask Generation Function
*
- * @var array
- * @access private
+ * @var Hash
*/
- var $exponents;
+ protected $mgfHash;
/**
- * Coefficients for Chinese Remainder Theorem (ie. qInv)
+ * Length of MGF hash function output
*
- * @var array
- * @access private
+ * @var int
*/
- var $coefficients;
+ protected $mgfHLen;
/**
- * Hash name
+ * Modulus (ie. n)
*
- * @var string
- * @access private
+ * @var Math\BigInteger
*/
- var $hashName;
+ protected $modulus;
/**
- * Hash function
+ * Modulus length
*
- * @var \phpseclib\Crypt\Hash
- * @access private
+ * @var Math\BigInteger
*/
- var $hash;
+ protected $k;
/**
- * Length of hash function output
+ * Exponent (ie. e or d)
*
- * @var int
- * @access private
+ * @var Math\BigInteger
*/
- var $hLen;
+ protected $exponent;
/**
- * Length of salt
+ * Default public exponent
*
* @var int
- * @access private
+ * @link http://en.wikipedia.org/wiki/65537_%28number%29
*/
- var $sLen;
+ private static $defaultExponent = 65537;
/**
- * Hash function for the Mask Generation Function
+ * Enable Blinding?
*
- * @var \phpseclib\Crypt\Hash
- * @access private
+ * @var bool
*/
- var $mgfHash;
+ protected static $enableBlinding = true;
/**
- * Length of MGF hash function output
+ * OpenSSL configuration file name.
*
- * @var int
- * @access private
+ * @see self::createKey()
+ * @var ?string
*/
- var $mgfHLen;
+ protected static $configFile;
/**
- * Encryption mode
+ * Smallest Prime
*
- * @var int
- * @access private
- */
- var $encryptionMode = self::ENCRYPTION_OAEP;
-
- /**
- * Signature mode
+ * Per <http://cseweb.ucsd.edu/~hovav/dist/survey.pdf#page=5>, this number ought not result in primes smaller
+ * than 256 bits. As a consequence if the key you're trying to create is 1024 bits and you've set smallestPrime
+ * to 384 bits then you're going to get a 384 bit prime and a 640 bit prime (384 + 1024 % 384). At least if
+ * engine is set to self::ENGINE_INTERNAL. If Engine is set to self::ENGINE_OPENSSL then smallest Prime is
+ * ignored (ie. multi-prime RSA support is more intended as a way to speed up RSA key generation when there's
+ * a chance neither gmp nor OpenSSL are installed)
*
* @var int
- * @access private
*/
- var $signatureMode = self::SIGNATURE_PSS;
+ private static $smallestPrime = 4096;
/**
* Public Exponent
*
- * @var mixed
- * @access private
+ * @var Math\BigInteger
*/
- var $publicExponent = false;
+ protected $publicExponent;
/**
- * Password
+ * Sets the public exponent for key generation
*
- * @var string
- * @access private
- */
- var $password = false;
-
- /**
- * Components
+ * This will be 65537 unless changed.
*
- * For use with parsing XML formatted keys. PHP's XML Parser functions use utilized - instead of PHP's DOM functions -
- * because PHP's XML Parser functions work on PHP4 whereas PHP's DOM functions - although surperior - don't.
- *
- * @see self::_start_element_handler()
- * @var array
- * @access private
+ * @param int $val
*/
- var $components = array();
-
- /**
- * Current String
- *
- * For use with parsing XML formatted keys.
- *
- * @see self::_character_handler()
- * @see self::_stop_element_handler()
- * @var mixed
- * @access private
- */
- var $current;
+ public static function setExponent($val)
+ {
+ self::$defaultExponent = $val;
+ }
/**
- * OpenSSL configuration file name.
+ * Sets the smallest prime number in bits. Used for key generation
*
- * Set to null to use system configuration file.
- * @see self::createKey()
- * @var mixed
- * @Access public
- */
- var $configFile;
-
- /**
- * Public key comment field.
+ * This will be 4096 unless changed.
*
- * @var string
- * @access private
+ * @param int $val
*/
- var $comment = 'phpseclib-generated-key';
+ public static function setSmallestPrime($val)
+ {
+ self::$smallestPrime = $val;
+ }
/**
- * The constructor
+ * Sets the OpenSSL config file path
*
- * If you want to make use of the openssl extension, you'll need to set the mode manually, yourself. The reason
- * \phpseclib\Crypt\RSA doesn't do it is because OpenSSL doesn't fail gracefully. openssl_pkey_new(), in particular, requires
- * openssl.cnf be present somewhere and, unfortunately, the only real way to find out is too late.
+ * Set to the empty string to use the default config file
*
- * @return \phpseclib\Crypt\RSA
- * @access public
+ * @param string $val
*/
- function __construct()
+ public static function setOpenSSLConfigPath($val)
{
- $this->configFile = dirname(__FILE__) . '/../openssl.cnf';
-
- if (!defined('CRYPT_RSA_MODE')) {
- switch (true) {
- // Math/BigInteger's openssl requirements are a little less stringent than Crypt/RSA's. in particular,
- // Math/BigInteger doesn't require an openssl.cfg file whereas Crypt/RSA does. so if Math/BigInteger
- // can't use OpenSSL it can be pretty trivially assumed, then, that Crypt/RSA can't either.
- case defined('MATH_BIGINTEGER_OPENSSL_DISABLE'):
- define('CRYPT_RSA_MODE', self::MODE_INTERNAL);
- break;
- case function_exists('phpinfo') && extension_loaded('openssl') && file_exists($this->configFile):
- // some versions of XAMPP have mismatched versions of OpenSSL which causes it not to work
- $versions = array();
-
- // avoid generating errors (even with suppression) when phpinfo() is disabled (common in production systems)
- if (strpos(ini_get('disable_functions'), 'phpinfo') === false) {
- ob_start();
- @phpinfo();
- $content = ob_get_contents();
- ob_end_clean();
-
- preg_match_all('#OpenSSL (Header|Library) Version(.*)#im', $content, $matches);
-
- if (!empty($matches[1])) {
- for ($i = 0; $i < count($matches[1]); $i++) {
- $fullVersion = trim(str_replace('=>', '', strip_tags($matches[2][$i])));
-
- // Remove letter part in OpenSSL version
- if (!preg_match('/(\d+\.\d+\.\d+)/i', $fullVersion, $m)) {
- $versions[$matches[1][$i]] = $fullVersion;
- } else {
- $versions[$matches[1][$i]] = $m[0];
- }
- }
- }
- }
-
- // it doesn't appear that OpenSSL versions were reported upon until PHP 5.3+
- switch (true) {
- case !isset($versions['Header']):
- case !isset($versions['Library']):
- case $versions['Header'] == $versions['Library']:
- case version_compare($versions['Header'], '1.0.0') >= 0 && version_compare($versions['Library'], '1.0.0') >= 0:
- define('CRYPT_RSA_MODE', self::MODE_OPENSSL);
- break;
- default:
- define('CRYPT_RSA_MODE', self::MODE_INTERNAL);
- define('MATH_BIGINTEGER_OPENSSL_DISABLE', true);
- }
- break;
- default:
- define('CRYPT_RSA_MODE', self::MODE_INTERNAL);
- }
- }
-
- $this->zero = new BigInteger();
- $this->one = new BigInteger(1);
-
- $this->hash = new Hash('sha1');
- $this->hLen = $this->hash->getLength();
- $this->hashName = 'sha1';
- $this->mgfHash = new Hash('sha1');
- $this->mgfHLen = $this->mgfHash->getLength();
+ self::$configFile = $val;
}
/**
- * Create public / private key pair
+ * Create a private key
*
- * Returns an array with the following three elements:
- * - 'privatekey': The private key.
- * - 'publickey': The public key.
- * - 'partialkey': A partially computed key (if the execution time exceeded $timeout).
- * Will need to be passed back to \phpseclib\Crypt\RSA::createKey() as the third parameter for further processing.
+ * The public key can be extracted from the private key
*
- * @access public
+ * @return PrivateKey
* @param int $bits
- * @param int $timeout
- * @param array $partial
*/
- function createKey($bits = 1024, $timeout = false, $partial = array())
+ public static function createKey($bits = 2048)
{
- if (!defined('CRYPT_RSA_EXPONENT')) {
- // http://en.wikipedia.org/wiki/65537_%28number%29
- define('CRYPT_RSA_EXPONENT', '65537');
+ self::initialize_static_variables();
+
+ $class = new \ReflectionClass(static::class);
+ if ($class->isFinal()) {
+ throw new \RuntimeException('createKey() should not be called from final classes (' . static::class . ')');
}
- // per <http://cseweb.ucsd.edu/~hovav/dist/survey.pdf#page=5>, this number ought not result in primes smaller
- // than 256 bits. as a consequence if the key you're trying to create is 1024 bits and you've set CRYPT_RSA_SMALLEST_PRIME
- // to 384 bits then you're going to get a 384 bit prime and a 640 bit prime (384 + 1024 % 384). at least if
- // CRYPT_RSA_MODE is set to self::MODE_INTERNAL. if CRYPT_RSA_MODE is set to self::MODE_OPENSSL then
- // CRYPT_RSA_SMALLEST_PRIME is ignored (ie. multi-prime RSA support is more intended as a way to speed up RSA key
- // generation when there's a chance neither gmp nor OpenSSL are installed)
- if (!defined('CRYPT_RSA_SMALLEST_PRIME')) {
- define('CRYPT_RSA_SMALLEST_PRIME', 4096);
+
+ $regSize = $bits >> 1; // divide by two to see how many bits P and Q would be
+ if ($regSize > self::$smallestPrime) {
+ $num_primes = floor($bits / self::$smallestPrime);
+ $regSize = self::$smallestPrime;
+ } else {
+ $num_primes = 2;
}
- // OpenSSL uses 65537 as the exponent and requires RSA keys be 384 bits minimum
- if (CRYPT_RSA_MODE == self::MODE_OPENSSL && $bits >= 384 && CRYPT_RSA_EXPONENT == 65537) {
- $config = array();
- if (isset($this->configFile)) {
- $config['config'] = $this->configFile;
+ if ($num_primes == 2 && $bits >= 384 && self::$defaultExponent == 65537) {
+ if (!isset(self::$engines['PHP'])) {
+ self::useBestEngine();
}
- $rsa = openssl_pkey_new(array('private_key_bits' => $bits) + $config);
- openssl_pkey_export($rsa, $privatekey, null, $config);
- $publickey = openssl_pkey_get_details($rsa);
- $publickey = $publickey['key'];
- $privatekey = call_user_func_array(array($this, '_convertPrivateKey'), array_values($this->_parseKey($privatekey, self::PRIVATE_FORMAT_PKCS1)));
- $publickey = call_user_func_array(array($this, '_convertPublicKey'), array_values($this->_parseKey($publickey, self::PUBLIC_FORMAT_PKCS1)));
+ // OpenSSL uses 65537 as the exponent and requires RSA keys be 384 bits minimum
+ if (self::$engines['OpenSSL']) {
+ $config = [];
+ if (self::$configFile) {
+ $config['config'] = self::$configFile;
+ }
+ $rsa = openssl_pkey_new(['private_key_bits' => $bits] + $config);
+ openssl_pkey_export($rsa, $privatekeystr, null, $config);
+
+ // clear the buffer of error strings stemming from a minimalistic openssl.cnf
+ // https://github.com/php/php-src/issues/11054 talks about other errors this'll pick up
+ while (openssl_error_string() !== false) {
+ }
- // clear the buffer of error strings stemming from a minimalistic openssl.cnf
- // https://github.com/php/php-src/issues/11054 talks about other errors this'll pick up
- while (openssl_error_string() !== false) {
+ return RSA::load($privatekeystr);
}
-
- return array(
- 'privatekey' => $privatekey,
- 'publickey' => $publickey,
- 'partialkey' => false
- );
}
static $e;
if (!isset($e)) {
- $e = new BigInteger(CRYPT_RSA_EXPONENT);
- }
-
- extract($this->_generateMinMax($bits));
- $absoluteMin = $min;
- $temp = $bits >> 1; // divide by two to see how many bits P and Q would be
- if ($temp > CRYPT_RSA_SMALLEST_PRIME) {
- $num_primes = floor($bits / CRYPT_RSA_SMALLEST_PRIME);
- $temp = CRYPT_RSA_SMALLEST_PRIME;
- } else {
- $num_primes = 2;
- }
- extract($this->_generateMinMax($temp + $bits % $temp));
- $finalMax = $max;
- extract($this->_generateMinMax($temp));
-
- $generator = new BigInteger();
-
- $n = $this->one->copy();
- if (!empty($partial)) {
- extract(unserialize($partial));
- } else {
- $exponents = $coefficients = $primes = array();
- $lcm = array(
- 'top' => $this->one->copy(),
- 'bottom' => false
- );
+ $e = new BigInteger(self::$defaultExponent);
}
- $start = time();
- $i0 = count($primes) + 1;
+ $n = clone self::$one;
+ $exponents = $coefficients = $primes = [];
+ $lcm = [
+ 'top' => clone self::$one,
+ 'bottom' => false
+ ];
do {
- for ($i = $i0; $i <= $num_primes; $i++) {
- if ($timeout !== false) {
- $timeout-= time() - $start;
- $start = time();
- if ($timeout <= 0) {
- return array(
- 'privatekey' => '',
- 'publickey' => '',
- 'partialkey' => serialize(array(
- 'primes' => $primes,
- 'coefficients' => $coefficients,
- 'lcm' => $lcm,
- 'exponents' => $exponents
- ))
- );
- }
- }
-
- if ($i == $num_primes) {
- list($min, $temp) = $absoluteMin->divide($n);
- if (!$temp->equals($this->zero)) {
- $min = $min->add($this->one); // ie. ceil()
- }
- $primes[$i] = $generator->randomPrime($min, $finalMax, $timeout);
+ for ($i = 1; $i <= $num_primes; $i++) {
+ if ($i != $num_primes) {
+ $primes[$i] = BigInteger::randomPrime($regSize);
} else {
- $primes[$i] = $generator->randomPrime($min, $max, $timeout);
- }
-
- if ($primes[$i] === false) { // if we've reached the timeout
- if (count($primes) > 1) {
- $partialkey = '';
- } else {
- array_pop($primes);
- $partialkey = serialize(array(
- 'primes' => $primes,
- 'coefficients' => $coefficients,
- 'lcm' => $lcm,
- 'exponents' => $exponents
- ));
- }
-
- return array(
- 'privatekey' => '',
- 'publickey' => '',
- 'partialkey' => $partialkey
- );
+ $minMax = BigInteger::minMaxBits($bits);
+ $min = $minMax['min'];
+ $max = $minMax['max'];
+ list($min) = $min->divide($n);
+ $min = $min->add(self::$one);
+ list($max) = $max->divide($n);
+ $primes[$i] = BigInteger::randomRangePrime($min, $max);
}
// the first coefficient is calculated differently from the rest
@@ -672,24 +374,27 @@ class RSA
$n = $n->multiply($primes[$i]);
- $temp = $primes[$i]->subtract($this->one);
+ $temp = $primes[$i]->subtract(self::$one);
// textbook RSA implementations use Euler's totient function instead of the least common multiple.
// see http://en.wikipedia.org/wiki/Euler%27s_totient_function
$lcm['top'] = $lcm['top']->multiply($temp);
$lcm['bottom'] = $lcm['bottom'] === false ? $temp : $lcm['bottom']->gcd($temp);
-
- $exponents[$i] = $e->modInverse($temp);
}
list($temp) = $lcm['top']->divide($lcm['bottom']);
$gcd = $temp->gcd($e);
$i0 = 1;
- } while (!$gcd->equals($this->one));
+ } while (!$gcd->equals(self::$one));
+
+ $coefficients[2] = $primes[2]->modInverse($primes[1]);
$d = $e->modInverse($temp);
- $coefficients[2] = $primes[2]->modInverse($primes[1]);
+ foreach ($primes as $i => $prime) {
+ $temp = $prime->subtract(self::$one);
+ $exponents[$i] = $e->modInverse($temp);
+ }
// from <http://tools.ietf.org/html/rfc3447#appendix-A.1.2>:
// RSAPrivateKey ::= SEQUENCE {
@@ -704,1575 +409,99 @@ class RSA
// coefficient INTEGER, -- (inverse of q) mod p
// otherPrimeInfos OtherPrimeInfos OPTIONAL
// }
+ $privatekey = new PrivateKey();
+ $privatekey->modulus = $n;
+ $privatekey->k = $bits >> 3;
+ $privatekey->publicExponent = $e;
+ $privatekey->exponent = $d;
+ $privatekey->primes = $primes;
+ $privatekey->exponents = $exponents;
+ $privatekey->coefficients = $coefficients;
- return array(
- 'privatekey' => $this->_convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients),
- 'publickey' => $this->_convertPublicKey($n, $e),
- 'partialkey' => false
- );
- }
-
- /**
- * Convert a private key to the appropriate format.
- *
- * @access private
- * @see self::setPrivateKeyFormat()
- * @param Math_BigInteger $n
- * @param Math_BigInteger $e
- * @param Math_BigInteger $d
- * @param array<int,Math_BigInteger> $primes
- * @param array<int,Math_BigInteger> $exponents
- * @param array<int,Math_BigInteger> $coefficients
- * @return string
- */
- function _convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients)
- {
- $signed = $this->privateKeyFormat != self::PRIVATE_FORMAT_XML;
- $num_primes = count($primes);
- $raw = array(
- 'version' => $num_primes == 2 ? chr(0) : chr(1), // two-prime vs. multi
- 'modulus' => $n->toBytes($signed),
- 'publicExponent' => $e->toBytes($signed),
- 'privateExponent' => $d->toBytes($signed),
- 'prime1' => $primes[1]->toBytes($signed),
- 'prime2' => $primes[2]->toBytes($signed),
- 'exponent1' => $exponents[1]->toBytes($signed),
- 'exponent2' => $exponents[2]->toBytes($signed),
- 'coefficient' => $coefficients[2]->toBytes($signed)
- );
-
- // if the format in question does not support multi-prime rsa and multi-prime rsa was used,
- // call _convertPublicKey() instead.
- switch ($this->privateKeyFormat) {
- case self::PRIVATE_FORMAT_XML:
- if ($num_primes != 2) {
- return false;
- }
- return "<RSAKeyValue>\r\n" .
- ' <Modulus>' . base64_encode($raw['modulus']) . "</Modulus>\r\n" .
- ' <Exponent>' . base64_encode($raw['publicExponent']) . "</Exponent>\r\n" .
- ' <P>' . base64_encode($raw['prime1']) . "</P>\r\n" .
- ' <Q>' . base64_encode($raw['prime2']) . "</Q>\r\n" .
- ' <DP>' . base64_encode($raw['exponent1']) . "</DP>\r\n" .
- ' <DQ>' . base64_encode($raw['exponent2']) . "</DQ>\r\n" .
- ' <InverseQ>' . base64_encode($raw['coefficient']) . "</InverseQ>\r\n" .
- ' <D>' . base64_encode($raw['privateExponent']) . "</D>\r\n" .
- '</RSAKeyValue>';
- break;
- case self::PRIVATE_FORMAT_PUTTY:
- if ($num_primes != 2) {
- return false;
- }
- $key = "PuTTY-User-Key-File-2: ssh-rsa\r\nEncryption: ";
- $encryption = (!empty($this->password) || is_string($this->password)) ? 'aes256-cbc' : 'none';
- $key.= $encryption;
- $key.= "\r\nComment: " . $this->comment . "\r\n";
- $public = pack(
- 'Na*Na*Na*',
- strlen('ssh-rsa'),
- 'ssh-rsa',
- strlen($raw['publicExponent']),
- $raw['publicExponent'],
- strlen($raw['modulus']),
- $raw['modulus']
- );
- $source = pack(
- 'Na*Na*Na*Na*',
- strlen('ssh-rsa'),
- 'ssh-rsa',
- strlen($encryption),
- $encryption,
- strlen($this->comment),
- $this->comment,
- strlen($public),
- $public
- );
- $public = base64_encode($public);
- $key.= "Public-Lines: " . ((strlen($public) + 63) >> 6) . "\r\n";
- $key.= chunk_split($public, 64);
- $private = pack(
- 'Na*Na*Na*Na*',
- strlen($raw['privateExponent']),
- $raw['privateExponent'],
- strlen($raw['prime1']),
- $raw['prime1'],
- strlen($raw['prime2']),
- $raw['prime2'],
- strlen($raw['coefficient']),
- $raw['coefficient']
- );
- if (empty($this->password) && !is_string($this->password)) {
- $source.= pack('Na*', strlen($private), $private);
- $hashkey = 'putty-private-key-file-mac-key';
- } else {
- $private.= Random::string(16 - (strlen($private) & 15));
- $source.= pack('Na*', strlen($private), $private);
- $sequence = 0;
- $symkey = '';
- while (strlen($symkey) < 32) {
- $temp = pack('Na*', $sequence++, $this->password);
- $symkey.= pack('H*', sha1($temp));
- }
- $symkey = substr($symkey, 0, 32);
- $crypto = new AES();
-
- $crypto->setKey($symkey);
- $crypto->disablePadding();
- $private = $crypto->encrypt($private);
- $hashkey = 'putty-private-key-file-mac-key' . $this->password;
- }
-
- $private = base64_encode($private);
- $key.= 'Private-Lines: ' . ((strlen($private) + 63) >> 6) . "\r\n";
- $key.= chunk_split($private, 64);
- $hash = new Hash('sha1');
- $hash->setKey(pack('H*', sha1($hashkey)));
- $key.= 'Private-MAC: ' . bin2hex($hash->hash($source)) . "\r\n";
-
- return $key;
- case self::PRIVATE_FORMAT_OPENSSH:
- if ($num_primes != 2) {
- return false;
- }
- $publicKey = pack('Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($raw['publicExponent']), $raw['publicExponent'], strlen($raw['modulus']), $raw['modulus']);
- $privateKey = pack(
- 'Na*Na*Na*Na*Na*Na*Na*',
- strlen('ssh-rsa'),
- 'ssh-rsa',
- strlen($raw['modulus']),
- $raw['modulus'],
- strlen($raw['publicExponent']),
- $raw['publicExponent'],
- strlen($raw['privateExponent']),
- $raw['privateExponent'],
- strlen($raw['coefficient']),
- $raw['coefficient'],
- strlen($raw['prime1']),
- $raw['prime1'],
- strlen($raw['prime2']),
- $raw['prime2']
- );
- $checkint = Random::string(4);
- $paddedKey = pack(
- 'a*Na*',
- $checkint . $checkint . $privateKey,
- strlen($this->comment),
- $this->comment
- );
- $paddingLength = (7 * strlen($paddedKey)) % 8;
- for ($i = 1; $i <= $paddingLength; $i++) {
- $paddedKey.= chr($i);
- }
- $key = pack(
- 'Na*Na*Na*NNa*Na*',
- strlen('none'),
- 'none',
- strlen('none'),
- 'none',
- 0,
- '',
- 1,
- strlen($publicKey),
- $publicKey,
- strlen($paddedKey),
- $paddedKey
- );
- $key = "openssh-key-v1\0$key";
-
- return "-----BEGIN OPENSSH PRIVATE KEY-----\n" .
- chunk_split(base64_encode($key), 70, "\n") .
- "-----END OPENSSH PRIVATE KEY-----\n";
- default: // eg. self::PRIVATE_FORMAT_PKCS1
- $components = array();
- foreach ($raw as $name => $value) {
- $components[$name] = pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($value)), $value);
- }
-
- $RSAPrivateKey = implode('', $components);
-
- if ($num_primes > 2) {
- $OtherPrimeInfos = '';
- for ($i = 3; $i <= $num_primes; $i++) {
- // OtherPrimeInfos ::= SEQUENCE SIZE(1..MAX) OF OtherPrimeInfo
- //
- // OtherPrimeInfo ::= SEQUENCE {
- // prime INTEGER, -- ri
- // exponent INTEGER, -- di
- // coefficient INTEGER -- ti
- // }
- $OtherPrimeInfo = pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($primes[$i]->toBytes(true))), $primes[$i]->toBytes(true));
- $OtherPrimeInfo.= pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($exponents[$i]->toBytes(true))), $exponents[$i]->toBytes(true));
- $OtherPrimeInfo.= pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($coefficients[$i]->toBytes(true))), $coefficients[$i]->toBytes(true));
- $OtherPrimeInfos.= pack('Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($OtherPrimeInfo)), $OtherPrimeInfo);
- }
- $RSAPrivateKey.= pack('Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($OtherPrimeInfos)), $OtherPrimeInfos);
- }
-
- $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey);
-
- if ($this->privateKeyFormat == self::PRIVATE_FORMAT_PKCS8) {
- $rsaOID = pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA
- $RSAPrivateKey = pack(
- 'Ca*a*Ca*a*',
- self::ASN1_INTEGER,
- "\01\00",
- $rsaOID,
- 4,
- $this->_encodeLength(strlen($RSAPrivateKey)),
- $RSAPrivateKey
- );
- $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey);
- if (!empty($this->password) || is_string($this->password)) {
- $salt = Random::string(8);
- $iterationCount = 2048;
-
- $crypto = new DES();
- $crypto->setPassword($this->password, 'pbkdf1', 'md5', $salt, $iterationCount);
- $RSAPrivateKey = $crypto->encrypt($RSAPrivateKey);
-
- $parameters = pack(
- 'Ca*a*Ca*N',
- self::ASN1_OCTETSTRING,
- $this->_encodeLength(strlen($salt)),
- $salt,
- self::ASN1_INTEGER,
- $this->_encodeLength(4),
- $iterationCount
- );
- $pbeWithMD5AndDES_CBC = "\x2a\x86\x48\x86\xf7\x0d\x01\x05\x03";
-
- $encryptionAlgorithm = pack(
- 'Ca*a*Ca*a*',
- self::ASN1_OBJECT,
- $this->_encodeLength(strlen($pbeWithMD5AndDES_CBC)),
- $pbeWithMD5AndDES_CBC,
- self::ASN1_SEQUENCE,
- $this->_encodeLength(strlen($parameters)),
- $parameters
- );
-
- $RSAPrivateKey = pack(
- 'Ca*a*Ca*a*',
- self::ASN1_SEQUENCE,
- $this->_encodeLength(strlen($encryptionAlgorithm)),
- $encryptionAlgorithm,
- self::ASN1_OCTETSTRING,
- $this->_encodeLength(strlen($RSAPrivateKey)),
- $RSAPrivateKey
- );
-
- $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey);
-
- $RSAPrivateKey = "-----BEGIN ENCRYPTED PRIVATE KEY-----\r\n" .
- chunk_split(base64_encode($RSAPrivateKey), 64) .
- '-----END ENCRYPTED PRIVATE KEY-----';
- } else {
- $RSAPrivateKey = "-----BEGIN PRIVATE KEY-----\r\n" .
- chunk_split(base64_encode($RSAPrivateKey), 64) .
- '-----END PRIVATE KEY-----';
- }
- return $RSAPrivateKey;
- }
-
- if (!empty($this->password) || is_string($this->password)) {
- $iv = Random::string(8);
- $symkey = pack('H*', md5($this->password . $iv)); // symkey is short for symmetric key
- $symkey.= substr(pack('H*', md5($symkey . $this->password . $iv)), 0, 8);
- $des = new TripleDES();
- $des->setKey($symkey);
- $des->setIV($iv);
- $iv = strtoupper(bin2hex($iv));
- $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" .
- "Proc-Type: 4,ENCRYPTED\r\n" .
- "DEK-Info: DES-EDE3-CBC,$iv\r\n" .
- "\r\n" .
- chunk_split(base64_encode($des->encrypt($RSAPrivateKey)), 64) .
- '-----END RSA PRIVATE KEY-----';
- } else {
- $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" .
- chunk_split(base64_encode($RSAPrivateKey), 64) .
- '-----END RSA PRIVATE KEY-----';
- }
+ /*
+ $publickey = new PublicKey;
+ $publickey->modulus = $n;
+ $publickey->k = $bits >> 3;
+ $publickey->exponent = $e;
+ $publickey->publicExponent = $e;
+ $publickey->isPublic = true;
+ */
- return $RSAPrivateKey;
- }
+ return $privatekey;
}
/**
- * Convert a public key to the appropriate format
+ * OnLoad Handler
*
- * @access private
- * @see self::setPublicKeyFormat()
- * @param Math_BigInteger $n
- * @param Math_BigInteger $e
- * @return string|array<string,Math_BigInteger>
- */
- function _convertPublicKey($n, $e)
- {
- $signed = $this->publicKeyFormat != self::PUBLIC_FORMAT_XML;
-
- $modulus = $n->toBytes($signed);
- $publicExponent = $e->toBytes($signed);
-
- switch ($this->publicKeyFormat) {
- case self::PUBLIC_FORMAT_RAW:
- return array('e' => $e->copy(), 'n' => $n->copy());
- case self::PUBLIC_FORMAT_XML:
- return "<RSAKeyValue>\r\n" .
- ' <Modulus>' . base64_encode($modulus) . "</Modulus>\r\n" .
- ' <Exponent>' . base64_encode($publicExponent) . "</Exponent>\r\n" .
- '</RSAKeyValue>';
- break;
- case self::PUBLIC_FORMAT_OPENSSH:
- // from <http://tools.ietf.org/html/rfc4253#page-15>:
- // string "ssh-rsa"
- // mpint e
- // mpint n
- $RSAPublicKey = pack('Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($publicExponent), $publicExponent, strlen($modulus), $modulus);
- $RSAPublicKey = 'ssh-rsa ' . base64_encode($RSAPublicKey) . ' ' . $this->comment;
-
- return $RSAPublicKey;
- default: // eg. self::PUBLIC_FORMAT_PKCS1_RAW or self::PUBLIC_FORMAT_PKCS1
- // from <http://tools.ietf.org/html/rfc3447#appendix-A.1.1>:
- // RSAPublicKey ::= SEQUENCE {
- // modulus INTEGER, -- n
- // publicExponent INTEGER -- e
- // }
- $components = array(
- 'modulus' => pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($modulus)), $modulus),
- 'publicExponent' => pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($publicExponent)), $publicExponent)
- );
-
- $RSAPublicKey = pack(
- 'Ca*a*a*',
- self::ASN1_SEQUENCE,
- $this->_encodeLength(strlen($components['modulus']) + strlen($components['publicExponent'])),
- $components['modulus'],
- $components['publicExponent']
- );
-
- if ($this->publicKeyFormat == self::PUBLIC_FORMAT_PKCS1_RAW) {
- $RSAPublicKey = "-----BEGIN RSA PUBLIC KEY-----\r\n" .
- chunk_split(base64_encode($RSAPublicKey), 64) .
- '-----END RSA PUBLIC KEY-----';
- } else {
- // sequence(oid(1.2.840.113549.1.1.1), null)) = rsaEncryption.
- $rsaOID = pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA
- $RSAPublicKey = chr(0) . $RSAPublicKey;
- $RSAPublicKey = chr(3) . $this->_encodeLength(strlen($RSAPublicKey)) . $RSAPublicKey;
-
- $RSAPublicKey = pack(
- 'Ca*a*',
- self::ASN1_SEQUENCE,
- $this->_encodeLength(strlen($rsaOID . $RSAPublicKey)),
- $rsaOID . $RSAPublicKey
- );
-
- $RSAPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" .
- chunk_split(base64_encode($RSAPublicKey), 64) .
- '-----END PUBLIC KEY-----';
- }
-
- return $RSAPublicKey;
- }
- }
-
- /**
- * Break a public or private key down into its constituant components
- *
- * @access private
- * @see self::_convertPublicKey()
- * @see self::_convertPrivateKey()
- * @param string|array $key
- * @param int $type
- * @return array|bool
- */
- function _parseKey($key, $type)
- {
- if ($type != self::PUBLIC_FORMAT_RAW && !is_string($key)) {
- return false;
- }
-
- switch ($type) {
- case self::PUBLIC_FORMAT_RAW:
- if (!is_array($key)) {
- return false;
- }
- $components = array();
- switch (true) {
- case isset($key['e']):
- $components['publicExponent'] = $key['e']->copy();
- break;
- case isset($key['exponent']):
- $components['publicExponent'] = $key['exponent']->copy();
- break;
- case isset($key['publicExponent']):
- $components['publicExponent'] = $key['publicExponent']->copy();
- break;
- case isset($key[0]):
- $components['publicExponent'] = $key[0]->copy();
- }
- switch (true) {
- case isset($key['n']):
- $components['modulus'] = $key['n']->copy();
- break;
- case isset($key['modulo']):
- $components['modulus'] = $key['modulo']->copy();
- break;
- case isset($key['modulus']):
- $components['modulus'] = $key['modulus']->copy();
- break;
- case isset($key[1]):
- $components['modulus'] = $key[1]->copy();
- }
- return isset($components['modulus']) && isset($components['publicExponent']) ? $components : false;
- case self::PRIVATE_FORMAT_PKCS1:
- case self::PRIVATE_FORMAT_PKCS8:
- case self::PUBLIC_FORMAT_PKCS1:
- /* Although PKCS#1 proposes a format that public and private keys can use, encrypting them is
- "outside the scope" of PKCS#1. PKCS#1 then refers you to PKCS#12 and PKCS#15 if you're wanting to
- protect private keys, however, that's not what OpenSSL* does. OpenSSL protects private keys by adding
- two new "fields" to the key - DEK-Info and Proc-Type. These fields are discussed here:
-
- http://tools.ietf.org/html/rfc1421#section-4.6.1.1
- http://tools.ietf.org/html/rfc1421#section-4.6.1.3
-
- DES-EDE3-CBC as an algorithm, however, is not discussed anywhere, near as I can tell.
- DES-CBC and DES-EDE are discussed in RFC1423, however, DES-EDE3-CBC isn't, nor is its key derivation
- function. As is, the definitive authority on this encoding scheme isn't the IETF but rather OpenSSL's
- own implementation. ie. the implementation *is* the standard and any bugs that may exist in that
- implementation are part of the standard, as well.
-
- * OpenSSL is the de facto standard. It's utilized by OpenSSH and other projects */
- if (preg_match('#DEK-Info: (.+),(.+)#', $key, $matches)) {
- $iv = pack('H*', trim($matches[2]));
- $symkey = pack('H*', md5($this->password . substr($iv, 0, 8))); // symkey is short for symmetric key
- $symkey.= pack('H*', md5($symkey . $this->password . substr($iv, 0, 8)));
- // remove the Proc-Type / DEK-Info sections as they're no longer needed
- $key = preg_replace('#^(?:Proc-Type|DEK-Info): .*#m', '', $key);
- $ciphertext = $this->_extractBER($key);
- if ($ciphertext === false) {
- $ciphertext = $key;
- }
- switch ($matches[1]) {
- case 'AES-256-CBC':
- $crypto = new AES();
- break;
- case 'AES-128-CBC':
- $symkey = substr($symkey, 0, 16);
- $crypto = new AES();
- break;
- case 'DES-EDE3-CFB':
- $crypto = new TripleDES(Base::MODE_CFB);
- break;
- case 'DES-EDE3-CBC':
- $symkey = substr($symkey, 0, 24);
- $crypto = new TripleDES();
- break;
- case 'DES-CBC':
- $crypto = new DES();
- break;
- default:
- return false;
- }
- $crypto->setKey($symkey);
- $crypto->setIV($iv);
- $decoded = $crypto->decrypt($ciphertext);
- } else {
- $decoded = $this->_extractBER($key);
- }
-
- if ($decoded !== false) {
- $key = $decoded;
- }
-
- $components = array();
-
- if (ord($this->_string_shift($key)) != self::ASN1_SEQUENCE) {
- return false;
- }
- if ($this->_decodeLength($key) != strlen($key)) {
- return false;
- }
-
- $tag = ord($this->_string_shift($key));
- /* intended for keys for which OpenSSL's asn1parse returns the following:
-
- 0:d=0 hl=4 l= 631 cons: SEQUENCE
- 4:d=1 hl=2 l= 1 prim: INTEGER :00
- 7:d=1 hl=2 l= 13 cons: SEQUENCE
- 9:d=2 hl=2 l= 9 prim: OBJECT :rsaEncryption
- 20:d=2 hl=2 l= 0 prim: NULL
- 22:d=1 hl=4 l= 609 prim: OCTET STRING
-
- ie. PKCS8 keys*/
-
- if ($tag == self::ASN1_INTEGER && substr($key, 0, 3) == "\x01\x00\x30") {
- $this->_string_shift($key, 3);
- $tag = self::ASN1_SEQUENCE;
- }
-
- if ($tag == self::ASN1_SEQUENCE) {
- $temp = $this->_string_shift($key, $this->_decodeLength($key));
- if (ord($this->_string_shift($temp)) != self::ASN1_OBJECT) {
- return false;
- }
- $length = $this->_decodeLength($temp);
- switch ($this->_string_shift($temp, $length)) {
- case "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01": // rsaEncryption
- case "\x2A\x86\x48\x86\xF7\x0D\x01\x01\x0A": // rsaPSS
- break;
- case "\x2a\x86\x48\x86\xf7\x0d\x01\x05\x03": // pbeWithMD5AndDES-CBC
- /*
- PBEParameter ::= SEQUENCE {
- salt OCTET STRING (SIZE(8)),
- iterationCount INTEGER }
- */
- if (ord($this->_string_shift($temp)) != self::ASN1_SEQUENCE) {
- return false;
- }
- if ($this->_decodeLength($temp) != strlen($temp)) {
- return false;
- }
- $this->_string_shift($temp); // assume it's an octet string
- $salt = $this->_string_shift($temp, $this->_decodeLength($temp));
- if (ord($this->_string_shift($temp)) != self::ASN1_INTEGER) {
- return false;
- }
- $this->_decodeLength($temp);
- list(, $iterationCount) = unpack('N', str_pad($temp, 4, chr(0), STR_PAD_LEFT));
- $this->_string_shift($key); // assume it's an octet string
- $length = $this->_decodeLength($key);
- if (strlen($key) != $length) {
- return false;
- }
-
- $crypto = new DES();
- $crypto->setPassword($this->password, 'pbkdf1', 'md5', $salt, $iterationCount);
- $key = $crypto->decrypt($key);
- if ($key === false) {
- return false;
- }
- return $this->_parseKey($key, self::PRIVATE_FORMAT_PKCS1);
- default:
- return false;
- }
- /* intended for keys for which OpenSSL's asn1parse returns the following:
-
- 0:d=0 hl=4 l= 290 cons: SEQUENCE
- 4:d=1 hl=2 l= 13 cons: SEQUENCE
- 6:d=2 hl=2 l= 9 prim: OBJECT :rsaEncryption
- 17:d=2 hl=2 l= 0 prim: NULL
- 19:d=1 hl=4 l= 271 prim: BIT STRING */
- $tag = ord($this->_string_shift($key)); // skip over the BIT STRING / OCTET STRING tag
- $this->_decodeLength($key); // skip over the BIT STRING / OCTET STRING length
- // "The initial octet shall encode, as an unsigned binary integer wtih bit 1 as the least significant bit, the number of
- // unused bits in the final subsequent octet. The number shall be in the range zero to seven."
- // -- http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf (section 8.6.2.2)
- if ($tag == self::ASN1_BITSTRING) {
- $this->_string_shift($key);
- }
- if (ord($this->_string_shift($key)) != self::ASN1_SEQUENCE) {
- return false;
- }
- if ($this->_decodeLength($key) != strlen($key)) {
- return false;
- }
- $tag = ord($this->_string_shift($key));
- }
- if ($tag != self::ASN1_INTEGER) {
- return false;
- }
-
- $length = $this->_decodeLength($key);
- $temp = $this->_string_shift($key, $length);
- if (strlen($temp) != 1 || ord($temp) > 2) {
- $components['modulus'] = new BigInteger($temp, 256);
- $this->_string_shift($key); // skip over self::ASN1_INTEGER
- $length = $this->_decodeLength($key);
- $components[$type == self::PUBLIC_FORMAT_PKCS1 ? 'publicExponent' : 'privateExponent'] = new BigInteger($this->_string_shift($key, $length), 256);
-
- return $components;
- }
- if (ord($this->_string_shift($key)) != self::ASN1_INTEGER) {
- return false;
- }
- $length = $this->_decodeLength($key);
- $components['modulus'] = new BigInteger($this->_string_shift($key, $length), 256);
- $this->_string_shift($key);
- $length = $this->_decodeLength($key);
- $components['publicExponent'] = new BigInteger($this->_string_shift($key, $length), 256);
- $this->_string_shift($key);
- $length = $this->_decodeLength($key);
- $components['privateExponent'] = new BigInteger($this->_string_shift($key, $length), 256);
- $this->_string_shift($key);
- $length = $this->_decodeLength($key);
- $components['primes'] = array(1 => new BigInteger($this->_string_shift($key, $length), 256));
- $this->_string_shift($key);
- $length = $this->_decodeLength($key);
- $components['primes'][] = new BigInteger($this->_string_shift($key, $length), 256);
- $this->_string_shift($key);
- $length = $this->_decodeLength($key);
- $components['exponents'] = array(1 => new BigInteger($this->_string_shift($key, $length), 256));
- $this->_string_shift($key);
- $length = $this->_decodeLength($key);
- $components['exponents'][] = new BigInteger($this->_string_shift($key, $length), 256);
- $this->_string_shift($key);
- $length = $this->_decodeLength($key);
- $components['coefficients'] = array(2 => new BigInteger($this->_string_shift($key, $length), 256));
-
- if (!empty($key)) {
- if (ord($this->_string_shift($key)) != self::ASN1_SEQUENCE) {
- return false;
- }
- $this->_decodeLength($key);
- while (!empty($key)) {
- if (ord($this->_string_shift($key)) != self::ASN1_SEQUENCE) {
- return false;
- }
- $this->_decodeLength($key);
- $key = substr($key, 1);
- $length = $this->_decodeLength($key);
- $components['primes'][] = new BigInteger($this->_string_shift($key, $length), 256);
- $this->_string_shift($key);
- $length = $this->_decodeLength($key);
- $components['exponents'][] = new BigInteger($this->_string_shift($key, $length), 256);
- $this->_string_shift($key);
- $length = $this->_decodeLength($key);
- $components['coefficients'][] = new BigInteger($this->_string_shift($key, $length), 256);
- }
- }
-
- return $components;
- case self::PUBLIC_FORMAT_OPENSSH:
- $parts = explode(' ', $key, 3);
-
- $key = isset($parts[1]) ? base64_decode($parts[1]) : false;
- if ($key === false) {
- return false;
- }
-
- $comment = isset($parts[2]) ? $parts[2] : false;
-
- $cleanup = substr($key, 0, 11) == "\0\0\0\7ssh-rsa";
-
- if (strlen($key) <= 4) {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($key, 4)));
- $publicExponent = new BigInteger($this->_string_shift($key, $length), -256);
- if (strlen($key) <= 4) {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($key, 4)));
- $modulus = new BigInteger($this->_string_shift($key, $length), -256);
-
- if ($cleanup && strlen($key)) {
- if (strlen($key) <= 4) {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($key, 4)));
- $realModulus = new BigInteger($this->_string_shift($key, $length), -256);
- return strlen($key) ? false : array(
- 'modulus' => $realModulus,
- 'publicExponent' => $modulus,
- 'comment' => $comment
- );
- } else {
- return strlen($key) ? false : array(
- 'modulus' => $modulus,
- 'publicExponent' => $publicExponent,
- 'comment' => $comment
- );
- }
- // http://www.w3.org/TR/xmldsig-core/#sec-RSAKeyValue
- // http://en.wikipedia.org/wiki/XML_Signature
- case self::PRIVATE_FORMAT_XML:
- case self::PUBLIC_FORMAT_XML:
- if (!extension_loaded('xml')) {
- return false;
- }
-
- $this->components = array();
-
- $xml = xml_parser_create('UTF-8');
- if (version_compare(PHP_VERSION, '8.4.0', '>=')) {
- xml_set_element_handler($xml, array($this, '_start_element_handler'), array($this, '_stop_element_handler'));
- xml_set_character_data_handler($xml, array($this, '_data_handler'));
- } else {
- xml_set_object($xml, $this);
- xml_set_element_handler($xml, '_start_element_handler', '_stop_element_handler');
- xml_set_character_data_handler($xml, '_data_handler');
- }
- // add <xml></xml> to account for "dangling" tags like <BitStrength>...</BitStrength> that are sometimes added
- if (!xml_parse($xml, '<xml>' . $key . '</xml>')) {
- xml_parser_free($xml);
- unset($xml);
- return false;
- }
-
- xml_parser_free($xml);
- unset($xml);
-
- return isset($this->components['modulus']) && isset($this->components['publicExponent']) ? $this->components : false;
- // see PuTTY's SSHPUBK.C and https://tartarus.org/~simon/putty-snapshots/htmldoc/AppendixC.html
- case self::PRIVATE_FORMAT_PUTTY:
- $components = array();
- $key = preg_split('#\r\n|\r|\n#', $key);
- if ($this->_string_shift($key[0], strlen('PuTTY-User-Key-File-')) != 'PuTTY-User-Key-File-') {
- return false;
- }
- $version = (int) $this->_string_shift($key[0], 3); // should be either "2: " or "3: 0" prior to int casting
- if ($version != 2 && $version != 3) {
- return false;
- }
- $type = rtrim($key[0]);
- if ($type != 'ssh-rsa') {
- return false;
- }
- $encryption = trim(preg_replace('#Encryption: (.+)#', '$1', $key[1]));
- $comment = trim(preg_replace('#Comment: (.+)#', '$1', $key[2]));
-
- $publicLength = trim(preg_replace('#Public-Lines: (\d+)#', '$1', $key[3]));
- $public = base64_decode(implode('', array_map('trim', array_slice($key, 4, $publicLength))));
- $public = substr($public, 11);
- extract(unpack('Nlength', $this->_string_shift($public, 4)));
- $components['publicExponent'] = new BigInteger($this->_string_shift($public, $length), -256);
- extract(unpack('Nlength', $this->_string_shift($public, 4)));
- $components['modulus'] = new BigInteger($this->_string_shift($public, $length), -256);
-
- $offset = $publicLength + 4;
- switch ($encryption) {
- case 'aes256-cbc':
- $crypto = new AES();
- switch ($version) {
- case 3:
- if (!function_exists('sodium_crypto_pwhash')) {
- return false;
- }
- $flavour = trim(preg_replace('#Key-Derivation: (.*)#', '$1', $key[$offset++]));
- switch ($flavour) {
- case 'Argon2i':
- $flavour = SODIUM_CRYPTO_PWHASH_ALG_ARGON2I13;
- break;
- case 'Argon2id':
- $flavour = SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13;
- break;
- default:
- return false;
- }
- $memory = trim(preg_replace('#Argon2-Memory: (\d+)#', '$1', $key[$offset++]));
- $passes = trim(preg_replace('#Argon2-Passes: (\d+)#', '$1', $key[$offset++]));
- $parallelism = trim(preg_replace('#Argon2-Parallelism: (\d+)#', '$1', $key[$offset++]));
- $salt = pack('H*', trim(preg_replace('#Argon2-Salt: ([0-9a-f]+)#', '$1', $key[$offset++])));
-
- $length = 80; // keylen + ivlen + mac_keylen
- $temp = sodium_crypto_pwhash($length, $this->password, $salt, $passes, $memory << 10, $flavour);
-
- $symkey = substr($temp, 0, 32);
- $symiv = substr($temp, 32, 16);
- break;
- case 2:
- $symkey = '';
- $sequence = 0;
- while (strlen($symkey) < 32) {
- $temp = pack('Na*', $sequence++, $this->password);
- $symkey.= pack('H*', sha1($temp));
- }
- $symkey = substr($symkey, 0, 32);
- $symiv = str_repeat("\0", 16);
- }
- }
-
- $privateLength = trim(preg_replace('#Private-Lines: (\d+)#', '$1', $key[$offset++]));
- $private = base64_decode(implode('', array_map('trim', array_slice($key, $offset, $privateLength))));
-
- if ($encryption != 'none') {
- $crypto->setKey($symkey);
- $crypto->setIV($symiv);
- $crypto->disablePadding();
- $private = $crypto->decrypt($private);
- if ($private === false) {
- return false;
- }
- }
-
- extract(unpack('Nlength', $this->_string_shift($private, 4)));
- if (strlen($private) < $length) {
- return false;
- }
- $components['privateExponent'] = new BigInteger($this->_string_shift($private, $length), -256);
- extract(unpack('Nlength', $this->_string_shift($private, 4)));
- if (strlen($private) < $length) {
- return false;
- }
- $components['primes'] = array(1 => new BigInteger($this->_string_shift($private, $length), -256));
- extract(unpack('Nlength', $this->_string_shift($private, 4)));
- if (strlen($private) < $length) {
- return false;
- }
- $components['primes'][] = new BigInteger($this->_string_shift($private, $length), -256);
-
- $temp = $components['primes'][1]->subtract($this->one);
- $components['exponents'] = array(1 => $components['publicExponent']->modInverse($temp));
- $temp = $components['primes'][2]->subtract($this->one);
- $components['exponents'][] = $components['publicExponent']->modInverse($temp);
-
- extract(unpack('Nlength', $this->_string_shift($private, 4)));
- if (strlen($private) < $length) {
- return false;
- }
- $components['coefficients'] = array(2 => new BigInteger($this->_string_shift($private, $length), -256));
-
- return $components;
- case self::PRIVATE_FORMAT_OPENSSH:
- $components = array();
- $decoded = $this->_extractBER($key);
- $magic = $this->_string_shift($decoded, 15);
- if ($magic !== "openssh-key-v1\0") {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($decoded, 4)));
- if (strlen($decoded) < $length) {
- return false;
- }
- $ciphername = $this->_string_shift($decoded, $length);
- extract(unpack('Nlength', $this->_string_shift($decoded, 4)));
- if (strlen($decoded) < $length) {
- return false;
- }
- $kdfname = $this->_string_shift($decoded, $length);
- extract(unpack('Nlength', $this->_string_shift($decoded, 4)));
- if (strlen($decoded) < $length) {
- return false;
- }
- $kdfoptions = $this->_string_shift($decoded, $length);
- extract(unpack('Nnumkeys', $this->_string_shift($decoded, 4)));
- if ($numkeys != 1 || ($ciphername != 'none' && $kdfname != 'bcrypt')) {
- return false;
- }
- switch ($ciphername) {
- case 'none':
- break;
- case 'aes256-ctr':
- extract(unpack('Nlength', $this->_string_shift($kdfoptions, 4)));
- if (strlen($kdfoptions) < $length) {
- return false;
- }
- $salt = $this->_string_shift($kdfoptions, $length);
- extract(unpack('Nrounds', $this->_string_shift($kdfoptions, 4)));
- $crypto = new AES(AES::MODE_CTR);
- $crypto->disablePadding();
- if (!$crypto->setPassword($this->password, 'bcrypt', $salt, $rounds, 32)) {
- return false;
- }
- break;
- default:
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($decoded, 4)));
- if (strlen($decoded) < $length) {
- return false;
- }
- $publicKey = $this->_string_shift($decoded, $length);
- extract(unpack('Nlength', $this->_string_shift($decoded, 4)));
- if (strlen($decoded) < $length) {
- return false;
- }
-
- if ($this->_string_shift($publicKey, 11) !== "\0\0\0\7ssh-rsa") {
- return false;
- }
-
- $paddedKey = $this->_string_shift($decoded, $length);
- if (isset($crypto)) {
- $paddedKey = $crypto->decrypt($paddedKey);
- }
-
- $checkint1 = $this->_string_shift($paddedKey, 4);
- $checkint2 = $this->_string_shift($paddedKey, 4);
- if (strlen($checkint1) != 4 || $checkint1 !== $checkint2) {
- return false;
- }
-
- if ($this->_string_shift($paddedKey, 11) !== "\0\0\0\7ssh-rsa") {
- return false;
- }
-
- $values = array(
- &$components['modulus'],
- &$components['publicExponent'],
- &$components['privateExponent'],
- &$components['coefficients'][2],
- &$components['primes'][1],
- &$components['primes'][2]
- );
-
- foreach ($values as &$value) {
- extract(unpack('Nlength', $this->_string_shift($paddedKey, 4)));
- if (strlen($paddedKey) < $length) {
- return false;
- }
- $value = new BigInteger($this->_string_shift($paddedKey, $length), -256);
- }
-
- extract(unpack('Nlength', $this->_string_shift($paddedKey, 4)));
- if (strlen($paddedKey) < $length) {
- return false;
- }
- $components['comment'] = $this->_string_shift($decoded, $length);
-
- $temp = $components['primes'][1]->subtract($this->one);
- $components['exponents'] = array(1 => $components['publicExponent']->modInverse($temp));
- $temp = $components['primes'][2]->subtract($this->one);
- $components['exponents'][] = $components['publicExponent']->modInverse($temp);
-
- return $components;
- }
-
- return false;
- }
-
- /**
- * Returns the key size
- *
- * More specifically, this returns the size of the modulo in bits.
- *
- * @access public
- * @return int
- */
- function getSize()
- {
- return !isset($this->modulus) ? 0 : strlen($this->modulus->toBits());
- }
-
- /**
- * Start Element Handler
- *
- * Called by xml_set_element_handler()
- *
- * @access private
- * @param resource $parser
- * @param string $name
- * @param array $attribs
- */
- function _start_element_handler($parser, $name, $attribs)
- {
- //$name = strtoupper($name);
- switch ($name) {
- case 'MODULUS':
- $this->current = &$this->components['modulus'];
- break;
- case 'EXPONENT':
- $this->current = &$this->components['publicExponent'];
- break;
- case 'P':
- $this->current = &$this->components['primes'][1];
- break;
- case 'Q':
- $this->current = &$this->components['primes'][2];
- break;
- case 'DP':
- $this->current = &$this->components['exponents'][1];
- break;
- case 'DQ':
- $this->current = &$this->components['exponents'][2];
- break;
- case 'INVERSEQ':
- $this->current = &$this->components['coefficients'][2];
- break;
- case 'D':
- $this->current = &$this->components['privateExponent'];
- }
- $this->current = '';
- }
-
- /**
- * Stop Element Handler
- *
- * Called by xml_set_element_handler()
- *
- * @access private
- * @param resource $parser
- * @param string $name
- */
- function _stop_element_handler($parser, $name)
- {
- if (isset($this->current)) {
- $this->current = new BigInteger(base64_decode($this->current), 256);
- unset($this->current);
- }
- }
-
- /**
- * Data Handler
- *
- * Called by xml_set_character_data_handler()
- *
- * @access private
- * @param resource $parser
- * @param string $data
- */
- function _data_handler($parser, $data)
- {
- if (!isset($this->current) || is_object($this->current)) {
- return;
- }
- $this->current.= trim($data);
- }
-
- /**
- * Loads a public or private key
- *
- * Returns true on success and false on failure (ie. an incorrect password was provided or the key was malformed)
- *
- * @access public
- * @param string|RSA|array $key
- * @param bool|int $type optional
* @return bool
*/
- function loadKey($key, $type = false)
+ protected static function onLoad(array $components)
{
- if ($key instanceof RSA) {
- $this->privateKeyFormat = $key->privateKeyFormat;
- $this->publicKeyFormat = $key->publicKeyFormat;
- $this->k = $key->k;
- $this->hLen = $key->hLen;
- $this->sLen = $key->sLen;
- $this->mgfHLen = $key->mgfHLen;
- $this->encryptionMode = $key->encryptionMode;
- $this->signatureMode = $key->signatureMode;
- $this->password = $key->password;
- $this->configFile = $key->configFile;
- $this->comment = $key->comment;
-
- if (is_object($key->hash)) {
- $this->hash = new Hash($key->hash->getHash());
- }
- if (is_object($key->mgfHash)) {
- $this->mgfHash = new Hash($key->mgfHash->getHash());
- }
+ $key = $components['isPublicKey'] ?
+ new PublicKey() :
+ new PrivateKey();
- if (is_object($key->modulus)) {
- $this->modulus = $key->modulus->copy();
- }
- if (is_object($key->exponent)) {
- $this->exponent = $key->exponent->copy();
- }
- if (is_object($key->publicExponent)) {
- $this->publicExponent = $key->publicExponent->copy();
- }
-
- $this->primes = array();
- $this->exponents = array();
- $this->coefficients = array();
+ $key->modulus = $components['modulus'];
+ $key->publicExponent = $components['publicExponent'];
+ $key->k = $key->modulus->getLengthInBytes();
- foreach ($this->primes as $prime) {
- $this->primes[] = $prime->copy();
- }
- foreach ($this->exponents as $exponent) {
- $this->exponents[] = $exponent->copy();
- }
- foreach ($this->coefficients as $coefficient) {
- $this->coefficients[] = $coefficient->copy();
+ if ($components['isPublicKey'] || !isset($components['privateExponent'])) {
+ $key->exponent = $key->publicExponent;
+ } else {
+ $key->privateExponent = $components['privateExponent'];
+ $key->exponent = $key->privateExponent;
+ $key->primes = $components['primes'];
+ $key->exponents = $components['exponents'];
+ $key->coefficients = $components['coefficients'];
+ }
+
+ if ($components['format'] == PSS::class) {
+ // in the X509 world RSA keys are assumed to use PKCS1 padding by default. only if the key is
+ // explicitly a PSS key is the use of PSS assumed. phpseclib does not work like this. phpseclib
+ // uses PSS padding by default. it assumes the more secure method by default and altho it provides
+ // for the less secure PKCS1 method you have to go out of your way to use it. this is consistent
+ // with the latest trends in crypto. libsodium (NaCl) is actually a little more extreme in that
+ // not only does it defaults to the most secure methods - it doesn't even let you choose less
+ // secure methods
+ //$key = $key->withPadding(self::SIGNATURE_PSS);
+ if (isset($components['hash'])) {
+ $key = $key->withHash($components['hash']);
}
-
- return true;
- }
-
- if ($type === false) {
- $types = array(
- self::PUBLIC_FORMAT_RAW,
- self::PRIVATE_FORMAT_PKCS1,
- self::PRIVATE_FORMAT_XML,
- self::PRIVATE_FORMAT_PUTTY,
- self::PUBLIC_FORMAT_OPENSSH,
- self::PRIVATE_FORMAT_OPENSSH
- );
- foreach ($types as $type) {
- $components = $this->_parseKey($key, $type);
- if ($components !== false) {
- break;
- }
+ if (isset($components['MGFHash'])) {
+ $key = $key->withMGFHash($components['MGFHash']);
}
- } else {
- $components = $this->_parseKey($key, $type);
- }
-
- if ($components === false) {
- $this->comment = null;
- $this->modulus = null;
- $this->k = null;
- $this->exponent = null;
- $this->primes = null;
- $this->exponents = null;
- $this->coefficients = null;
- $this->publicExponent = null;
-
- return false;
- }
-
- if (isset($components['comment']) && $components['comment'] !== false) {
- $this->comment = $components['comment'];
- }
- $this->modulus = $components['modulus'];
- $this->k = strlen($this->modulus->toBytes());
- $this->exponent = isset($components['privateExponent']) ? $components['privateExponent'] : $components['publicExponent'];
- if (isset($components['primes'])) {
- $this->primes = $components['primes'];
- $this->exponents = $components['exponents'];
- $this->coefficients = $components['coefficients'];
- $this->publicExponent = $components['publicExponent'];
- } else {
- $this->primes = array();
- $this->exponents = array();
- $this->coefficients = array();
- $this->publicExponent = false;
- }
-
- switch ($type) {
- case self::PUBLIC_FORMAT_OPENSSH:
- case self::PUBLIC_FORMAT_RAW:
- $this->setPublicKey();
- break;
- case self::PRIVATE_FORMAT_PKCS1:
- switch (true) {
- case strpos($key, '-BEGIN PUBLIC KEY-') !== false:
- case strpos($key, '-BEGIN RSA PUBLIC KEY-') !== false:
- $this->setPublicKey();
- }
- }
-
- return true;
- }
-
- /**
- * Sets the password
- *
- * Private keys can be encrypted with a password. To unset the password, pass in the empty string or false.
- * Or rather, pass in $password such that empty($password) && !is_string($password) is true.
- *
- * @see self::createKey()
- * @see self::loadKey()
- * @access public
- * @param string $password
- */
- function setPassword($password = false)
- {
- $this->password = $password;
- }
-
- /**
- * Defines the public key
- *
- * Some private key formats define the public exponent and some don't. Those that don't define it are problematic when
- * used in certain contexts. For example, in SSH-2, RSA authentication works by sending the public key along with a
- * message signed by the private key to the server. The SSH-2 server looks the public key up in an index of public keys
- * and if it's present then proceeds to verify the signature. Problem is, if your private key doesn't include the public
- * exponent this won't work unless you manually add the public exponent. phpseclib tries to guess if the key being used
- * is the public key but in the event that it guesses incorrectly you might still want to explicitly set the key as being
- * public.
- *
- * Do note that when a new key is loaded the index will be cleared.
- *
- * Returns true on success, false on failure
- *
- * @see self::getPublicKey()
- * @access public
- * @param string $key optional
- * @param int $type optional
- * @return bool
- */
- function setPublicKey($key = false, $type = false)
- {
- // if a public key has already been loaded return false
- if (!empty($this->publicExponent)) {
- return false;
- }
-
- if ($key === false && !empty($this->modulus)) {
- $this->publicExponent = $this->exponent;
- return true;
- }
-
- if ($type === false) {
- $types = array(
- self::PUBLIC_FORMAT_RAW,
- self::PUBLIC_FORMAT_PKCS1,
- self::PUBLIC_FORMAT_XML,
- self::PUBLIC_FORMAT_OPENSSH
- );
- foreach ($types as $type) {
- $components = $this->_parseKey($key, $type);
- if ($components !== false) {
- break;
- }
+ if (isset($components['saltLength'])) {
+ $key = $key->withSaltLength($components['saltLength']);
}
- } else {
- $components = $this->_parseKey($key, $type);
- }
-
- if ($components === false) {
- return false;
- }
-
- if (empty($this->modulus) || !$this->modulus->equals($components['modulus'])) {
- $this->modulus = $components['modulus'];
- $this->exponent = $this->publicExponent = $components['publicExponent'];
- return true;
- }
-
- $this->publicExponent = $components['publicExponent'];
-
- return true;
- }
-
- /**
- * Defines the private key
- *
- * If phpseclib guessed a private key was a public key and loaded it as such it might be desirable to force
- * phpseclib to treat the key as a private key. This function will do that.
- *
- * Do note that when a new key is loaded the index will be cleared.
- *
- * Returns true on success, false on failure
- *
- * @see self::getPublicKey()
- * @access public
- * @param string $key optional
- * @param int $type optional
- * @return bool
- */
- function setPrivateKey($key = false, $type = false)
- {
- if ($key === false && !empty($this->publicExponent)) {
- $this->publicExponent = false;
- return true;
- }
-
- $rsa = new RSA();
- if (!$rsa->loadKey($key, $type)) {
- return false;
- }
- $rsa->publicExponent = false;
-
- // don't overwrite the old key if the new key is invalid
- $this->loadKey($rsa);
- return true;
- }
-
- /**
- * Returns the public key
- *
- * The public key is only returned under two circumstances - if the private key had the public key embedded within it
- * or if the public key was set via setPublicKey(). If the currently loaded key is supposed to be the public key this
- * function won't return it since this library, for the most part, doesn't distinguish between public and private keys.
- *
- * @see self::getPublicKey()
- * @access public
- * @param int $type optional
- */
- function getPublicKey($type = self::PUBLIC_FORMAT_PKCS8)
- {
- if (empty($this->modulus) || empty($this->publicExponent)) {
- return false;
- }
-
- $oldFormat = $this->publicKeyFormat;
- $this->publicKeyFormat = $type;
- $temp = $this->_convertPublicKey($this->modulus, $this->publicExponent);
- $this->publicKeyFormat = $oldFormat;
- return $temp;
- }
-
- /**
- * Returns the public key's fingerprint
- *
- * The public key's fingerprint is returned, which is equivalent to running `ssh-keygen -lf rsa.pub`. If there is
- * no public key currently loaded, false is returned.
- * Example output (md5): "c1:b1:30:29:d7:b8:de:6c:97:77:10:d7:46:41:63:87" (as specified by RFC 4716)
- *
- * @access public
- * @param string $algorithm The hashing algorithm to be used. Valid options are 'md5' and 'sha256'. False is returned
- * for invalid values.
- * @return mixed
- */
- function getPublicKeyFingerprint($algorithm = 'md5')
- {
- if (empty($this->modulus) || empty($this->publicExponent)) {
- return false;
- }
-
- $modulus = $this->modulus->toBytes(true);
- $publicExponent = $this->publicExponent->toBytes(true);
-
- $RSAPublicKey = pack('Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($publicExponent), $publicExponent, strlen($modulus), $modulus);
-
- switch ($algorithm) {
- case 'sha256':
- $hash = new Hash('sha256');
- $base = base64_encode($hash->hash($RSAPublicKey));
- return substr($base, 0, strlen($base) - 1);
- case 'md5':
- return substr(chunk_split(md5($RSAPublicKey), 2, ':'), 0, -1);
- default:
- return false;
- }
- }
-
- /**
- * Returns the private key
- *
- * The private key is only returned if the currently loaded key contains the constituent prime numbers.
- *
- * @see self::getPublicKey()
- * @access public
- * @param int $type optional
- * @return mixed
- */
- function getPrivateKey($type = self::PUBLIC_FORMAT_PKCS1)
- {
- if (empty($this->primes)) {
- return false;
- }
-
- $oldFormat = $this->privateKeyFormat;
- $this->privateKeyFormat = $type;
- $temp = $this->_convertPrivateKey($this->modulus, $this->publicExponent, $this->exponent, $this->primes, $this->exponents, $this->coefficients);
- $this->privateKeyFormat = $oldFormat;
- return $temp;
- }
-
- /**
- * Returns a minimalistic private key
- *
- * Returns the private key without the prime number constituants. Structurally identical to a public key that
- * hasn't been set as the public key
- *
- * @see self::getPrivateKey()
- * @access private
- * @param int $mode optional
- */
- function _getPrivatePublicKey($mode = self::PUBLIC_FORMAT_PKCS8)
- {
- if (empty($this->modulus) || empty($this->exponent)) {
- return false;
- }
-
- $oldFormat = $this->publicKeyFormat;
- $this->publicKeyFormat = $mode;
- $temp = $this->_convertPublicKey($this->modulus, $this->exponent);
- $this->publicKeyFormat = $oldFormat;
- return $temp;
- }
-
- /**
- * __toString() magic method
- *
- * @access public
- * @return string
- */
- function __toString()
- {
- $key = $this->getPrivateKey($this->privateKeyFormat);
- if ($key !== false) {
- return $key;
}
- $key = $this->_getPrivatePublicKey($this->publicKeyFormat);
- return $key !== false ? $key : '';
- }
- /**
- * __clone() magic method
- *
- * @access public
- * @return Crypt_RSA
- */
- function __clone()
- {
- $key = new RSA();
- $key->loadKey($this);
return $key;
}
/**
- * Generates the smallest and largest numbers requiring $bits bits
- *
- * @access private
- * @param int $bits
- * @return array
+ * Initialize static variables
*/
- function _generateMinMax($bits)
+ protected static function initialize_static_variables()
{
- $bytes = $bits >> 3;
- $min = str_repeat(chr(0), $bytes);
- $max = str_repeat(chr(0xFF), $bytes);
- $msb = $bits & 7;
- if ($msb) {
- $min = chr(1 << ($msb - 1)) . $min;
- $max = chr((1 << $msb) - 1) . $max;
- } else {
- $min[0] = chr(0x80);
+ if (!isset(self::$configFile)) {
+ self::$configFile = dirname(__FILE__) . '/../openssl.cnf';
}
- return array(
- 'min' => new BigInteger($min, 256),
- 'max' => new BigInteger($max, 256)
- );
+ parent::initialize_static_variables();
}
/**
- * DER-decode the length
- *
- * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See
- * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
+ * Constructor
*
- * @access private
- * @param string $string
- * @return int
+ * PublicKey and PrivateKey objects can only be created from abstract RSA class
*/
- function _decodeLength(&$string)
+ protected function __construct()
{
- $length = ord($this->_string_shift($string));
- if ($length & 0x80) { // definite length, long form
- $length&= 0x7F;
- $temp = $this->_string_shift($string, $length);
- list(, $length) = unpack('N', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4));
- }
- return $length;
- }
+ parent::__construct();
- /**
- * DER-encode the length
- *
- * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See
- * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
- *
- * @access private
- * @param int $length
- * @return string
- */
- function _encodeLength($length)
- {
- if ($length <= 0x7F) {
- return chr($length);
- }
-
- $temp = ltrim(pack('N', $length), chr(0));
- return pack('Ca*', 0x80 | strlen($temp), $temp);
- }
-
- /**
- * String Shift
- *
- * Inspired by array_shift
- *
- * @param string $string
- * @param int $index
- * @return string
- * @access private
- */
- function _string_shift(&$string, $index = 1)
- {
- $substr = substr($string, 0, $index);
- $string = substr($string, $index);
- return $substr;
- }
-
- /**
- * Determines the private key format
- *
- * @see self::createKey()
- * @access public
- * @param int $format
- */
- function setPrivateKeyFormat($format)
- {
- $this->privateKeyFormat = $format;
- }
-
- /**
- * Determines the public key format
- *
- * @see self::createKey()
- * @access public
- * @param int $format
- */
- function setPublicKeyFormat($format)
- {
- $this->publicKeyFormat = $format;
- }
-
- /**
- * Determines which hashing function should be used
- *
- * Used with signature production / verification and (if the encryption mode is self::ENCRYPTION_OAEP) encryption and
- * decryption. If $hash isn't supported, sha1 is used.
- *
- * @access public
- * @param string $hash
- */
- function setHash($hash)
- {
- // \phpseclib\Crypt\Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example.
- switch ($hash) {
- case 'md2':
- case 'md5':
- case 'sha1':
- case 'sha256':
- case 'sha384':
- case 'sha512':
- $this->hash = new Hash($hash);
- $this->hashName = $hash;
- break;
- default:
- $this->hash = new Hash('sha1');
- $this->hashName = 'sha1';
- }
- $this->hLen = $this->hash->getLength();
- }
-
- /**
- * Determines which hashing function should be used for the mask generation function
- *
- * The mask generation function is used by self::ENCRYPTION_OAEP and self::SIGNATURE_PSS and although it's
- * best if Hash and MGFHash are set to the same thing this is not a requirement.
- *
- * @access public
- * @param string $hash
- */
- function setMGFHash($hash)
- {
- // \phpseclib\Crypt\Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example.
- switch ($hash) {
- case 'md2':
- case 'md5':
- case 'sha1':
- case 'sha256':
- case 'sha384':
- case 'sha512':
- $this->mgfHash = new Hash($hash);
- break;
- default:
- $this->mgfHash = new Hash('sha1');
- }
- $this->mgfHLen = $this->mgfHash->getLength();
- }
-
- /**
- * Determines the salt length
- *
- * To quote from {@link http://tools.ietf.org/html/rfc3447#page-38 RFC3447#page-38}:
- *
- * Typical salt lengths in octets are hLen (the length of the output
- * of the hash function Hash) and 0.
- *
- * @access public
- * @param int $sLen
- */
- function setSaltLength($sLen)
- {
- $this->sLen = $sLen;
+ $this->hLen = $this->hash->getLengthInBytes();
+ $this->mgfHash = new Hash('sha256');
+ $this->mgfHLen = $this->mgfHash->getLengthInBytes();
}
/**
@@ -2280,17 +509,18 @@ class RSA
*
* See {@link http://tools.ietf.org/html/rfc3447#section-4.1 RFC3447#section-4.1}.
*
- * @access private
- * @param \phpseclib\Math\BigInteger $x
+ * @param bool|Math\BigInteger $x
* @param int $xLen
- * @return string
+ * @return bool|string
*/
- function _i2osp($x, $xLen)
+ protected function i2osp($x, $xLen)
{
+ if ($x === false) {
+ return false;
+ }
$x = $x->toBytes();
if (strlen($x) > $xLen) {
- user_error('Integer too large');
- return false;
+ throw new \OutOfRangeException('Resultant string length out of range');
}
return str_pad($x, $xLen, chr(0), STR_PAD_LEFT);
}
@@ -2300,652 +530,12 @@ class RSA
*
* See {@link http://tools.ietf.org/html/rfc3447#section-4.2 RFC3447#section-4.2}.
*
- * @access private
- * @param int|string|resource $x
- * @return \phpseclib\Math\BigInteger
- */
- function _os2ip($x)
- {
- return new BigInteger($x, 256);
- }
-
- /**
- * Exponentiate with or without Chinese Remainder Theorem
- *
- * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.1 RFC3447#section-5.1.2}.
- *
- * @access private
- * @param \phpseclib\Math\BigInteger $x
- * @return \phpseclib\Math\BigInteger
- */
- function _exponentiate($x)
- {
- switch (true) {
- case empty($this->primes):
- case $this->primes[1]->equals($this->zero):
- case empty($this->coefficients):
- case $this->coefficients[2]->equals($this->zero):
- case empty($this->exponents):
- case $this->exponents[1]->equals($this->zero):
- return $x->modPow($this->exponent, $this->modulus);
- }
-
- $num_primes = count($this->primes);
-
- if (defined('CRYPT_RSA_DISABLE_BLINDING')) {
- $m_i = array(
- 1 => $x->modPow($this->exponents[1], $this->primes[1]),
- 2 => $x->modPow($this->exponents[2], $this->primes[2])
- );
- $h = $m_i[1]->subtract($m_i[2]);
- $h = $h->multiply($this->coefficients[2]);
- list(, $h) = $h->divide($this->primes[1]);
- $m = $m_i[2]->add($h->multiply($this->primes[2]));
-
- $r = $this->primes[1];
- for ($i = 3; $i <= $num_primes; $i++) {
- $m_i = $x->modPow($this->exponents[$i], $this->primes[$i]);
-
- $r = $r->multiply($this->primes[$i - 1]);
-
- $h = $m_i->subtract($m);
- $h = $h->multiply($this->coefficients[$i]);
- list(, $h) = $h->divide($this->primes[$i]);
-
- $m = $m->add($r->multiply($h));
- }
- } else {
- $smallest = $this->primes[1];
- for ($i = 2; $i <= $num_primes; $i++) {
- if ($smallest->compare($this->primes[$i]) > 0) {
- $smallest = $this->primes[$i];
- }
- }
-
- $one = new BigInteger(1);
-
- $r = $one->random($one, $smallest->subtract($one));
-
- $m_i = array(
- 1 => $this->_blind($x, $r, 1),
- 2 => $this->_blind($x, $r, 2)
- );
- $h = $m_i[1]->subtract($m_i[2]);
- $h = $h->multiply($this->coefficients[2]);
- list(, $h) = $h->divide($this->primes[1]);
- $m = $m_i[2]->add($h->multiply($this->primes[2]));
-
- $r = $this->primes[1];
- for ($i = 3; $i <= $num_primes; $i++) {
- $m_i = $this->_blind($x, $r, $i);
-
- $r = $r->multiply($this->primes[$i - 1]);
-
- $h = $m_i->subtract($m);
- $h = $h->multiply($this->coefficients[$i]);
- list(, $h) = $h->divide($this->primes[$i]);
-
- $m = $m->add($r->multiply($h));
- }
- }
-
- return $m;
- }
-
- /**
- * Performs RSA Blinding
- *
- * Protects against timing attacks by employing RSA Blinding.
- * Returns $x->modPow($this->exponents[$i], $this->primes[$i])
- *
- * @access private
- * @param \phpseclib\Math\BigInteger $x
- * @param \phpseclib\Math\BigInteger $r
- * @param int $i
- * @return \phpseclib\Math\BigInteger
- */
- function _blind($x, $r, $i)
- {
- $x = $x->multiply($r->modPow($this->publicExponent, $this->primes[$i]));
- $x = $x->modPow($this->exponents[$i], $this->primes[$i]);
-
- $r = $r->modInverse($this->primes[$i]);
- $x = $x->multiply($r);
- list(, $x) = $x->divide($this->primes[$i]);
-
- return $x;
- }
-
- /**
- * Performs blinded RSA equality testing
- *
- * Protects against a particular type of timing attack described.
- *
- * See {@link http://codahale.com/a-lesson-in-timing-attacks/ A Lesson In Timing Attacks (or, Don't use MessageDigest.isEquals)}
- *
- * Thanks for the heads up singpolyma!
- *
- * @access private
* @param string $x
- * @param string $y
- * @return bool
- */
- function _equals($x, $y)
- {
- if (function_exists('hash_equals')) {
- return hash_equals($x, $y);
- }
-
- if (strlen($x) != strlen($y)) {
- return false;
- }
-
- $result = "\0";
- $x^= $y;
- for ($i = 0; $i < strlen($x); $i++) {
- $result|= $x[$i];
- }
-
- return $result === "\0";
- }
-
- /**
- * RSAEP
- *
- * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.1 RFC3447#section-5.1.1}.
- *
- * @access private
- * @param \phpseclib\Math\BigInteger $m
- * @return \phpseclib\Math\BigInteger
- */
- function _rsaep($m)
- {
- if ($m->compare($this->zero) < 0 || $m->compare($this->modulus) > 0) {
- user_error('Message representative out of range');
- return false;
- }
- return $this->_exponentiate($m);
- }
-
- /**
- * RSADP
- *
- * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.2 RFC3447#section-5.1.2}.
- *
- * @access private
- * @param \phpseclib\Math\BigInteger $c
- * @return \phpseclib\Math\BigInteger
- */
- function _rsadp($c)
- {
- if ($c->compare($this->zero) < 0 || $c->compare($this->modulus) > 0) {
- user_error('Ciphertext representative out of range');
- return false;
- }
- return $this->_exponentiate($c);
- }
-
- /**
- * RSASP1
- *
- * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.1 RFC3447#section-5.2.1}.
- *
- * @access private
- * @param \phpseclib\Math\BigInteger $m
- * @return \phpseclib\Math\BigInteger
- */
- function _rsasp1($m)
- {
- if ($m->compare($this->zero) < 0 || $m->compare($this->modulus) > 0) {
- user_error('Message representative out of range');
- return false;
- }
- return $this->_exponentiate($m);
- }
-
- /**
- * RSAVP1
- *
- * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.2 RFC3447#section-5.2.2}.
- *
- * @access private
- * @param \phpseclib\Math\BigInteger $s
- * @return \phpseclib\Math\BigInteger
- */
- function _rsavp1($s)
- {
- if ($s->compare($this->zero) < 0 || $s->compare($this->modulus) > 0) {
- user_error('Signature representative out of range');
- return false;
- }
- return $this->_exponentiate($s);
- }
-
- /**
- * MGF1
- *
- * See {@link http://tools.ietf.org/html/rfc3447#appendix-B.2.1 RFC3447#appendix-B.2.1}.
- *
- * @access private
- * @param string $mgfSeed
- * @param int $maskLen
- * @return string
- */
- function _mgf1($mgfSeed, $maskLen)
- {
- // if $maskLen would yield strings larger than 4GB, PKCS#1 suggests a "Mask too long" error be output.
-
- $t = '';
- $count = ceil($maskLen / $this->mgfHLen);
- for ($i = 0; $i < $count; $i++) {
- $c = pack('N', $i);
- $t.= $this->mgfHash->hash($mgfSeed . $c);
- }
-
- return substr($t, 0, $maskLen);
- }
-
- /**
- * RSAES-OAEP-ENCRYPT
- *
- * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.1 RFC3447#section-7.1.1} and
- * {http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding OAES}.
- *
- * @access private
- * @param string $m
- * @param string $l
- * @return string
- */
- function _rsaes_oaep_encrypt($m, $l = '')
- {
- $mLen = strlen($m);
-
- // Length checking
-
- // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
- // be output.
-
- if ($mLen > $this->k - 2 * $this->hLen - 2) {
- user_error('Message too long');
- return false;
- }
-
- // EME-OAEP encoding
-
- $lHash = $this->hash->hash($l);
- $ps = str_repeat(chr(0), $this->k - $mLen - 2 * $this->hLen - 2);
- $db = $lHash . $ps . chr(1) . $m;
- $seed = Random::string($this->hLen);
- $dbMask = $this->_mgf1($seed, $this->k - $this->hLen - 1);
- $maskedDB = $db ^ $dbMask;
- $seedMask = $this->_mgf1($maskedDB, $this->hLen);
- $maskedSeed = $seed ^ $seedMask;
- $em = chr(0) . $maskedSeed . $maskedDB;
-
- // RSA encryption
-
- $m = $this->_os2ip($em);
- $c = $this->_rsaep($m);
- $c = $this->_i2osp($c, $this->k);
-
- // Output the ciphertext C
-
- return $c;
- }
-
- /**
- * RSAES-OAEP-DECRYPT
- *
- * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.2 RFC3447#section-7.1.2}. The fact that the error
- * messages aren't distinguishable from one another hinders debugging, but, to quote from RFC3447#section-7.1.2:
- *
- * Note. Care must be taken to ensure that an opponent cannot
- * distinguish the different error conditions in Step 3.g, whether by
- * error message or timing, or, more generally, learn partial
- * information about the encoded message EM. Otherwise an opponent may
- * be able to obtain useful information about the decryption of the
- * ciphertext C, leading to a chosen-ciphertext attack such as the one
- * observed by Manger [36].
- *
- * As for $l... to quote from {@link http://tools.ietf.org/html/rfc3447#page-17 RFC3447#page-17}:
- *
- * Both the encryption and the decryption operations of RSAES-OAEP take
- * the value of a label L as input. In this version of PKCS #1, L is
- * the empty string; other uses of the label are outside the scope of
- * this document.
- *
- * @access private
- * @param string $c
- * @param string $l
- * @return string
- */
- function _rsaes_oaep_decrypt($c, $l = '')
- {
- // Length checking
-
- // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
- // be output.
-
- if (strlen($c) != $this->k || $this->k < 2 * $this->hLen + 2) {
- user_error('Decryption error');
- return false;
- }
-
- // RSA decryption
-
- $c = $this->_os2ip($c);
- $m = $this->_rsadp($c);
- if ($m === false) {
- user_error('Decryption error');
- return false;
- }
- $em = $this->_i2osp($m, $this->k);
-
- // EME-OAEP decoding
-
- $lHash = $this->hash->hash($l);
- $y = ord($em[0]);
- $maskedSeed = substr($em, 1, $this->hLen);
- $maskedDB = substr($em, $this->hLen + 1);
- $seedMask = $this->_mgf1($maskedDB, $this->hLen);
- $seed = $maskedSeed ^ $seedMask;
- $dbMask = $this->_mgf1($seed, $this->k - $this->hLen - 1);
- $db = $maskedDB ^ $dbMask;
- $lHash2 = substr($db, 0, $this->hLen);
- $m = substr($db, $this->hLen);
- $hashesMatch = $this->_equals($lHash, $lHash2);
- $leadingZeros = 1;
- $patternMatch = 0;
- $offset = 0;
- for ($i = 0; $i < strlen($m); $i++) {
- $patternMatch|= $leadingZeros & ($m[$i] === "\1");
- $leadingZeros&= $m[$i] === "\0";
- $offset+= $patternMatch ? 0 : 1;
- }
-
- // we do | instead of || to avoid https://en.wikipedia.org/wiki/Short-circuit_evaluation
- // to protect against timing attacks
- if (!$hashesMatch | !$patternMatch) {
- user_error('Decryption error');
- return false;
- }
-
- // Output the message M
-
- return substr($m, $offset + 1);
- }
-
- /**
- * Raw Encryption / Decryption
- *
- * Doesn't use padding and is not recommended.
- *
- * @access private
- * @param string $m
- * @return string
- */
- function _raw_encrypt($m)
- {
- $temp = $this->_os2ip($m);
- $temp = $this->_rsaep($temp);
- return $this->_i2osp($temp, $this->k);
- }
-
- /**
- * RSAES-PKCS1-V1_5-ENCRYPT
- *
- * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.1 RFC3447#section-7.2.1}.
- *
- * @access private
- * @param string $m
- * @return string
- */
- function _rsaes_pkcs1_v1_5_encrypt($m)
- {
- $mLen = strlen($m);
-
- // Length checking
-
- if ($mLen > $this->k - 11) {
- user_error('Message too long');
- return false;
- }
-
- // EME-PKCS1-v1_5 encoding
-
- $psLen = $this->k - $mLen - 3;
- $ps = '';
- while (strlen($ps) != $psLen) {
- $temp = Random::string($psLen - strlen($ps));
- $temp = str_replace("\x00", '', $temp);
- $ps.= $temp;
- }
- $type = 2;
- // see the comments of _rsaes_pkcs1_v1_5_decrypt() to understand why this is being done
- if (defined('CRYPT_RSA_PKCS15_COMPAT') && (!isset($this->publicExponent) || $this->exponent !== $this->publicExponent)) {
- $type = 1;
- // "The padding string PS shall consist of k-3-||D|| octets. ... for block type 01, they shall have value FF"
- $ps = str_repeat("\xFF", $psLen);
- }
- $em = chr(0) . chr($type) . $ps . chr(0) . $m;
-
- // RSA encryption
- $m = $this->_os2ip($em);
- $c = $this->_rsaep($m);
- $c = $this->_i2osp($c, $this->k);
-
- // Output the ciphertext C
-
- return $c;
- }
-
- /**
- * RSAES-PKCS1-V1_5-DECRYPT
- *
- * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.2 RFC3447#section-7.2.2}.
- *
- * For compatibility purposes, this function departs slightly from the description given in RFC3447.
- * The reason being that RFC2313#section-8.1 (PKCS#1 v1.5) states that ciphertext's encrypted by the
- * private key should have the second byte set to either 0 or 1 and that ciphertext's encrypted by the
- * public key should have the second byte set to 2. In RFC3447 (PKCS#1 v2.1), the second byte is supposed
- * to be 2 regardless of which key is used. For compatibility purposes, we'll just check to make sure the
- * second byte is 2 or less. If it is, we'll accept the decrypted string as valid.
- *
- * As a consequence of this, a private key encrypted ciphertext produced with \phpseclib\Crypt\RSA may not decrypt
- * with a strictly PKCS#1 v1.5 compliant RSA implementation. Public key encrypted ciphertext's should but
- * not private key encrypted ciphertext's.
- *
- * @access private
- * @param string $c
- * @return string
- */
- function _rsaes_pkcs1_v1_5_decrypt($c)
- {
- // Length checking
-
- if (strlen($c) != $this->k) { // or if k < 11
- user_error('Decryption error');
- return false;
- }
-
- // RSA decryption
-
- $c = $this->_os2ip($c);
- $m = $this->_rsadp($c);
-
- if ($m === false) {
- user_error('Decryption error');
- return false;
- }
- $em = $this->_i2osp($m, $this->k);
-
- // EME-PKCS1-v1_5 decoding
-
- if (ord($em[0]) != 0 || ord($em[1]) > 2) {
- user_error('Decryption error');
- return false;
- }
-
- $ps = substr($em, 2, strpos($em, chr(0), 2) - 2);
- $m = substr($em, strlen($ps) + 3);
-
- if (strlen($ps) < 8) {
- user_error('Decryption error');
- return false;
- }
-
- // Output M
-
- return $m;
- }
-
- /**
- * EMSA-PSS-ENCODE
- *
- * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.1 RFC3447#section-9.1.1}.
- *
- * @access private
- * @param string $m
- * @param int $emBits
- */
- function _emsa_pss_encode($m, $emBits)
- {
- // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
- // be output.
-
- $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8)
- $sLen = $this->sLen !== null ? $this->sLen : $this->hLen;
-
- $mHash = $this->hash->hash($m);
- if ($emLen < $this->hLen + $sLen + 2) {
- user_error('Encoding error');
- return false;
- }
-
- $salt = Random::string($sLen);
- $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt;
- $h = $this->hash->hash($m2);
- $ps = str_repeat(chr(0), $emLen - $sLen - $this->hLen - 2);
- $db = $ps . chr(1) . $salt;
- $dbMask = $this->_mgf1($h, $emLen - $this->hLen - 1);
- $maskedDB = $db ^ $dbMask;
- $maskedDB[0] = ~chr(0xFF << ($emBits & 7)) & $maskedDB[0];
- $em = $maskedDB . $h . chr(0xBC);
-
- return $em;
- }
-
- /**
- * EMSA-PSS-VERIFY
- *
- * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.2 RFC3447#section-9.1.2}.
- *
- * @access private
- * @param string $m
- * @param string $em
- * @param int $emBits
- * @return string
+ * @return Math\BigInteger
*/
- function _emsa_pss_verify($m, $em, $emBits)
+ protected function os2ip($x)
{
- // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
- // be output.
-
- $emLen = ($emBits + 7) >> 3; // ie. ceil($emBits / 8);
- $sLen = $this->sLen !== null ? $this->sLen : $this->hLen;
-
- $mHash = $this->hash->hash($m);
- if ($emLen < $this->hLen + $sLen + 2) {
- return false;
- }
-
- if ($em[strlen($em) - 1] != chr(0xBC)) {
- return false;
- }
-
- $maskedDB = substr($em, 0, -$this->hLen - 1);
- $h = substr($em, -$this->hLen - 1, $this->hLen);
- $temp = chr(0xFF << ($emBits & 7));
- if ((~$maskedDB[0] & $temp) != $temp) {
- return false;
- }
- $dbMask = $this->_mgf1($h, $emLen - $this->hLen - 1);
- $db = $maskedDB ^ $dbMask;
- $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0];
- $temp = $emLen - $this->hLen - $sLen - 2;
- if (substr($db, 0, $temp) != str_repeat(chr(0), $temp) || ord($db[$temp]) != 1) {
- return false;
- }
- $salt = substr($db, $temp + 1); // should be $sLen long
- $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt;
- $h2 = $this->hash->hash($m2);
- return $this->_equals($h, $h2);
- }
-
- /**
- * RSASSA-PSS-SIGN
- *
- * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.1 RFC3447#section-8.1.1}.
- *
- * @access private
- * @param string $m
- * @return string
- */
- function _rsassa_pss_sign($m)
- {
- // EMSA-PSS encoding
-
- $em = $this->_emsa_pss_encode($m, 8 * $this->k - 1);
-
- // RSA signature
-
- $m = $this->_os2ip($em);
- $s = $this->_rsasp1($m);
- $s = $this->_i2osp($s, $this->k);
-
- // Output the signature S
-
- return $s;
- }
-
- /**
- * RSASSA-PSS-VERIFY
- *
- * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.2 RFC3447#section-8.1.2}.
- *
- * @access private
- * @param string $m
- * @param string $s
- * @return string
- */
- function _rsassa_pss_verify($m, $s)
- {
- // Length checking
-
- if (strlen($s) != $this->k) {
- user_error('Invalid signature');
- return false;
- }
-
- // RSA verification
-
- $modBits = strlen($this->modulus->toBits());
-
- $s2 = $this->_os2ip($s);
- $m2 = $this->_rsavp1($s2);
- if ($m2 === false) {
- user_error('Invalid signature');
- return false;
- }
- $em = $this->_i2osp($m2, $this->k);
- if ($em === false) {
- user_error('Invalid signature');
- return false;
- }
-
- // EMSA-PSS verification
-
- return $this->_emsa_pss_verify($m, $em, $modBits - 1);
+ return new BigInteger($x, 256);
}
/**
@@ -2953,44 +543,50 @@ class RSA
*
* See {@link http://tools.ietf.org/html/rfc3447#section-9.2 RFC3447#section-9.2}.
*
- * @access private
* @param string $m
* @param int $emLen
+ * @throws \LengthException if the intended encoded message length is too short
* @return string
*/
- function _emsa_pkcs1_v1_5_encode($m, $emLen)
+ protected function emsa_pkcs1_v1_5_encode($m, $emLen)
{
$h = $this->hash->hash($m);
- if ($h === false) {
- return false;
- }
// see http://tools.ietf.org/html/rfc3447#page-43
- switch ($this->hashName) {
+ switch ($this->hash->getHash()) {
case 'md2':
- $t = pack('H*', '3020300c06082a864886f70d020205000410');
+ $t = "\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x02\x05\x00\x04\x10";
break;
case 'md5':
- $t = pack('H*', '3020300c06082a864886f70d020505000410');
+ $t = "\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x04\x10";
break;
case 'sha1':
- $t = pack('H*', '3021300906052b0e03021a05000414');
+ $t = "\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14";
break;
case 'sha256':
- $t = pack('H*', '3031300d060960864801650304020105000420');
+ $t = "\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20";
break;
case 'sha384':
- $t = pack('H*', '3041300d060960864801650304020205000430');
+ $t = "\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30";
break;
case 'sha512':
- $t = pack('H*', '3051300d060960864801650304020305000440');
+ $t = "\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40";
+ break;
+ // from https://www.emc.com/collateral/white-papers/h11300-pkcs-1v2-2-rsa-cryptography-standard-wp.pdf#page=40
+ case 'sha224':
+ $t = "\x30\x2d\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x04\x05\x00\x04\x1c";
+ break;
+ case 'sha512/224':
+ $t = "\x30\x2d\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x05\x05\x00\x04\x1c";
+ break;
+ case 'sha512/256':
+ $t = "\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x06\x05\x00\x04\x20";
}
- $t.= $h;
+ $t .= $h;
$tLen = strlen($t);
if ($emLen < $tLen + 11) {
- user_error('Intended encoded message length too short');
- return false;
+ throw new \LengthException('Intended encoded message length too short');
}
$ps = str_repeat(chr(0xFF), $emLen - $tLen - 3);
@@ -3010,40 +606,46 @@ class RSA
* generally be omitted, but if present, it shall have a value of type
* NULL"
*
- * @access private
* @param string $m
* @param int $emLen
* @return string
*/
- function _emsa_pkcs1_v1_5_encode_without_null($m, $emLen)
+ protected function emsa_pkcs1_v1_5_encode_without_null($m, $emLen)
{
$h = $this->hash->hash($m);
- if ($h === false) {
- return false;
- }
- switch ($this->hashName) {
+ // see http://tools.ietf.org/html/rfc3447#page-43
+ switch ($this->hash->getHash()) {
case 'sha1':
- $t = pack('H*', '301f300706052b0e03021a0414');
+ $t = "\x30\x1f\x30\x07\x06\x05\x2b\x0e\x03\x02\x1a\x04\x14";
break;
case 'sha256':
- $t = pack('H*', '302f300b06096086480165030402010420');
+ $t = "\x30\x2f\x30\x0b\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x04\x20";
break;
case 'sha384':
- $t = pack('H*', '303f300b06096086480165030402020430');
+ $t = "\x30\x3f\x30\x0b\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x04\x30";
break;
case 'sha512':
- $t = pack('H*', '304f300b06096086480165030402030440');
+ $t = "\x30\x4f\x30\x0b\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x04\x40";
+ break;
+ // from https://www.emc.com/collateral/white-papers/h11300-pkcs-1v2-2-rsa-cryptography-standard-wp.pdf#page=40
+ case 'sha224':
+ $t = "\x30\x2b\x30\x0b\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x04\x04\x1c";
+ break;
+ case 'sha512/224':
+ $t = "\x30\x2b\x30\x0b\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x05\x04\x1c";
+ break;
+ case 'sha512/256':
+ $t = "\x30\x2f\x30\x0b\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x06\x04\x20";
break;
default:
- return false;
+ throw new UnsupportedAlgorithmException('md2 and md5 require NULLs');
}
- $t.= $h;
+ $t .= $h;
$tLen = strlen($t);
if ($emLen < $tLen + 11) {
- user_error('Intended encoded message length too short');
- return false;
+ throw new \LengthException('Intended encoded message length too short');
}
$ps = str_repeat(chr(0xFF), $emLen - $tLen - 3);
@@ -3054,295 +656,278 @@ class RSA
}
/**
- * RSASSA-PKCS1-V1_5-SIGN
+ * MGF1
*
- * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.1 RFC3447#section-8.2.1}.
+ * See {@link http://tools.ietf.org/html/rfc3447#appendix-B.2.1 RFC3447#appendix-B.2.1}.
*
- * @access private
- * @param string $m
+ * @param string $mgfSeed
+ * @param int $maskLen
* @return string
*/
- function _rsassa_pkcs1_v1_5_sign($m)
+ protected function mgf1($mgfSeed, $maskLen)
{
- // EMSA-PKCS1-v1_5 encoding
+ // if $maskLen would yield strings larger than 4GB, PKCS#1 suggests a "Mask too long" error be output.
- $em = $this->_emsa_pkcs1_v1_5_encode($m, $this->k);
- if ($em === false) {
- user_error('RSA modulus too short');
- return false;
+ $t = '';
+ $count = ceil($maskLen / $this->mgfHLen);
+ for ($i = 0; $i < $count; $i++) {
+ $c = pack('N', $i);
+ $t .= $this->mgfHash->hash($mgfSeed . $c);
}
- // RSA signature
-
- $m = $this->_os2ip($em);
- $s = $this->_rsasp1($m);
- $s = $this->_i2osp($s, $this->k);
-
- // Output the signature S
-
- return $s;
+ return substr($t, 0, $maskLen);
}
/**
- * RSASSA-PKCS1-V1_5-VERIFY
+ * Returns the key size
*
- * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.2 RFC3447#section-8.2.2}.
+ * More specifically, this returns the size of the modulo in bits.
*
- * @access private
- * @param string $m
- * @param string $s
- * @return string
+ * @return int
*/
- function _rsassa_pkcs1_v1_5_verify($m, $s)
+ public function getLength()
{
- // Length checking
-
- if (strlen($s) != $this->k) {
- user_error('Invalid signature');
- return false;
- }
+ return !isset($this->modulus) ? 0 : $this->modulus->getLength();
+ }
- // RSA verification
+ /**
+ * Determines which hashing function should be used
+ *
+ * Used with signature production / verification and (if the encryption mode is self::PADDING_OAEP) encryption and
+ * decryption.
+ *
+ * @param string $hash
+ */
+ public function withHash($hash)
+ {
+ $new = clone $this;
- $s = $this->_os2ip($s);
- $m2 = $this->_rsavp1($s);
- if ($m2 === false) {
- user_error('Invalid signature');
- return false;
- }
- $em = $this->_i2osp($m2, $this->k);
- if ($em === false) {
- user_error('Invalid signature');
- return false;
+ // Crypt\Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example.
+ switch (strtolower($hash)) {
+ case 'md2':
+ case 'md5':
+ case 'sha1':
+ case 'sha256':
+ case 'sha384':
+ case 'sha512':
+ case 'sha224':
+ case 'sha512/224':
+ case 'sha512/256':
+ $new->hash = new Hash($hash);
+ break;
+ default:
+ throw new UnsupportedAlgorithmException(
+ 'The only supported hash algorithms are: md2, md5, sha1, sha256, sha384, sha512, sha224, sha512/224, sha512/256'
+ );
}
+ $new->hLen = $new->hash->getLengthInBytes();
- // EMSA-PKCS1-v1_5 encoding
+ return $new;
+ }
- $em2 = $this->_emsa_pkcs1_v1_5_encode($m, $this->k);
- $em3 = $this->_emsa_pkcs1_v1_5_encode_without_null($m, $this->k);
+ /**
+ * Determines which hashing function should be used for the mask generation function
+ *
+ * The mask generation function is used by self::PADDING_OAEP and self::PADDING_PSS and although it's
+ * best if Hash and MGFHash are set to the same thing this is not a requirement.
+ *
+ * @param string $hash
+ */
+ public function withMGFHash($hash)
+ {
+ $new = clone $this;
- if ($em2 === false && $em3 === false) {
- user_error('RSA modulus too short');
- return false;
+ // Crypt\Hash supports algorithms that PKCS#1 doesn't support. md5-96 and sha1-96, for example.
+ switch (strtolower($hash)) {
+ case 'md2':
+ case 'md5':
+ case 'sha1':
+ case 'sha256':
+ case 'sha384':
+ case 'sha512':
+ case 'sha224':
+ case 'sha512/224':
+ case 'sha512/256':
+ $new->mgfHash = new Hash($hash);
+ break;
+ default:
+ throw new UnsupportedAlgorithmException(
+ 'The only supported hash algorithms are: md2, md5, sha1, sha256, sha384, sha512, sha224, sha512/224, sha512/256'
+ );
}
+ $new->mgfHLen = $new->mgfHash->getLengthInBytes();
- // Compare
-
- return ($em2 !== false && $this->_equals($em, $em2)) ||
- ($em3 !== false && $this->_equals($em, $em3));
+ return $new;
}
/**
- * Set Encryption Mode
+ * Returns the MGF hash algorithm currently being used
*
- * Valid values include self::ENCRYPTION_OAEP and self::ENCRYPTION_PKCS1.
- *
- * @access public
- * @param int $mode
*/
- function setEncryptionMode($mode)
+ public function getMGFHash()
{
- $this->encryptionMode = $mode;
+ return clone $this->mgfHash;
}
/**
- * Set Signature Mode
+ * Determines the salt length
+ *
+ * Used by RSA::PADDING_PSS
+ *
+ * To quote from {@link http://tools.ietf.org/html/rfc3447#page-38 RFC3447#page-38}:
*
- * Valid values include self::SIGNATURE_PSS and self::SIGNATURE_PKCS1
+ * Typical salt lengths in octets are hLen (the length of the output
+ * of the hash function Hash) and 0.
*
- * @access public
- * @param int $mode
+ * @param int $sLen
*/
- function setSignatureMode($mode)
+ public function withSaltLength($sLen)
{
- $this->signatureMode = $mode;
+ $new = clone $this;
+ $new->sLen = $sLen;
+ return $new;
}
/**
- * Set public key comment.
+ * Returns the salt length currently being used
*
- * @access public
- * @param string $comment
*/
- function setComment($comment)
+ public function getSaltLength()
{
- $this->comment = $comment;
+ return $this->sLen !== null ? $this->sLen : $this->hLen;
}
/**
- * Get public key comment.
+ * Determines the label
*
- * @access public
- * @return string
+ * Used by RSA::PADDING_OAEP
+ *
+ * To quote from {@link http://tools.ietf.org/html/rfc3447#page-17 RFC3447#page-17}:
+ *
+ * Both the encryption and the decryption operations of RSAES-OAEP take
+ * the value of a label L as input. In this version of PKCS #1, L is
+ * the empty string; other uses of the label are outside the scope of
+ * this document.
+ *
+ * @param string $label
*/
- function getComment()
+ public function withLabel($label)
{
- return $this->comment;
+ $new = clone $this;
+ $new->label = $label;
+ return $new;
}
/**
- * Encryption
+ * Returns the label currently being used
*
- * Both self::ENCRYPTION_OAEP and self::ENCRYPTION_PKCS1 both place limits on how long $plaintext can be.
- * If $plaintext exceeds those limits it will be broken up so that it does and the resultant ciphertext's will
- * be concatenated together.
- *
- * @see self::decrypt()
- * @access public
- * @param string $plaintext
- * @return string
*/
- function encrypt($plaintext)
+ public function getLabel()
{
- switch ($this->encryptionMode) {
- case self::ENCRYPTION_NONE:
- $plaintext = str_split($plaintext, $this->k);
- $ciphertext = '';
- foreach ($plaintext as $m) {
- $ciphertext.= $this->_raw_encrypt($m);
- }
- return $ciphertext;
- case self::ENCRYPTION_PKCS1:
- $length = $this->k - 11;
- if ($length <= 0) {
- return false;
- }
-
- $plaintext = str_split($plaintext, $length);
- $ciphertext = '';
- foreach ($plaintext as $m) {
- $ciphertext.= $this->_rsaes_pkcs1_v1_5_encrypt($m);
- }
- return $ciphertext;
- //case self::ENCRYPTION_OAEP:
- default:
- $length = $this->k - 2 * $this->hLen - 2;
- if ($length <= 0) {
- return false;
- }
-
- $plaintext = str_split($plaintext, $length);
- $ciphertext = '';
- foreach ($plaintext as $m) {
- $ciphertext.= $this->_rsaes_oaep_encrypt($m);
- }
- return $ciphertext;
- }
+ return $this->label;
}
/**
- * Decryption
+ * Determines the padding modes
*
- * @see self::encrypt()
- * @access public
- * @param string $ciphertext
- * @return string
+ * Example: $key->withPadding(RSA::ENCRYPTION_PKCS1 | RSA::SIGNATURE_PKCS1);
+ *
+ * @param int $padding
*/
- function decrypt($ciphertext)
+ public function withPadding($padding)
{
- if ($this->k <= 0) {
- return false;
+ $masks = [
+ self::ENCRYPTION_OAEP,
+ self::ENCRYPTION_PKCS1,
+ self::ENCRYPTION_NONE
+ ];
+ $encryptedCount = 0;
+ $selected = 0;
+ foreach ($masks as $mask) {
+ if ($padding & $mask) {
+ $selected = $mask;
+ $encryptedCount++;
+ }
}
-
- $ciphertext = str_split($ciphertext, $this->k);
- $ciphertext[count($ciphertext) - 1] = str_pad($ciphertext[count($ciphertext) - 1], $this->k, chr(0), STR_PAD_LEFT);
-
- $plaintext = '';
-
- switch ($this->encryptionMode) {
- case self::ENCRYPTION_NONE:
- $decrypt = '_raw_encrypt';
- break;
- case self::ENCRYPTION_PKCS1:
- $decrypt = '_rsaes_pkcs1_v1_5_decrypt';
- break;
- //case self::ENCRYPTION_OAEP:
- default:
- $decrypt = '_rsaes_oaep_decrypt';
+ if ($encryptedCount > 1) {
+ throw new InconsistentSetupException('Multiple encryption padding modes have been selected; at most only one should be selected');
+ }
+ $encryptionPadding = $selected;
+
+ $masks = [
+ self::SIGNATURE_PSS,
+ self::SIGNATURE_RELAXED_PKCS1,
+ self::SIGNATURE_PKCS1
+ ];
+ $signatureCount = 0;
+ $selected = 0;
+ foreach ($masks as $mask) {
+ if ($padding & $mask) {
+ $selected = $mask;
+ $signatureCount++;
+ }
+ }
+ if ($signatureCount > 1) {
+ throw new InconsistentSetupException('Multiple signature padding modes have been selected; at most only one should be selected');
}
+ $signaturePadding = $selected;
- foreach ($ciphertext as $c) {
- $temp = $this->$decrypt($c);
- if ($temp === false) {
- return false;
- }
- $plaintext.= $temp;
+ $new = clone $this;
+ if ($encryptedCount) {
+ $new->encryptionPadding = $encryptionPadding;
+ }
+ if ($signatureCount) {
+ $new->signaturePadding = $signaturePadding;
}
+ return $new;
+ }
- return $plaintext;
+ /**
+ * Returns the padding currently being used
+ *
+ */
+ public function getPadding()
+ {
+ return $this->signaturePadding | $this->encryptionPadding;
}
/**
- * Create a signature
+ * Returns the current engine being used
*
- * @see self::verify()
- * @access public
- * @param string $message
+ * OpenSSL is only used in this class (and it's subclasses) for key generation
+ * Even then it depends on the parameters you're using. It's not used for
+ * multi-prime RSA nor is it used if the key length is outside of the range
+ * supported by OpenSSL
+ *
+ * @see self::useInternalEngine()
+ * @see self::useBestEngine()
* @return string
*/
- function sign($message)
+ public function getEngine()
{
- if (empty($this->modulus) || empty($this->exponent)) {
- return false;
- }
-
- switch ($this->signatureMode) {
- case self::SIGNATURE_PKCS1:
- return $this->_rsassa_pkcs1_v1_5_sign($message);
- //case self::SIGNATURE_PSS:
- default:
- return $this->_rsassa_pss_sign($message);
+ if (!isset(self::$engines['PHP'])) {
+ self::useBestEngine();
}
+ return self::$engines['OpenSSL'] && self::$defaultExponent == 65537 ?
+ 'OpenSSL' :
+ 'PHP';
}
/**
- * Verifies a signature
+ * Enable RSA Blinding
*
- * @see self::sign()
- * @access public
- * @param string $message
- * @param string $signature
- * @return bool
*/
- function verify($message, $signature)
+ public static function enableBlinding()
{
- if (empty($this->modulus) || empty($this->exponent)) {
- return false;
- }
-
- switch ($this->signatureMode) {
- case self::SIGNATURE_PKCS1:
- return $this->_rsassa_pkcs1_v1_5_verify($message, $signature);
- //case self::SIGNATURE_PSS:
- default:
- return $this->_rsassa_pss_verify($message, $signature);
- }
+ static::$enableBlinding = true;
}
/**
- * Extract raw BER from Base64 encoding
+ * Disable RSA Blinding
*
- * @access private
- * @param string $str
- * @return string
*/
- function _extractBER($str)
+ public static function disableBlinding()
{
- /* X.509 certs are assumed to be base64 encoded but sometimes they'll have additional things in them
- * above and beyond the ceritificate.
- * ie. some may have the following preceding the -----BEGIN CERTIFICATE----- line:
- *
- * Bag Attributes
- * localKeyID: 01 00 00 00
- * subject=/O=organization/OU=org unit/CN=common name
- * issuer=/O=organization/CN=common name
- */
- $temp = preg_replace('#.*?^-+[^-]+-+[\r\n ]*$#ms', '', $str, 1);
- // remove the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- stuff
- $temp = preg_replace('#-+[^-]+-+#', '', $temp);
- // remove new lines
- $temp = str_replace(array("\r", "\n", ' '), '', $temp);
- $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false;
- return $temp != false ? $temp : $str;
+ static::$enableBlinding = false;
}
}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/JWK.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/JWK.php
new file mode 100644
index 000000000..6dcf1cb03
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/JWK.php
@@ -0,0 +1,142 @@
+<?php
+
+/**
+ * JSON Web Key (RFC7517) Formatted RSA Handler
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\RSA\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\Formats\Keys\JWK as Progenitor;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * JWK Formatted RSA Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class JWK extends Progenitor
+{
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ $key = parent::load($key, $password);
+
+ if ($key->kty != 'RSA') {
+ throw new \RuntimeException('Only RSA JWK keys are supported');
+ }
+
+ $count = $publicCount = 0;
+ $vars = ['n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi'];
+ foreach ($vars as $var) {
+ if (!isset($key->$var) || !is_string($key->$var)) {
+ continue;
+ }
+ $count++;
+ $value = new BigInteger(Strings::base64url_decode($key->$var), 256);
+ switch ($var) {
+ case 'n':
+ $publicCount++;
+ $components['modulus'] = $value;
+ break;
+ case 'e':
+ $publicCount++;
+ $components['publicExponent'] = $value;
+ break;
+ case 'd':
+ $components['privateExponent'] = $value;
+ break;
+ case 'p':
+ $components['primes'][1] = $value;
+ break;
+ case 'q':
+ $components['primes'][2] = $value;
+ break;
+ case 'dp':
+ $components['exponents'][1] = $value;
+ break;
+ case 'dq':
+ $components['exponents'][2] = $value;
+ break;
+ case 'qi':
+ $components['coefficients'][2] = $value;
+ }
+ }
+
+ if ($count == count($vars)) {
+ return $components + ['isPublicKey' => false];
+ }
+
+ if ($count == 2 && $publicCount == 2) {
+ return $components + ['isPublicKey' => true];
+ }
+
+ throw new \UnexpectedValueException('Key does not have an appropriate number of RSA parameters');
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param BigInteger $d
+ * @param array $primes
+ * @param array $exponents
+ * @param array $coefficients
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '', array $options = [])
+ {
+ if (count($primes) != 2) {
+ throw new \InvalidArgumentException('JWK does not support multi-prime RSA keys');
+ }
+
+ $key = [
+ 'kty' => 'RSA',
+ 'n' => Strings::base64url_encode($n->toBytes()),
+ 'e' => Strings::base64url_encode($e->toBytes()),
+ 'd' => Strings::base64url_encode($d->toBytes()),
+ 'p' => Strings::base64url_encode($primes[1]->toBytes()),
+ 'q' => Strings::base64url_encode($primes[2]->toBytes()),
+ 'dp' => Strings::base64url_encode($exponents[1]->toBytes()),
+ 'dq' => Strings::base64url_encode($exponents[2]->toBytes()),
+ 'qi' => Strings::base64url_encode($coefficients[2]->toBytes())
+ ];
+
+ return self::wrapKey($key, $options);
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $n, BigInteger $e, array $options = [])
+ {
+ $key = [
+ 'kty' => 'RSA',
+ 'n' => Strings::base64url_encode($n->toBytes()),
+ 'e' => Strings::base64url_encode($e->toBytes())
+ ];
+
+ return self::wrapKey($key, $options);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/MSBLOB.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/MSBLOB.php
new file mode 100644
index 000000000..035fc8c38
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/MSBLOB.php
@@ -0,0 +1,224 @@
+<?php
+
+/**
+ * Miccrosoft BLOB Formatted RSA Key Handler
+ *
+ * More info:
+ *
+ * https://msdn.microsoft.com/en-us/library/windows/desktop/aa375601(v=vs.85).aspx
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\RSA\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Exception\UnsupportedFormatException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Microsoft BLOB Formatted RSA Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class MSBLOB
+{
+ /**
+ * Public/Private Key Pair
+ *
+ */
+ const PRIVATEKEYBLOB = 0x7;
+ /**
+ * Public Key
+ *
+ */
+ const PUBLICKEYBLOB = 0x6;
+ /**
+ * Public Key
+ *
+ */
+ const PUBLICKEYBLOBEX = 0xA;
+ /**
+ * RSA public key exchange algorithm
+ *
+ */
+ const CALG_RSA_KEYX = 0x0000A400;
+ /**
+ * RSA public key exchange algorithm
+ *
+ */
+ const CALG_RSA_SIGN = 0x00002400;
+ /**
+ * Public Key
+ *
+ */
+ const RSA1 = 0x31415352;
+ /**
+ * Private Key
+ *
+ */
+ const RSA2 = 0x32415352;
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ if (!Strings::is_stringable($key)) {
+ throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
+ }
+
+ $key = Strings::base64_decode($key);
+
+ if (!is_string($key)) {
+ throw new \UnexpectedValueException('Base64 decoding produced an error');
+ }
+ if (strlen($key) < 20) {
+ throw new \UnexpectedValueException('Key appears to be malformed');
+ }
+
+ // PUBLICKEYSTRUC publickeystruc
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/aa387453(v=vs.85).aspx
+ $unpacked = unpack('atype/aversion/vreserved/Valgo', Strings::shift($key, 8));
+ $type = $unpacked['type'];
+ $version = $unpacked['version'];
+ $reserved = $unpacked['reserved'];
+ $algo = $unpacked['algo'];
+ switch (ord($type)) {
+ case self::PUBLICKEYBLOB:
+ case self::PUBLICKEYBLOBEX:
+ $publickey = true;
+ break;
+ case self::PRIVATEKEYBLOB:
+ $publickey = false;
+ break;
+ default:
+ throw new \UnexpectedValueException('Key appears to be malformed');
+ }
+
+ $components = ['isPublicKey' => $publickey];
+
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/aa375549(v=vs.85).aspx
+ switch ($algo) {
+ case self::CALG_RSA_KEYX:
+ case self::CALG_RSA_SIGN:
+ break;
+ default:
+ throw new \UnexpectedValueException('Key appears to be malformed');
+ }
+
+ // RSAPUBKEY rsapubkey
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/aa387685(v=vs.85).aspx
+ // could do V for pubexp but that's unsigned 32-bit whereas some PHP installs only do signed 32-bit
+ $unpacked = unpack('Vmagic/Vbitlen/a4pubexp', Strings::shift($key, 12));
+ $magic = $unpacked['magic'];
+ $bitlen = $unpacked['bitlen'];
+ $pubexp = $unpacked['pubexp'];
+ switch ($magic) {
+ case self::RSA2:
+ $components['isPublicKey'] = false;
+ // fall-through
+ case self::RSA1:
+ break;
+ default:
+ throw new \UnexpectedValueException('Key appears to be malformed');
+ }
+
+ $baseLength = $bitlen / 16;
+ if (strlen($key) != 2 * $baseLength && strlen($key) != 9 * $baseLength) {
+ throw new \UnexpectedValueException('Key appears to be malformed');
+ }
+
+ $components[$components['isPublicKey'] ? 'publicExponent' : 'privateExponent'] = new BigInteger(strrev($pubexp), 256);
+ // BYTE modulus[rsapubkey.bitlen/8]
+ $components['modulus'] = new BigInteger(strrev(Strings::shift($key, $bitlen / 8)), 256);
+
+ if ($publickey) {
+ return $components;
+ }
+
+ $components['isPublicKey'] = false;
+
+ // BYTE prime1[rsapubkey.bitlen/16]
+ $components['primes'] = [1 => new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256)];
+ // BYTE prime2[rsapubkey.bitlen/16]
+ $components['primes'][] = new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256);
+ // BYTE exponent1[rsapubkey.bitlen/16]
+ $components['exponents'] = [1 => new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256)];
+ // BYTE exponent2[rsapubkey.bitlen/16]
+ $components['exponents'][] = new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256);
+ // BYTE coefficient[rsapubkey.bitlen/16]
+ $components['coefficients'] = [2 => new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256)];
+ if (isset($components['privateExponent'])) {
+ $components['publicExponent'] = $components['privateExponent'];
+ }
+ // BYTE privateExponent[rsapubkey.bitlen/8]
+ $components['privateExponent'] = new BigInteger(strrev(Strings::shift($key, $bitlen / 8)), 256);
+
+ return $components;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param BigInteger $d
+ * @param array $primes
+ * @param array $exponents
+ * @param array $coefficients
+ * @param string $password optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '')
+ {
+ if (count($primes) != 2) {
+ throw new \InvalidArgumentException('MSBLOB does not support multi-prime RSA keys');
+ }
+
+ if (!empty($password) && is_string($password)) {
+ throw new UnsupportedFormatException('MSBLOB private keys do not support encryption');
+ }
+
+ $n = strrev($n->toBytes());
+ $e = str_pad(strrev($e->toBytes()), 4, "\0");
+ $key = pack('aavV', chr(self::PRIVATEKEYBLOB), chr(2), 0, self::CALG_RSA_KEYX);
+ $key .= pack('VVa*', self::RSA2, 8 * strlen($n), $e);
+ $key .= $n;
+ $key .= strrev($primes[1]->toBytes());
+ $key .= strrev($primes[2]->toBytes());
+ $key .= strrev($exponents[1]->toBytes());
+ $key .= strrev($exponents[2]->toBytes());
+ $key .= strrev($coefficients[2]->toBytes());
+ $key .= strrev($d->toBytes());
+
+ return Strings::base64_encode($key);
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $n, BigInteger $e)
+ {
+ $n = strrev($n->toBytes());
+ $e = str_pad(strrev($e->toBytes()), 4, "\0");
+ $key = pack('aavV', chr(self::PUBLICKEYBLOB), chr(2), 0, self::CALG_RSA_KEYX);
+ $key .= pack('VVa*', self::RSA1, 8 * strlen($n), $e);
+ $key .= $n;
+
+ return Strings::base64_encode($key);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/OpenSSH.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/OpenSSH.php
new file mode 100644
index 000000000..ca74ea481
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/OpenSSH.php
@@ -0,0 +1,132 @@
+<?php
+
+/**
+ * OpenSSH Formatted RSA Key Handler
+ *
+ * PHP version 5
+ *
+ * Place in $HOME/.ssh/authorized_keys
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\RSA\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\Formats\Keys\OpenSSH as Progenitor;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * OpenSSH Formatted RSA Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class OpenSSH extends Progenitor
+{
+ /**
+ * Supported Key Types
+ *
+ * @var array
+ */
+ protected static $types = ['ssh-rsa'];
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ static $one;
+ if (!isset($one)) {
+ $one = new BigInteger(1);
+ }
+
+ $parsed = parent::load($key, $password);
+
+ if (isset($parsed['paddedKey'])) {
+ list($type) = Strings::unpackSSH2('s', $parsed['paddedKey']);
+ if ($type != $parsed['type']) {
+ throw new \RuntimeException("The public and private keys are not of the same type ($type vs $parsed[type])");
+ }
+
+ $primes = $coefficients = [];
+
+ list(
+ $modulus,
+ $publicExponent,
+ $privateExponent,
+ $coefficients[2],
+ $primes[1],
+ $primes[2],
+ $comment,
+ ) = Strings::unpackSSH2('i6s', $parsed['paddedKey']);
+
+ $temp = $primes[1]->subtract($one);
+ $exponents = [1 => $publicExponent->modInverse($temp)];
+ $temp = $primes[2]->subtract($one);
+ $exponents[] = $publicExponent->modInverse($temp);
+
+ $isPublicKey = false;
+
+ return compact('publicExponent', 'modulus', 'privateExponent', 'primes', 'coefficients', 'exponents', 'comment', 'isPublicKey');
+ }
+
+ list($publicExponent, $modulus) = Strings::unpackSSH2('ii', $parsed['publicKey']);
+
+ return [
+ 'isPublicKey' => true,
+ 'modulus' => $modulus,
+ 'publicExponent' => $publicExponent,
+ 'comment' => $parsed['comment']
+ ];
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $n, BigInteger $e, array $options = [])
+ {
+ $RSAPublicKey = Strings::packSSH2('sii', 'ssh-rsa', $e, $n);
+
+ if (isset($options['binary']) ? $options['binary'] : self::$binary) {
+ return $RSAPublicKey;
+ }
+
+ $comment = isset($options['comment']) ? $options['comment'] : self::$comment;
+ $RSAPublicKey = 'ssh-rsa ' . base64_encode($RSAPublicKey) . ' ' . $comment;
+
+ return $RSAPublicKey;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param BigInteger $d
+ * @param array $primes
+ * @param array $exponents
+ * @param array $coefficients
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '', array $options = [])
+ {
+ $publicKey = self::savePublicKey($n, $e, ['binary' => true]);
+ $privateKey = Strings::packSSH2('si6', 'ssh-rsa', $n, $e, $d, $coefficients[2], $primes[1], $primes[2]);
+
+ return self::wrapPrivateKey($publicKey, $privateKey, $password, $options);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PKCS1.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PKCS1.php
new file mode 100644
index 000000000..68d92701e
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PKCS1.php
@@ -0,0 +1,187 @@
+<?php
+
+/**
+ * PKCS#1 Formatted RSA Key Handler
+ *
+ * PHP version 5
+ *
+ * Used by File/X509.php
+ *
+ * Processes keys with the following headers:
+ *
+ * -----BEGIN RSA PRIVATE KEY-----
+ * -----BEGIN RSA PUBLIC KEY-----
+ *
+ * Analogous to ssh-keygen's pem format (as specified by -m)
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\RSA\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\Formats\Keys\PKCS1 as Progenitor;
+use phpseclib3\File\ASN1;
+use phpseclib3\File\ASN1\Maps;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * PKCS#1 Formatted RSA Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PKCS1 extends Progenitor
+{
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ if (!Strings::is_stringable($key)) {
+ throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
+ }
+
+ if (strpos($key, 'PUBLIC') !== false) {
+ $components = ['isPublicKey' => true];
+ } elseif (strpos($key, 'PRIVATE') !== false) {
+ $components = ['isPublicKey' => false];
+ } else {
+ $components = [];
+ }
+
+ $key = parent::load($key, $password);
+
+ $decoded = ASN1::decodeBER($key);
+ if (!$decoded) {
+ throw new \RuntimeException('Unable to decode BER');
+ }
+
+ $key = ASN1::asn1map($decoded[0], Maps\RSAPrivateKey::MAP);
+ if (is_array($key)) {
+ $components += [
+ 'modulus' => $key['modulus'],
+ 'publicExponent' => $key['publicExponent'],
+ 'privateExponent' => $key['privateExponent'],
+ 'primes' => [1 => $key['prime1'], $key['prime2']],
+ 'exponents' => [1 => $key['exponent1'], $key['exponent2']],
+ 'coefficients' => [2 => $key['coefficient']]
+ ];
+ if ($key['version'] == 'multi') {
+ foreach ($key['otherPrimeInfos'] as $primeInfo) {
+ $components['primes'][] = $primeInfo['prime'];
+ $components['exponents'][] = $primeInfo['exponent'];
+ $components['coefficients'][] = $primeInfo['coefficient'];
+ }
+ }
+ if (!isset($components['isPublicKey'])) {
+ $components['isPublicKey'] = false;
+ }
+ return $components;
+ }
+
+ $key = ASN1::asn1map($decoded[0], Maps\RSAPublicKey::MAP);
+
+ if (!is_array($key)) {
+ throw new \RuntimeException('Unable to perform ASN1 mapping');
+ }
+
+ if (!isset($components['isPublicKey'])) {
+ $components['isPublicKey'] = true;
+ }
+
+ $components = $components + $key;
+ foreach ($components as &$val) {
+ if ($val instanceof BigInteger) {
+ $val = self::makePositive($val);
+ }
+ if (is_array($val)) {
+ foreach ($val as &$subval) {
+ if ($subval instanceof BigInteger) {
+ $subval = self::makePositive($subval);
+ }
+ }
+ }
+ }
+
+ return $components + $key;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param BigInteger $d
+ * @param array $primes
+ * @param array $exponents
+ * @param array $coefficients
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '', array $options = [])
+ {
+ $num_primes = count($primes);
+ $key = [
+ 'version' => $num_primes == 2 ? 'two-prime' : 'multi',
+ 'modulus' => $n,
+ 'publicExponent' => $e,
+ 'privateExponent' => $d,
+ 'prime1' => $primes[1],
+ 'prime2' => $primes[2],
+ 'exponent1' => $exponents[1],
+ 'exponent2' => $exponents[2],
+ 'coefficient' => $coefficients[2]
+ ];
+ for ($i = 3; $i <= $num_primes; $i++) {
+ $key['otherPrimeInfos'][] = [
+ 'prime' => $primes[$i],
+ 'exponent' => $exponents[$i],
+ 'coefficient' => $coefficients[$i]
+ ];
+ }
+
+ $key = ASN1::encodeDER($key, Maps\RSAPrivateKey::MAP);
+
+ return self::wrapPrivateKey($key, 'RSA', $password, $options);
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $n, BigInteger $e)
+ {
+ $key = [
+ 'modulus' => $n,
+ 'publicExponent' => $e
+ ];
+
+ $key = ASN1::encodeDER($key, Maps\RSAPublicKey::MAP);
+
+ return self::wrapPublicKey($key, 'RSA');
+ }
+
+ /**
+ * Negative numbers make no sense in RSA so convert them to positive
+ *
+ * @param BigInteger $x
+ * @return string
+ */
+ private static function makePositive(BigInteger $x)
+ {
+ return $x->isNegative() ?
+ new BigInteger($x->toBytes(true), 256) :
+ $x;
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PKCS8.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PKCS8.php
new file mode 100644
index 000000000..30f63ff97
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PKCS8.php
@@ -0,0 +1,122 @@
+<?php
+
+/**
+ * PKCS#8 Formatted RSA Key Handler
+ *
+ * PHP version 5
+ *
+ * Used by PHP's openssl_public_encrypt() and openssl's rsautl (when -pubin is set)
+ *
+ * Processes keys with the following headers:
+ *
+ * -----BEGIN ENCRYPTED PRIVATE KEY-----
+ * -----BEGIN PRIVATE KEY-----
+ * -----BEGIN PUBLIC KEY-----
+ *
+ * Analogous to ssh-keygen's pkcs8 format (as specified by -m). Although PKCS8
+ * is specific to private keys it's basically creating a DER-encoded wrapper
+ * for keys. This just extends that same concept to public keys (much like ssh-keygen)
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\RSA\Formats\Keys;
+
+use phpseclib3\Crypt\Common\Formats\Keys\PKCS8 as Progenitor;
+use phpseclib3\File\ASN1;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * PKCS#8 Formatted RSA Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PKCS8 extends Progenitor
+{
+ /**
+ * OID Name
+ *
+ * @var string
+ */
+ const OID_NAME = 'rsaEncryption';
+
+ /**
+ * OID Value
+ *
+ * @var string
+ */
+ const OID_VALUE = '1.2.840.113549.1.1.1';
+
+ /**
+ * Child OIDs loaded
+ *
+ * @var bool
+ */
+ protected static $childOIDsLoaded = false;
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ $key = parent::load($key, $password);
+
+ if (isset($key['privateKey'])) {
+ $components['isPublicKey'] = false;
+ $type = 'private';
+ } else {
+ $components['isPublicKey'] = true;
+ $type = 'public';
+ }
+
+ $result = $components + PKCS1::load($key[$type . 'Key']);
+
+ if (isset($key['meta'])) {
+ $result['meta'] = $key['meta'];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param BigInteger $d
+ * @param array $primes
+ * @param array $exponents
+ * @param array $coefficients
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '', array $options = [])
+ {
+ $key = PKCS1::savePrivateKey($n, $e, $d, $primes, $exponents, $coefficients);
+ $key = ASN1::extractBER($key);
+ return self::wrapPrivateKey($key, [], null, $password, null, '', $options);
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $n, BigInteger $e, array $options = [])
+ {
+ $key = PKCS1::savePublicKey($n, $e);
+ $key = ASN1::extractBER($key);
+ return self::wrapPublicKey($key, null, null, $options);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PSS.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PSS.php
new file mode 100644
index 000000000..bf51bcf76
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PSS.php
@@ -0,0 +1,238 @@
+<?php
+
+/**
+ * PKCS#8 Formatted RSA-PSS Key Handler
+ *
+ * PHP version 5
+ *
+ * Used by PHP's openssl_public_encrypt() and openssl's rsautl (when -pubin is set)
+ *
+ * Processes keys with the following headers:
+ *
+ * -----BEGIN ENCRYPTED PRIVATE KEY-----
+ * -----BEGIN PRIVATE KEY-----
+ * -----BEGIN PUBLIC KEY-----
+ *
+ * Analogous to "openssl genpkey -algorithm rsa-pss".
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\RSA\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\Formats\Keys\PKCS8 as Progenitor;
+use phpseclib3\File\ASN1;
+use phpseclib3\File\ASN1\Maps;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * PKCS#8 Formatted RSA-PSS Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PSS extends Progenitor
+{
+ /**
+ * OID Name
+ *
+ * @var string
+ */
+ const OID_NAME = 'id-RSASSA-PSS';
+
+ /**
+ * OID Value
+ *
+ * @var string
+ */
+ const OID_VALUE = '1.2.840.113549.1.1.10';
+
+ /**
+ * OIDs loaded
+ *
+ * @var bool
+ */
+ private static $oidsLoaded = false;
+
+ /**
+ * Child OIDs loaded
+ *
+ * @var bool
+ */
+ protected static $childOIDsLoaded = false;
+
+ /**
+ * Initialize static variables
+ */
+ private static function initialize_static_variables()
+ {
+ if (!self::$oidsLoaded) {
+ ASN1::loadOIDs([
+ 'md2' => '1.2.840.113549.2.2',
+ 'md4' => '1.2.840.113549.2.4',
+ 'md5' => '1.2.840.113549.2.5',
+ 'id-sha1' => '1.3.14.3.2.26',
+ 'id-sha256' => '2.16.840.1.101.3.4.2.1',
+ 'id-sha384' => '2.16.840.1.101.3.4.2.2',
+ 'id-sha512' => '2.16.840.1.101.3.4.2.3',
+ 'id-sha224' => '2.16.840.1.101.3.4.2.4',
+ 'id-sha512/224' => '2.16.840.1.101.3.4.2.5',
+ 'id-sha512/256' => '2.16.840.1.101.3.4.2.6',
+
+ 'id-mgf1' => '1.2.840.113549.1.1.8'
+ ]);
+ self::$oidsLoaded = true;
+ }
+ }
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ self::initialize_static_variables();
+
+ if (!Strings::is_stringable($key)) {
+ throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
+ }
+
+ $components = ['isPublicKey' => strpos($key, 'PUBLIC') !== false];
+
+ $key = parent::load($key, $password);
+
+ $type = isset($key['privateKey']) ? 'private' : 'public';
+
+ $result = $components + PKCS1::load($key[$type . 'Key']);
+
+ if (isset($key[$type . 'KeyAlgorithm']['parameters'])) {
+ $decoded = ASN1::decodeBER($key[$type . 'KeyAlgorithm']['parameters']);
+ if ($decoded === false) {
+ throw new \UnexpectedValueException('Unable to decode parameters');
+ }
+ $params = ASN1::asn1map($decoded[0], Maps\RSASSA_PSS_params::MAP);
+ } else {
+ $params = [];
+ }
+
+ if (isset($params['maskGenAlgorithm']['parameters'])) {
+ $decoded = ASN1::decodeBER($params['maskGenAlgorithm']['parameters']);
+ if ($decoded === false) {
+ throw new \UnexpectedValueException('Unable to decode parameters');
+ }
+ $params['maskGenAlgorithm']['parameters'] = ASN1::asn1map($decoded[0], Maps\HashAlgorithm::MAP);
+ } else {
+ $params['maskGenAlgorithm'] = [
+ 'algorithm' => 'id-mgf1',
+ 'parameters' => ['algorithm' => 'id-sha1']
+ ];
+ }
+
+ if (!isset($params['hashAlgorithm']['algorithm'])) {
+ $params['hashAlgorithm']['algorithm'] = 'id-sha1';
+ }
+
+ $result['hash'] = str_replace('id-', '', $params['hashAlgorithm']['algorithm']);
+ $result['MGFHash'] = str_replace('id-', '', $params['maskGenAlgorithm']['parameters']['algorithm']);
+ if (isset($params['saltLength'])) {
+ $result['saltLength'] = (int) $params['saltLength']->toString();
+ }
+
+ if (isset($key['meta'])) {
+ $result['meta'] = $key['meta'];
+ }
+
+ return $result;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param BigInteger $d
+ * @param array $primes
+ * @param array $exponents
+ * @param array $coefficients
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '', array $options = [])
+ {
+ self::initialize_static_variables();
+
+ $key = PKCS1::savePrivateKey($n, $e, $d, $primes, $exponents, $coefficients);
+ $key = ASN1::extractBER($key);
+ $params = self::savePSSParams($options);
+ return self::wrapPrivateKey($key, [], $params, $password, null, '', $options);
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $n, BigInteger $e, array $options = [])
+ {
+ self::initialize_static_variables();
+
+ $key = PKCS1::savePublicKey($n, $e);
+ $key = ASN1::extractBER($key);
+ $params = self::savePSSParams($options);
+ return self::wrapPublicKey($key, $params);
+ }
+
+ /**
+ * Encodes PSS parameters
+ *
+ * @param array $options
+ * @return string
+ */
+ public static function savePSSParams(array $options)
+ {
+ /*
+ The trailerField field is an integer. It provides
+ compatibility with IEEE Std 1363a-2004 [P1363A]. The value
+ MUST be 1, which represents the trailer field with hexadecimal
+ value 0xBC. Other trailer fields, including the trailer field
+ composed of HashID concatenated with 0xCC that is specified in
+ IEEE Std 1363a, are not supported. Implementations that
+ perform signature generation MUST omit the trailerField field,
+ indicating that the default trailer field value was used.
+ Implementations that perform signature validation MUST
+ recognize both a present trailerField field with value 1 and an
+ absent trailerField field.
+
+ source: https://tools.ietf.org/html/rfc4055#page-9
+ */
+ $params = [
+ 'trailerField' => new BigInteger(1)
+ ];
+ if (isset($options['hash'])) {
+ $params['hashAlgorithm']['algorithm'] = 'id-' . $options['hash'];
+ }
+ if (isset($options['MGFHash'])) {
+ $temp = ['algorithm' => 'id-' . $options['MGFHash']];
+ $temp = ASN1::encodeDER($temp, Maps\HashAlgorithm::MAP);
+ $params['maskGenAlgorithm'] = [
+ 'algorithm' => 'id-mgf1',
+ 'parameters' => new ASN1\Element($temp)
+ ];
+ }
+ if (isset($options['saltLength'])) {
+ $params['saltLength'] = new BigInteger($options['saltLength']);
+ }
+
+ return new ASN1\Element(ASN1::encodeDER($params, Maps\RSASSA_PSS_params::MAP));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PuTTY.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PuTTY.php
new file mode 100644
index 000000000..8416758c2
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/PuTTY.php
@@ -0,0 +1,124 @@
+<?php
+
+/**
+ * PuTTY Formatted RSA Key Handler
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\RSA\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\Formats\Keys\PuTTY as Progenitor;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * PuTTY Formatted RSA Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PuTTY extends Progenitor
+{
+ /**
+ * Public Handler
+ *
+ * @var string
+ */
+ const PUBLIC_HANDLER = 'phpseclib3\Crypt\RSA\Formats\Keys\OpenSSH';
+
+ /**
+ * Algorithm Identifier
+ *
+ * @var array
+ */
+ protected static $types = ['ssh-rsa'];
+
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ static $one;
+ if (!isset($one)) {
+ $one = new BigInteger(1);
+ }
+
+ $components = parent::load($key, $password);
+ if (!isset($components['private'])) {
+ return $components;
+ }
+ $type = $components['type'];
+ $comment = $components['comment'];
+ $public = $components['public'];
+ $private = $components['private'];
+ unset($components['public'], $components['private']);
+
+ $isPublicKey = false;
+
+ $result = Strings::unpackSSH2('ii', $public);
+ if ($result === false) {
+ throw new \UnexpectedValueException('Key appears to be malformed');
+ }
+ list($publicExponent, $modulus) = $result;
+
+ $result = Strings::unpackSSH2('iiii', $private);
+ if ($result === false) {
+ throw new \UnexpectedValueException('Key appears to be malformed');
+ }
+ $primes = $coefficients = [];
+ list($privateExponent, $primes[1], $primes[2], $coefficients[2]) = $result;
+
+ $temp = $primes[1]->subtract($one);
+ $exponents = [1 => $publicExponent->modInverse($temp)];
+ $temp = $primes[2]->subtract($one);
+ $exponents[] = $publicExponent->modInverse($temp);
+
+ return compact('publicExponent', 'modulus', 'privateExponent', 'primes', 'coefficients', 'exponents', 'comment', 'isPublicKey');
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param BigInteger $d
+ * @param array $primes
+ * @param array $exponents
+ * @param array $coefficients
+ * @param string $password optional
+ * @param array $options optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '', array $options = [])
+ {
+ if (count($primes) != 2) {
+ throw new \InvalidArgumentException('PuTTY does not support multi-prime RSA keys');
+ }
+
+ $public = Strings::packSSH2('ii', $e, $n);
+ $private = Strings::packSSH2('iiii', $d, $primes[1], $primes[2], $coefficients[2]);
+
+ return self::wrapPrivateKey($public, $private, 'ssh-rsa', $password, $options);
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $n, BigInteger $e)
+ {
+ return self::wrapPublicKey(Strings::packSSH2('ii', $e, $n), 'ssh-rsa');
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/Raw.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/Raw.php
new file mode 100644
index 000000000..55c7ccd7a
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/Raw.php
@@ -0,0 +1,184 @@
+<?php
+
+/**
+ * Raw RSA Key Handler
+ *
+ * PHP version 5
+ *
+ * An array containing two \phpseclib3\Math\BigInteger objects.
+ *
+ * The exponent can be indexed with any of the following:
+ *
+ * 0, e, exponent, publicExponent
+ *
+ * The modulus can be indexed with any of the following:
+ *
+ * 1, n, modulo, modulus
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\RSA\Formats\Keys;
+
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Raw RSA Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Raw
+{
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ if (!is_array($key)) {
+ throw new \UnexpectedValueException('Key should be a array - not a ' . gettype($key));
+ }
+
+ $key = array_change_key_case($key, CASE_LOWER);
+
+ $components = ['isPublicKey' => false];
+
+ foreach (['e', 'exponent', 'publicexponent', 0, 'privateexponent', 'd'] as $index) {
+ if (isset($key[$index])) {
+ $components['publicExponent'] = $key[$index];
+ break;
+ }
+ }
+
+ foreach (['n', 'modulo', 'modulus', 1] as $index) {
+ if (isset($key[$index])) {
+ $components['modulus'] = $key[$index];
+ break;
+ }
+ }
+
+ if (!isset($components['publicExponent']) || !isset($components['modulus'])) {
+ throw new \UnexpectedValueException('Modulus / exponent not present');
+ }
+
+ if (isset($key['primes'])) {
+ $components['primes'] = $key['primes'];
+ } elseif (isset($key['p']) && isset($key['q'])) {
+ $indices = [
+ ['p', 'q'],
+ ['prime1', 'prime2']
+ ];
+ foreach ($indices as $index) {
+ list($i0, $i1) = $index;
+ if (isset($key[$i0]) && isset($key[$i1])) {
+ $components['primes'] = [1 => $key[$i0], $key[$i1]];
+ }
+ }
+ }
+
+ if (isset($key['exponents'])) {
+ $components['exponents'] = $key['exponents'];
+ } else {
+ $indices = [
+ ['dp', 'dq'],
+ ['exponent1', 'exponent2']
+ ];
+ foreach ($indices as $index) {
+ list($i0, $i1) = $index;
+ if (isset($key[$i0]) && isset($key[$i1])) {
+ $components['exponents'] = [1 => $key[$i0], $key[$i1]];
+ }
+ }
+ }
+
+ if (isset($key['coefficients'])) {
+ $components['coefficients'] = $key['coefficients'];
+ } else {
+ foreach (['inverseq', 'q\'', 'coefficient'] as $index) {
+ if (isset($key[$index])) {
+ $components['coefficients'] = [2 => $key[$index]];
+ }
+ }
+ }
+
+ if (!isset($components['primes'])) {
+ $components['isPublicKey'] = true;
+ return $components;
+ }
+
+ if (!isset($components['exponents'])) {
+ $one = new BigInteger(1);
+ $temp = $components['primes'][1]->subtract($one);
+ $exponents = [1 => $components['publicExponent']->modInverse($temp)];
+ $temp = $components['primes'][2]->subtract($one);
+ $exponents[] = $components['publicExponent']->modInverse($temp);
+ $components['exponents'] = $exponents;
+ }
+
+ if (!isset($components['coefficients'])) {
+ $components['coefficients'] = [2 => $components['primes'][2]->modInverse($components['primes'][1])];
+ }
+
+ foreach (['privateexponent', 'd'] as $index) {
+ if (isset($key[$index])) {
+ $components['privateExponent'] = $key[$index];
+ break;
+ }
+ }
+
+ return $components;
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param BigInteger $d
+ * @param array $primes
+ * @param array $exponents
+ * @param array $coefficients
+ * @param string $password optional
+ * @param array $options optional
+ * @return array
+ */
+ public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '', array $options = [])
+ {
+ if (!empty($password) && is_string($password)) {
+ throw new UnsupportedFormatException('Raw private keys do not support encryption');
+ }
+
+ return [
+ 'e' => clone $e,
+ 'n' => clone $n,
+ 'd' => clone $d,
+ 'primes' => array_map(function ($var) {
+ return clone $var;
+ }, $primes),
+ 'exponents' => array_map(function ($var) {
+ return clone $var;
+ }, $exponents),
+ 'coefficients' => array_map(function ($var) {
+ return clone $var;
+ }, $coefficients)
+ ];
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @return array
+ */
+ public static function savePublicKey(BigInteger $n, BigInteger $e)
+ {
+ return ['e' => clone $e, 'n' => clone $n];
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/XML.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/XML.php
new file mode 100644
index 000000000..d569dea6d
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/Formats/Keys/XML.php
@@ -0,0 +1,171 @@
+<?php
+
+/**
+ * XML Formatted RSA Key Handler
+ *
+ * More info:
+ *
+ * http://www.w3.org/TR/xmldsig-core/#sec-RSAKeyValue
+ * http://www.w3.org/TR/xkms2/#XKMS_2_0_Paragraph_269
+ * http://en.wikipedia.org/wiki/XML_Signature
+ * http://en.wikipedia.org/wiki/XKMS
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\RSA\Formats\Keys;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Exception\BadConfigurationException;
+use phpseclib3\Exception\UnsupportedFormatException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * XML Formatted RSA Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class XML
+{
+ /**
+ * Break a public or private key down into its constituent components
+ *
+ * @param string $key
+ * @param string $password optional
+ * @return array
+ */
+ public static function load($key, $password = '')
+ {
+ if (!Strings::is_stringable($key)) {
+ throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
+ }
+
+ if (!class_exists('DOMDocument')) {
+ throw new BadConfigurationException('The dom extension is not setup correctly on this system');
+ }
+
+ $components = [
+ 'isPublicKey' => false,
+ 'primes' => [],
+ 'exponents' => [],
+ 'coefficients' => []
+ ];
+
+ $use_errors = libxml_use_internal_errors(true);
+
+ $dom = new \DOMDocument();
+ if (substr($key, 0, 5) != '<?xml') {
+ $key = '<xml>' . $key . '</xml>';
+ }
+ if (!$dom->loadXML($key)) {
+ libxml_use_internal_errors($use_errors);
+ throw new \UnexpectedValueException('Key does not appear to contain XML');
+ }
+ $xpath = new \DOMXPath($dom);
+ $keys = ['modulus', 'exponent', 'p', 'q', 'dp', 'dq', 'inverseq', 'd'];
+ foreach ($keys as $key) {
+ // $dom->getElementsByTagName($key) is case-sensitive
+ $temp = $xpath->query("//*[translate(local-name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')='$key']");
+ if (!$temp->length) {
+ continue;
+ }
+ $value = new BigInteger(Strings::base64_decode($temp->item(0)->nodeValue), 256);
+ switch ($key) {
+ case 'modulus':
+ $components['modulus'] = $value;
+ break;
+ case 'exponent':
+ $components['publicExponent'] = $value;
+ break;
+ case 'p':
+ $components['primes'][1] = $value;
+ break;
+ case 'q':
+ $components['primes'][2] = $value;
+ break;
+ case 'dp':
+ $components['exponents'][1] = $value;
+ break;
+ case 'dq':
+ $components['exponents'][2] = $value;
+ break;
+ case 'inverseq':
+ $components['coefficients'][2] = $value;
+ break;
+ case 'd':
+ $components['privateExponent'] = $value;
+ }
+ }
+
+ libxml_use_internal_errors($use_errors);
+
+ foreach ($components as $key => $value) {
+ if (is_array($value) && !count($value)) {
+ unset($components[$key]);
+ }
+ }
+
+ if (isset($components['modulus']) && isset($components['publicExponent'])) {
+ if (count($components) == 3) {
+ $components['isPublicKey'] = true;
+ }
+ return $components;
+ }
+
+ throw new \UnexpectedValueException('Modulus / exponent not present');
+ }
+
+ /**
+ * Convert a private key to the appropriate format.
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @param BigInteger $d
+ * @param array $primes
+ * @param array $exponents
+ * @param array $coefficients
+ * @param string $password optional
+ * @return string
+ */
+ public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '')
+ {
+ if (count($primes) != 2) {
+ throw new \InvalidArgumentException('XML does not support multi-prime RSA keys');
+ }
+
+ if (!empty($password) && is_string($password)) {
+ throw new UnsupportedFormatException('XML private keys do not support encryption');
+ }
+
+ return "<RSAKeyPair>\r\n" .
+ ' <Modulus>' . Strings::base64_encode($n->toBytes()) . "</Modulus>\r\n" .
+ ' <Exponent>' . Strings::base64_encode($e->toBytes()) . "</Exponent>\r\n" .
+ ' <P>' . Strings::base64_encode($primes[1]->toBytes()) . "</P>\r\n" .
+ ' <Q>' . Strings::base64_encode($primes[2]->toBytes()) . "</Q>\r\n" .
+ ' <DP>' . Strings::base64_encode($exponents[1]->toBytes()) . "</DP>\r\n" .
+ ' <DQ>' . Strings::base64_encode($exponents[2]->toBytes()) . "</DQ>\r\n" .
+ ' <InverseQ>' . Strings::base64_encode($coefficients[2]->toBytes()) . "</InverseQ>\r\n" .
+ ' <D>' . Strings::base64_encode($d->toBytes()) . "</D>\r\n" .
+ '</RSAKeyPair>';
+ }
+
+ /**
+ * Convert a public key to the appropriate format
+ *
+ * @param BigInteger $n
+ * @param BigInteger $e
+ * @return string
+ */
+ public static function savePublicKey(BigInteger $n, BigInteger $e)
+ {
+ return "<RSAKeyValue>\r\n" .
+ ' <Modulus>' . Strings::base64_encode($n->toBytes()) . "</Modulus>\r\n" .
+ ' <Exponent>' . Strings::base64_encode($e->toBytes()) . "</Exponent>\r\n" .
+ '</RSAKeyValue>';
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/PrivateKey.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/PrivateKey.php
new file mode 100644
index 000000000..8c828b316
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/PrivateKey.php
@@ -0,0 +1,530 @@
+<?php
+
+/**
+ * RSA Private Key
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\RSA;
+
+use phpseclib3\Crypt\Common;
+use phpseclib3\Crypt\Random;
+use phpseclib3\Crypt\RSA;
+use phpseclib3\Crypt\RSA\Formats\Keys\PSS;
+use phpseclib3\Exception\UnsupportedFormatException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Raw RSA Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+final class PrivateKey extends RSA implements Common\PrivateKey
+{
+ use Common\Traits\PasswordProtected;
+
+ /**
+ * Primes for Chinese Remainder Theorem (ie. p and q)
+ *
+ * @var array
+ */
+ protected $primes;
+
+ /**
+ * Exponents for Chinese Remainder Theorem (ie. dP and dQ)
+ *
+ * @var array
+ */
+ protected $exponents;
+
+ /**
+ * Coefficients for Chinese Remainder Theorem (ie. qInv)
+ *
+ * @var array
+ */
+ protected $coefficients;
+
+ /**
+ * Private Exponent
+ *
+ * @var BigInteger
+ */
+ protected $privateExponent;
+
+ /**
+ * RSADP
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.2 RFC3447#section-5.1.2}.
+ *
+ * @return bool|BigInteger
+ */
+ private function rsadp(BigInteger $c)
+ {
+ if ($c->compare(self::$zero) < 0 || $c->compare($this->modulus) > 0) {
+ throw new \OutOfRangeException('Ciphertext representative out of range');
+ }
+ return $this->exponentiate($c);
+ }
+
+ /**
+ * RSASP1
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.1 RFC3447#section-5.2.1}.
+ *
+ * @return bool|BigInteger
+ */
+ private function rsasp1(BigInteger $m)
+ {
+ if ($m->compare(self::$zero) < 0 || $m->compare($this->modulus) > 0) {
+ throw new \OutOfRangeException('Signature representative out of range');
+ }
+ return $this->exponentiate($m);
+ }
+
+ /**
+ * Exponentiate
+ *
+ * @param BigInteger $x
+ * @return BigInteger
+ */
+ protected function exponentiate(BigInteger $x)
+ {
+ switch (true) {
+ case empty($this->primes):
+ case $this->primes[1]->equals(self::$zero):
+ case empty($this->coefficients):
+ case $this->coefficients[2]->equals(self::$zero):
+ case empty($this->exponents):
+ case $this->exponents[1]->equals(self::$zero):
+ return $x->modPow($this->exponent, $this->modulus);
+ }
+
+ $num_primes = count($this->primes);
+
+ if (!static::$enableBlinding) {
+ $m_i = [
+ 1 => $x->modPow($this->exponents[1], $this->primes[1]),
+ 2 => $x->modPow($this->exponents[2], $this->primes[2])
+ ];
+ $h = $m_i[1]->subtract($m_i[2]);
+ $h = $h->multiply($this->coefficients[2]);
+ list(, $h) = $h->divide($this->primes[1]);
+ $m = $m_i[2]->add($h->multiply($this->primes[2]));
+
+ $r = $this->primes[1];
+ for ($i = 3; $i <= $num_primes; $i++) {
+ $m_i = $x->modPow($this->exponents[$i], $this->primes[$i]);
+
+ $r = $r->multiply($this->primes[$i - 1]);
+
+ $h = $m_i->subtract($m);
+ $h = $h->multiply($this->coefficients[$i]);
+ list(, $h) = $h->divide($this->primes[$i]);
+
+ $m = $m->add($r->multiply($h));
+ }
+ } else {
+ $smallest = $this->primes[1];
+ for ($i = 2; $i <= $num_primes; $i++) {
+ if ($smallest->compare($this->primes[$i]) > 0) {
+ $smallest = $this->primes[$i];
+ }
+ }
+
+ $r = BigInteger::randomRange(self::$one, $smallest->subtract(self::$one));
+
+ $m_i = [
+ 1 => $this->blind($x, $r, 1),
+ 2 => $this->blind($x, $r, 2)
+ ];
+ $h = $m_i[1]->subtract($m_i[2]);
+ $h = $h->multiply($this->coefficients[2]);
+ list(, $h) = $h->divide($this->primes[1]);
+ $m = $m_i[2]->add($h->multiply($this->primes[2]));
+
+ $r = $this->primes[1];
+ for ($i = 3; $i <= $num_primes; $i++) {
+ $m_i = $this->blind($x, $r, $i);
+
+ $r = $r->multiply($this->primes[$i - 1]);
+
+ $h = $m_i->subtract($m);
+ $h = $h->multiply($this->coefficients[$i]);
+ list(, $h) = $h->divide($this->primes[$i]);
+
+ $m = $m->add($r->multiply($h));
+ }
+ }
+
+ return $m;
+ }
+
+ /**
+ * Performs RSA Blinding
+ *
+ * Protects against timing attacks by employing RSA Blinding.
+ * Returns $x->modPow($this->exponents[$i], $this->primes[$i])
+ *
+ * @param BigInteger $x
+ * @param BigInteger $r
+ * @param int $i
+ * @return BigInteger
+ */
+ private function blind(BigInteger $x, BigInteger $r, $i)
+ {
+ $x = $x->multiply($r->modPow($this->publicExponent, $this->primes[$i]));
+ $x = $x->modPow($this->exponents[$i], $this->primes[$i]);
+
+ $r = $r->modInverse($this->primes[$i]);
+ $x = $x->multiply($r);
+ list(, $x) = $x->divide($this->primes[$i]);
+
+ return $x;
+ }
+
+ /**
+ * EMSA-PSS-ENCODE
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.1 RFC3447#section-9.1.1}.
+ *
+ * @return string
+ * @param string $m
+ * @throws \RuntimeException on encoding error
+ * @param int $emBits
+ */
+ private function emsa_pss_encode($m, $emBits)
+ {
+ // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
+ // be output.
+
+ $emLen = ($emBits + 1) >> 3; // ie. ceil($emBits / 8)
+ $sLen = $this->sLen !== null ? $this->sLen : $this->hLen;
+
+ $mHash = $this->hash->hash($m);
+ if ($emLen < $this->hLen + $sLen + 2) {
+ throw new \LengthException('RSA modulus too short');
+ }
+
+ $salt = Random::string($sLen);
+ $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt;
+ $h = $this->hash->hash($m2);
+ $ps = str_repeat(chr(0), $emLen - $sLen - $this->hLen - 2);
+ $db = $ps . chr(1) . $salt;
+ $dbMask = $this->mgf1($h, $emLen - $this->hLen - 1); // ie. stlren($db)
+ $maskedDB = $db ^ $dbMask;
+ $maskedDB[0] = ~chr(0xFF << ($emBits & 7)) & $maskedDB[0];
+ $em = $maskedDB . $h . chr(0xBC);
+
+ return $em;
+ }
+
+ /**
+ * RSASSA-PSS-SIGN
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.1 RFC3447#section-8.1.1}.
+ *
+ * @param string $m
+ * @return bool|string
+ */
+ private function rsassa_pss_sign($m)
+ {
+ // EMSA-PSS encoding
+
+ $em = $this->emsa_pss_encode($m, 8 * $this->k - 1);
+
+ // RSA signature
+
+ $m = $this->os2ip($em);
+ $s = $this->rsasp1($m);
+ $s = $this->i2osp($s, $this->k);
+
+ // Output the signature S
+
+ return $s;
+ }
+
+ /**
+ * RSASSA-PKCS1-V1_5-SIGN
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.1 RFC3447#section-8.2.1}.
+ *
+ * @param string $m
+ * @throws \LengthException if the RSA modulus is too short
+ * @return bool|string
+ */
+ private function rsassa_pkcs1_v1_5_sign($m)
+ {
+ // EMSA-PKCS1-v1_5 encoding
+
+ // If the encoding operation outputs "intended encoded message length too short," output "RSA modulus
+ // too short" and stop.
+ try {
+ $em = $this->emsa_pkcs1_v1_5_encode($m, $this->k);
+ } catch (\LengthException $e) {
+ throw new \LengthException('RSA modulus too short');
+ }
+
+ // RSA signature
+
+ $m = $this->os2ip($em);
+ $s = $this->rsasp1($m);
+ $s = $this->i2osp($s, $this->k);
+
+ // Output the signature S
+
+ return $s;
+ }
+
+ /**
+ * Create a signature
+ *
+ * @see self::verify()
+ * @param string $message
+ * @return string
+ */
+ public function sign($message)
+ {
+ switch ($this->signaturePadding) {
+ case self::SIGNATURE_PKCS1:
+ case self::SIGNATURE_RELAXED_PKCS1:
+ return $this->rsassa_pkcs1_v1_5_sign($message);
+ //case self::SIGNATURE_PSS:
+ default:
+ return $this->rsassa_pss_sign($message);
+ }
+ }
+
+ /**
+ * RSAES-PKCS1-V1_5-DECRYPT
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.2 RFC3447#section-7.2.2}.
+ *
+ * @param string $c
+ * @return bool|string
+ */
+ private function rsaes_pkcs1_v1_5_decrypt($c)
+ {
+ // Length checking
+
+ if (strlen($c) != $this->k) { // or if k < 11
+ throw new \LengthException('Ciphertext representative too long');
+ }
+
+ // RSA decryption
+
+ $c = $this->os2ip($c);
+ $m = $this->rsadp($c);
+ $em = $this->i2osp($m, $this->k);
+
+ // EME-PKCS1-v1_5 decoding
+
+ if (ord($em[0]) != 0 || ord($em[1]) > 2) {
+ throw new \RuntimeException('Decryption error');
+ }
+
+ $ps = substr($em, 2, strpos($em, chr(0), 2) - 2);
+ $m = substr($em, strlen($ps) + 3);
+
+ if (strlen($ps) < 8) {
+ throw new \RuntimeException('Decryption error');
+ }
+
+ // Output M
+
+ return $m;
+ }
+
+ /**
+ * RSAES-OAEP-DECRYPT
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.2 RFC3447#section-7.1.2}. The fact that the error
+ * messages aren't distinguishable from one another hinders debugging, but, to quote from RFC3447#section-7.1.2:
+ *
+ * Note. Care must be taken to ensure that an opponent cannot
+ * distinguish the different error conditions in Step 3.g, whether by
+ * error message or timing, or, more generally, learn partial
+ * information about the encoded message EM. Otherwise an opponent may
+ * be able to obtain useful information about the decryption of the
+ * ciphertext C, leading to a chosen-ciphertext attack such as the one
+ * observed by Manger [36].
+ *
+ * @param string $c
+ * @return bool|string
+ */
+ private function rsaes_oaep_decrypt($c)
+ {
+ // Length checking
+
+ // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
+ // be output.
+
+ if (strlen($c) != $this->k || $this->k < 2 * $this->hLen + 2) {
+ throw new \LengthException('Ciphertext representative too long');
+ }
+
+ // RSA decryption
+
+ $c = $this->os2ip($c);
+ $m = $this->rsadp($c);
+ $em = $this->i2osp($m, $this->k);
+
+ // EME-OAEP decoding
+
+ $lHash = $this->hash->hash($this->label);
+ $y = ord($em[0]);
+ $maskedSeed = substr($em, 1, $this->hLen);
+ $maskedDB = substr($em, $this->hLen + 1);
+ $seedMask = $this->mgf1($maskedDB, $this->hLen);
+ $seed = $maskedSeed ^ $seedMask;
+ $dbMask = $this->mgf1($seed, $this->k - $this->hLen - 1);
+ $db = $maskedDB ^ $dbMask;
+ $lHash2 = substr($db, 0, $this->hLen);
+ $m = substr($db, $this->hLen);
+ $hashesMatch = hash_equals($lHash, $lHash2);
+ $leadingZeros = 1;
+ $patternMatch = 0;
+ $offset = 0;
+ for ($i = 0; $i < strlen($m); $i++) {
+ $patternMatch |= $leadingZeros & ($m[$i] === "\1");
+ $leadingZeros &= $m[$i] === "\0";
+ $offset += $patternMatch ? 0 : 1;
+ }
+
+ // we do | instead of || to avoid https://en.wikipedia.org/wiki/Short-circuit_evaluation
+ // to protect against timing attacks
+ if (!$hashesMatch | !$patternMatch) {
+ throw new \RuntimeException('Decryption error');
+ }
+
+ // Output the message M
+
+ return substr($m, $offset + 1);
+ }
+
+ /**
+ * Raw Encryption / Decryption
+ *
+ * Doesn't use padding and is not recommended.
+ *
+ * @param string $m
+ * @return bool|string
+ * @throws \LengthException if strlen($m) > $this->k
+ */
+ private function raw_encrypt($m)
+ {
+ if (strlen($m) > $this->k) {
+ throw new \LengthException('Ciphertext representative too long');
+ }
+
+ $temp = $this->os2ip($m);
+ $temp = $this->rsadp($temp);
+ return $this->i2osp($temp, $this->k);
+ }
+
+ /**
+ * Decryption
+ *
+ * @see self::encrypt()
+ * @param string $ciphertext
+ * @return bool|string
+ */
+ public function decrypt($ciphertext)
+ {
+ switch ($this->encryptionPadding) {
+ case self::ENCRYPTION_NONE:
+ return $this->raw_encrypt($ciphertext);
+ case self::ENCRYPTION_PKCS1:
+ return $this->rsaes_pkcs1_v1_5_decrypt($ciphertext);
+ //case self::ENCRYPTION_OAEP:
+ default:
+ return $this->rsaes_oaep_decrypt($ciphertext);
+ }
+ }
+
+ /**
+ * Returns the public key
+ *
+ * @return mixed
+ */
+ public function getPublicKey()
+ {
+ $type = self::validatePlugin('Keys', 'PKCS8', 'savePublicKey');
+ if (empty($this->modulus) || empty($this->publicExponent)) {
+ throw new \RuntimeException('Public key components not found');
+ }
+
+ $key = $type::savePublicKey($this->modulus, $this->publicExponent);
+ return RSA::loadFormat('PKCS8', $key)
+ ->withHash($this->hash->getHash())
+ ->withMGFHash($this->mgfHash->getHash())
+ ->withSaltLength($this->sLen)
+ ->withLabel($this->label)
+ ->withPadding($this->signaturePadding | $this->encryptionPadding);
+ }
+
+ /**
+ * Returns the private key
+ *
+ * @param string $type
+ * @param array $options optional
+ * @return string
+ */
+ public function toString($type, array $options = [])
+ {
+ $type = self::validatePlugin(
+ 'Keys',
+ $type,
+ empty($this->primes) ? 'savePublicKey' : 'savePrivateKey'
+ );
+
+ if ($type == PSS::class) {
+ if ($this->signaturePadding == self::SIGNATURE_PSS) {
+ $options += [
+ 'hash' => $this->hash->getHash(),
+ 'MGFHash' => $this->mgfHash->getHash(),
+ 'saltLength' => $this->getSaltLength()
+ ];
+ } else {
+ throw new UnsupportedFormatException('The PSS format can only be used when the signature method has been explicitly set to PSS');
+ }
+ }
+
+ if (empty($this->primes)) {
+ return $type::savePublicKey($this->modulus, $this->exponent, $options);
+ }
+
+ return $type::savePrivateKey($this->modulus, $this->publicExponent, $this->exponent, $this->primes, $this->exponents, $this->coefficients, $this->password, $options);
+
+ /*
+ $key = $type::savePrivateKey($this->modulus, $this->publicExponent, $this->exponent, $this->primes, $this->exponents, $this->coefficients, $this->password, $options);
+ if ($key !== false || count($this->primes) == 2) {
+ return $key;
+ }
+
+ $nSize = $this->getSize() >> 1;
+
+ $primes = [1 => clone self::$one, clone self::$one];
+ $i = 1;
+ foreach ($this->primes as $prime) {
+ $primes[$i] = $primes[$i]->multiply($prime);
+ if ($primes[$i]->getLength() >= $nSize) {
+ $i++;
+ }
+ }
+
+ $exponents = [];
+ $coefficients = [2 => $primes[2]->modInverse($primes[1])];
+
+ foreach ($primes as $i => $prime) {
+ $temp = $prime->subtract(self::$one);
+ $exponents[$i] = $this->modulus->modInverse($temp);
+ }
+
+ return $type::savePrivateKey($this->modulus, $this->publicExponent, $this->exponent, $primes, $exponents, $coefficients, $this->password, $options);
+ */
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/PublicKey.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/PublicKey.php
new file mode 100644
index 000000000..ff80ae79c
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/RSA/PublicKey.php
@@ -0,0 +1,513 @@
+<?php
+
+/**
+ * RSA Public Key
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt\RSA;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common;
+use phpseclib3\Crypt\Hash;
+use phpseclib3\Crypt\Random;
+use phpseclib3\Crypt\RSA;
+use phpseclib3\Crypt\RSA\Formats\Keys\PSS;
+use phpseclib3\Exception\UnsupportedAlgorithmException;
+use phpseclib3\Exception\UnsupportedFormatException;
+use phpseclib3\File\ASN1;
+use phpseclib3\File\ASN1\Maps\DigestInfo;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Raw RSA Key Handler
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+final class PublicKey extends RSA implements Common\PublicKey
+{
+ use Common\Traits\Fingerprint;
+
+ /**
+ * Exponentiate
+ *
+ * @param BigInteger $x
+ * @return BigInteger
+ */
+ private function exponentiate(BigInteger $x)
+ {
+ return $x->modPow($this->exponent, $this->modulus);
+ }
+
+ /**
+ * RSAVP1
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-5.2.2 RFC3447#section-5.2.2}.
+ *
+ * @param BigInteger $s
+ * @return bool|BigInteger
+ */
+ private function rsavp1($s)
+ {
+ if ($s->compare(self::$zero) < 0 || $s->compare($this->modulus) > 0) {
+ return false;
+ }
+ return $this->exponentiate($s);
+ }
+
+ /**
+ * RSASSA-PKCS1-V1_5-VERIFY
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-8.2.2 RFC3447#section-8.2.2}.
+ *
+ * @param string $m
+ * @param string $s
+ * @throws \LengthException if the RSA modulus is too short
+ * @return bool
+ */
+ private function rsassa_pkcs1_v1_5_verify($m, $s)
+ {
+ // Length checking
+
+ if (strlen($s) != $this->k) {
+ return false;
+ }
+
+ // RSA verification
+
+ $s = $this->os2ip($s);
+ $m2 = $this->rsavp1($s);
+ if ($m2 === false) {
+ return false;
+ }
+ $em = $this->i2osp($m2, $this->k);
+ if ($em === false) {
+ return false;
+ }
+
+ // EMSA-PKCS1-v1_5 encoding
+
+ $exception = false;
+
+ // If the encoding operation outputs "intended encoded message length too short," output "RSA modulus
+ // too short" and stop.
+ try {
+ $em2 = $this->emsa_pkcs1_v1_5_encode($m, $this->k);
+ $r1 = hash_equals($em, $em2);
+ } catch (\LengthException $e) {
+ $exception = true;
+ }
+
+ try {
+ $em3 = $this->emsa_pkcs1_v1_5_encode_without_null($m, $this->k);
+ $r2 = hash_equals($em, $em3);
+ } catch (\LengthException $e) {
+ $exception = true;
+ } catch (UnsupportedAlgorithmException $e) {
+ $r2 = false;
+ }
+
+ if ($exception) {
+ throw new \LengthException('RSA modulus too short');
+ }
+
+ // Compare
+ return $r1 || $r2;
+ }
+
+ /**
+ * RSASSA-PKCS1-V1_5-VERIFY (relaxed matching)
+ *
+ * Per {@link http://tools.ietf.org/html/rfc3447#page-43 RFC3447#page-43} PKCS1 v1.5
+ * specified the use BER encoding rather than DER encoding that PKCS1 v2.0 specified.
+ * This means that under rare conditions you can have a perfectly valid v1.5 signature
+ * that fails to validate with _rsassa_pkcs1_v1_5_verify(). PKCS1 v2.1 also recommends
+ * that if you're going to validate these types of signatures you "should indicate
+ * whether the underlying BER encoding is a DER encoding and hence whether the signature
+ * is valid with respect to the specification given in [PKCS1 v2.0+]". so if you do
+ * $rsa->getLastPadding() and get RSA::PADDING_RELAXED_PKCS1 back instead of
+ * RSA::PADDING_PKCS1... that means BER encoding was used.
+ *
+ * @param string $m
+ * @param string $s
+ * @return bool
+ */
+ private function rsassa_pkcs1_v1_5_relaxed_verify($m, $s)
+ {
+ // Length checking
+
+ if (strlen($s) != $this->k) {
+ return false;
+ }
+
+ // RSA verification
+
+ $s = $this->os2ip($s);
+ $m2 = $this->rsavp1($s);
+ if ($m2 === false) {
+ return false;
+ }
+ $em = $this->i2osp($m2, $this->k);
+ if ($em === false) {
+ return false;
+ }
+
+ if (Strings::shift($em, 2) != "\0\1") {
+ return false;
+ }
+
+ $em = ltrim($em, "\xFF");
+ if (Strings::shift($em) != "\0") {
+ return false;
+ }
+
+ $decoded = ASN1::decodeBER($em);
+ if (!is_array($decoded) || empty($decoded[0]) || strlen($em) > $decoded[0]['length']) {
+ return false;
+ }
+
+ static $oids;
+ if (!isset($oids)) {
+ $oids = [
+ 'md2' => '1.2.840.113549.2.2',
+ 'md4' => '1.2.840.113549.2.4', // from PKCS1 v1.5
+ 'md5' => '1.2.840.113549.2.5',
+ 'id-sha1' => '1.3.14.3.2.26',
+ 'id-sha256' => '2.16.840.1.101.3.4.2.1',
+ 'id-sha384' => '2.16.840.1.101.3.4.2.2',
+ 'id-sha512' => '2.16.840.1.101.3.4.2.3',
+ // from PKCS1 v2.2
+ 'id-sha224' => '2.16.840.1.101.3.4.2.4',
+ 'id-sha512/224' => '2.16.840.1.101.3.4.2.5',
+ 'id-sha512/256' => '2.16.840.1.101.3.4.2.6',
+ ];
+ ASN1::loadOIDs($oids);
+ }
+
+ $decoded = ASN1::asn1map($decoded[0], DigestInfo::MAP);
+ if (!isset($decoded) || $decoded === false) {
+ return false;
+ }
+
+ if (!isset($oids[$decoded['digestAlgorithm']['algorithm']])) {
+ return false;
+ }
+
+ if (isset($decoded['digestAlgorithm']['parameters']) && $decoded['digestAlgorithm']['parameters'] !== ['null' => '']) {
+ return false;
+ }
+
+ $hash = $decoded['digestAlgorithm']['algorithm'];
+ $hash = substr($hash, 0, 3) == 'id-' ?
+ substr($hash, 3) :
+ $hash;
+ $hash = new Hash($hash);
+ $em = $hash->hash($m);
+ $em2 = $decoded['digest'];
+
+ return hash_equals($em, $em2);
+ }
+
+ /**
+ * EMSA-PSS-VERIFY
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-9.1.2 RFC3447#section-9.1.2}.
+ *
+ * @param string $m
+ * @param string $em
+ * @param int $emBits
+ * @return string
+ */
+ private function emsa_pss_verify($m, $em, $emBits)
+ {
+ // if $m is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
+ // be output.
+
+ $emLen = ($emBits + 7) >> 3; // ie. ceil($emBits / 8);
+ $sLen = $this->sLen !== null ? $this->sLen : $this->hLen;
+
+ $mHash = $this->hash->hash($m);
+ if ($emLen < $this->hLen + $sLen + 2) {
+ return false;
+ }
+
+ if ($em[strlen($em) - 1] != chr(0xBC)) {
+ return false;
+ }
+
+ $maskedDB = substr($em, 0, -$this->hLen - 1);
+ $h = substr($em, -$this->hLen - 1, $this->hLen);
+ $temp = chr(0xFF << ($emBits & 7));
+ if ((~$maskedDB[0] & $temp) != $temp) {
+ return false;
+ }
+ $dbMask = $this->mgf1($h, $emLen - $this->hLen - 1);
+ $db = $maskedDB ^ $dbMask;
+ $db[0] = ~chr(0xFF << ($emBits & 7)) & $db[0];
+ $temp = $emLen - $this->hLen - $sLen - 2;
+ if (substr($db, 0, $temp) != str_repeat(chr(0), $temp) || ord($db[$temp]) != 1) {
+ return false;
+ }
+ $salt = substr($db, $temp + 1); // should be $sLen long
+ $m2 = "\0\0\0\0\0\0\0\0" . $mHash . $salt;
+ $h2 = $this->hash->hash($m2);
+ return hash_equals($h, $h2);
+ }
+
+ /**
+ * RSASSA-PSS-VERIFY
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-8.1.2 RFC3447#section-8.1.2}.
+ *
+ * @param string $m
+ * @param string $s
+ * @return bool|string
+ */
+ private function rsassa_pss_verify($m, $s)
+ {
+ // Length checking
+
+ if (strlen($s) != $this->k) {
+ return false;
+ }
+
+ // RSA verification
+
+ $modBits = strlen($this->modulus->toBits());
+
+ $s2 = $this->os2ip($s);
+ $m2 = $this->rsavp1($s2);
+ $em = $this->i2osp($m2, $this->k);
+ if ($em === false) {
+ return false;
+ }
+
+ // EMSA-PSS verification
+
+ return $this->emsa_pss_verify($m, $em, $modBits - 1);
+ }
+
+ /**
+ * Verifies a signature
+ *
+ * @see self::sign()
+ * @param string $message
+ * @param string $signature
+ * @return bool
+ */
+ public function verify($message, $signature)
+ {
+ switch ($this->signaturePadding) {
+ case self::SIGNATURE_RELAXED_PKCS1:
+ return $this->rsassa_pkcs1_v1_5_relaxed_verify($message, $signature);
+ case self::SIGNATURE_PKCS1:
+ return $this->rsassa_pkcs1_v1_5_verify($message, $signature);
+ //case self::SIGNATURE_PSS:
+ default:
+ return $this->rsassa_pss_verify($message, $signature);
+ }
+ }
+
+ /**
+ * RSAES-PKCS1-V1_5-ENCRYPT
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-7.2.1 RFC3447#section-7.2.1}.
+ *
+ * @param string $m
+ * @param bool $pkcs15_compat optional
+ * @throws \LengthException if strlen($m) > $this->k - 11
+ * @return bool|string
+ */
+ private function rsaes_pkcs1_v1_5_encrypt($m, $pkcs15_compat = false)
+ {
+ $mLen = strlen($m);
+
+ // Length checking
+
+ if ($mLen > $this->k - 11) {
+ throw new \LengthException('Message too long');
+ }
+
+ // EME-PKCS1-v1_5 encoding
+
+ $psLen = $this->k - $mLen - 3;
+ $ps = '';
+ while (strlen($ps) != $psLen) {
+ $temp = Random::string($psLen - strlen($ps));
+ $temp = str_replace("\x00", '', $temp);
+ $ps .= $temp;
+ }
+ $type = 2;
+ $em = chr(0) . chr($type) . $ps . chr(0) . $m;
+
+ // RSA encryption
+ $m = $this->os2ip($em);
+ $c = $this->rsaep($m);
+ $c = $this->i2osp($c, $this->k);
+
+ // Output the ciphertext C
+
+ return $c;
+ }
+
+ /**
+ * RSAES-OAEP-ENCRYPT
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-7.1.1 RFC3447#section-7.1.1} and
+ * {http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding OAES}.
+ *
+ * @param string $m
+ * @throws \LengthException if strlen($m) > $this->k - 2 * $this->hLen - 2
+ * @return string
+ */
+ private function rsaes_oaep_encrypt($m)
+ {
+ $mLen = strlen($m);
+
+ // Length checking
+
+ // if $l is larger than two million terrabytes and you're using sha1, PKCS#1 suggests a "Label too long" error
+ // be output.
+
+ if ($mLen > $this->k - 2 * $this->hLen - 2) {
+ throw new \LengthException('Message too long');
+ }
+
+ // EME-OAEP encoding
+
+ $lHash = $this->hash->hash($this->label);
+ $ps = str_repeat(chr(0), $this->k - $mLen - 2 * $this->hLen - 2);
+ $db = $lHash . $ps . chr(1) . $m;
+ $seed = Random::string($this->hLen);
+ $dbMask = $this->mgf1($seed, $this->k - $this->hLen - 1);
+ $maskedDB = $db ^ $dbMask;
+ $seedMask = $this->mgf1($maskedDB, $this->hLen);
+ $maskedSeed = $seed ^ $seedMask;
+ $em = chr(0) . $maskedSeed . $maskedDB;
+
+ // RSA encryption
+
+ $m = $this->os2ip($em);
+ $c = $this->rsaep($m);
+ $c = $this->i2osp($c, $this->k);
+
+ // Output the ciphertext C
+
+ return $c;
+ }
+
+ /**
+ * RSAEP
+ *
+ * See {@link http://tools.ietf.org/html/rfc3447#section-5.1.1 RFC3447#section-5.1.1}.
+ *
+ * @param BigInteger $m
+ * @return bool|BigInteger
+ */
+ private function rsaep($m)
+ {
+ if ($m->compare(self::$zero) < 0 || $m->compare($this->modulus) > 0) {
+ throw new \OutOfRangeException('Message representative out of range');
+ }
+ return $this->exponentiate($m);
+ }
+
+ /**
+ * Raw Encryption / Decryption
+ *
+ * Doesn't use padding and is not recommended.
+ *
+ * @param string $m
+ * @return bool|string
+ * @throws \LengthException if strlen($m) > $this->k
+ */
+ private function raw_encrypt($m)
+ {
+ if (strlen($m) > $this->k) {
+ throw new \LengthException('Message too long');
+ }
+
+ $temp = $this->os2ip($m);
+ $temp = $this->rsaep($temp);
+ return $this->i2osp($temp, $this->k);
+ }
+
+ /**
+ * Encryption
+ *
+ * Both self::PADDING_OAEP and self::PADDING_PKCS1 both place limits on how long $plaintext can be.
+ * If $plaintext exceeds those limits it will be broken up so that it does and the resultant ciphertext's will
+ * be concatenated together.
+ *
+ * @see self::decrypt()
+ * @param string $plaintext
+ * @return bool|string
+ * @throws \LengthException if the RSA modulus is too short
+ */
+ public function encrypt($plaintext)
+ {
+ switch ($this->encryptionPadding) {
+ case self::ENCRYPTION_NONE:
+ return $this->raw_encrypt($plaintext);
+ case self::ENCRYPTION_PKCS1:
+ return $this->rsaes_pkcs1_v1_5_encrypt($plaintext);
+ //case self::ENCRYPTION_OAEP:
+ default:
+ return $this->rsaes_oaep_encrypt($plaintext);
+ }
+ }
+
+ /**
+ * Returns the public key
+ *
+ * The public key is only returned under two circumstances - if the private key had the public key embedded within it
+ * or if the public key was set via setPublicKey(). If the currently loaded key is supposed to be the public key this
+ * function won't return it since this library, for the most part, doesn't distinguish between public and private keys.
+ *
+ * @param string $type
+ * @param array $options optional
+ * @return mixed
+ */
+ public function toString($type, array $options = [])
+ {
+ $type = self::validatePlugin('Keys', $type, 'savePublicKey');
+
+ if ($type == PSS::class) {
+ if ($this->signaturePadding == self::SIGNATURE_PSS) {
+ $options += [
+ 'hash' => $this->hash->getHash(),
+ 'MGFHash' => $this->mgfHash->getHash(),
+ 'saltLength' => $this->getSaltLength()
+ ];
+ } else {
+ throw new UnsupportedFormatException('The PSS format can only be used when the signature method has been explicitly set to PSS');
+ }
+ }
+
+ return $type::savePublicKey($this->modulus, $this->publicExponent, $options);
+ }
+
+ /**
+ * Converts a public key to a private key
+ *
+ * @return RSA
+ */
+ public function asPrivateKey()
+ {
+ $new = new PrivateKey();
+ $new->exponent = $this->exponent;
+ $new->modulus = $this->modulus;
+ $new->k = $this->k;
+ $new->format = $this->format;
+ return $new
+ ->withHash($this->hash->getHash())
+ ->withMGFHash($this->mgfHash->getHash())
+ ->withSaltLength($this->sLen)
+ ->withLabel($this->label)
+ ->withPadding($this->signaturePadding | $this->encryptionPadding);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Random.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Random.php
index e039340c5..f813a2eac 100644
--- a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Random.php
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Random.php
@@ -10,28 +10,24 @@
* <?php
* include 'vendor/autoload.php';
*
- * echo bin2hex(\phpseclib\Crypt\Random::string(8));
+ * echo bin2hex(\phpseclib3\Crypt\Random::string(8));
* ?>
* </code>
*
- * @category Crypt
- * @package Random
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
-namespace phpseclib\Crypt;
+namespace phpseclib3\Crypt;
/**
* Pure-PHP Random Number Generator
*
- * @package Random
* @author Jim Wigginton <terrafrost@php.net>
- * @access public
*/
-class Random
+abstract class Random
{
/**
* Generate a random string.
@@ -41,75 +37,26 @@ class Random
* eg. for RSA key generation.
*
* @param int $length
+ * @throws \RuntimeException if a symmetric cipher is needed but not loaded
* @return string
*/
- static function string($length)
+ public static function string($length)
{
if (!$length) {
return '';
}
- if (version_compare(PHP_VERSION, '7.0.0', '>=')) {
- try {
- return \random_bytes($length);
- } catch (\Throwable $e) {
- // If a sufficient source of randomness is unavailable, random_bytes() will throw an
- // object that implements the Throwable interface (Exception, TypeError, Error).
- // We don't actually need to do anything here. The string() method should just continue
- // as normal. Note, however, that if we don't have a sufficient source of randomness for
- // random_bytes(), most of the other calls here will fail too, so we'll end up using
- // the PHP implementation.
- }
- }
-
- if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
- // method 1. prior to PHP 5.3 this would call rand() on windows hence the function_exists('class_alias') call.
- // ie. class_alias is a function that was introduced in PHP 5.3
- if (extension_loaded('mcrypt') && function_exists('class_alias')) {
- return @mcrypt_create_iv($length);
- }
- // method 2. openssl_random_pseudo_bytes was introduced in PHP 5.3.0 but prior to PHP 5.3.4 there was,
- // to quote <http://php.net/ChangeLog-5.php#5.3.4>, "possible blocking behavior". as of 5.3.4
- // openssl_random_pseudo_bytes and mcrypt_create_iv do the exact same thing on Windows. ie. they both
- // call php_win32_get_random_bytes():
- //
- // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/ext/openssl/openssl.c#L5008
- // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/ext/mcrypt/mcrypt.c#L1392
- //
- // php_win32_get_random_bytes() is defined thusly:
- //
- // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/win32/winutil.c#L80
- //
- // we're calling it, all the same, in the off chance that the mcrypt extension is not available
- if (extension_loaded('openssl') && version_compare(PHP_VERSION, '5.3.4', '>=')) {
- return openssl_random_pseudo_bytes($length);
- }
- } else {
- // method 1. the fastest
- if (extension_loaded('openssl')) {
- return openssl_random_pseudo_bytes($length);
- }
- // method 2
- static $fp = true;
- if ($fp === true) {
- // warning's will be output unles the error suppression operator is used. errors such as
- // "open_basedir restriction in effect", "Permission denied", "No such file or directory", etc.
- $fp = @fopen('/dev/urandom', 'rb');
- }
- if ($fp !== true && $fp !== false) { // surprisingly faster than !is_bool() or is_resource()
- $temp = fread($fp, $length);
- if (strlen($temp) == $length) {
- return $temp;
- }
- }
- // method 3. pretty much does the same thing as method 2 per the following url:
- // https://github.com/php/php-src/blob/7014a0eb6d1611151a286c0ff4f2238f92c120d6/ext/mcrypt/mcrypt.c#L1391
- // surprisingly slower than method 2. maybe that's because mcrypt_create_iv does a bunch of error checking that we're
- // not doing. regardless, this'll only be called if this PHP script couldn't open /dev/urandom due to open_basedir
- // restrictions or some such
- if (extension_loaded('mcrypt')) {
- return @mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
- }
+ try {
+ return random_bytes($length);
+ } catch (\Exception $e) {
+ // random_compat will throw an Exception, which in PHP 5 does not implement Throwable
+ } catch (\Throwable $e) {
+ // If a sufficient source of randomness is unavailable, random_bytes() will throw an
+ // object that implements the Throwable interface (Exception, TypeError, Error).
+ // We don't actually need to do anything here. The string() method should just continue
+ // as normal. Note, however, that if we don't have a sufficient source of randomness for
+ // random_bytes(), most of the other calls here will fail too, so we'll end up using
+ // the PHP implementation.
}
// at this point we have no choice but to use a pure-PHP CSPRNG
@@ -122,11 +69,11 @@ class Random
// PHP isn't low level to be able to use those as sources and on a web server there's not likely
// going to be a ton of keyboard or mouse action. web servers do have one thing that we can use
// however, a ton of people visiting the website. obviously you don't want to base your seeding
- // soley on parameters a potential attacker sends but (1) not everything in $_SERVER is controlled
+ // solely on parameters a potential attacker sends but (1) not everything in $_SERVER is controlled
// by the user and (2) this isn't just looking at the data sent by the current user - it's based
// on the data sent by all users. one user requests the page and a hash of their info is saved.
// another user visits the page and the serialization of their data is utilized along with the
- // server envirnment stuff and a hash of the previous http request data (which itself utilizes
+ // server environment stuff and a hash of the previous http request data (which itself utilizes
// a hash of the session data before that). certainly an attacker should be assumed to have
// full control over his own http requests. he, however, is not going to have control over
// everyone's http requests.
@@ -146,18 +93,17 @@ class Random
session_cache_limiter('');
session_start();
- $v = $seed = $_SESSION['seed'] = pack('H*', sha1(
- (isset($_SERVER) ? phpseclib_safe_serialize($_SERVER) : '') .
- (isset($_POST) ? phpseclib_safe_serialize($_POST) : '') .
- (isset($_GET) ? phpseclib_safe_serialize($_GET) : '') .
- (isset($_COOKIE) ? phpseclib_safe_serialize($_COOKIE) : '') .
- // as of PHP 8.1 $GLOBALS can't be accessed by reference, which eliminates
- // the need for phpseclib_safe_serialize. see https://wiki.php.net/rfc/restrict_globals_usage
- // for more info
- (version_compare(PHP_VERSION, '8.1.0', '>=') ? serialize($GLOBALS) : phpseclib_safe_serialize($GLOBALS)) .
- phpseclib_safe_serialize($_SESSION) .
- phpseclib_safe_serialize($_OLD_SESSION)
- ));
+ $v = (isset($_SERVER) ? self::safe_serialize($_SERVER) : '') .
+ (isset($_POST) ? self::safe_serialize($_POST) : '') .
+ (isset($_GET) ? self::safe_serialize($_GET) : '') .
+ (isset($_COOKIE) ? self::safe_serialize($_COOKIE) : '') .
+ // as of PHP 8.1 $GLOBALS can't be accessed by reference, which eliminates
+ // the need for phpseclib_safe_serialize. see https://wiki.php.net/rfc/restrict_globals_usage
+ // for more info
+ (version_compare(PHP_VERSION, '8.1.0', '>=') ? serialize($GLOBALS) : self::safe_serialize($GLOBALS)) .
+ self::safe_serialize($_SESSION) .
+ self::safe_serialize($_OLD_SESSION);
+ $v = $seed = $_SESSION['seed'] = sha1($v, true);
if (!isset($_SESSION['count'])) {
$_SESSION['count'] = 0;
}
@@ -188,38 +134,37 @@ class Random
// http://tools.ietf.org/html/rfc4253#section-7.2
//
// see the is_string($crypto) part for an example of how to expand the keys
- $key = pack('H*', sha1($seed . 'A'));
- $iv = pack('H*', sha1($seed . 'C'));
+ $key = sha1($seed . 'A', true);
+ $iv = sha1($seed . 'C', true);
// ciphers are used as per the nist.gov link below. also, see this link:
//
// http://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator#Designs_based_on_cryptographic_primitives
switch (true) {
- case class_exists('\phpseclib\Crypt\AES'):
- $crypto = new AES(Base::MODE_CTR);
+ case class_exists('\phpseclib3\Crypt\AES'):
+ $crypto = new AES('ctr');
break;
- case class_exists('\phpseclib\Crypt\Twofish'):
- $crypto = new Twofish(Base::MODE_CTR);
+ case class_exists('\phpseclib3\Crypt\Twofish'):
+ $crypto = new Twofish('ctr');
break;
- case class_exists('\phpseclib\Crypt\Blowfish'):
- $crypto = new Blowfish(Base::MODE_CTR);
+ case class_exists('\phpseclib3\Crypt\Blowfish'):
+ $crypto = new Blowfish('ctr');
break;
- case class_exists('\phpseclib\Crypt\TripleDES'):
- $crypto = new TripleDES(Base::MODE_CTR);
+ case class_exists('\phpseclib3\Crypt\TripleDES'):
+ $crypto = new TripleDES('ctr');
break;
- case class_exists('\phpseclib\Crypt\DES'):
- $crypto = new DES(Base::MODE_CTR);
+ case class_exists('\phpseclib3\Crypt\DES'):
+ $crypto = new DES('ctr');
break;
- case class_exists('\phpseclib\Crypt\RC4'):
+ case class_exists('\phpseclib3\Crypt\RC4'):
$crypto = new RC4();
break;
default:
- user_error(__CLASS__ . ' requires at least one symmetric cipher be loaded');
- return false;
+ throw new \RuntimeException(__CLASS__ . ' requires at least one symmetric cipher be loaded');
}
- $crypto->setKey($key);
- $crypto->setIV($iv);
+ $crypto->setKey(substr($key, 0, $crypto->getKeyLength() >> 3));
+ $crypto->setIV(substr($iv, 0, $crypto->getBlockLength() >> 3));
$crypto->enableContinuousBuffer();
}
@@ -238,23 +183,20 @@ class Random
$i = $crypto->encrypt(microtime()); // strlen(microtime()) == 21
$r = $crypto->encrypt($i ^ $v); // strlen($v) == 20
$v = $crypto->encrypt($r ^ $i); // strlen($r) == 20
- $result.= $r;
+ $result .= $r;
}
+
return substr($result, 0, $length);
}
-}
-if (!function_exists('phpseclib_safe_serialize')) {
/**
* Safely serialize variables
*
- * If a class has a private __sleep() method it'll give a fatal error on PHP 5.2 and earlier.
- * PHP 5.3 will emit a warning.
- *
+ * If a class has a private __sleep() it'll emit a warning
+ * @return mixed
* @param mixed $arr
- * @access public
*/
- function phpseclib_safe_serialize(&$arr)
+ private static function safe_serialize(&$arr)
{
if (is_object($arr)) {
return '';
@@ -266,12 +208,12 @@ if (!function_exists('phpseclib_safe_serialize')) {
if (isset($arr['__phpseclib_marker'])) {
return '';
}
- $safearr = array();
+ $safearr = [];
$arr['__phpseclib_marker'] = true;
foreach (array_keys($arr) as $key) {
// do not recurse on the '__phpseclib_marker' key itself, for smaller memory usage
if ($key !== '__phpseclib_marker') {
- $safearr[$key] = phpseclib_safe_serialize($arr[$key]);
+ $safearr[$key] = self::safe_serialize($arr[$key]);
}
}
unset($arr['__phpseclib_marker']);
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Rijndael.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Rijndael.php
index 4665738e1..5ba7cf7fe 100644
--- a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Rijndael.php
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Rijndael.php
@@ -30,7 +30,7 @@
* <?php
* include 'vendor/autoload.php';
*
- * $rijndael = new \phpseclib\Crypt\Rijndael();
+ * $rijndael = new \phpseclib3\Crypt\Rijndael('ctr');
*
* $rijndael->setKey('abcdefghijklmnop');
*
@@ -44,135 +44,136 @@
* ?>
* </code>
*
- * @category Crypt
- * @package Rijndael
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2008 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
-namespace phpseclib\Crypt;
+namespace phpseclib3\Crypt;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\BlockCipher;
+use phpseclib3\Exception\BadDecryptionException;
+use phpseclib3\Exception\BadModeException;
+use phpseclib3\Exception\InconsistentSetupException;
+use phpseclib3\Exception\InsufficientSetupException;
/**
* Pure-PHP implementation of Rijndael.
*
- * @package Rijndael
* @author Jim Wigginton <terrafrost@php.net>
- * @access public
*/
-class Rijndael extends Base
+class Rijndael extends BlockCipher
{
/**
* The mcrypt specific name of the cipher
*
* Mcrypt is useable for 128/192/256-bit $block_size/$key_length. For 160/224 not.
- * \phpseclib\Crypt\Rijndael determines automatically whether mcrypt is useable
+ * \phpseclib3\Crypt\Rijndael determines automatically whether mcrypt is useable
* or not for the current $block_size/$key_length.
* In case of, $cipher_name_mcrypt will be set dynamically at run time accordingly.
*
- * @see \phpseclib\Crypt\Base::cipher_name_mcrypt
- * @see \phpseclib\Crypt\Base::engine
+ * @see Common\SymmetricKey::cipher_name_mcrypt
+ * @see Common\SymmetricKey::engine
* @see self::isValidEngine()
* @var string
- * @access private
*/
- var $cipher_name_mcrypt = 'rijndael-128';
-
- /**
- * The default salt used by setPassword()
- *
- * @see \phpseclib\Crypt\Base::password_default_salt
- * @see \phpseclib\Crypt\Base::setPassword()
- * @var string
- * @access private
- */
- var $password_default_salt = 'phpseclib';
+ protected $cipher_name_mcrypt = 'rijndael-128';
/**
* The Key Schedule
*
- * @see self::_setup()
+ * @see self::setup()
* @var array
- * @access private
*/
- var $w;
+ private $w;
/**
* The Inverse Key Schedule
*
- * @see self::_setup()
+ * @see self::setup()
* @var array
- * @access private
*/
- var $dw;
+ private $dw;
/**
* The Block Length divided by 32
*
- * @see self::setBlockLength()
- * @var int
- * @access private
- * @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4. Exists in conjunction with $block_size
+ * {@internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4. Exists in conjunction with $block_size
* because the encryption / decryption / key schedule creation requires this number and not $block_size. We could
* derive this from $block_size or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu
- * of that, we'll just precompute it once.
+ * of that, we'll just precompute it once.}
+ *
+ * @see self::setBlockLength()
+ * @var int
*/
- var $Nb = 4;
+ private $Nb = 4;
/**
* The Key Length (in bytes)
*
- * @see self::setKeyLength()
- * @var int
- * @access private
- * @internal The max value is 256 / 8 = 32, the min value is 128 / 8 = 16. Exists in conjunction with $Nk
+ * {@internal The max value is 256 / 8 = 32, the min value is 128 / 8 = 16. Exists in conjunction with $Nk
* because the encryption / decryption / key schedule creation requires this number and not $key_length. We could
* derive this from $key_length or vice versa, but that'd mean we'd have to do multiple shift operations, so in lieu
- * of that, we'll just precompute it once.
+ * of that, we'll just precompute it once.}
+ *
+ * @see self::setKeyLength()
+ * @var int
*/
- var $key_length = 16;
+ protected $key_length = 16;
/**
* The Key Length divided by 32
*
* @see self::setKeyLength()
* @var int
- * @access private
* @internal The max value is 256 / 32 = 8, the min value is 128 / 32 = 4
*/
- var $Nk = 4;
+ private $Nk = 4;
/**
* The Number of Rounds
*
+ * {@internal The max value is 14, the min value is 10.}
+ *
* @var int
- * @access private
- * @internal The max value is 14, the min value is 10.
*/
- var $Nr;
+ private $Nr;
/**
* Shift offsets
*
* @var array
- * @access private
*/
- var $c;
+ private $c;
/**
* Holds the last used key- and block_size information
*
* @var array
- * @access private
*/
- var $kl;
+ private $kl;
+
+ /**
+ * Default Constructor.
+ *
+ * @param string $mode
+ * @throws \InvalidArgumentException if an invalid / unsupported mode is provided
+ */
+ public function __construct($mode)
+ {
+ parent::__construct($mode);
+
+ if ($this->mode == self::MODE_STREAM) {
+ throw new BadModeException('Block ciphers cannot be ran in stream mode');
+ }
+ }
/**
* Sets the key length.
*
- * Valid key lengths are 128, 160, 192, 224, and 256. If the length is less than 128, it will be rounded up to
- * 128. If the length is greater than 128 and invalid, it will be rounded down to the closest valid amount.
+ * Valid key lengths are 128, 160, 192, 224, and 256.
*
* Note: phpseclib extends Rijndael (and AES) for using 160- and 224-bit keys but they are officially not defined
* and the most (if not all) implementations are not able using 160/224-bit keys but round/pad them up to
@@ -185,73 +186,111 @@ class Rijndael extends Base
* the mcrypt php extension, even if available.
* This results then in slower encryption.
*
- * @access public
+ * @throws \LengthException if the key length is invalid
* @param int $length
*/
- function setKeyLength($length)
+ public function setKeyLength($length)
{
- switch (true) {
- case $length <= 128:
- $this->key_length = 16;
- break;
- case $length <= 160:
- $this->key_length = 20;
- break;
- case $length <= 192:
- $this->key_length = 24;
- break;
- case $length <= 224:
- $this->key_length = 28;
+ switch ($length) {
+ case 128:
+ case 160:
+ case 192:
+ case 224:
+ case 256:
+ $this->key_length = $length >> 3;
break;
default:
- $this->key_length = 32;
+ throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys of sizes 128, 160, 192, 224 or 256 bits are supported');
}
parent::setKeyLength($length);
}
/**
+ * Sets the key.
+ *
+ * Rijndael supports five different key lengths
+ *
+ * @see setKeyLength()
+ * @param string $key
+ * @throws \LengthException if the key length isn't supported
+ */
+ public function setKey($key)
+ {
+ switch (strlen($key)) {
+ case 16:
+ case 20:
+ case 24:
+ case 28:
+ case 32:
+ break;
+ default:
+ throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes 16, 20, 24, 28 or 32 are supported');
+ }
+
+ parent::setKey($key);
+ }
+
+ /**
* Sets the block length
*
- * Valid block lengths are 128, 160, 192, 224, and 256. If the length is less than 128, it will be rounded up to
- * 128. If the length is greater than 128 and invalid, it will be rounded down to the closest valid amount.
+ * Valid block lengths are 128, 160, 192, 224, and 256.
*
- * @access public
* @param int $length
*/
- function setBlockLength($length)
+ public function setBlockLength($length)
{
- $length >>= 5;
- if ($length > 8) {
- $length = 8;
- } elseif ($length < 4) {
- $length = 4;
- }
- $this->Nb = $length;
- $this->block_size = $length << 2;
- $this->changed = true;
- $this->_setEngine();
+ switch ($length) {
+ case 128:
+ case 160:
+ case 192:
+ case 224:
+ case 256:
+ break;
+ default:
+ throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys of sizes 128, 160, 192, 224 or 256 bits are supported');
+ }
+
+ $this->Nb = $length >> 5;
+ $this->block_size = $length >> 3;
+ $this->changed = $this->nonIVChanged = true;
+ $this->setEngine();
}
/**
* Test for engine validity
*
- * This is mainly just a wrapper to set things up for \phpseclib\Crypt\Base::isValidEngine()
+ * This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine()
*
- * @see \phpseclib\Crypt\Base::__construct()
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
* @param int $engine
- * @access public
* @return bool
*/
- function isValidEngine($engine)
+ protected function isValidEngineHelper($engine)
{
switch ($engine) {
+ case self::ENGINE_LIBSODIUM:
+ return function_exists('sodium_crypto_aead_aes256gcm_is_available') &&
+ sodium_crypto_aead_aes256gcm_is_available() &&
+ $this->mode == self::MODE_GCM &&
+ $this->key_length == 32 &&
+ $this->nonce && strlen($this->nonce) == 12 &&
+ $this->block_size == 16;
+ case self::ENGINE_OPENSSL_GCM:
+ if (!extension_loaded('openssl')) {
+ return false;
+ }
+ $methods = openssl_get_cipher_methods();
+ return $this->mode == self::MODE_GCM &&
+ version_compare(PHP_VERSION, '7.1.0', '>=') &&
+ in_array('aes-' . $this->getKeyLength() . '-gcm', $methods) &&
+ $this->block_size == 16;
case self::ENGINE_OPENSSL:
if ($this->block_size != 16) {
return false;
}
$this->cipher_name_openssl_ecb = 'aes-' . ($this->key_length << 3) . '-ecb';
- $this->cipher_name_openssl = 'aes-' . ($this->key_length << 3) . '-' . $this->_openssl_translate_mode();
+ $this->cipher_name_openssl = 'aes-' . ($this->key_length << 3) . '-' . $this->openssl_translate_mode();
break;
case self::ENGINE_MCRYPT:
$this->cipher_name_mcrypt = 'rijndael-' . ($this->block_size << 3);
@@ -261,21 +300,20 @@ class Rijndael extends Base
}
}
- return parent::isValidEngine($engine);
+ return parent::isValidEngineHelper($engine);
}
/**
* Encrypts a block
*
- * @access private
* @param string $in
* @return string
*/
- function _encryptBlock($in)
+ protected function encryptBlock($in)
{
static $tables;
if (empty($tables)) {
- $tables = &$this->_getTables();
+ $tables = &$this->getTables();
}
$t0 = $tables[0];
$t1 = $tables[1];
@@ -283,7 +321,7 @@ class Rijndael extends Base
$t3 = $tables[3];
$sbox = $tables[4];
- $state = array();
+ $state = [];
$words = unpack('N*', $in);
$c = $this->c;
@@ -305,7 +343,7 @@ class Rijndael extends Base
// equation (7.4.7) is supposed to use addition instead of subtraction, so we'll do that here, as well.
// [1] http://fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.v316.pdf
- $temp = array();
+ $temp = [];
for ($round = 1; $round < $Nr; ++$round) {
$i = 0; // $c[0] == 0
$j = $c[1];
@@ -351,32 +389,20 @@ class Rijndael extends Base
$l = ($l + 1) % $Nb;
}
- switch ($Nb) {
- case 8:
- return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5], $temp[6], $temp[7]);
- case 7:
- return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5], $temp[6]);
- case 6:
- return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5]);
- case 5:
- return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4]);
- default:
- return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3]);
- }
+ return pack('N*', ...$temp);
}
/**
* Decrypts a block
*
- * @access private
* @param string $in
* @return string
*/
- function _decryptBlock($in)
+ protected function decryptBlock($in)
{
static $invtables;
if (empty($invtables)) {
- $invtables = &$this->_getInvTables();
+ $invtables = &$this->getInvTables();
}
$dt0 = $invtables[0];
$dt1 = $invtables[1];
@@ -384,7 +410,7 @@ class Rijndael extends Base
$dt3 = $invtables[3];
$isbox = $invtables[4];
- $state = array();
+ $state = [];
$words = unpack('N*', $in);
$c = $this->c;
@@ -398,7 +424,7 @@ class Rijndael extends Base
$state[] = $word ^ $dw[++$wc];
}
- $temp = array();
+ $temp = [];
for ($round = $Nr - 1; $round > 0; --$round) {
$i = 0; // $c[0] == 0
$j = $Nb - $c[1];
@@ -441,41 +467,67 @@ class Rijndael extends Base
$l = ($l + 1) % $Nb;
}
- switch ($Nb) {
- case 8:
- return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5], $temp[6], $temp[7]);
- case 7:
- return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5], $temp[6]);
- case 6:
- return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4], $temp[5]);
- case 5:
- return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3], $temp[4]);
- default:
- return pack('N*', $temp[0], $temp[1], $temp[2], $temp[3]);
+ return pack('N*', ...$temp);
+ }
+
+ /**
+ * Setup the self::ENGINE_INTERNAL $engine
+ *
+ * (re)init, if necessary, the internal cipher $engine and flush all $buffers
+ * Used (only) if $engine == self::ENGINE_INTERNAL
+ *
+ * _setup() will be called each time if $changed === true
+ * typically this happens when using one or more of following public methods:
+ *
+ * - setKey()
+ *
+ * - setIV()
+ *
+ * - disableContinuousBuffer()
+ *
+ * - First run of encrypt() / decrypt() with no init-settings
+ *
+ * {@internal setup() is always called before en/decryption.}
+ *
+ * {@internal Could, but not must, extend by the child Crypt_* class}
+ *
+ * @see self::setKey()
+ * @see self::setIV()
+ * @see self::disableContinuousBuffer()
+ */
+ protected function setup()
+ {
+ if (!$this->changed) {
+ return;
+ }
+
+ parent::setup();
+
+ if (is_string($this->iv) && strlen($this->iv) != $this->block_size) {
+ throw new InconsistentSetupException('The IV length (' . strlen($this->iv) . ') does not match the block size (' . $this->block_size . ')');
}
}
/**
* Setup the key (expansion)
*
- * @see \phpseclib\Crypt\Base::_setupKey()
- * @access private
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::setupKey()
*/
- function _setupKey()
+ protected function setupKey()
{
// Each number in $rcon is equal to the previous number multiplied by two in Rijndael's finite field.
// See http://en.wikipedia.org/wiki/Finite_field_arithmetic#Multiplicative_inverse
static $rcon;
if (!isset($rcon)) {
- $rcon = array(0,
+ $rcon = [0,
0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000,
0x20000000, 0x40000000, 0x80000000, 0x1B000000, 0x36000000,
0x6C000000, 0xD8000000, 0xAB000000, 0x4D000000, 0x9A000000,
0x2F000000, 0x5E000000, 0xBC000000, 0x63000000, 0xC6000000,
0x97000000, 0x35000000, 0x6A000000, 0xD4000000, 0xB3000000,
0x7D000000, 0xFA000000, 0xEF000000, 0xC5000000, 0x91000000
- );
+ ];
$rcon = array_map('intval', $rcon);
}
@@ -483,7 +535,7 @@ class Rijndael extends Base
// already expanded
return;
}
- $this->kl = array('key' => $this->key, 'key_length' => $this->key_length, 'block_size' => $this->block_size);
+ $this->kl = ['key' => $this->key, 'key_length' => $this->key_length, 'block_size' => $this->block_size];
$this->Nk = $this->key_length >> 2;
// see Rijndael-ammended.pdf#page=44
@@ -497,13 +549,13 @@ class Rijndael extends Base
case 4:
case 5:
case 6:
- $this->c = array(0, 1, 2, 3);
+ $this->c = [0, 1, 2, 3];
break;
case 7:
- $this->c = array(0, 1, 2, 4);
+ $this->c = [0, 1, 2, 4];
break;
case 8:
- $this->c = array(0, 1, 3, 4);
+ $this->c = [0, 1, 3, 4];
}
$w = array_values(unpack('N*words', $this->key));
@@ -517,9 +569,9 @@ class Rijndael extends Base
// 0xFFFFFFFF << 8 == 0xFFFFFF00, but on a 64-bit machine, it equals 0xFFFFFFFF00. as such, doing 'and'
// with 0xFFFFFFFF (or 0xFFFFFF00) on a 32-bit machine is unnecessary, but on a 64-bit machine, it is.
$temp = (($temp << 8) & intval(0xFFFFFF00)) | (($temp >> 24) & 0x000000FF); // rotWord
- $temp = $this->_subWord($temp) ^ $rcon[$i / $this->Nk];
+ $temp = $this->subWord($temp) ^ $rcon[$i / $this->Nk];
} elseif ($this->Nk > 6 && $i % $this->Nk == 4) {
- $temp = $this->_subWord($temp);
+ $temp = $this->subWord($temp);
}
$w[$i] = $w[$i - $this->Nk] ^ $temp;
}
@@ -531,8 +583,8 @@ class Rijndael extends Base
// 1. Apply the Key Expansion.
// 2. Apply InvMixColumn to all Round Keys except the first and the last one."
// also, see fips-197.pdf#page=27, "5.3.5 Equivalent Inverse Cipher"
- list($dt0, $dt1, $dt2, $dt3) = $this->_getInvTables();
- $temp = $this->w = $this->dw = array();
+ list($dt0, $dt1, $dt2, $dt3) = $this->getInvTables();
+ $temp = $this->w = $this->dw = [];
for ($i = $row = $col = 0; $i < $length; $i++, $col++) {
if ($col == $this->Nb) {
if ($row == 0) {
@@ -541,7 +593,7 @@ class Rijndael extends Base
// subWord + invMixColumn + invSubWord = invMixColumn
$j = 0;
while ($j < $this->Nb) {
- $dw = $this->_subWord($this->w[$row][$j]);
+ $dw = $this->subWord($this->w[$row][$j]);
$temp[$j] = $dt0[$dw >> 24 & 0x000000FF] ^
$dt1[$dw >> 16 & 0x000000FF] ^
$dt2[$dw >> 8 & 0x000000FF] ^
@@ -576,14 +628,14 @@ class Rijndael extends Base
/**
* Performs S-Box substitutions
*
- * @access private
+ * @return array
* @param int $word
*/
- function _subWord($word)
+ private function subWord($word)
{
static $sbox;
if (empty($sbox)) {
- list(, , , , $sbox) = $this->_getTables();
+ list(, , , , $sbox) = self::getTables();
}
return $sbox[$word & 0x000000FF] |
@@ -595,20 +647,19 @@ class Rijndael extends Base
/**
* Provides the mixColumns and sboxes tables
*
- * @see self::_encryptBlock()
- * @see self::_setupInlineCrypt()
- * @see self::_subWord()
- * @access private
+ * @see self::encryptBlock()
+ * @see self::setupInlineCrypt()
+ * @see self::subWord()
* @return array &$tables
*/
- function &_getTables()
+ protected function &getTables()
{
static $tables;
if (empty($tables)) {
// according to <http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=19> (section 5.2.1),
// precomputed tables can be used in the mixColumns phase. in that example, they're assigned t0...t3, so
// those are the names we'll use.
- $t3 = array_map('intval', array(
+ $t3 = array_map('intval', [
// with array_map('intval', ...) we ensure we have only int's and not
// some slower floats converted by php automatically on high values
0x6363A5C6, 0x7C7C84F8, 0x777799EE, 0x7B7B8DF6, 0xF2F20DFF, 0x6B6BBDD6, 0x6F6FB1DE, 0xC5C55491,
@@ -643,7 +694,7 @@ class Rijndael extends Base
0x9B9BB62D, 0x1E1E223C, 0x87879215, 0xE9E920C9, 0xCECE4987, 0x5555FFAA, 0x28287850, 0xDFDF7AA5,
0x8C8C8F03, 0xA1A1F859, 0x89898009, 0x0D0D171A, 0xBFBFDA65, 0xE6E631D7, 0x4242C684, 0x6868B8D0,
0x4141C382, 0x9999B029, 0x2D2D775A, 0x0F0F111E, 0xB0B0CB7B, 0x5454FCA8, 0xBBBBD66D, 0x16163A2C
- ));
+ ]);
foreach ($t3 as $t3i) {
$t0[] = (($t3i << 24) & intval(0xFF000000)) | (($t3i >> 8) & 0x00FFFFFF);
@@ -651,14 +702,14 @@ class Rijndael extends Base
$t2[] = (($t3i << 8) & intval(0xFFFFFF00)) | (($t3i >> 24) & 0x000000FF);
}
- $tables = array(
+ $tables = [
// The Precomputed mixColumns tables t0 - t3
$t0,
$t1,
$t2,
$t3,
// The SubByte S-Box
- array(
+ [
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
@@ -675,8 +726,8 @@ class Rijndael extends Base
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
- )
- );
+ ]
+ ];
}
return $tables;
}
@@ -684,17 +735,16 @@ class Rijndael extends Base
/**
* Provides the inverse mixColumns and inverse sboxes tables
*
- * @see self::_decryptBlock()
- * @see self::_setupInlineCrypt()
- * @see self::_setupKey()
- * @access private
+ * @see self::decryptBlock()
+ * @see self::setupInlineCrypt()
+ * @see self::setupKey()
* @return array &$tables
*/
- function &_getInvTables()
+ protected function &getInvTables()
{
static $tables;
if (empty($tables)) {
- $dt3 = array_map('intval', array(
+ $dt3 = array_map('intval', [
0xF4A75051, 0x4165537E, 0x17A4C31A, 0x275E963A, 0xAB6BCB3B, 0x9D45F11F, 0xFA58ABAC, 0xE303934B,
0x30FA5520, 0x766DF6AD, 0xCC769188, 0x024C25F5, 0xE5D7FC4F, 0x2ACBD7C5, 0x35448026, 0x62A38FB5,
0xB15A49DE, 0xBA1B6725, 0xEA0E9845, 0xFEC0E15D, 0x2F7502C3, 0x4CF01281, 0x4697A38D, 0xD3F9C66B,
@@ -727,7 +777,7 @@ class Rijndael extends Base
0xD2DF599C, 0xF2733F55, 0x14CE7918, 0xC737BF73, 0xF7CDEA53, 0xFDAA5B5F, 0x3D6F14DF, 0x44DB8678,
0xAFF381CA, 0x68C43EB9, 0x24342C38, 0xA3405FC2, 0x1DC37216, 0xE2250CBC, 0x3C498B28, 0x0D9541FF,
0xA8017139, 0x0CB3DE08, 0xB4E49CD8, 0x56C19064, 0xCB84617B, 0x32B670D5, 0x6C5C7448, 0xB85742D0
- ));
+ ]);
foreach ($dt3 as $dt3i) {
$dt0[] = (($dt3i << 24) & intval(0xFF000000)) | (($dt3i >> 8) & 0x00FFFFFF);
@@ -735,14 +785,14 @@ class Rijndael extends Base
$dt2[] = (($dt3i << 8) & intval(0xFFFFFF00)) | (($dt3i >> 24) & 0x000000FF);
};
- $tables = array(
+ $tables = [
// The Precomputed inverse mixColumns tables dt0 - dt3
$dt0,
$dt1,
$dt2,
$dt3,
// The inverse SubByte S-Box
- array(
+ [
0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
@@ -759,8 +809,8 @@ class Rijndael extends Base
0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D
- )
- );
+ ]
+ ];
}
return $tables;
}
@@ -768,172 +818,219 @@ class Rijndael extends Base
/**
* Setup the performance-optimized function for de/encrypt()
*
- * @see \phpseclib\Crypt\Base::_setupInlineCrypt()
- * @access private
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::setupInlineCrypt()
*/
- function _setupInlineCrypt()
+ protected function setupInlineCrypt()
{
- // Note: _setupInlineCrypt() will be called only if $this->changed === true
- // So here we are'nt under the same heavy timing-stress as we are in _de/encryptBlock() or de/encrypt().
- // However...the here generated function- $code, stored as php callback in $this->inline_crypt, must work as fast as even possible.
-
- $lambda_functions =& self::_getLambdaFunctions();
-
- // We create max. 10 hi-optimized code for memory reason. Means: For each $key one ultra fast inline-crypt function.
- // (Currently, for Crypt_Rijndael/AES, one generated $lambda_function cost on php5.5@32bit ~80kb unfreeable mem and ~130kb on php5.5@64bit)
- // After that, we'll still create very fast optimized code but not the hi-ultimative code, for each $mode one.
- $gen_hi_opt_code = (bool)(count($lambda_functions) < 10);
-
- // Generation of a uniqe hash for our generated code
- $code_hash = "Crypt_Rijndael, {$this->mode}, {$this->Nr}, {$this->Nb}";
- if ($gen_hi_opt_code) {
- $code_hash = str_pad($code_hash, 32) . $this->_hashInlineCryptFunction($this->key);
- }
-
- if (!isset($lambda_functions[$code_hash])) {
- switch (true) {
- case $gen_hi_opt_code:
- // The hi-optimized $lambda_functions will use the key-words hardcoded for better performance.
- $w = $this->w;
- $dw = $this->dw;
- $init_encrypt = '';
- $init_decrypt = '';
- break;
- default:
- for ($i = 0, $cw = count($this->w); $i < $cw; ++$i) {
- $w[] = '$w[' . $i . ']';
- $dw[] = '$dw[' . $i . ']';
- }
- $init_encrypt = '$w = $self->w;';
- $init_decrypt = '$dw = $self->dw;';
- }
+ $w = $this->w;
+ $dw = $this->dw;
+ $init_encrypt = '';
+ $init_decrypt = '';
- $Nr = $this->Nr;
- $Nb = $this->Nb;
- $c = $this->c;
+ $Nr = $this->Nr;
+ $Nb = $this->Nb;
+ $c = $this->c;
- // Generating encrypt code:
- $init_encrypt.= '
- if (empty($tables)) {
- $tables = &$self->_getTables();
- }
- $t0 = $tables[0];
- $t1 = $tables[1];
- $t2 = $tables[2];
- $t3 = $tables[3];
- $sbox = $tables[4];
- ';
-
- $s = 'e';
- $e = 's';
- $wc = $Nb - 1;
-
- // Preround: addRoundKey
- $encrypt_block = '$in = unpack("N*", $in);'."\n";
- for ($i = 0; $i < $Nb; ++$i) {
- $encrypt_block .= '$s'.$i.' = $in['.($i + 1).'] ^ '.$w[++$wc].";\n";
+ // Generating encrypt code:
+ $init_encrypt .= '
+ if (empty($tables)) {
+ $tables = &$this->getTables();
}
+ $t0 = $tables[0];
+ $t1 = $tables[1];
+ $t2 = $tables[2];
+ $t3 = $tables[3];
+ $sbox = $tables[4];
+ ';
+
+ $s = 'e';
+ $e = 's';
+ $wc = $Nb - 1;
- // Mainrounds: shiftRows + subWord + mixColumns + addRoundKey
- for ($round = 1; $round < $Nr; ++$round) {
- list($s, $e) = array($e, $s);
- for ($i = 0; $i < $Nb; ++$i) {
- $encrypt_block.=
- '$'.$e.$i.' =
- $t0[($'.$s.$i .' >> 24) & 0xff] ^
- $t1[($'.$s.(($i + $c[1]) % $Nb).' >> 16) & 0xff] ^
- $t2[($'.$s.(($i + $c[2]) % $Nb).' >> 8) & 0xff] ^
- $t3[ $'.$s.(($i + $c[3]) % $Nb).' & 0xff] ^
- '.$w[++$wc].";\n";
- }
- }
+ // Preround: addRoundKey
+ $encrypt_block = '$in = unpack("N*", $in);' . "\n";
+ for ($i = 0; $i < $Nb; ++$i) {
+ $encrypt_block .= '$s' . $i . ' = $in[' . ($i + 1) . '] ^ ' . $w[++$wc] . ";\n";
+ }
- // Finalround: subWord + shiftRows + addRoundKey
+ // Mainrounds: shiftRows + subWord + mixColumns + addRoundKey
+ for ($round = 1; $round < $Nr; ++$round) {
+ list($s, $e) = [$e, $s];
for ($i = 0; $i < $Nb; ++$i) {
- $encrypt_block.=
- '$'.$e.$i.' =
- $sbox[ $'.$e.$i.' & 0xff] |
- ($sbox[($'.$e.$i.' >> 8) & 0xff] << 8) |
- ($sbox[($'.$e.$i.' >> 16) & 0xff] << 16) |
- ($sbox[($'.$e.$i.' >> 24) & 0xff] << 24);'."\n";
+ $encrypt_block .=
+ '$' . $e . $i . ' =
+ $t0[($' . $s . $i . ' >> 24) & 0xff] ^
+ $t1[($' . $s . (($i + $c[1]) % $Nb) . ' >> 16) & 0xff] ^
+ $t2[($' . $s . (($i + $c[2]) % $Nb) . ' >> 8) & 0xff] ^
+ $t3[ $' . $s . (($i + $c[3]) % $Nb) . ' & 0xff] ^
+ ' . $w[++$wc] . ";\n";
}
- $encrypt_block .= '$in = pack("N*"'."\n";
- for ($i = 0; $i < $Nb; ++$i) {
- $encrypt_block.= ',
- ($'.$e.$i .' & '.((int)0xFF000000).') ^
- ($'.$e.(($i + $c[1]) % $Nb).' & 0x00FF0000 ) ^
- ($'.$e.(($i + $c[2]) % $Nb).' & 0x0000FF00 ) ^
- ($'.$e.(($i + $c[3]) % $Nb).' & 0x000000FF ) ^
- '.$w[$i]."\n";
+ }
+
+ // Finalround: subWord + shiftRows + addRoundKey
+ for ($i = 0; $i < $Nb; ++$i) {
+ $encrypt_block .=
+ '$' . $e . $i . ' =
+ $sbox[ $' . $e . $i . ' & 0xff] |
+ ($sbox[($' . $e . $i . ' >> 8) & 0xff] << 8) |
+ ($sbox[($' . $e . $i . ' >> 16) & 0xff] << 16) |
+ ($sbox[($' . $e . $i . ' >> 24) & 0xff] << 24);' . "\n";
+ }
+ $encrypt_block .= '$in = pack("N*"' . "\n";
+ for ($i = 0; $i < $Nb; ++$i) {
+ $encrypt_block .= ',
+ ($' . $e . $i . ' & ' . ((int)0xFF000000) . ') ^
+ ($' . $e . (($i + $c[1]) % $Nb) . ' & 0x00FF0000 ) ^
+ ($' . $e . (($i + $c[2]) % $Nb) . ' & 0x0000FF00 ) ^
+ ($' . $e . (($i + $c[3]) % $Nb) . ' & 0x000000FF ) ^
+ ' . $w[$i] . "\n";
+ }
+ $encrypt_block .= ');';
+
+ // Generating decrypt code:
+ $init_decrypt .= '
+ if (empty($invtables)) {
+ $invtables = &$this->getInvTables();
}
- $encrypt_block .= ');';
+ $dt0 = $invtables[0];
+ $dt1 = $invtables[1];
+ $dt2 = $invtables[2];
+ $dt3 = $invtables[3];
+ $isbox = $invtables[4];
+ ';
+
+ $s = 'e';
+ $e = 's';
+ $wc = $Nb - 1;
- // Generating decrypt code:
- $init_decrypt.= '
- if (empty($invtables)) {
- $invtables = &$self->_getInvTables();
- }
- $dt0 = $invtables[0];
- $dt1 = $invtables[1];
- $dt2 = $invtables[2];
- $dt3 = $invtables[3];
- $isbox = $invtables[4];
- ';
-
- $s = 'e';
- $e = 's';
- $wc = $Nb - 1;
-
- // Preround: addRoundKey
- $decrypt_block = '$in = unpack("N*", $in);'."\n";
+ // Preround: addRoundKey
+ $decrypt_block = '$in = unpack("N*", $in);' . "\n";
+ for ($i = 0; $i < $Nb; ++$i) {
+ $decrypt_block .= '$s' . $i . ' = $in[' . ($i + 1) . '] ^ ' . $dw[++$wc] . ';' . "\n";
+ }
+
+ // Mainrounds: shiftRows + subWord + mixColumns + addRoundKey
+ for ($round = 1; $round < $Nr; ++$round) {
+ list($s, $e) = [$e, $s];
for ($i = 0; $i < $Nb; ++$i) {
- $decrypt_block .= '$s'.$i.' = $in['.($i + 1).'] ^ '.$dw[++$wc].';'."\n";
+ $decrypt_block .=
+ '$' . $e . $i . ' =
+ $dt0[($' . $s . $i . ' >> 24) & 0xff] ^
+ $dt1[($' . $s . (($Nb + $i - $c[1]) % $Nb) . ' >> 16) & 0xff] ^
+ $dt2[($' . $s . (($Nb + $i - $c[2]) % $Nb) . ' >> 8) & 0xff] ^
+ $dt3[ $' . $s . (($Nb + $i - $c[3]) % $Nb) . ' & 0xff] ^
+ ' . $dw[++$wc] . ";\n";
}
+ }
+
+ // Finalround: subWord + shiftRows + addRoundKey
+ for ($i = 0; $i < $Nb; ++$i) {
+ $decrypt_block .=
+ '$' . $e . $i . ' =
+ $isbox[ $' . $e . $i . ' & 0xff] |
+ ($isbox[($' . $e . $i . ' >> 8) & 0xff] << 8) |
+ ($isbox[($' . $e . $i . ' >> 16) & 0xff] << 16) |
+ ($isbox[($' . $e . $i . ' >> 24) & 0xff] << 24);' . "\n";
+ }
+ $decrypt_block .= '$in = pack("N*"' . "\n";
+ for ($i = 0; $i < $Nb; ++$i) {
+ $decrypt_block .= ',
+ ($' . $e . $i . ' & ' . ((int)0xFF000000) . ') ^
+ ($' . $e . (($Nb + $i - $c[1]) % $Nb) . ' & 0x00FF0000 ) ^
+ ($' . $e . (($Nb + $i - $c[2]) % $Nb) . ' & 0x0000FF00 ) ^
+ ($' . $e . (($Nb + $i - $c[3]) % $Nb) . ' & 0x000000FF ) ^
+ ' . $dw[$i] . "\n";
+ }
+ $decrypt_block .= ');';
+
+ $this->inline_crypt = $this->createInlineCryptFunction(
+ [
+ 'init_crypt' => 'static $tables; static $invtables;',
+ 'init_encrypt' => $init_encrypt,
+ 'init_decrypt' => $init_decrypt,
+ 'encrypt_block' => $encrypt_block,
+ 'decrypt_block' => $decrypt_block
+ ]
+ );
+ }
+
+ /**
+ * Encrypts a message.
+ *
+ * @see self::decrypt()
+ * @see parent::encrypt()
+ * @param string $plaintext
+ * @return string
+ */
+ public function encrypt($plaintext)
+ {
+ $this->setup();
+
+ switch ($this->engine) {
+ case self::ENGINE_LIBSODIUM:
+ $this->newtag = sodium_crypto_aead_aes256gcm_encrypt($plaintext, $this->aad, $this->nonce, $this->key);
+ return Strings::shift($this->newtag, strlen($plaintext));
+ case self::ENGINE_OPENSSL_GCM:
+ return openssl_encrypt(
+ $plaintext,
+ 'aes-' . $this->getKeyLength() . '-gcm',
+ $this->key,
+ OPENSSL_RAW_DATA,
+ $this->nonce,
+ $this->newtag,
+ $this->aad
+ );
+ }
+
+ return parent::encrypt($plaintext);
+ }
- // Mainrounds: shiftRows + subWord + mixColumns + addRoundKey
- for ($round = 1; $round < $Nr; ++$round) {
- list($s, $e) = array($e, $s);
- for ($i = 0; $i < $Nb; ++$i) {
- $decrypt_block.=
- '$'.$e.$i.' =
- $dt0[($'.$s.$i .' >> 24) & 0xff] ^
- $dt1[($'.$s.(($Nb + $i - $c[1]) % $Nb).' >> 16) & 0xff] ^
- $dt2[($'.$s.(($Nb + $i - $c[2]) % $Nb).' >> 8) & 0xff] ^
- $dt3[ $'.$s.(($Nb + $i - $c[3]) % $Nb).' & 0xff] ^
- '.$dw[++$wc].";\n";
+ /**
+ * Decrypts a message.
+ *
+ * @see self::encrypt()
+ * @see parent::decrypt()
+ * @param string $ciphertext
+ * @return string
+ */
+ public function decrypt($ciphertext)
+ {
+ $this->setup();
+
+ switch ($this->engine) {
+ case self::ENGINE_LIBSODIUM:
+ if ($this->oldtag === false) {
+ throw new InsufficientSetupException('Authentication Tag has not been set');
}
- }
+ if (strlen($this->oldtag) != 16) {
+ break;
+ }
+ $plaintext = sodium_crypto_aead_aes256gcm_decrypt($ciphertext . $this->oldtag, $this->aad, $this->nonce, $this->key);
+ if ($plaintext === false) {
+ $this->oldtag = false;
+ throw new BadDecryptionException('Error decrypting ciphertext with libsodium');
+ }
+ return $plaintext;
+ case self::ENGINE_OPENSSL_GCM:
+ if ($this->oldtag === false) {
+ throw new InsufficientSetupException('Authentication Tag has not been set');
+ }
+ $plaintext = openssl_decrypt(
+ $ciphertext,
+ 'aes-' . $this->getKeyLength() . '-gcm',
+ $this->key,
+ OPENSSL_RAW_DATA,
+ $this->nonce,
+ $this->oldtag,
+ $this->aad
+ );
+ if ($plaintext === false) {
+ $this->oldtag = false;
+ throw new BadDecryptionException('Error decrypting ciphertext with OpenSSL');
+ }
+ return $plaintext;
+ }
- // Finalround: subWord + shiftRows + addRoundKey
- for ($i = 0; $i < $Nb; ++$i) {
- $decrypt_block.=
- '$'.$e.$i.' =
- $isbox[ $'.$e.$i.' & 0xff] |
- ($isbox[($'.$e.$i.' >> 8) & 0xff] << 8) |
- ($isbox[($'.$e.$i.' >> 16) & 0xff] << 16) |
- ($isbox[($'.$e.$i.' >> 24) & 0xff] << 24);'."\n";
- }
- $decrypt_block .= '$in = pack("N*"'."\n";
- for ($i = 0; $i < $Nb; ++$i) {
- $decrypt_block.= ',
- ($'.$e.$i. ' & '.((int)0xFF000000).') ^
- ($'.$e.(($Nb + $i - $c[1]) % $Nb).' & 0x00FF0000 ) ^
- ($'.$e.(($Nb + $i - $c[2]) % $Nb).' & 0x0000FF00 ) ^
- ($'.$e.(($Nb + $i - $c[3]) % $Nb).' & 0x000000FF ) ^
- '.$dw[$i]."\n";
- }
- $decrypt_block .= ');';
-
- $lambda_functions[$code_hash] = $this->_createInlineCryptFunction(
- array(
- 'init_crypt' => 'static $tables; static $invtables;',
- 'init_encrypt' => $init_encrypt,
- 'init_decrypt' => $init_decrypt,
- 'encrypt_block' => $encrypt_block,
- 'decrypt_block' => $decrypt_block
- )
- );
- }
- $this->inline_crypt = $lambda_functions[$code_hash];
+ return parent::decrypt($ciphertext);
}
}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Salsa20.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Salsa20.php
new file mode 100644
index 000000000..785e7aa2d
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Salsa20.php
@@ -0,0 +1,528 @@
+<?php
+
+/**
+ * Pure-PHP implementation of Salsa20.
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2019 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Crypt;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\StreamCipher;
+use phpseclib3\Exception\BadDecryptionException;
+use phpseclib3\Exception\InsufficientSetupException;
+
+/**
+ * Pure-PHP implementation of Salsa20.
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class Salsa20 extends StreamCipher
+{
+ /**
+ * Part 1 of the state
+ *
+ * @var string|false
+ */
+ protected $p1 = false;
+
+ /**
+ * Part 2 of the state
+ *
+ * @var string|false
+ */
+ protected $p2 = false;
+
+ /**
+ * Key Length (in bytes)
+ *
+ * @var int
+ */
+ protected $key_length = 32; // = 256 bits
+
+ /**
+ * @see \phpseclib3\Crypt\Salsa20::crypt()
+ */
+ const ENCRYPT = 0;
+
+ /**
+ * @see \phpseclib3\Crypt\Salsa20::crypt()
+ */
+ const DECRYPT = 1;
+
+ /**
+ * Encryption buffer for continuous mode
+ *
+ * @var array
+ */
+ protected $enbuffer;
+
+ /**
+ * Decryption buffer for continuous mode
+ *
+ * @var array
+ */
+ protected $debuffer;
+
+ /**
+ * Counter
+ *
+ * @var int
+ */
+ protected $counter = 0;
+
+ /**
+ * Using Generated Poly1305 Key
+ *
+ * @var boolean
+ */
+ protected $usingGeneratedPoly1305Key = false;
+
+ /**
+ * Salsa20 uses a nonce
+ *
+ * @return bool
+ */
+ public function usesNonce()
+ {
+ return true;
+ }
+
+ /**
+ * Sets the key.
+ *
+ * @param string $key
+ * @throws \LengthException if the key length isn't supported
+ */
+ public function setKey($key)
+ {
+ switch (strlen($key)) {
+ case 16:
+ case 32:
+ break;
+ default:
+ throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes 16 or 32 are supported');
+ }
+
+ parent::setKey($key);
+ }
+
+ /**
+ * Sets the nonce.
+ *
+ * @param string $nonce
+ */
+ public function setNonce($nonce)
+ {
+ if (strlen($nonce) != 8) {
+ throw new \LengthException('Nonce of size ' . strlen($key) . ' not supported by this algorithm. Only an 64-bit nonce is supported');
+ }
+
+ $this->nonce = $nonce;
+ $this->changed = true;
+ $this->setEngine();
+ }
+
+ /**
+ * Sets the counter.
+ *
+ * @param int $counter
+ */
+ public function setCounter($counter)
+ {
+ $this->counter = $counter;
+ $this->setEngine();
+ }
+
+ /**
+ * Creates a Poly1305 key using the method discussed in RFC8439
+ *
+ * See https://tools.ietf.org/html/rfc8439#section-2.6.1
+ */
+ protected function createPoly1305Key()
+ {
+ if ($this->nonce === false) {
+ throw new InsufficientSetupException('No nonce has been defined');
+ }
+
+ if ($this->key === false) {
+ throw new InsufficientSetupException('No key has been defined');
+ }
+
+ $c = clone $this;
+ $c->setCounter(0);
+ $c->usePoly1305 = false;
+ $block = $c->encrypt(str_repeat("\0", 256));
+ $this->setPoly1305Key(substr($block, 0, 32));
+
+ if ($this->counter == 0) {
+ $this->counter++;
+ }
+ }
+
+ /**
+ * Setup the self::ENGINE_INTERNAL $engine
+ *
+ * (re)init, if necessary, the internal cipher $engine
+ *
+ * _setup() will be called each time if $changed === true
+ * typically this happens when using one or more of following public methods:
+ *
+ * - setKey()
+ *
+ * - setNonce()
+ *
+ * - First run of encrypt() / decrypt() with no init-settings
+ *
+ * @see self::setKey()
+ * @see self::setNonce()
+ * @see self::disableContinuousBuffer()
+ */
+ protected function setup()
+ {
+ if (!$this->changed) {
+ return;
+ }
+
+ $this->enbuffer = $this->debuffer = ['ciphertext' => '', 'counter' => $this->counter];
+
+ $this->changed = $this->nonIVChanged = false;
+
+ if ($this->nonce === false) {
+ throw new InsufficientSetupException('No nonce has been defined');
+ }
+
+ if ($this->key === false) {
+ throw new InsufficientSetupException('No key has been defined');
+ }
+
+ if ($this->usePoly1305 && !isset($this->poly1305Key)) {
+ $this->usingGeneratedPoly1305Key = true;
+ $this->createPoly1305Key();
+ }
+
+ $key = $this->key;
+ if (strlen($key) == 16) {
+ $constant = 'expand 16-byte k';
+ $key .= $key;
+ } else {
+ $constant = 'expand 32-byte k';
+ }
+
+ $this->p1 = substr($constant, 0, 4) .
+ substr($key, 0, 16) .
+ substr($constant, 4, 4) .
+ $this->nonce .
+ "\0\0\0\0";
+ $this->p2 = substr($constant, 8, 4) .
+ substr($key, 16, 16) .
+ substr($constant, 12, 4);
+ }
+
+ /**
+ * Setup the key (expansion)
+ */
+ protected function setupKey()
+ {
+ // Salsa20 does not utilize this method
+ }
+
+ /**
+ * Encrypts a message.
+ *
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::decrypt()
+ * @see self::crypt()
+ * @param string $plaintext
+ * @return string $ciphertext
+ */
+ public function encrypt($plaintext)
+ {
+ $ciphertext = $this->crypt($plaintext, self::ENCRYPT);
+ if (isset($this->poly1305Key)) {
+ $this->newtag = $this->poly1305($ciphertext);
+ }
+ return $ciphertext;
+ }
+
+ /**
+ * Decrypts a message.
+ *
+ * $this->decrypt($this->encrypt($plaintext)) == $this->encrypt($this->encrypt($plaintext)).
+ * At least if the continuous buffer is disabled.
+ *
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::encrypt()
+ * @see self::crypt()
+ * @param string $ciphertext
+ * @return string $plaintext
+ */
+ public function decrypt($ciphertext)
+ {
+ if (isset($this->poly1305Key)) {
+ if ($this->oldtag === false) {
+ throw new InsufficientSetupException('Authentication Tag has not been set');
+ }
+ $newtag = $this->poly1305($ciphertext);
+ if ($this->oldtag != substr($newtag, 0, strlen($this->oldtag))) {
+ $this->oldtag = false;
+ throw new BadDecryptionException('Derived authentication tag and supplied authentication tag do not match');
+ }
+ $this->oldtag = false;
+ }
+
+ return $this->crypt($ciphertext, self::DECRYPT);
+ }
+
+ /**
+ * Encrypts a block
+ *
+ * @param string $in
+ */
+ protected function encryptBlock($in)
+ {
+ // Salsa20 does not utilize this method
+ }
+
+ /**
+ * Decrypts a block
+ *
+ * @param string $in
+ */
+ protected function decryptBlock($in)
+ {
+ // Salsa20 does not utilize this method
+ }
+
+ /**
+ * Encrypts or decrypts a message.
+ *
+ * @see self::encrypt()
+ * @see self::decrypt()
+ * @param string $text
+ * @param int $mode
+ * @return string $text
+ */
+ private function crypt($text, $mode)
+ {
+ $this->setup();
+ if (!$this->continuousBuffer) {
+ if ($this->engine == self::ENGINE_OPENSSL) {
+ $iv = pack('V', $this->counter) . $this->p2;
+ return openssl_encrypt(
+ $text,
+ $this->cipher_name_openssl,
+ $this->key,
+ OPENSSL_RAW_DATA,
+ $iv
+ );
+ }
+ $i = $this->counter;
+ $blocks = str_split($text, 64);
+ foreach ($blocks as &$block) {
+ $block ^= static::salsa20($this->p1 . pack('V', $i++) . $this->p2);
+ }
+ unset($block);
+ return implode('', $blocks);
+ }
+
+ if ($mode == self::ENCRYPT) {
+ $buffer = &$this->enbuffer;
+ } else {
+ $buffer = &$this->debuffer;
+ }
+ if (!strlen($buffer['ciphertext'])) {
+ $ciphertext = '';
+ } else {
+ $ciphertext = $text ^ Strings::shift($buffer['ciphertext'], strlen($text));
+ $text = substr($text, strlen($ciphertext));
+ if (!strlen($text)) {
+ return $ciphertext;
+ }
+ }
+
+ $overflow = strlen($text) % 64; // & 0x3F
+ if ($overflow) {
+ $text2 = Strings::pop($text, $overflow);
+ if ($this->engine == self::ENGINE_OPENSSL) {
+ $iv = pack('V', $buffer['counter']) . $this->p2;
+ // at this point $text should be a multiple of 64
+ $buffer['counter'] += (strlen($text) >> 6) + 1; // ie. divide by 64
+ $encrypted = openssl_encrypt(
+ $text . str_repeat("\0", 64),
+ $this->cipher_name_openssl,
+ $this->key,
+ OPENSSL_RAW_DATA,
+ $iv
+ );
+ $temp = Strings::pop($encrypted, 64);
+ } else {
+ $blocks = str_split($text, 64);
+ if (strlen($text)) {
+ foreach ($blocks as &$block) {
+ $block ^= static::salsa20($this->p1 . pack('V', $buffer['counter']++) . $this->p2);
+ }
+ unset($block);
+ }
+ $encrypted = implode('', $blocks);
+ $temp = static::salsa20($this->p1 . pack('V', $buffer['counter']++) . $this->p2);
+ }
+ $ciphertext .= $encrypted . ($text2 ^ $temp);
+ $buffer['ciphertext'] = substr($temp, $overflow);
+ } elseif (!strlen($buffer['ciphertext'])) {
+ if ($this->engine == self::ENGINE_OPENSSL) {
+ $iv = pack('V', $buffer['counter']) . $this->p2;
+ $buffer['counter'] += (strlen($text) >> 6);
+ $ciphertext .= openssl_encrypt(
+ $text,
+ $this->cipher_name_openssl,
+ $this->key,
+ OPENSSL_RAW_DATA,
+ $iv
+ );
+ } else {
+ $blocks = str_split($text, 64);
+ foreach ($blocks as &$block) {
+ $block ^= static::salsa20($this->p1 . pack('V', $buffer['counter']++) . $this->p2);
+ }
+ unset($block);
+ $ciphertext .= implode('', $blocks);
+ }
+ }
+
+ return $ciphertext;
+ }
+
+ /**
+ * Left Rotate
+ *
+ * @param int $x
+ * @param int $n
+ * @return int
+ */
+ protected static function leftRotate($x, $n)
+ {
+ if (PHP_INT_SIZE == 8) {
+ $r1 = $x << $n;
+ $r1 &= 0xFFFFFFFF;
+ $r2 = ($x & 0xFFFFFFFF) >> (32 - $n);
+ } else {
+ $x = (int) $x;
+ $r1 = $x << $n;
+ $r2 = $x >> (32 - $n);
+ $r2 &= (1 << $n) - 1;
+ }
+ return $r1 | $r2;
+ }
+
+ /**
+ * The quarterround function
+ *
+ * @param int $a
+ * @param int $b
+ * @param int $c
+ * @param int $d
+ */
+ protected static function quarterRound(&$a, &$b, &$c, &$d)
+ {
+ $b ^= self::leftRotate($a + $d, 7);
+ $c ^= self::leftRotate($b + $a, 9);
+ $d ^= self::leftRotate($c + $b, 13);
+ $a ^= self::leftRotate($d + $c, 18);
+ }
+
+ /**
+ * The doubleround function
+ *
+ * @param int $x0 (by reference)
+ * @param int $x1 (by reference)
+ * @param int $x2 (by reference)
+ * @param int $x3 (by reference)
+ * @param int $x4 (by reference)
+ * @param int $x5 (by reference)
+ * @param int $x6 (by reference)
+ * @param int $x7 (by reference)
+ * @param int $x8 (by reference)
+ * @param int $x9 (by reference)
+ * @param int $x10 (by reference)
+ * @param int $x11 (by reference)
+ * @param int $x12 (by reference)
+ * @param int $x13 (by reference)
+ * @param int $x14 (by reference)
+ * @param int $x15 (by reference)
+ */
+ protected static function doubleRound(&$x0, &$x1, &$x2, &$x3, &$x4, &$x5, &$x6, &$x7, &$x8, &$x9, &$x10, &$x11, &$x12, &$x13, &$x14, &$x15)
+ {
+ // columnRound
+ static::quarterRound($x0, $x4, $x8, $x12);
+ static::quarterRound($x5, $x9, $x13, $x1);
+ static::quarterRound($x10, $x14, $x2, $x6);
+ static::quarterRound($x15, $x3, $x7, $x11);
+ // rowRound
+ static::quarterRound($x0, $x1, $x2, $x3);
+ static::quarterRound($x5, $x6, $x7, $x4);
+ static::quarterRound($x10, $x11, $x8, $x9);
+ static::quarterRound($x15, $x12, $x13, $x14);
+ }
+
+ /**
+ * The Salsa20 hash function function
+ *
+ * @param string $x
+ */
+ protected static function salsa20($x)
+ {
+ $z = $x = unpack('V*', $x);
+ for ($i = 0; $i < 10; $i++) {
+ static::doubleRound($z[1], $z[2], $z[3], $z[4], $z[5], $z[6], $z[7], $z[8], $z[9], $z[10], $z[11], $z[12], $z[13], $z[14], $z[15], $z[16]);
+ }
+
+ for ($i = 1; $i <= 16; $i++) {
+ $x[$i] += $z[$i];
+ }
+
+ return pack('V*', ...$x);
+ }
+
+ /**
+ * Calculates Poly1305 MAC
+ *
+ * @see self::decrypt()
+ * @see self::encrypt()
+ * @param string $ciphertext
+ * @return string
+ */
+ protected function poly1305($ciphertext)
+ {
+ if (!$this->usingGeneratedPoly1305Key) {
+ return parent::poly1305($this->aad . $ciphertext);
+ } else {
+ /*
+ sodium_crypto_aead_chacha20poly1305_encrypt does not calculate the poly1305 tag
+ the same way sodium_crypto_aead_chacha20poly1305_ietf_encrypt does. you can see
+ how the latter encrypts it in Salsa20::encrypt(). here's how the former encrypts
+ it:
+
+ $this->newtag = $this->poly1305(
+ $this->aad .
+ pack('V', strlen($this->aad)) . "\0\0\0\0" .
+ $ciphertext .
+ pack('V', strlen($ciphertext)) . "\0\0\0\0"
+ );
+
+ phpseclib opts to use the IETF construction, even when the nonce is 64-bits
+ instead of 96-bits
+ */
+ return parent::poly1305(
+ self::nullPad128($this->aad) .
+ self::nullPad128($ciphertext) .
+ pack('V', strlen($this->aad)) . "\0\0\0\0" .
+ pack('V', strlen($ciphertext)) . "\0\0\0\0"
+ );
+ }
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/TripleDES.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/TripleDES.php
index bf2df95ed..932b7c611 100644
--- a/vendor/phpseclib/phpseclib/phpseclib/Crypt/TripleDES.php
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/TripleDES.php
@@ -12,7 +12,7 @@
* <?php
* include 'vendor/autoload.php';
*
- * $des = new \phpseclib\Crypt\TripleDES();
+ * $des = new \phpseclib3\Crypt\TripleDES('ctr');
*
* $des->setKey('abcdefghijklmnopqrstuvwx');
*
@@ -26,22 +26,18 @@
* ?>
* </code>
*
- * @category Crypt
- * @package TripleDES
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
-namespace phpseclib\Crypt;
+namespace phpseclib3\Crypt;
/**
* Pure-PHP implementation of Triple DES.
*
- * @package TripleDES
* @author Jim Wigginton <terrafrost@php.net>
- * @access public
*/
class TripleDES extends DES
{
@@ -55,161 +51,148 @@ class TripleDES extends DES
/**
* Encrypt / decrypt using outer chaining
*
- * Outer chaining is used by SSH-2 and when the mode is set to \phpseclib\Crypt\Base::MODE_CBC.
+ * Outer chaining is used by SSH-2 and when the mode is set to \phpseclib3\Crypt\Common\BlockCipher::MODE_CBC.
*/
const MODE_CBC3 = self::MODE_CBC;
/**
* Key Length (in bytes)
*
- * @see \phpseclib\Crypt\TripleDES::setKeyLength()
+ * @see \phpseclib3\Crypt\TripleDES::setKeyLength()
* @var int
- * @access private
*/
- var $key_length = 24;
-
- /**
- * The default salt used by setPassword()
- *
- * @see \phpseclib\Crypt\Base::password_default_salt
- * @see \phpseclib\Crypt\Base::setPassword()
- * @var string
- * @access private
- */
- var $password_default_salt = 'phpseclib';
+ protected $key_length = 24;
/**
* The mcrypt specific name of the cipher
*
- * @see \phpseclib\Crypt\DES::cipher_name_mcrypt
- * @see \phpseclib\Crypt\Base::cipher_name_mcrypt
+ * @see DES::cipher_name_mcrypt
+ * @see Common\SymmetricKey::cipher_name_mcrypt
* @var string
- * @access private
*/
- var $cipher_name_mcrypt = 'tripledes';
+ protected $cipher_name_mcrypt = 'tripledes';
/**
* Optimizing value while CFB-encrypting
*
- * @see \phpseclib\Crypt\Base::cfb_init_len
+ * @see Common\SymmetricKey::cfb_init_len
* @var int
- * @access private
*/
- var $cfb_init_len = 750;
+ protected $cfb_init_len = 750;
/**
* max possible size of $key
*
* @see self::setKey()
- * @see \phpseclib\Crypt\DES::setKey()
+ * @see DES::setKey()
* @var string
- * @access private
*/
- var $key_length_max = 24;
+ protected $key_length_max = 24;
/**
* Internal flag whether using self::MODE_3CBC or not
*
* @var bool
- * @access private
*/
- var $mode_3cbc;
+ private $mode_3cbc;
/**
- * The \phpseclib\Crypt\DES objects
+ * The \phpseclib3\Crypt\DES objects
*
* Used only if $mode_3cbc === true
*
* @var array
- * @access private
*/
- var $des;
+ private $des;
/**
* Default Constructor.
*
- * Determines whether or not the mcrypt extension should be used.
+ * Determines whether or not the mcrypt or OpenSSL extensions should be used.
*
* $mode could be:
*
- * - \phpseclib\Crypt\Base::MODE_ECB
+ * - ecb
*
- * - \phpseclib\Crypt\Base::MODE_CBC
+ * - cbc
*
- * - \phpseclib\Crypt\Base::MODE_CTR
+ * - ctr
*
- * - \phpseclib\Crypt\Base::MODE_CFB
+ * - cfb
*
- * - \phpseclib\Crypt\Base::MODE_OFB
+ * - ofb
*
- * - \phpseclib\Crypt\TripleDES::MODE_3CBC
+ * - 3cbc
*
- * If not explicitly set, \phpseclib\Crypt\Base::MODE_CBC will be used.
+ * - cbc3 (same as cbc)
*
- * @see \phpseclib\Crypt\DES::__construct()
- * @see \phpseclib\Crypt\Base::__construct()
- * @param int $mode
- * @access public
+ * @see Crypt\DES::__construct()
+ * @see Common\SymmetricKey::__construct()
+ * @param string $mode
*/
- function __construct($mode = self::MODE_CBC)
+ public function __construct($mode)
{
- switch ($mode) {
+ switch (strtolower($mode)) {
// In case of self::MODE_3CBC, we init as CRYPT_DES_MODE_CBC
// and additional flag us internally as 3CBC
- case self::MODE_3CBC:
- parent::__construct(self::MODE_CBC);
+ case '3cbc':
+ parent::__construct('cbc');
$this->mode_3cbc = true;
// This three $des'es will do the 3CBC work (if $key > 64bits)
- $this->des = array(
- new DES(self::MODE_CBC),
- new DES(self::MODE_CBC),
- new DES(self::MODE_CBC),
- );
+ $this->des = [
+ new DES('cbc'),
+ new DES('cbc'),
+ new DES('cbc'),
+ ];
- // we're going to be doing the padding, ourselves, so disable it in the \phpseclib\Crypt\DES objects
+ // we're going to be doing the padding, ourselves, so disable it in the \phpseclib3\Crypt\DES objects
$this->des[0]->disablePadding();
$this->des[1]->disablePadding();
$this->des[2]->disablePadding();
break;
+ case 'cbc3':
+ $mode = 'cbc';
+ // fall-through
// If not 3CBC, we init as usual
default:
parent::__construct($mode);
+
+ if ($this->mode == self::MODE_STREAM) {
+ throw new BadModeException('Block ciphers cannot be ran in stream mode');
+ }
}
}
/**
* Test for engine validity
*
- * This is mainly just a wrapper to set things up for \phpseclib\Crypt\Base::isValidEngine()
+ * This is mainly just a wrapper to set things up for \phpseclib3\Crypt\Common\SymmetricKey::isValidEngine()
*
- * @see \phpseclib\Crypt\Base::__construct()
+ * @see Common\SymmetricKey::__construct()
* @param int $engine
- * @access public
* @return bool
*/
- function isValidEngine($engine)
+ protected function isValidEngineHelper($engine)
{
if ($engine == self::ENGINE_OPENSSL) {
$this->cipher_name_openssl_ecb = 'des-ede3';
- $mode = $this->_openssl_translate_mode();
+ $mode = $this->openssl_translate_mode();
$this->cipher_name_openssl = $mode == 'ecb' ? 'des-ede3' : 'des-ede3-' . $mode;
}
- return parent::isValidEngine($engine);
+ return parent::isValidEngineHelper($engine);
}
/**
- * Sets the initialization vector. (optional)
+ * Sets the initialization vector.
*
- * SetIV is not required when \phpseclib\Crypt\Base::MODE_ECB is being used. If not explicitly set, it'll be assumed
- * to be all zero's.
+ * SetIV is not required when \phpseclib3\Crypt\Common\SymmetricKey::MODE_ECB is being used.
*
- * @see \phpseclib\Crypt\Base::setIV()
- * @access public
+ * @see Common\SymmetricKey::setIV()
* @param string $iv
*/
- function setIV($iv)
+ public function setIV($iv)
{
parent::setIV($iv);
if ($this->mode_3cbc) {
@@ -222,24 +205,22 @@ class TripleDES extends DES
/**
* Sets the key length.
*
- * Valid key lengths are 64, 128 and 192
+ * Valid key lengths are 128 and 192 bits.
+ *
+ * If you want to use a 64-bit key use DES.php
*
- * @see \phpseclib\Crypt\Base:setKeyLength()
- * @access public
+ * @see Common\SymmetricKey:setKeyLength()
+ * @throws \LengthException if the key length is invalid
* @param int $length
*/
- function setKeyLength($length)
+ public function setKeyLength($length)
{
- $length >>= 3;
- switch (true) {
- case $length <= 8:
- $this->key_length = 8;
- break;
- case $length <= 16:
- $this->key_length = 16;
+ switch ($length) {
+ case 128:
+ case 192:
break;
default:
- $this->key_length = 24;
+ throw new \LengthException('Key size of ' . $length . ' bits is not supported by this algorithm. Only keys of sizes 128 or 192 bits are supported');
}
parent::setKeyLength($length);
@@ -248,38 +229,40 @@ class TripleDES extends DES
/**
* Sets the key.
*
- * Keys can be of any length. Triple DES, itself, can use 128-bit (eg. strlen($key) == 16) or
- * 192-bit (eg. strlen($key) == 24) keys. This function pads and truncates $key as appropriate.
+ * Triple DES can use 128-bit (eg. strlen($key) == 16) or 192-bit (eg. strlen($key) == 24) keys.
*
* DES also requires that every eighth bit be a parity bit, however, we'll ignore that.
*
- * If the key is not explicitly set, it'll be assumed to be all null bytes.
- *
- * @access public
- * @see \phpseclib\Crypt\DES::setKey()
- * @see \phpseclib\Crypt\Base::setKey()
+ * @see DES::setKey()
+ * @see Common\SymmetricKey::setKey()
+ * @throws \LengthException if the key length is invalid
* @param string $key
*/
- function setKey($key)
+ public function setKey($key)
{
- $length = $this->explicit_key_length ? $this->key_length : strlen($key);
- if ($length > 8) {
- $key = str_pad(substr($key, 0, 24), 24, chr(0));
- // if $key is between 64 and 128-bits, use the first 64-bits as the last, per this:
- // http://php.net/function.mcrypt-encrypt#47973
- $key = $length <= 16 ? substr_replace($key, substr($key, 0, 8), 16) : substr($key, 0, 24);
- } else {
- $key = str_pad($key, 8, chr(0));
+ if ($this->explicit_key_length !== false && strlen($key) != $this->explicit_key_length) {
+ throw new \LengthException('Key length has already been set to ' . $this->explicit_key_length . ' bytes and this key is ' . strlen($key) . ' bytes');
}
- parent::setKey($key);
-
- // And in case of self::MODE_3CBC:
- // if key <= 64bits we not need the 3 $des to work,
- // because we will then act as regular DES-CBC with just a <= 64bit key.
- // So only if the key > 64bits (> 8 bytes) we will call setKey() for the 3 $des.
- if ($this->mode_3cbc && $length > 8) {
- $this->des[0]->setKey(substr($key, 0, 8));
- $this->des[1]->setKey(substr($key, 8, 8));
+
+ switch (strlen($key)) {
+ case 16:
+ $key .= substr($key, 0, 8);
+ break;
+ case 24:
+ break;
+ default:
+ throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes 16 or 24 are supported');
+ }
+
+ // copied from self::setKey()
+ $this->key = $key;
+ $this->key_length = strlen($key);
+ $this->changed = $this->nonIVChanged = true;
+ $this->setEngine();
+
+ if ($this->mode_3cbc) {
+ $this->des[0]->setKey(substr($key, 0, 8));
+ $this->des[1]->setKey(substr($key, 8, 8));
$this->des[2]->setKey(substr($key, 16, 8));
}
}
@@ -287,12 +270,11 @@ class TripleDES extends DES
/**
* Encrypts a message.
*
- * @see \phpseclib\Crypt\Base::encrypt()
- * @access public
+ * @see Common\SymmetricKey::encrypt()
* @param string $plaintext
* @return string $cipertext
*/
- function encrypt($plaintext)
+ public function encrypt($plaintext)
{
// parent::en/decrypt() is able to do all the work for all modes and keylengths,
// except for: self::MODE_3CBC (inner chaining CBC) with a key > 64bits
@@ -302,7 +284,7 @@ class TripleDES extends DES
return $this->des[2]->encrypt(
$this->des[1]->decrypt(
$this->des[0]->encrypt(
- $this->_pad($plaintext)
+ $this->pad($plaintext)
)
)
);
@@ -314,15 +296,14 @@ class TripleDES extends DES
/**
* Decrypts a message.
*
- * @see \phpseclib\Crypt\Base::decrypt()
- * @access public
+ * @see Common\SymmetricKey::decrypt()
* @param string $ciphertext
* @return string $plaintext
*/
- function decrypt($ciphertext)
+ public function decrypt($ciphertext)
{
if ($this->mode_3cbc && strlen($this->key) > 8) {
- return $this->_unpad(
+ return $this->unpad(
$this->des[0]->decrypt(
$this->des[1]->encrypt(
$this->des[2]->decrypt(
@@ -365,16 +346,15 @@ class TripleDES extends DES
* outputs. The reason is due to the fact that the initialization vector's change after every encryption /
* decryption round when the continuous buffer is enabled. When it's disabled, they remain constant.
*
- * Put another way, when the continuous buffer is enabled, the state of the \phpseclib\Crypt\DES() object changes after each
+ * Put another way, when the continuous buffer is enabled, the state of the \phpseclib3\Crypt\DES() object changes after each
* encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that
* continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them),
* however, they are also less intuitive and more likely to cause you problems.
*
- * @see \phpseclib\Crypt\Base::enableContinuousBuffer()
+ * @see Common\SymmetricKey::enableContinuousBuffer()
* @see self::disableContinuousBuffer()
- * @access public
*/
- function enableContinuousBuffer()
+ public function enableContinuousBuffer()
{
parent::enableContinuousBuffer();
if ($this->mode_3cbc) {
@@ -389,11 +369,10 @@ class TripleDES extends DES
*
* The default behavior.
*
- * @see \phpseclib\Crypt\Base::disableContinuousBuffer()
+ * @see Common\SymmetricKey::disableContinuousBuffer()
* @see self::enableContinuousBuffer()
- * @access public
*/
- function disableContinuousBuffer()
+ public function disableContinuousBuffer()
{
parent::disableContinuousBuffer();
if ($this->mode_3cbc) {
@@ -406,11 +385,10 @@ class TripleDES extends DES
/**
* Creates the key schedule
*
- * @see \phpseclib\Crypt\DES::_setupKey()
- * @see \phpseclib\Crypt\Base::_setupKey()
- * @access private
+ * @see DES::setupKey()
+ * @see Common\SymmetricKey::setupKey()
*/
- function _setupKey()
+ protected function setupKey()
{
switch (true) {
// if $key <= 64bits we configure our internal pure-php cipher engine
@@ -425,29 +403,27 @@ class TripleDES extends DES
// (only) if 3CBC is used we have, of course, to setup the $des[0-2] keys also separately.
if ($this->mode_3cbc) {
- $this->des[0]->_setupKey();
- $this->des[1]->_setupKey();
- $this->des[2]->_setupKey();
+ $this->des[0]->setupKey();
+ $this->des[1]->setupKey();
+ $this->des[2]->setupKey();
// because $des[0-2] will, now, do all the work we can return here
- // not need unnecessary stress parent::_setupKey() with our, now unused, $key.
+ // not need unnecessary stress parent::setupKey() with our, now unused, $key.
return;
}
}
// setup our key
- parent::_setupKey();
+ parent::setupKey();
}
/**
* Sets the internal crypt engine
*
- * @see \phpseclib\Crypt\Base::__construct()
- * @see \phpseclib\Crypt\Base::setPreferredEngine()
+ * @see Common\SymmetricKey::__construct()
+ * @see Common\SymmetricKey::setPreferredEngine()
* @param int $engine
- * @access public
- * @return int
*/
- function setPreferredEngine($engine)
+ public function setPreferredEngine($engine)
{
if ($this->mode_3cbc) {
$this->des[0]->setPreferredEngine($engine);
@@ -455,6 +431,6 @@ class TripleDES extends DES
$this->des[2]->setPreferredEngine($engine);
}
- return parent::setPreferredEngine($engine);
+ parent::setPreferredEngine($engine);
}
}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Twofish.php b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Twofish.php
index 1c020481a..141ad0141 100644
--- a/vendor/phpseclib/phpseclib/phpseclib/Crypt/Twofish.php
+++ b/vendor/phpseclib/phpseclib/phpseclib/Crypt/Twofish.php
@@ -16,7 +16,7 @@
* <?php
* include 'vendor/autoload.php';
*
- * $twofish = new \phpseclib\Crypt\Twofish();
+ * $twofish = new \phpseclib3\Crypt\Twofish('ctr');
*
* $twofish->setKey('12345678901234567890123456789012');
*
@@ -26,8 +26,6 @@
* ?>
* </code>
*
- * @category Crypt
- * @package Twofish
* @author Jim Wigginton <terrafrost@php.net>
* @author Hans-Juergen Petrich <petrich@tronic-media.com>
* @copyright 2007 Jim Wigginton
@@ -35,43 +33,41 @@
* @link http://phpseclib.sourceforge.net
*/
-namespace phpseclib\Crypt;
+namespace phpseclib3\Crypt;
+
+use phpseclib3\Crypt\Common\BlockCipher;
+use phpseclib3\Exception\BadModeException;
/**
* Pure-PHP implementation of Twofish.
*
- * @package Twofish
* @author Jim Wigginton <terrafrost@php.net>
* @author Hans-Juergen Petrich <petrich@tronic-media.com>
- * @access public
*/
-class Twofish extends Base
+class Twofish extends BlockCipher
{
/**
* The mcrypt specific name of the cipher
*
- * @see \phpseclib\Crypt\Base::cipher_name_mcrypt
+ * @see Common\SymmetricKey::cipher_name_mcrypt
* @var string
- * @access private
*/
- var $cipher_name_mcrypt = 'twofish';
+ protected $cipher_name_mcrypt = 'twofish';
/**
* Optimizing value while CFB-encrypting
*
- * @see \phpseclib\Crypt\Base::cfb_init_len
+ * @see Common\SymmetricKey::cfb_init_len
* @var int
- * @access private
*/
- var $cfb_init_len = 800;
+ protected $cfb_init_len = 800;
/**
* Q-Table
*
* @var array
- * @access private
*/
- var $q0 = array(
+ private static $q0 = [
0xA9, 0x67, 0xB3, 0xE8, 0x04, 0xFD, 0xA3, 0x76,
0x9A, 0x92, 0x80, 0x78, 0xE4, 0xDD, 0xD1, 0x38,
0x0D, 0xC6, 0x35, 0x98, 0x18, 0xF7, 0xEC, 0x6C,
@@ -104,15 +100,14 @@ class Twofish extends Base
0xC8, 0xA8, 0x2B, 0x40, 0xDC, 0xFE, 0x32, 0xA4,
0xCA, 0x10, 0x21, 0xF0, 0xD3, 0x5D, 0x0F, 0x00,
0x6F, 0x9D, 0x36, 0x42, 0x4A, 0x5E, 0xC1, 0xE0
- );
+ ];
/**
* Q-Table
*
* @var array
- * @access private
*/
- var $q1 = array(
+ private static $q1 = [
0x75, 0xF3, 0xC6, 0xF4, 0xDB, 0x7B, 0xFB, 0xC8,
0x4A, 0xD3, 0xE6, 0x6B, 0x45, 0x7D, 0xE8, 0x4B,
0xD6, 0x32, 0xD8, 0xFD, 0x37, 0x71, 0xF1, 0xE1,
@@ -145,15 +140,14 @@ class Twofish extends Base
0x12, 0xA2, 0x0D, 0x52, 0xBB, 0x02, 0x2F, 0xA9,
0xD7, 0x61, 0x1E, 0xB4, 0x50, 0x04, 0xF6, 0xC2,
0x16, 0x25, 0x86, 0x56, 0x55, 0x09, 0xBE, 0x91
- );
+ ];
/**
* M-Table
*
* @var array
- * @access private
*/
- var $m0 = array(
+ private static $m0 = [
0xBCBC3275, 0xECEC21F3, 0x202043C6, 0xB3B3C9F4, 0xDADA03DB, 0x02028B7B, 0xE2E22BFB, 0x9E9EFAC8,
0xC9C9EC4A, 0xD4D409D3, 0x18186BE6, 0x1E1E9F6B, 0x98980E45, 0xB2B2387D, 0xA6A6D2E8, 0x2626B74B,
0x3C3C57D6, 0x93938A32, 0x8282EED8, 0x525298FD, 0x7B7BD437, 0xBBBB3771, 0x5B5B97F1, 0x474783E1,
@@ -186,15 +180,14 @@ class Twofish extends Base
0xABABA212, 0x6F6F3EA2, 0xE6E6540D, 0xDBDBF252, 0x92927BBB, 0xB7B7B602, 0x6969CA2F, 0x3939D9A9,
0xD3D30CD7, 0xA7A72361, 0xA2A2AD1E, 0xC3C399B4, 0x6C6C4450, 0x07070504, 0x04047FF6, 0x272746C2,
0xACACA716, 0xD0D07625, 0x50501386, 0xDCDCF756, 0x84841A55, 0xE1E15109, 0x7A7A25BE, 0x1313EF91
- );
+ ];
/**
* M-Table
*
* @var array
- * @access private
*/
- var $m1 = array(
+ private static $m1 = [
0xA9D93939, 0x67901717, 0xB3719C9C, 0xE8D2A6A6, 0x04050707, 0xFD985252, 0xA3658080, 0x76DFE4E4,
0x9A084545, 0x92024B4B, 0x80A0E0E0, 0x78665A5A, 0xE4DDAFAF, 0xDDB06A6A, 0xD1BF6363, 0x38362A2A,
0x0D54E6E6, 0xC6432020, 0x3562CCCC, 0x98BEF2F2, 0x181E1212, 0xF724EBEB, 0xECD7A1A1, 0x6C774141,
@@ -227,15 +220,14 @@ class Twofish extends Base
0xC8FA9E9E, 0xA882D6D6, 0x2BCF6E6E, 0x40507070, 0xDCEB8585, 0xFE750A0A, 0x328A9393, 0xA48DDFDF,
0xCA4C2929, 0x10141C1C, 0x2173D7D7, 0xF0CCB4B4, 0xD309D4D4, 0x5D108A8A, 0x0FE25151, 0x00000000,
0x6F9A1919, 0x9DE01A1A, 0x368F9494, 0x42E6C7C7, 0x4AECC9C9, 0x5EFDD2D2, 0xC1AB7F7F, 0xE0D8A8A8
- );
+ ];
/**
* M-Table
*
* @var array
- * @access private
*/
- var $m2 = array(
+ private static $m2 = [
0xBC75BC32, 0xECF3EC21, 0x20C62043, 0xB3F4B3C9, 0xDADBDA03, 0x027B028B, 0xE2FBE22B, 0x9EC89EFA,
0xC94AC9EC, 0xD4D3D409, 0x18E6186B, 0x1E6B1E9F, 0x9845980E, 0xB27DB238, 0xA6E8A6D2, 0x264B26B7,
0x3CD63C57, 0x9332938A, 0x82D882EE, 0x52FD5298, 0x7B377BD4, 0xBB71BB37, 0x5BF15B97, 0x47E14783,
@@ -268,15 +260,14 @@ class Twofish extends Base
0xAB12ABA2, 0x6FA26F3E, 0xE60DE654, 0xDB52DBF2, 0x92BB927B, 0xB702B7B6, 0x692F69CA, 0x39A939D9,
0xD3D7D30C, 0xA761A723, 0xA21EA2AD, 0xC3B4C399, 0x6C506C44, 0x07040705, 0x04F6047F, 0x27C22746,
0xAC16ACA7, 0xD025D076, 0x50865013, 0xDC56DCF7, 0x8455841A, 0xE109E151, 0x7ABE7A25, 0x139113EF
- );
+ ];
/**
* M-Table
*
* @var array
- * @access private
*/
- var $m3 = array(
+ private static $m3 = [
0xD939A9D9, 0x90176790, 0x719CB371, 0xD2A6E8D2, 0x05070405, 0x9852FD98, 0x6580A365, 0xDFE476DF,
0x08459A08, 0x024B9202, 0xA0E080A0, 0x665A7866, 0xDDAFE4DD, 0xB06ADDB0, 0xBF63D1BF, 0x362A3836,
0x54E60D54, 0x4320C643, 0x62CC3562, 0xBEF298BE, 0x1E12181E, 0x24EBF724, 0xD7A1ECD7, 0x77416C77,
@@ -309,99 +300,88 @@ class Twofish extends Base
0xFA9EC8FA, 0x82D6A882, 0xCF6E2BCF, 0x50704050, 0xEB85DCEB, 0x750AFE75, 0x8A93328A, 0x8DDFA48D,
0x4C29CA4C, 0x141C1014, 0x73D72173, 0xCCB4F0CC, 0x09D4D309, 0x108A5D10, 0xE2510FE2, 0x00000000,
0x9A196F9A, 0xE01A9DE0, 0x8F94368F, 0xE6C742E6, 0xECC94AEC, 0xFDD25EFD, 0xAB7FC1AB, 0xD8A8E0D8
- );
+ ];
/**
* The Key Schedule Array
*
* @var array
- * @access private
*/
- var $K = array();
+ private $K = [];
/**
* The Key depended S-Table 0
*
* @var array
- * @access private
*/
- var $S0 = array();
+ private $S0 = [];
/**
* The Key depended S-Table 1
*
* @var array
- * @access private
*/
- var $S1 = array();
+ private $S1 = [];
/**
* The Key depended S-Table 2
*
* @var array
- * @access private
*/
- var $S2 = array();
+ private $S2 = [];
/**
* The Key depended S-Table 3
*
* @var array
- * @access private
*/
- var $S3 = array();
+ private $S3 = [];
/**
* Holds the last used key
*
* @var array
- * @access private
*/
- var $kl;
+ private $kl;
/**
* The Key Length (in bytes)
*
* @see Crypt_Twofish::setKeyLength()
* @var int
- * @access private
*/
- var $key_length = 16;
+ protected $key_length = 16;
/**
* Default Constructor.
*
- * Determines whether or not the mcrypt extension should be used.
- *
- * $mode could be:
- *
- * - CRYPT_MODE_ECB
- *
- * - CRYPT_MODE_CBC
- *
- * - CRYPT_MODE_CTR
- *
- * - CRYPT_MODE_CFB
- *
- * - CRYPT_MODE_OFB
- *
- * (or the alias constants of the chosen cipher, for example for AES: CRYPT_AES_MODE_ECB or CRYPT_AES_MODE_CBC ...)
- *
- * If not explicitly set, CRYPT_MODE_CBC will be used.
- *
- * @param int $mode
- * @access public
+ * @param string $mode
+ * @throws BadModeException if an invalid / unsupported mode is provided
*/
- function __construct($mode = self::MODE_CBC)
+ public function __construct($mode)
{
parent::__construct($mode);
- $this->m0 = array_map('intval', $this->m0);
- $this->m1 = array_map('intval', $this->m1);
- $this->m2 = array_map('intval', $this->m2);
- $this->m3 = array_map('intval', $this->m3);
- $this->q0 = array_map('intval', $this->q0);
- $this->q1 = array_map('intval', $this->q1);
+ if ($this->mode == self::MODE_STREAM) {
+ throw new BadModeException('Block ciphers cannot be ran in stream mode');
+ }
+ }
+
+ /**
+ * Initialize Static Variables
+ */
+ protected static function initialize_static_variables()
+ {
+ if (is_float(self::$m3[0])) {
+ self::$m0 = array_map('intval', self::$m0);
+ self::$m1 = array_map('intval', self::$m1);
+ self::$m2 = array_map('intval', self::$m2);
+ self::$m3 = array_map('intval', self::$m3);
+ self::$q0 = array_map('intval', self::$q0);
+ self::$q1 = array_map('intval', self::$q1);
+ }
+
+ parent::initialize_static_variables();
}
/**
@@ -409,56 +389,75 @@ class Twofish extends Base
*
* Valid key lengths are 128, 192 or 256 bits
*
- * @access public
* @param int $length
*/
- function setKeyLength($length)
+ public function setKeyLength($length)
{
- switch (true) {
- case $length <= 128:
- $this->key_length = 16;
- break;
- case $length <= 192:
- $this->key_length = 24;
+ switch ($length) {
+ case 128:
+ case 192:
+ case 256:
break;
default:
- $this->key_length = 32;
+ throw new \LengthException('Key of size ' . $length . ' not supported by this algorithm. Only keys of sizes 16, 24 or 32 supported');
}
parent::setKeyLength($length);
}
/**
+ * Sets the key.
+ *
+ * Rijndael supports five different key lengths
+ *
+ * @see setKeyLength()
+ * @param string $key
+ * @throws \LengthException if the key length isn't supported
+ */
+ public function setKey($key)
+ {
+ switch (strlen($key)) {
+ case 16:
+ case 24:
+ case 32:
+ break;
+ default:
+ throw new \LengthException('Key of size ' . strlen($key) . ' not supported by this algorithm. Only keys of sizes 16, 24 or 32 supported');
+ }
+
+ parent::setKey($key);
+ }
+
+ /**
* Setup the key (expansion)
*
- * @see \phpseclib\Crypt\Base::_setupKey()
- * @access private
+ * @see Common\SymmetricKey::_setupKey()
*/
- function _setupKey()
+ protected function setupKey()
{
if (isset($this->kl['key']) && $this->key === $this->kl['key']) {
// already expanded
return;
}
- $this->kl = array('key' => $this->key);
+ $this->kl = ['key' => $this->key];
/* Key expanding and generating the key-depended s-boxes */
$le_longs = unpack('V*', $this->key);
$key = unpack('C*', $this->key);
- $m0 = $this->m0;
- $m1 = $this->m1;
- $m2 = $this->m2;
- $m3 = $this->m3;
- $q0 = $this->q0;
- $q1 = $this->q1;
+ $m0 = self::$m0;
+ $m1 = self::$m1;
+ $m2 = self::$m2;
+ $m3 = self::$m3;
+ $q0 = self::$q0;
+ $q1 = self::$q1;
- $K = $S0 = $S1 = $S2 = $S3 = array();
+ $K = $S0 = $S1 = $S2 = $S3 = [];
switch (strlen($this->key)) {
case 16:
- list($s7, $s6, $s5, $s4) = $this->_mdsrem($le_longs[1], $le_longs[2]);
- list($s3, $s2, $s1, $s0) = $this->_mdsrem($le_longs[3], $le_longs[4]);
- for ($i = 0, $j = 1; $i < 40; $i+= 2, $j+= 2) {
+ list($s7, $s6, $s5, $s4) = $this->mdsrem($le_longs[1], $le_longs[2]);
+ list($s3, $s2, $s1, $s0) = $this->mdsrem($le_longs[3], $le_longs[4]);
+ for ($i = 0, $j = 1; $i < 40; $i += 2, $j += 2) {
$A = $m0[$q0[$q0[$i] ^ $key[ 9]] ^ $key[1]] ^
$m1[$q0[$q1[$i] ^ $key[10]] ^ $key[2]] ^
$m2[$q1[$q0[$i] ^ $key[11]] ^ $key[3]] ^
@@ -468,9 +467,9 @@ class Twofish extends Base
$m2[$q1[$q0[$j] ^ $key[15]] ^ $key[7]] ^
$m3[$q1[$q1[$j] ^ $key[16]] ^ $key[8]];
$B = ($B << 8) | ($B >> 24 & 0xff);
- $A = $this->safe_intval($A + $B);
+ $A = self::safe_intval($A + $B);
$K[] = $A;
- $A = $this->safe_intval($A + $B);
+ $A = self::safe_intval($A + $B);
$K[] = ($A << 9 | $A >> 23 & 0x1ff);
}
for ($i = 0; $i < 256; ++$i) {
@@ -481,10 +480,10 @@ class Twofish extends Base
}
break;
case 24:
- list($sb, $sa, $s9, $s8) = $this->_mdsrem($le_longs[1], $le_longs[2]);
- list($s7, $s6, $s5, $s4) = $this->_mdsrem($le_longs[3], $le_longs[4]);
- list($s3, $s2, $s1, $s0) = $this->_mdsrem($le_longs[5], $le_longs[6]);
- for ($i = 0, $j = 1; $i < 40; $i+= 2, $j+= 2) {
+ list($sb, $sa, $s9, $s8) = $this->mdsrem($le_longs[1], $le_longs[2]);
+ list($s7, $s6, $s5, $s4) = $this->mdsrem($le_longs[3], $le_longs[4]);
+ list($s3, $s2, $s1, $s0) = $this->mdsrem($le_longs[5], $le_longs[6]);
+ for ($i = 0, $j = 1; $i < 40; $i += 2, $j += 2) {
$A = $m0[$q0[$q0[$q1[$i] ^ $key[17]] ^ $key[ 9]] ^ $key[1]] ^
$m1[$q0[$q1[$q1[$i] ^ $key[18]] ^ $key[10]] ^ $key[2]] ^
$m2[$q1[$q0[$q0[$i] ^ $key[19]] ^ $key[11]] ^ $key[3]] ^
@@ -494,9 +493,9 @@ class Twofish extends Base
$m2[$q1[$q0[$q0[$j] ^ $key[23]] ^ $key[15]] ^ $key[7]] ^
$m3[$q1[$q1[$q0[$j] ^ $key[24]] ^ $key[16]] ^ $key[8]];
$B = ($B << 8) | ($B >> 24 & 0xff);
- $A = $this->safe_intval($A + $B);
+ $A = self::safe_intval($A + $B);
$K[] = $A;
- $A = $this->safe_intval($A + $B);
+ $A = self::safe_intval($A + $B);
$K[] = ($A << 9 | $A >> 23 & 0x1ff);
}
for ($i = 0; $i < 256; ++$i) {
@@ -507,11 +506,11 @@ class Twofish extends Base
}
break;
default: // 32
- list($sf, $se, $sd, $sc) = $this->_mdsrem($le_longs[1], $le_longs[2]);
- list($sb, $sa, $s9, $s8) = $this->_mdsrem($le_longs[3], $le_longs[4]);
- list($s7, $s6, $s5, $s4) = $this->_mdsrem($le_longs[5], $le_longs[6]);
- list($s3, $s2, $s1, $s0) = $this->_mdsrem($le_longs[7], $le_longs[8]);
- for ($i = 0, $j = 1; $i < 40; $i+= 2, $j+= 2) {
+ list($sf, $se, $sd, $sc) = $this->mdsrem($le_longs[1], $le_longs[2]);
+ list($sb, $sa, $s9, $s8) = $this->mdsrem($le_longs[3], $le_longs[4]);
+ list($s7, $s6, $s5, $s4) = $this->mdsrem($le_longs[5], $le_longs[6]);
+ list($s3, $s2, $s1, $s0) = $this->mdsrem($le_longs[7], $le_longs[8]);
+ for ($i = 0, $j = 1; $i < 40; $i += 2, $j += 2) {
$A = $m0[$q0[$q0[$q1[$q1[$i] ^ $key[25]] ^ $key[17]] ^ $key[ 9]] ^ $key[1]] ^
$m1[$q0[$q1[$q1[$q0[$i] ^ $key[26]] ^ $key[18]] ^ $key[10]] ^ $key[2]] ^
$m2[$q1[$q0[$q0[$q0[$i] ^ $key[27]] ^ $key[19]] ^ $key[11]] ^ $key[3]] ^
@@ -521,9 +520,9 @@ class Twofish extends Base
$m2[$q1[$q0[$q0[$q0[$j] ^ $key[31]] ^ $key[23]] ^ $key[15]] ^ $key[7]] ^
$m3[$q1[$q1[$q0[$q1[$j] ^ $key[32]] ^ $key[24]] ^ $key[16]] ^ $key[8]];
$B = ($B << 8) | ($B >> 24 & 0xff);
- $A = $this->safe_intval($A + $B);
+ $A = self::safe_intval($A + $B);
$K[] = $A;
- $A = $this->safe_intval($A + $B);
+ $A = self::safe_intval($A + $B);
$K[] = ($A << 9 | $A >> 23 & 0x1ff);
}
for ($i = 0; $i < 256; ++$i) {
@@ -544,12 +543,11 @@ class Twofish extends Base
/**
* _mdsrem function using by the twofish cipher algorithm
*
- * @access private
* @param string $A
* @param string $B
* @return array
*/
- function _mdsrem($A, $B)
+ private function mdsrem($A, $B)
{
// No gain by unrolling this loop.
for ($i = 0; $i < 8; ++$i) {
@@ -558,45 +556,44 @@ class Twofish extends Base
// Shift the others up.
$B = ($B << 8) | (0xff & ($A >> 24));
- $A<<= 8;
+ $A <<= 8;
$u = $t << 1;
// Subtract the modular polynomial on overflow.
if ($t & 0x80) {
- $u^= 0x14d;
+ $u ^= 0x14d;
}
// Remove t * (a * x^2 + 1).
$B ^= $t ^ ($u << 16);
// Form u = a*t + t/a = t*(a + 1/a).
- $u^= 0x7fffffff & ($t >> 1);
+ $u ^= 0x7fffffff & ($t >> 1);
// Add the modular polynomial on underflow.
if ($t & 0x01) {
- $u^= 0xa6 ;
+ $u ^= 0xa6 ;
}
// Remove t * (a + 1/a) * (x^3 + x).
- $B^= ($u << 24) | ($u << 8);
+ $B ^= ($u << 24) | ($u << 8);
}
- return array(
+ return [
0xff & $B >> 24,
0xff & $B >> 16,
0xff & $B >> 8,
- 0xff & $B);
+ 0xff & $B];
}
/**
* Encrypts a block
*
- * @access private
* @param string $in
* @return string
*/
- function _encryptBlock($in)
+ protected function encryptBlock($in)
{
$S0 = $this->S0;
$S1 = $this->S1;
@@ -620,9 +617,9 @@ class Twofish extends Base
$S1[ $R1 & 0xff] ^
$S2[($R1 >> 8) & 0xff] ^
$S3[($R1 >> 16) & 0xff];
- $R2^= $this->safe_intval($t0 + $t1 + $K[++$ki]);
+ $R2 ^= self::safe_intval($t0 + $t1 + $K[++$ki]);
$R2 = ($R2 >> 1 & 0x7fffffff) | ($R2 << 31);
- $R3 = ((($R3 >> 31) & 1) | ($R3 << 1)) ^ $this->safe_intval($t0 + ($t1 << 1) + $K[++$ki]);
+ $R3 = ((($R3 >> 31) & 1) | ($R3 << 1)) ^ self::safe_intval($t0 + ($t1 << 1) + $K[++$ki]);
$t0 = $S0[ $R2 & 0xff] ^
$S1[($R2 >> 8) & 0xff] ^
@@ -632,9 +629,9 @@ class Twofish extends Base
$S1[ $R3 & 0xff] ^
$S2[($R3 >> 8) & 0xff] ^
$S3[($R3 >> 16) & 0xff];
- $R0^= $this->safe_intval($t0 + $t1 + $K[++$ki]);
+ $R0 ^= self::safe_intval($t0 + $t1 + $K[++$ki]);
$R0 = ($R0 >> 1 & 0x7fffffff) | ($R0 << 31);
- $R1 = ((($R1 >> 31) & 1) | ($R1 << 1)) ^ $this->safe_intval($t0 + ($t1 << 1) + $K[++$ki]);
+ $R1 = ((($R1 >> 31) & 1) | ($R1 << 1)) ^ self::safe_intval($t0 + ($t1 << 1) + $K[++$ki]);
}
// @codingStandardsIgnoreStart
@@ -648,11 +645,10 @@ class Twofish extends Base
/**
* Decrypts a block
*
- * @access private
* @param string $in
* @return string
*/
- function _decryptBlock($in)
+ protected function decryptBlock($in)
{
$S0 = $this->S0;
$S1 = $this->S1;
@@ -676,9 +672,9 @@ class Twofish extends Base
$S1[$R1 & 0xff] ^
$S2[$R1 >> 8 & 0xff] ^
$S3[$R1 >> 16 & 0xff];
- $R3^= $this->safe_intval($t0 + ($t1 << 1) + $K[--$ki]);
+ $R3 ^= self::safe_intval($t0 + ($t1 << 1) + $K[--$ki]);
$R3 = $R3 >> 1 & 0x7fffffff | $R3 << 31;
- $R2 = ($R2 >> 31 & 0x1 | $R2 << 1) ^ $this->safe_intval($t0 + $t1 + $K[--$ki]);
+ $R2 = ($R2 >> 31 & 0x1 | $R2 << 1) ^ self::safe_intval($t0 + $t1 + $K[--$ki]);
$t0 = $S0[$R2 & 0xff] ^
$S1[$R2 >> 8 & 0xff] ^
@@ -688,9 +684,9 @@ class Twofish extends Base
$S1[$R3 & 0xff] ^
$S2[$R3 >> 8 & 0xff] ^
$S3[$R3 >> 16 & 0xff];
- $R1^= $this->safe_intval($t0 + ($t1 << 1) + $K[--$ki]);
+ $R1 ^= self::safe_intval($t0 + ($t1 << 1) + $K[--$ki]);
$R1 = $R1 >> 1 & 0x7fffffff | $R1 << 31;
- $R0 = ($R0 >> 31 & 0x1 | $R0 << 1) ^ $this->safe_intval($t0 + $t1 + $K[--$ki]);
+ $R0 = ($R0 >> 31 & 0x1 | $R0 << 1) ^ self::safe_intval($t0 + $t1 + $K[--$ki]);
}
// @codingStandardsIgnoreStart
@@ -704,149 +700,117 @@ class Twofish extends Base
/**
* Setup the performance-optimized function for de/encrypt()
*
- * @see \phpseclib\Crypt\Base::_setupInlineCrypt()
- * @access private
+ * @see Common\SymmetricKey::_setupInlineCrypt()
*/
- function _setupInlineCrypt()
+ protected function setupInlineCrypt()
{
- $lambda_functions =& self::_getLambdaFunctions();
-
- // Max. 10 Ultra-Hi-optimized inline-crypt functions. After that, we'll (still) create very fast code, but not the ultimate fast one.
- // (Currently, for Crypt_Twofish, one generated $lambda_function cost on php5.5@32bit ~140kb unfreeable mem and ~240kb on php5.5@64bit)
- $gen_hi_opt_code = (bool)(count($lambda_functions) < 10);
-
- // Generation of a unique hash for our generated code
- $code_hash = "Crypt_Twofish, {$this->mode}";
- if ($gen_hi_opt_code) {
- $code_hash = str_pad($code_hash, 32) . $this->_hashInlineCryptFunction($this->key);
- }
-
- $safeint = $this->safe_intval_inline();
-
- if (!isset($lambda_functions[$code_hash])) {
- switch (true) {
- case $gen_hi_opt_code:
- $K = $this->K;
- $init_crypt = '
- static $S0, $S1, $S2, $S3;
- if (!$S0) {
- for ($i = 0; $i < 256; ++$i) {
- $S0[] = (int)$self->S0[$i];
- $S1[] = (int)$self->S1[$i];
- $S2[] = (int)$self->S2[$i];
- $S3[] = (int)$self->S3[$i];
- }
- }
- ';
- break;
- default:
- $K = array();
- for ($i = 0; $i < 40; ++$i) {
- $K[] = '$K_' . $i;
- }
- $init_crypt = '
- $S0 = $self->S0;
- $S1 = $self->S1;
- $S2 = $self->S2;
- $S3 = $self->S3;
- list(' . implode(',', $K) . ') = $self->K;
- ';
+ $K = $this->K;
+ $init_crypt = '
+ static $S0, $S1, $S2, $S3;
+ if (!$S0) {
+ for ($i = 0; $i < 256; ++$i) {
+ $S0[] = (int)$this->S0[$i];
+ $S1[] = (int)$this->S1[$i];
+ $S2[] = (int)$this->S2[$i];
+ $S3[] = (int)$this->S3[$i];
+ }
}
-
- // Generating encrypt code:
- $encrypt_block = '
- $in = unpack("V4", $in);
- $R0 = '.$K[0].' ^ $in[1];
- $R1 = '.$K[1].' ^ $in[2];
- $R2 = '.$K[2].' ^ $in[3];
- $R3 = '.$K[3].' ^ $in[4];
- ';
- for ($ki = 7, $i = 0; $i < 8; ++$i) {
- $encrypt_block.= '
- $t0 = $S0[ $R0 & 0xff] ^
- $S1[($R0 >> 8) & 0xff] ^
- $S2[($R0 >> 16) & 0xff] ^
- $S3[($R0 >> 24) & 0xff];
- $t1 = $S0[($R1 >> 24) & 0xff] ^
- $S1[ $R1 & 0xff] ^
- $S2[($R1 >> 8) & 0xff] ^
- $S3[($R1 >> 16) & 0xff];
+ ';
+
+ $safeint = self::safe_intval_inline();
+
+ // Generating encrypt code:
+ $encrypt_block = '
+ $in = unpack("V4", $in);
+ $R0 = ' . $K[0] . ' ^ $in[1];
+ $R1 = ' . $K[1] . ' ^ $in[2];
+ $R2 = ' . $K[2] . ' ^ $in[3];
+ $R3 = ' . $K[3] . ' ^ $in[4];
+ ';
+ for ($ki = 7, $i = 0; $i < 8; ++$i) {
+ $encrypt_block .= '
+ $t0 = $S0[ $R0 & 0xff] ^
+ $S1[($R0 >> 8) & 0xff] ^
+ $S2[($R0 >> 16) & 0xff] ^
+ $S3[($R0 >> 24) & 0xff];
+ $t1 = $S0[($R1 >> 24) & 0xff] ^
+ $S1[ $R1 & 0xff] ^
+ $S2[($R1 >> 8) & 0xff] ^
+ $S3[($R1 >> 16) & 0xff];
$R2^= ' . sprintf($safeint, '$t0 + $t1 + ' . $K[++$ki]) . ';
- $R2 = ($R2 >> 1 & 0x7fffffff) | ($R2 << 31);
- $R3 = ((($R3 >> 31) & 1) | ($R3 << 1)) ^ ' . sprintf($safeint, '($t0 + ($t1 << 1) + ' . $K[++$ki] . ')') . ';
-
- $t0 = $S0[ $R2 & 0xff] ^
- $S1[($R2 >> 8) & 0xff] ^
- $S2[($R2 >> 16) & 0xff] ^
- $S3[($R2 >> 24) & 0xff];
- $t1 = $S0[($R3 >> 24) & 0xff] ^
- $S1[ $R3 & 0xff] ^
- $S2[($R3 >> 8) & 0xff] ^
- $S3[($R3 >> 16) & 0xff];
- $R0^= ' . sprintf($safeint, '($t0 + $t1 + ' . $K[++$ki] . ')') . ';
- $R0 = ($R0 >> 1 & 0x7fffffff) | ($R0 << 31);
- $R1 = ((($R1 >> 31) & 1) | ($R1 << 1)) ^ ' . sprintf($safeint, '($t0 + ($t1 << 1) + ' . $K[++$ki] . ')') . ';
- ';
- }
- $encrypt_block.= '
- $in = pack("V4", ' . $K[4] . ' ^ $R2,
- ' . $K[5] . ' ^ $R3,
- ' . $K[6] . ' ^ $R0,
- ' . $K[7] . ' ^ $R1);
+ $R2 = ($R2 >> 1 & 0x7fffffff) | ($R2 << 31);
+ $R3 = ((($R3 >> 31) & 1) | ($R3 << 1)) ^ ' . sprintf($safeint, '($t0 + ($t1 << 1) + ' . $K[++$ki] . ')') . ';
+
+ $t0 = $S0[ $R2 & 0xff] ^
+ $S1[($R2 >> 8) & 0xff] ^
+ $S2[($R2 >> 16) & 0xff] ^
+ $S3[($R2 >> 24) & 0xff];
+ $t1 = $S0[($R3 >> 24) & 0xff] ^
+ $S1[ $R3 & 0xff] ^
+ $S2[($R3 >> 8) & 0xff] ^
+ $S3[($R3 >> 16) & 0xff];
+ $R0^= ' . sprintf($safeint, '($t0 + $t1 + ' . $K[++$ki] . ')') . ';
+ $R0 = ($R0 >> 1 & 0x7fffffff) | ($R0 << 31);
+ $R1 = ((($R1 >> 31) & 1) | ($R1 << 1)) ^ ' . sprintf($safeint, '($t0 + ($t1 << 1) + ' . $K[++$ki] . ')') . ';
';
-
- // Generating decrypt code:
- $decrypt_block = '
- $in = unpack("V4", $in);
- $R0 = '.$K[4].' ^ $in[1];
- $R1 = '.$K[5].' ^ $in[2];
- $R2 = '.$K[6].' ^ $in[3];
- $R3 = '.$K[7].' ^ $in[4];
- ';
- for ($ki = 40, $i = 0; $i < 8; ++$i) {
- $decrypt_block.= '
- $t0 = $S0[$R0 & 0xff] ^
- $S1[$R0 >> 8 & 0xff] ^
- $S2[$R0 >> 16 & 0xff] ^
- $S3[$R0 >> 24 & 0xff];
- $t1 = $S0[$R1 >> 24 & 0xff] ^
- $S1[$R1 & 0xff] ^
- $S2[$R1 >> 8 & 0xff] ^
- $S3[$R1 >> 16 & 0xff];
- $R3^= ' . sprintf($safeint, '$t0 + ($t1 << 1) + ' . $K[--$ki]) . ';
- $R3 = $R3 >> 1 & 0x7fffffff | $R3 << 31;
- $R2 = ($R2 >> 31 & 0x1 | $R2 << 1) ^ ' . sprintf($safeint, '($t0 + $t1 + '.$K[--$ki] . ')') . ';
-
- $t0 = $S0[$R2 & 0xff] ^
- $S1[$R2 >> 8 & 0xff] ^
- $S2[$R2 >> 16 & 0xff] ^
- $S3[$R2 >> 24 & 0xff];
- $t1 = $S0[$R3 >> 24 & 0xff] ^
- $S1[$R3 & 0xff] ^
- $S2[$R3 >> 8 & 0xff] ^
- $S3[$R3 >> 16 & 0xff];
- $R1^= ' . sprintf($safeint, '$t0 + ($t1 << 1) + ' . $K[--$ki]) . ';
- $R1 = $R1 >> 1 & 0x7fffffff | $R1 << 31;
- $R0 = ($R0 >> 31 & 0x1 | $R0 << 1) ^ ' . sprintf($safeint, '($t0 + $t1 + '.$K[--$ki] . ')') . ';
- ';
- }
- $decrypt_block.= '
- $in = pack("V4", ' . $K[0] . ' ^ $R2,
- ' . $K[1] . ' ^ $R3,
- ' . $K[2] . ' ^ $R0,
- ' . $K[3] . ' ^ $R1);
+ }
+ $encrypt_block .= '
+ $in = pack("V4", ' . $K[4] . ' ^ $R2,
+ ' . $K[5] . ' ^ $R3,
+ ' . $K[6] . ' ^ $R0,
+ ' . $K[7] . ' ^ $R1);
+ ';
+
+ // Generating decrypt code:
+ $decrypt_block = '
+ $in = unpack("V4", $in);
+ $R0 = ' . $K[4] . ' ^ $in[1];
+ $R1 = ' . $K[5] . ' ^ $in[2];
+ $R2 = ' . $K[6] . ' ^ $in[3];
+ $R3 = ' . $K[7] . ' ^ $in[4];
+ ';
+ for ($ki = 40, $i = 0; $i < 8; ++$i) {
+ $decrypt_block .= '
+ $t0 = $S0[$R0 & 0xff] ^
+ $S1[$R0 >> 8 & 0xff] ^
+ $S2[$R0 >> 16 & 0xff] ^
+ $S3[$R0 >> 24 & 0xff];
+ $t1 = $S0[$R1 >> 24 & 0xff] ^
+ $S1[$R1 & 0xff] ^
+ $S2[$R1 >> 8 & 0xff] ^
+ $S3[$R1 >> 16 & 0xff];
+ $R3^= ' . sprintf($safeint, '$t0 + ($t1 << 1) + ' . $K[--$ki]) . ';
+ $R3 = $R3 >> 1 & 0x7fffffff | $R3 << 31;
+ $R2 = ($R2 >> 31 & 0x1 | $R2 << 1) ^ ' . sprintf($safeint, '($t0 + $t1 + ' . $K[--$ki] . ')') . ';
+
+ $t0 = $S0[$R2 & 0xff] ^
+ $S1[$R2 >> 8 & 0xff] ^
+ $S2[$R2 >> 16 & 0xff] ^
+ $S3[$R2 >> 24 & 0xff];
+ $t1 = $S0[$R3 >> 24 & 0xff] ^
+ $S1[$R3 & 0xff] ^
+ $S2[$R3 >> 8 & 0xff] ^
+ $S3[$R3 >> 16 & 0xff];
+ $R1^= ' . sprintf($safeint, '$t0 + ($t1 << 1) + ' . $K[--$ki]) . ';
+ $R1 = $R1 >> 1 & 0x7fffffff | $R1 << 31;
+ $R0 = ($R0 >> 31 & 0x1 | $R0 << 1) ^ ' . sprintf($safeint, '($t0 + $t1 + ' . $K[--$ki] . ')') . ';
';
-
- $lambda_functions[$code_hash] = $this->_createInlineCryptFunction(
- array(
- 'init_crypt' => $init_crypt,
- 'init_encrypt' => '',
- 'init_decrypt' => '',
- 'encrypt_block' => $encrypt_block,
- 'decrypt_block' => $decrypt_block
- )
- );
}
- $this->inline_crypt = $lambda_functions[$code_hash];
+ $decrypt_block .= '
+ $in = pack("V4", ' . $K[0] . ' ^ $R2,
+ ' . $K[1] . ' ^ $R3,
+ ' . $K[2] . ' ^ $R0,
+ ' . $K[3] . ' ^ $R1);
+ ';
+
+ $this->inline_crypt = $this->createInlineCryptFunction(
+ [
+ 'init_crypt' => $init_crypt,
+ 'init_encrypt' => '',
+ 'init_decrypt' => '',
+ 'encrypt_block' => $encrypt_block,
+ 'decrypt_block' => $decrypt_block
+ ]
+ );
}
}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/BadConfigurationException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/BadConfigurationException.php
new file mode 100644
index 000000000..1aabcae09
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/BadConfigurationException.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * BadConfigurationException
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * BadConfigurationException
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class BadConfigurationException extends \RuntimeException
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/BadDecryptionException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/BadDecryptionException.php
new file mode 100644
index 000000000..88331dce0
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/BadDecryptionException.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * BadDecryptionException
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * BadDecryptionException
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class BadDecryptionException extends \RuntimeException
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/BadModeException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/BadModeException.php
new file mode 100644
index 000000000..87689b224
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/BadModeException.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * BadModeException
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * BadModeException
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class BadModeException extends \RuntimeException
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/ConnectionClosedException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/ConnectionClosedException.php
new file mode 100644
index 000000000..6aaccbad6
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/ConnectionClosedException.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * ConnectionClosedException
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * ConnectionClosedException
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class ConnectionClosedException extends \RuntimeException
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/FileNotFoundException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/FileNotFoundException.php
new file mode 100644
index 000000000..66e727091
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/FileNotFoundException.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * FileNotFoundException
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * FileNotFoundException
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class FileNotFoundException extends \RuntimeException
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/InconsistentSetupException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/InconsistentSetupException.php
new file mode 100644
index 000000000..23c38fb02
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/InconsistentSetupException.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * InconsistentSetupException
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * InconsistentSetupException
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class InconsistentSetupException extends \RuntimeException
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/InsufficientSetupException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/InsufficientSetupException.php
new file mode 100644
index 000000000..4f4114d70
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/InsufficientSetupException.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * InsufficientSetupException
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * InsufficientSetupException
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class InsufficientSetupException extends \RuntimeException
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/InvalidPacketLengthException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/InvalidPacketLengthException.php
new file mode 100644
index 000000000..b96ead1e3
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/InvalidPacketLengthException.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace phpseclib3\Exception;
+
+/**
+ * Indicates an absent or malformed packet length header
+ */
+class InvalidPacketLengthException extends ConnectionClosedException
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/NoKeyLoadedException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/NoKeyLoadedException.php
new file mode 100644
index 000000000..7ec2fe9b9
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/NoKeyLoadedException.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * NoKeyLoadedException
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * NoKeyLoadedException
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class NoKeyLoadedException extends \RuntimeException
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/NoSupportedAlgorithmsException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/NoSupportedAlgorithmsException.php
new file mode 100644
index 000000000..b3ea8f384
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/NoSupportedAlgorithmsException.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * NoSupportedAlgorithmsException
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * NoSupportedAlgorithmsException
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class NoSupportedAlgorithmsException extends \RuntimeException
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/TimeoutException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/TimeoutException.php
new file mode 100644
index 000000000..8701f8d76
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/TimeoutException.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace phpseclib3\Exception;
+
+/**
+ * Indicates a timeout awaiting server response
+ */
+class TimeoutException extends \RuntimeException
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/UnableToConnectException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/UnableToConnectException.php
new file mode 100644
index 000000000..bfa005b4f
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/UnableToConnectException.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * UnableToConnectException
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * UnableToConnectException
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class UnableToConnectException extends \RuntimeException
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedAlgorithmException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedAlgorithmException.php
new file mode 100644
index 000000000..210a9a5ce
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedAlgorithmException.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * UnsupportedAlgorithmException
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * UnsupportedAlgorithmException
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class UnsupportedAlgorithmException extends \RuntimeException
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedCurveException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedCurveException.php
new file mode 100644
index 000000000..99152152c
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedCurveException.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * UnsupportedCurveException
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * UnsupportedCurveException
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class UnsupportedCurveException extends \RuntimeException
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedFormatException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedFormatException.php
new file mode 100644
index 000000000..e207d7e21
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedFormatException.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * UnsupportedFormatException
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * UnsupportedFormatException
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class UnsupportedFormatException extends \RuntimeException
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedOperationException.php b/vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedOperationException.php
new file mode 100644
index 000000000..9a1154445
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Exception/UnsupportedOperationException.php
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * UnsupportedOperationException
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\Exception;
+
+/**
+ * UnsupportedOperationException
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class UnsupportedOperationException extends \RuntimeException
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ANSI.php b/vendor/phpseclib/phpseclib/phpseclib/File/ANSI.php
index b6874d357..41477ba5d 100644
--- a/vendor/phpseclib/phpseclib/phpseclib/File/ANSI.php
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ANSI.php
@@ -5,27 +5,23 @@
*
* PHP version 5
*
- * If you call read() in \phpseclib\Net\SSH2 you may get {@link http://en.wikipedia.org/wiki/ANSI_escape_code ANSI escape codes} back.
+ * If you call read() in \phpseclib3\Net\SSH2 you may get {@link http://en.wikipedia.org/wiki/ANSI_escape_code ANSI escape codes} back.
* They'd look like chr(0x1B) . '[00m' or whatever (0x1B = ESC). They tell a
* {@link http://en.wikipedia.org/wiki/Terminal_emulator terminal emulator} how to format the characters, what
- * color to display them in, etc. \phpseclib\File\ANSI is a {@link http://en.wikipedia.org/wiki/VT100 VT100} terminal emulator.
+ * color to display them in, etc. \phpseclib3\File\ANSI is a {@link http://en.wikipedia.org/wiki/VT100 VT100} terminal emulator.
*
- * @category File
- * @package ANSI
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2012 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
-namespace phpseclib\File;
+namespace phpseclib3\File;
/**
* Pure-PHP ANSI Decoder
*
- * @package ANSI
* @author Jim Wigginton <terrafrost@php.net>
- * @access public
*/
class ANSI
{
@@ -33,137 +29,120 @@ class ANSI
* Max Width
*
* @var int
- * @access private
*/
- var $max_x;
+ private $max_x;
/**
* Max Height
*
* @var int
- * @access private
*/
- var $max_y;
+ private $max_y;
/**
* Max History
*
* @var int
- * @access private
*/
- var $max_history;
+ private $max_history;
/**
* History
*
* @var array
- * @access private
*/
- var $history;
+ private $history;
/**
* History Attributes
*
* @var array
- * @access private
*/
- var $history_attrs;
+ private $history_attrs;
/**
* Current Column
*
* @var int
- * @access private
*/
- var $x;
+ private $x;
/**
* Current Row
*
* @var int
- * @access private
*/
- var $y;
+ private $y;
/**
* Old Column
*
* @var int
- * @access private
*/
- var $old_x;
+ private $old_x;
/**
* Old Row
*
* @var int
- * @access private
*/
- var $old_y;
+ private $old_y;
/**
* An empty attribute cell
*
* @var object
- * @access private
*/
- var $base_attr_cell;
+ private $base_attr_cell;
/**
* The current attribute cell
*
* @var object
- * @access private
*/
- var $attr_cell;
+ private $attr_cell;
/**
* An empty attribute row
*
* @var array
- * @access private
*/
- var $attr_row;
+ private $attr_row;
/**
* The current screen text
*
- * @var array
- * @access private
+ * @var list<string>
*/
- var $screen;
+ private $screen;
/**
* The current screen attributes
*
* @var array
- * @access private
*/
- var $attrs;
+ private $attrs;
/**
* Current ANSI code
*
* @var string
- * @access private
*/
- var $ansi;
+ private $ansi;
/**
* Tokenization
*
* @var array
- * @access private
*/
- var $tokenization;
+ private $tokenization;
/**
* Default Constructor.
*
- * @return \phpseclib\File\ANSI
- * @access public
+ * @return ANSI
*/
- function __construct()
+ public function __construct()
{
$attr_cell = new \stdClass();
$attr_cell->bold = false;
@@ -186,14 +165,13 @@ class ANSI
*
* @param int $x
* @param int $y
- * @access public
*/
- function setDimensions($x, $y)
+ public function setDimensions($x, $y)
{
$this->max_x = $x - 1;
$this->max_y = $y - 1;
$this->x = $this->y = 0;
- $this->history = $this->history_attrs = array();
+ $this->history = $this->history_attrs = [];
$this->attr_row = array_fill(0, $this->max_x + 2, $this->base_attr_cell);
$this->screen = array_fill(0, $this->max_y + 1, '');
$this->attrs = array_fill(0, $this->max_y + 1, $this->attr_row);
@@ -204,9 +182,8 @@ class ANSI
* Set the number of lines that should be logged past the terminal height
*
* @param int $history
- * @access public
*/
- function setHistory($history)
+ public function setHistory($history)
{
$this->max_history = $history;
}
@@ -215,9 +192,8 @@ class ANSI
* Load a string
*
* @param string $source
- * @access public
*/
- function loadString($source)
+ public function loadString($source)
{
$this->setDimensions($this->max_x + 1, $this->max_y + 1);
$this->appendString($source);
@@ -227,14 +203,13 @@ class ANSI
* Appdend a string
*
* @param string $source
- * @access public
*/
- function appendString($source)
+ public function appendString($source)
{
- $this->tokenization = array('');
+ $this->tokenization = [''];
for ($i = 0; $i < strlen($source); $i++) {
if (strlen($this->ansi)) {
- $this->ansi.= $source[$i];
+ $this->ansi .= $source[$i];
$chr = ord($source[$i]);
// http://en.wikipedia.org/wiki/ANSI_escape_code#Sequence_elements
// single character CSI's not currently supported
@@ -268,6 +243,7 @@ class ANSI
array_shift($this->history);
array_shift($this->history_attrs);
}
+ // fall-through
case "\x1B[K": // Clear screen from cursor right
$this->screen[$this->y] = substr($this->screen[$this->y], 0, $this->x);
@@ -282,28 +258,28 @@ class ANSI
case "\x1B(B": // set united states g0 character set
break;
case "\x1BE": // Move to next line
- $this->_newLine();
+ $this->newLine();
$this->x = 0;
break;
default:
switch (true) {
case preg_match('#\x1B\[(\d+)B#', $this->ansi, $match): // Move cursor down n lines
$this->old_y = $this->y;
- $this->y+= $match[1];
+ $this->y += (int) $match[1];
break;
case preg_match('#\x1B\[(\d+);(\d+)H#', $this->ansi, $match): // Move cursor to screen location v,h
$this->old_x = $this->x;
$this->old_y = $this->y;
$this->x = $match[2] - 1;
- $this->y = $match[1] - 1;
+ $this->y = (int) $match[1] - 1;
break;
case preg_match('#\x1B\[(\d+)C#', $this->ansi, $match): // Move cursor right n lines
$this->old_x = $this->x;
- $this->x+= $match[1];
+ $this->x += $match[1];
break;
case preg_match('#\x1B\[(\d+)D#', $this->ansi, $match): // Move cursor left n lines
$this->old_x = $this->x;
- $this->x-= $match[1];
+ $this->x -= $match[1];
if ($this->x < 0) {
$this->x = 0;
}
@@ -376,13 +352,13 @@ class ANSI
continue;
}
- $this->tokenization[count($this->tokenization) - 1].= $source[$i];
+ $this->tokenization[count($this->tokenization) - 1] .= $source[$i];
switch ($source[$i]) {
case "\r":
$this->x = 0;
break;
case "\n":
- $this->_newLine();
+ $this->newLine();
break;
case "\x08": // backspace
if ($this->x) {
@@ -403,7 +379,7 @@ class ANSI
//if (!strlen($this->tokenization[count($this->tokenization) - 1])) {
// array_pop($this->tokenization);
//}
- $this->ansi.= "\x1B";
+ $this->ansi .= "\x1B";
break;
default:
$this->attrs[$this->y][$this->x] = clone $this->attr_cell;
@@ -419,7 +395,7 @@ class ANSI
if ($this->x > $this->max_x) {
$this->x = 0;
- $this->_newLine();
+ $this->newLine();
} else {
$this->x++;
}
@@ -432,19 +408,18 @@ class ANSI
*
* Also update the $this->screen and $this->history buffers
*
- * @access private
*/
- function _newLine()
+ private function newLine()
{
//if ($this->y < $this->max_y) {
// $this->y++;
//}
while ($this->y >= $this->max_y) {
- $this->history = array_merge($this->history, array(array_shift($this->screen)));
+ $this->history = array_merge($this->history, [array_shift($this->screen)]);
$this->screen[] = '';
- $this->history_attrs = array_merge($this->history_attrs, array(array_shift($this->attrs)));
+ $this->history_attrs = array_merge($this->history_attrs, [array_shift($this->attrs)]);
$this->attrs[] = $this->attr_row;
if (count($this->history) >= $this->max_history) {
@@ -460,10 +435,12 @@ class ANSI
/**
* Returns the current coordinate without preformating
*
- * @access private
+ * @param \stdClass $last_attr
+ * @param \stdClass $cur_attr
+ * @param string $char
* @return string
*/
- function _processCoordinate($last_attr, $cur_attr, $char)
+ private function processCoordinate(\stdClass $last_attr, \stdClass $cur_attr, $char)
{
$output = '';
@@ -471,7 +448,7 @@ class ANSI
$close = $open = '';
if ($last_attr->foreground != $cur_attr->foreground) {
if ($cur_attr->foreground != 'white') {
- $open.= '<span style="color: ' . $cur_attr->foreground . '">';
+ $open .= '<span style="color: ' . $cur_attr->foreground . '">';
}
if ($last_attr->foreground != 'white') {
$close = '</span>' . $close;
@@ -479,7 +456,7 @@ class ANSI
}
if ($last_attr->background != $cur_attr->background) {
if ($cur_attr->background != 'black') {
- $open.= '<span style="background: ' . $cur_attr->background . '">';
+ $open .= '<span style="background: ' . $cur_attr->background . '">';
}
if ($last_attr->background != 'black') {
$close = '</span>' . $close;
@@ -487,29 +464,29 @@ class ANSI
}
if ($last_attr->bold != $cur_attr->bold) {
if ($cur_attr->bold) {
- $open.= '<b>';
+ $open .= '<b>';
} else {
$close = '</b>' . $close;
}
}
if ($last_attr->underline != $cur_attr->underline) {
if ($cur_attr->underline) {
- $open.= '<u>';
+ $open .= '<u>';
} else {
$close = '</u>' . $close;
}
}
if ($last_attr->blink != $cur_attr->blink) {
if ($cur_attr->blink) {
- $open.= '<blink>';
+ $open .= '<blink>';
} else {
$close = '</blink>' . $close;
}
}
- $output.= $close . $open;
+ $output .= $close . $open;
}
- $output.= htmlspecialchars($char);
+ $output .= htmlspecialchars($char);
return $output;
}
@@ -517,59 +494,56 @@ class ANSI
/**
* Returns the current screen without preformating
*
- * @access private
* @return string
*/
- function _getScreen()
+ private function getScreenHelper()
{
$output = '';
$last_attr = $this->base_attr_cell;
for ($i = 0; $i <= $this->max_y; $i++) {
for ($j = 0; $j <= $this->max_x; $j++) {
$cur_attr = $this->attrs[$i][$j];
- $output.= $this->_processCoordinate($last_attr, $cur_attr, isset($this->screen[$i][$j]) ? $this->screen[$i][$j] : '');
+ $output .= $this->processCoordinate($last_attr, $cur_attr, isset($this->screen[$i][$j]) ? $this->screen[$i][$j] : '');
$last_attr = $this->attrs[$i][$j];
}
- $output.= "\r\n";
+ $output .= "\r\n";
}
$output = substr($output, 0, -2);
// close any remaining open tags
- $output.= $this->_processCoordinate($last_attr, $this->base_attr_cell, '');
+ $output .= $this->processCoordinate($last_attr, $this->base_attr_cell, '');
return rtrim($output);
}
/**
* Returns the current screen
*
- * @access public
* @return string
*/
- function getScreen()
+ public function getScreen()
{
- return '<pre width="' . ($this->max_x + 1) . '" style="color: white; background: black">' . $this->_getScreen() . '</pre>';
+ return '<pre width="' . ($this->max_x + 1) . '" style="color: white; background: black">' . $this->getScreenHelper() . '</pre>';
}
/**
* Returns the current screen and the x previous lines
*
- * @access public
* @return string
*/
- function getHistory()
+ public function getHistory()
{
$scrollback = '';
$last_attr = $this->base_attr_cell;
for ($i = 0; $i < count($this->history); $i++) {
for ($j = 0; $j <= $this->max_x + 1; $j++) {
$cur_attr = $this->history_attrs[$i][$j];
- $scrollback.= $this->_processCoordinate($last_attr, $cur_attr, isset($this->history[$i][$j]) ? $this->history[$i][$j] : '');
+ $scrollback .= $this->processCoordinate($last_attr, $cur_attr, isset($this->history[$i][$j]) ? $this->history[$i][$j] : '');
$last_attr = $this->history_attrs[$i][$j];
}
- $scrollback.= "\r\n";
+ $scrollback .= "\r\n";
}
$base_attr_cell = $this->base_attr_cell;
$this->base_attr_cell = $last_attr;
- $scrollback.= $this->_getScreen();
+ $scrollback .= $this->getScreen();
$this->base_attr_cell = $base_attr_cell;
return '<pre width="' . ($this->max_x + 1) . '" style="color: white; background: black">' . $scrollback . '</span></pre>';
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1.php
index dba99de73..2f1fb8a67 100644
--- a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1.php
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1.php
@@ -9,52 +9,38 @@
* utilized scheme is DER or the "Distinguished Encoding Rules". PEM's are base64 encoded
* DER blobs.
*
- * \phpseclib\File\ASN1 decodes and encodes DER formatted messages and places them in a semantic context.
+ * \phpseclib3\File\ASN1 decodes and encodes DER formatted messages and places them in a semantic context.
*
* Uses the 1988 ASN.1 syntax.
*
- * @category File
- * @package ASN1
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2012 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
-namespace phpseclib\File;
+namespace phpseclib3\File;
-use phpseclib\File\ASN1\Element;
-use phpseclib\Math\BigInteger;
-use DateTime;
-use DateTimeZone;
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\File\ASN1\Element;
+use phpseclib3\Math\BigInteger;
/**
* Pure-PHP ASN.1 Parser
*
- * @package ASN1
* @author Jim Wigginton <terrafrost@php.net>
- * @access public
*/
-class ASN1
+abstract class ASN1
{
- /**#@+
- * Tag Classes
- *
- * @access private
- * @link http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=12
- */
+ // Tag Classes
+ // http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=12
const CLASS_UNIVERSAL = 0;
const CLASS_APPLICATION = 1;
const CLASS_CONTEXT_SPECIFIC = 2;
const CLASS_PRIVATE = 3;
- /**#@-*/
- /**#@+
- * Tag Classes
- *
- * @access private
- * @link http://www.obj-sys.com/asn1tutorial/node124.html
- */
+ // Tag Classes
+ // http://www.obj-sys.com/asn1tutorial/node124.html
const TYPE_BOOLEAN = 1;
const TYPE_INTEGER = 2;
const TYPE_BIT_STRING = 3;
@@ -70,13 +56,9 @@ class ASN1
//const TYPE_RELATIVE_OID = 13;
const TYPE_SEQUENCE = 16; // SEQUENCE OF
const TYPE_SET = 17; // SET OF
- /**#@-*/
- /**#@+
- * More Tag Classes
- *
- * @access private
- * @link http://www.obj-sys.com/asn1tutorial/node10.html
- */
+
+ // More Tag Classes
+ // http://www.obj-sys.com/asn1tutorial/node10.html
const TYPE_NUMERIC_STRING = 18;
const TYPE_PRINTABLE_STRING = 19;
const TYPE_TELETEX_STRING = 20; // T61String
@@ -90,47 +72,34 @@ class ASN1
const TYPE_UNIVERSAL_STRING = 28;
//const TYPE_CHARACTER_STRING = 29;
const TYPE_BMP_STRING = 30;
- /**#@-*/
- /**#@+
- * Tag Aliases
- *
- * These tags are kinda place holders for other tags.
- *
- * @access private
- */
+ // Tag Aliases
+ // These tags are kinda place holders for other tags.
const TYPE_CHOICE = -1;
const TYPE_ANY = -2;
- /**#@-*/
/**
- * ASN.1 object identifier
+ * ASN.1 object identifiers
*
* @var array
- * @access private
* @link http://en.wikipedia.org/wiki/Object_identifier
*/
- var $oids = array();
+ private static $oids = [];
/**
- * Default date format
+ * ASN.1 object identifier reverse mapping
*
- * @var string
- * @access private
- * @link http://php.net/class.datetime
+ * @var array
*/
- var $format = 'D, d M Y H:i:s O';
+ private static $reverseOIDs = [];
/**
* Default date format
*
- * @var array
- * @access private
- * @see self::setTimeFormat()
- * @see self::asn1map()
+ * @var string
* @link http://php.net/class.datetime
*/
- var $encoded;
+ private static $format = 'D, d M Y H:i:s O';
/**
* Filters
@@ -138,10 +107,9 @@ class ASN1
* If the mapping type is self::TYPE_ANY what do we actually encode it as?
*
* @var array
- * @access private
- * @see self::_encode_der()
+ * @see self::encode_der()
*/
- var $filters;
+ private static $filters;
/**
* Current Location of most recent ASN.1 encode process
@@ -151,19 +119,28 @@ class ASN1
* @var array
* @see self::encode_der()
*/
- var $location;
+ private static $location;
+
+ /**
+ * DER Encoded String
+ *
+ * In case we need to create ASN1\Element object's..
+ *
+ * @var string
+ * @see self::decodeDER()
+ */
+ private static $encoded;
/**
* Type mapping table for the ANY type.
*
- * Structured or unknown types are mapped to a \phpseclib\File\ASN1\Element.
+ * Structured or unknown types are mapped to a \phpseclib3\File\ASN1\Element.
* Unambiguous types get the direct mapping (int/real/bool).
* Others are mapped as a choice, with an extra indexing level.
*
* @var array
- * @access public
*/
- var $ANYmap = array(
+ const ANY_MAP = [
self::TYPE_BOOLEAN => true,
self::TYPE_INTEGER => true,
self::TYPE_BIT_STRING => 'bitString',
@@ -186,7 +163,7 @@ class ASN1
self::TYPE_UNIVERSAL_STRING => 'universalString',
//self::TYPE_CHARACTER_STRING => 'characterString',
self::TYPE_BMP_STRING => 'bmpString'
- );
+ ];
/**
* String type to character size mapping table.
@@ -195,9 +172,8 @@ class ASN1
* size == 0 indicates variable length encoding.
*
* @var array
- * @access public
*/
- var $stringTypeSize = array(
+ const STRING_TYPE_SIZE = [
self::TYPE_UTF8_STRING => 0,
self::TYPE_BMP_STRING => 2,
self::TYPE_UNIVERSAL_STRING => 4,
@@ -205,26 +181,30 @@ class ASN1
self::TYPE_TELETEX_STRING => 1,
self::TYPE_IA5_STRING => 1,
self::TYPE_VISIBLE_STRING => 1,
- );
+ ];
/**
* Parse BER-encoding
*
* Serves a similar purpose to openssl's asn1parse
*
- * @param string $encoded
- * @return array
- * @access public
+ * @param Element|string $encoded
+ * @return ?array
*/
- function decodeBER($encoded)
+ public static function decodeBER($encoded)
{
if ($encoded instanceof Element) {
$encoded = $encoded->element;
}
- $this->encoded = $encoded;
- // encapsulate in an array for BC with the old decodeBER
- return array($this->_decode_ber($encoded));
+ self::$encoded = $encoded;
+
+ $decoded = self::decode_ber($encoded);
+ if ($decoded === false) {
+ return null;
+ }
+
+ return [$decoded];
}
/**
@@ -237,12 +217,11 @@ class ASN1
* @param string $encoded
* @param int $start
* @param int $encoded_pos
- * @return array
- * @access private
+ * @return array|bool
*/
- function _decode_ber($encoded, $start = 0, $encoded_pos = 0)
+ private static function decode_ber($encoded, $start = 0, $encoded_pos = 0)
{
- $current = array('start' => $start);
+ $current = ['start' => $start];
if (!isset($encoded[$encoded_pos])) {
return false;
@@ -273,7 +252,7 @@ class ASN1
} while ($loop);
}
- $start+= $startOffset;
+ $start += $startOffset;
// Length, as discussed in paragraph 8.1.3 of X.690-0207.pdf#page=13
if (!isset($encoded[$encoded_pos])) {
@@ -288,15 +267,15 @@ class ASN1
} elseif ($length & 0x80) { // definite length, long form
// technically, the long form of the length can be represented by up to 126 octets (bytes), but we'll only
// support it up to four.
- $length&= 0x7F;
+ $length &= 0x7F;
$temp = substr($encoded, $encoded_pos, $length);
$encoded_pos += $length;
// tags of indefinte length don't really have a header length; this length includes the tag
- $current+= array('headerlength' => $length + 2);
- $start+= $length;
- extract(unpack('Nlength', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4)));
+ $current += ['headerlength' => $length + 2];
+ $start += $length;
+ $length = unpack('Nlength', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4))['length'];
} else {
- $current+= array('headerlength' => 2);
+ $current += ['headerlength' => 2];
}
if ($length > (strlen($encoded) - $encoded_pos)) {
@@ -323,36 +302,36 @@ class ASN1
case self::CLASS_PRIVATE:
case self::CLASS_CONTEXT_SPECIFIC:
if (!$constructed) {
- return array(
+ return [
'type' => $class,
'constant' => $tag,
'content' => $content,
'length' => $length + $start - $current['start']
- );
+ ] + $current;
}
- $newcontent = array();
+ $newcontent = [];
$remainingLength = $length;
while ($remainingLength > 0) {
- $temp = $this->_decode_ber($content, $start, $content_pos);
+ $temp = self::decode_ber($content, $start, $content_pos);
if ($temp === false) {
break;
}
$length = $temp['length'];
// end-of-content octets - see paragraph 8.1.5
if (substr($content, $content_pos + $length, 2) == "\0\0") {
- $length+= 2;
- $start+= $length;
+ $length += 2;
+ $start += $length;
$newcontent[] = $temp;
break;
}
- $start+= $length;
- $remainingLength-= $length;
+ $start += $length;
+ $remainingLength -= $length;
$newcontent[] = $temp;
$content_pos += $length;
}
- return array(
+ return [
'type' => $class,
'constant' => $tag,
// the array encapsulation is for BC with the old format
@@ -361,10 +340,10 @@ class ASN1
// the absence of $content['headerlength'] is how we know if something is indefinite or not.
// technically, it could be defined to be 2 and then another indicator could be used but whatever.
'length' => $start - $current['start']
- ) + $current;
+ ] + $current;
}
- $current+= array('type' => $tag);
+ $current += ['type' => $tag];
// decode UNIVERSAL tags
switch ($tag) {
@@ -391,18 +370,18 @@ class ASN1
if (!$constructed) {
$current['content'] = substr($content, $content_pos);
} else {
- $temp = $this->_decode_ber($content, $start, $content_pos);
+ $temp = self::decode_ber($content, $start, $content_pos);
if ($temp === false) {
return false;
}
- $length-= (strlen($content) - $content_pos);
+ $length -= (strlen($content) - $content_pos);
$last = count($temp) - 1;
for ($i = 0; $i < $last; $i++) {
// all subtags should be bit strings
if ($temp[$i]['type'] != self::TYPE_BIT_STRING) {
return false;
}
- $current['content'].= substr($temp[$i]['content'], 1);
+ $current['content'] .= substr($temp[$i]['content'], 1);
}
// all subtags should be bit strings
if ($temp[$last]['type'] != self::TYPE_BIT_STRING) {
@@ -418,7 +397,7 @@ class ASN1
$current['content'] = '';
$length = 0;
while (substr($content, $content_pos, 2) != "\0\0") {
- $temp = $this->_decode_ber($content, $length + $start, $content_pos);
+ $temp = self::decode_ber($content, $length + $start, $content_pos);
if ($temp === false) {
return false;
}
@@ -427,11 +406,11 @@ class ASN1
if ($temp['type'] != self::TYPE_OCTET_STRING) {
return false;
}
- $current['content'].= $temp['content'];
- $length+= $temp['length'];
+ $current['content'] .= $temp['content'];
+ $length += $temp['length'];
}
if (substr($content, $content_pos, 2) == "\0\0") {
- $length+= 2; // +2 for the EOC
+ $length += 2; // +2 for the EOC
}
}
break;
@@ -447,7 +426,7 @@ class ASN1
return false;
}
$offset = 0;
- $current['content'] = array();
+ $current['content'] = [];
$content_len = strlen($content);
while ($content_pos < $content_len) {
// if indefinite length construction was used and we have an end-of-content string next
@@ -456,20 +435,20 @@ class ASN1
$length = $offset + 2; // +2 for the EOC
break 2;
}
- $temp = $this->_decode_ber($content, $start + $offset, $content_pos);
+ $temp = self::decode_ber($content, $start + $offset, $content_pos);
if ($temp === false) {
return false;
}
$content_pos += $temp['length'];
$current['content'][] = $temp;
- $offset+= $temp['length'];
+ $offset += $temp['length'];
}
break;
case self::TYPE_OBJECT_IDENTIFIER:
if ($constructed) {
return false;
}
- $current['content'] = $this->_decodeOID(substr($content, $content_pos));
+ $current['content'] = self::decodeOID(substr($content, $content_pos));
if ($current['content'] === false) {
return false;
}
@@ -512,16 +491,16 @@ class ASN1
if ($constructed) {
return false;
}
- $current['content'] = $this->_decodeTime(substr($content, $content_pos), $tag);
+ $current['content'] = self::decodeTime(substr($content, $content_pos), $tag);
break;
default:
return false;
}
- $start+= $length;
+ $start += $length;
// ie. length is the length of the full TLV encoding - it's not just the length of the value
- return $current + array('length' => $start - $current['start']);
+ return $current + ['length' => $start - $current['start']];
}
/**
@@ -534,15 +513,10 @@ class ASN1
* @param array $decoded
* @param array $mapping
* @param array $special
- * @return array
- * @access public
+ * @return array|bool|Element|string|null
*/
- function asn1map($decoded, $mapping, $special = array())
+ public static function asn1map(array $decoded, $mapping, $special = [])
{
- if (!is_array($decoded)) {
- return false;
- }
-
if (isset($mapping['explicit']) && is_array($decoded['content'])) {
$decoded = $decoded['content'][0];
}
@@ -550,12 +524,13 @@ class ASN1
switch (true) {
case $mapping['type'] == self::TYPE_ANY:
$intype = $decoded['type'];
- if (isset($decoded['constant']) || !isset($this->ANYmap[$intype]) || (ord($this->encoded[$decoded['start']]) & 0x20)) {
- return new Element(substr($this->encoded, $decoded['start'], $decoded['length']));
+ // !isset(self::ANY_MAP[$intype]) produces a fatal error on PHP 5.6
+ if (isset($decoded['constant']) || !array_key_exists($intype, self::ANY_MAP) || (ord(self::$encoded[$decoded['start']]) & 0x20)) {
+ return new Element(substr(self::$encoded, $decoded['start'], $decoded['length']));
}
- $inmap = $this->ANYmap[$intype];
+ $inmap = self::ANY_MAP[$intype];
if (is_string($inmap)) {
- return array($inmap => $this->asn1map($decoded, array('type' => $intype) + $mapping, $special));
+ return [$inmap => self::asn1map($decoded, ['type' => $intype] + $mapping, $special)];
}
break;
case $mapping['type'] == self::TYPE_CHOICE:
@@ -563,19 +538,19 @@ class ASN1
switch (true) {
case isset($option['constant']) && $option['constant'] == $decoded['constant']:
case !isset($option['constant']) && $option['type'] == $decoded['type']:
- $value = $this->asn1map($decoded, $option, $special);
+ $value = self::asn1map($decoded, $option, $special);
break;
case !isset($option['constant']) && $option['type'] == self::TYPE_CHOICE:
- $v = $this->asn1map($decoded, $option, $special);
+ $v = self::asn1map($decoded, $option, $special);
if (isset($v)) {
$value = $v;
}
}
if (isset($value)) {
if (isset($special[$key])) {
- $value = call_user_func($special[$key], $value);
+ $value = $special[$key]($value);
}
- return array($key => $value);
+ return [$key => $value];
}
}
return null;
@@ -601,13 +576,13 @@ class ASN1
switch ($decoded['type']) {
case self::TYPE_SEQUENCE:
- $map = array();
+ $map = [];
// ignore the min and max
if (isset($mapping['min']) && isset($mapping['max'])) {
$child = $mapping['children'];
foreach ($decoded['content'] as $content) {
- if (($map[] = $this->asn1map($content, $child, $special)) === null) {
+ if (($map[] = self::asn1map($content, $child, $special)) === null) {
return null;
}
}
@@ -643,43 +618,43 @@ class ASN1
$maymatch = $constant == $temp['constant'] && $childClass == $tempClass;
} else {
// Can only match if no constant expected and type matches or is generic.
- $maymatch = !isset($child['constant']) && array_search($child['type'], array($temp['type'], self::TYPE_ANY, self::TYPE_CHOICE)) !== false;
+ $maymatch = !isset($child['constant']) && array_search($child['type'], [$temp['type'], self::TYPE_ANY, self::TYPE_CHOICE]) !== false;
}
}
}
if ($maymatch) {
// Attempt submapping.
- $candidate = $this->asn1map($temp, $child, $special);
+ $candidate = self::asn1map($temp, $child, $special);
$maymatch = $candidate !== null;
}
if ($maymatch) {
// Got the match: use it.
if (isset($special[$key])) {
- $candidate = call_user_func($special[$key], $candidate);
+ $candidate = $special[$key]($candidate);
}
$map[$key] = $candidate;
$i++;
} elseif (isset($child['default'])) {
- $map[$key] = $child['default']; // Use default.
+ $map[$key] = $child['default'];
} elseif (!isset($child['optional'])) {
return null; // Syntax error.
}
}
// Fail mapping if all input items have not been consumed.
- return $i < $n ? null: $map;
+ return $i < $n ? null : $map;
// the main diff between sets and sequences is the encapsulation of the foreach in another for loop
case self::TYPE_SET:
- $map = array();
+ $map = [];
// ignore the min and max
if (isset($mapping['min']) && isset($mapping['max'])) {
$child = $mapping['children'];
foreach ($decoded['content'] as $content) {
- if (($map[] = $this->asn1map($content, $child, $special)) === null) {
+ if (($map[] = self::asn1map($content, $child, $special)) === null) {
return null;
}
}
@@ -715,13 +690,13 @@ class ASN1
$maymatch = $constant == $temp['constant'] && $childClass == $tempClass;
} else {
// Can only match if no constant expected and type matches or is generic.
- $maymatch = !isset($child['constant']) && array_search($child['type'], array($temp['type'], self::TYPE_ANY, self::TYPE_CHOICE)) !== false;
+ $maymatch = !isset($child['constant']) && array_search($child['type'], [$temp['type'], self::TYPE_ANY, self::TYPE_CHOICE]) !== false;
}
}
if ($maymatch) {
// Attempt submapping.
- $candidate = $this->asn1map($temp, $child, $special);
+ $candidate = self::asn1map($temp, $child, $special);
$maymatch = $candidate !== null;
}
@@ -731,7 +706,7 @@ class ASN1
// Got the match: use it.
if (isset($special[$key])) {
- $candidate = call_user_func($special[$key], $candidate);
+ $candidate = $special[$key]($candidate);
}
$map[$key] = $candidate;
break;
@@ -749,7 +724,7 @@ class ASN1
}
return $map;
case self::TYPE_OBJECT_IDENTIFIER:
- return isset($this->oids[$decoded['content']]) ? $this->oids[$decoded['content']] : $decoded['content'];
+ return isset(self::$oids[$decoded['content']]) ? self::$oids[$decoded['content']] : $decoded['content'];
case self::TYPE_UTC_TIME:
case self::TYPE_GENERALIZED_TIME:
// for explicitly tagged optional stuff
@@ -760,9 +735,9 @@ class ASN1
// in theory, doing isset($mapping['implicit']) would work but malformed certs do exist
// in the wild that OpenSSL decodes without issue so we'll support them as well
if (!is_object($decoded['content'])) {
- $decoded['content'] = $this->_decodeTime($decoded['content'], $decoded['type']);
+ $decoded['content'] = self::decodeTime($decoded['content'], $decoded['type']);
}
- return $decoded['content'] ? $decoded['content']->format($this->format) : false;
+ return $decoded['content'] ? $decoded['content']->format(self::$format) : false;
case self::TYPE_BIT_STRING:
if (isset($mapping['mapping'])) {
$offset = ord($decoded['content'][0]);
@@ -775,7 +750,7 @@ class ASN1
therefore ensure that different semantics are not associated with such values which differ only in the number of trailing
0 bits."
*/
- $bits = count($mapping['mapping']) == $size ? array() : array_fill(0, count($mapping['mapping']) - $size, false);
+ $bits = count($mapping['mapping']) == $size ? [] : array_fill(0, count($mapping['mapping']) - $size, false);
for ($i = strlen($decoded['content']) - 1; $i > 0; $i--) {
$current = ord($decoded['content'][$i]);
for ($j = $offset; $j < 8; $j++) {
@@ -783,7 +758,7 @@ class ASN1
}
$offset = 0;
}
- $values = array();
+ $values = [];
$map = array_reverse($mapping['mapping']);
foreach ($map as $i => $value) {
if ($bits[$i]) {
@@ -792,12 +767,12 @@ class ASN1
}
return $values;
}
+ // fall-through
case self::TYPE_OCTET_STRING:
- return base64_encode($decoded['content']);
+ return $decoded['content'];
case self::TYPE_NULL:
return '';
case self::TYPE_BOOLEAN:
- return $decoded['content'];
case self::TYPE_NUMERIC_STRING:
case self::TYPE_PRINTABLE_STRING:
case self::TYPE_TELETEX_STRING:
@@ -827,6 +802,26 @@ class ASN1
}
/**
+ * DER-decode the length
+ *
+ * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See
+ * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
+ *
+ * @param string $string
+ * @return int
+ */
+ public static function decodeLength(&$string)
+ {
+ $length = ord(Strings::shift($string));
+ if ($length & 0x80) { // definite length, long form
+ $length &= 0x7F;
+ $temp = Strings::shift($string, $length);
+ list(, $length) = unpack('N', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4));
+ }
+ return $length;
+ }
+
+ /**
* ASN.1 Encode
*
* DER-encodes an ASN.1 semantic mapping ($mapping). Some libraries would probably call this function
@@ -834,29 +829,27 @@ class ASN1
*
* "Special" mappings can be applied via $special.
*
- * @param string $source
- * @param string $mapping
+ * @param Element|string|array $source
+ * @param array $mapping
* @param array $special
* @return string
- * @access public
*/
- function encodeDER($source, $mapping, $special = array())
+ public static function encodeDER($source, $mapping, $special = [])
{
- $this->location = array();
- return $this->_encode_der($source, $mapping, null, $special);
+ self::$location = [];
+ return self::encode_der($source, $mapping, null, $special);
}
/**
* ASN.1 Encode (Helper function)
*
- * @param string $source
- * @param string $mapping
+ * @param Element|string|array|null $source
+ * @param array $mapping
* @param int $idx
* @param array $special
* @return string
- * @access private
*/
- function _encode_der($source, $mapping, $idx = null, $special = array())
+ private static function encode_der($source, array $mapping, $idx = null, array $special = [])
{
if ($source instanceof Element) {
return $source->element;
@@ -869,9 +862,9 @@ class ASN1
if (isset($idx)) {
if (isset($special[$idx])) {
- $source = call_user_func($special[$idx], $source);
+ $source = $special[$idx]($source);
}
- $this->location[] = $idx;
+ self::$location[] = $idx;
}
$tag = $mapping['type'];
@@ -879,19 +872,19 @@ class ASN1
switch ($tag) {
case self::TYPE_SET: // Children order is not important, thus process in sequence.
case self::TYPE_SEQUENCE:
- $tag|= 0x20; // set the constructed bit
+ $tag |= 0x20; // set the constructed bit
// ignore the min and max
if (isset($mapping['min']) && isset($mapping['max'])) {
- $value = array();
+ $value = [];
$child = $mapping['children'];
foreach ($source as $content) {
- $temp = $this->_encode_der($content, $child, null, $special);
+ $temp = self::encode_der($content, $child, null, $special);
if ($temp === false) {
return false;
}
- $value[]= $temp;
+ $value[] = $temp;
}
/* "The encodings of the component values of a set-of value shall appear in ascending order, the encodings being compared
as octet strings with the shorter components being padded at their trailing end with 0-octets.
@@ -914,7 +907,7 @@ class ASN1
continue;
}
- $temp = $this->_encode_der($source[$key], $child, $key, $special);
+ $temp = self::encode_der($source[$key], $child, $key, $special);
if ($temp === false) {
return false;
}
@@ -937,14 +930,26 @@ class ASN1
an untagged "DummyReference" (see ITU-T Rec. X.683 | ISO/IEC 8824-4, 8.3)."
*/
if (isset($child['explicit']) || $child['type'] == self::TYPE_CHOICE) {
- $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']);
- $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp;
+ if ($child['constant'] <= 30) {
+ $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']);
+ } else {
+ $constant = $child['constant'];
+ $subtag = '';
+ while ($constant > 0) {
+ $subtagvalue = $constant & 0x7F;
+ $subtag = (chr(0x80 | $subtagvalue)) . $subtag;
+ $constant = $constant >> 7;
+ }
+ $subtag[strlen($subtag) - 1] = $subtag[strlen($subtag) - 1] & chr(0x7F);
+ $subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | 0x1f) . $subtag;
+ }
+ $temp = $subtag . self::encodeLength(strlen($temp)) . $temp;
} else {
$subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
$temp = $subtag . substr($temp, 1);
}
}
- $value.= $temp;
+ $value .= $temp;
}
break;
case self::TYPE_CHOICE:
@@ -955,7 +960,7 @@ class ASN1
continue;
}
- $temp = $this->_encode_der($source[$key], $child, $key, $special);
+ $temp = self::encode_der($source[$key], $child, $key, $special);
if ($temp === false) {
return false;
}
@@ -972,7 +977,7 @@ class ASN1
if (isset($child['constant'])) {
if (isset($child['explicit']) || $child['type'] == self::TYPE_CHOICE) {
$subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | 0x20 | $child['constant']);
- $temp = $subtag . $this->_encodeLength(strlen($temp)) . $temp;
+ $temp = $subtag . self::encodeLength(strlen($temp)) . $temp;
} else {
$subtag = chr((self::CLASS_CONTEXT_SPECIFIC << 6) | (ord($temp[0]) & 0x20) | $child['constant']);
$temp = $subtag . substr($temp, 1);
@@ -981,7 +986,7 @@ class ASN1
}
if (isset($idx)) {
- array_pop($this->location);
+ array_pop(self::$location);
}
if ($temp && isset($mapping['cast'])) {
@@ -1011,11 +1016,11 @@ class ASN1
case self::TYPE_UTC_TIME:
case self::TYPE_GENERALIZED_TIME:
$format = $mapping['type'] == self::TYPE_UTC_TIME ? 'y' : 'Y';
- $format.= 'mdHis';
+ $format .= 'mdHis';
// if $source does _not_ include timezone information within it then assume that the timezone is GMT
- $date = new DateTime($source, new DateTimeZone('GMT'));
+ $date = new \DateTime($source, new \DateTimeZone('GMT'));
// if $source _does_ include timezone information within it then convert the time to GMT
- $date->setTimezone(new DateTimeZone('GMT'));
+ $date->setTimezone(new \DateTimeZone('GMT'));
$value = $date->format($format) . 'Z';
break;
case self::TYPE_BIT_STRING:
@@ -1045,46 +1050,47 @@ class ASN1
$bits = implode('', array_pad($bits, $size + $offset + 1, 0));
$bytes = explode(' ', rtrim(chunk_split($bits, 8, ' ')));
foreach ($bytes as $byte) {
- $value.= chr(bindec($byte));
+ $value .= chr(bindec($byte));
}
break;
}
+ // fall-through
case self::TYPE_OCTET_STRING:
/* The initial octet shall encode, as an unsigned binary integer with bit 1 as the least significant bit,
the number of unused bits in the final subsequent octet. The number shall be in the range zero to seven.
-- http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#page=16 */
- $value = base64_decode($source);
+ $value = $source;
break;
case self::TYPE_OBJECT_IDENTIFIER:
- $value = $this->_encodeOID($source);
+ $value = self::encodeOID($source);
break;
case self::TYPE_ANY:
- $loc = $this->location;
+ $loc = self::$location;
if (isset($idx)) {
- array_pop($this->location);
+ array_pop(self::$location);
}
switch (true) {
case !isset($source):
- return $this->_encode_der(null, array('type' => self::TYPE_NULL) + $mapping, null, $special);
+ return self::encode_der(null, ['type' => self::TYPE_NULL] + $mapping, null, $special);
case is_int($source):
case $source instanceof BigInteger:
- return $this->_encode_der($source, array('type' => self::TYPE_INTEGER) + $mapping, null, $special);
+ return self::encode_der($source, ['type' => self::TYPE_INTEGER] + $mapping, null, $special);
case is_float($source):
- return $this->_encode_der($source, array('type' => self::TYPE_REAL) + $mapping, null, $special);
+ return self::encode_der($source, ['type' => self::TYPE_REAL] + $mapping, null, $special);
case is_bool($source):
- return $this->_encode_der($source, array('type' => self::TYPE_BOOLEAN) + $mapping, null, $special);
+ return self::encode_der($source, ['type' => self::TYPE_BOOLEAN] + $mapping, null, $special);
case is_array($source) && count($source) == 1:
$typename = implode('', array_keys($source));
- $outtype = array_search($typename, $this->ANYmap, true);
+ $outtype = array_search($typename, self::ANY_MAP, true);
if ($outtype !== false) {
- return $this->_encode_der($source[$typename], array('type' => $outtype) + $mapping, null, $special);
+ return self::encode_der($source[$typename], ['type' => $outtype] + $mapping, null, $special);
}
}
- $filters = $this->filters;
+ $filters = self::$filters;
foreach ($loc as $part) {
if (!isset($filters[$part])) {
$filters = false;
@@ -1093,10 +1099,9 @@ class ASN1
$filters = $filters[$part];
}
if ($filters === false) {
- user_error('No filters defined for ' . implode('/', $loc));
- return false;
+ throw new \RuntimeException('No filters defined for ' . implode('/', $loc));
}
- return $this->_encode_der($source, $filters + $mapping, null, $special);
+ return self::encode_der($source, $filters + $mapping, null, $special);
case self::TYPE_NULL:
$value = '';
break;
@@ -1117,44 +1122,23 @@ class ASN1
$value = $source ? "\xFF" : "\x00";
break;
default:
- user_error('Mapping provides no type definition for ' . implode('/', $this->location));
- return false;
+ throw new \RuntimeException('Mapping provides no type definition for ' . implode('/', self::$location));
}
if (isset($idx)) {
- array_pop($this->location);
+ array_pop(self::$location);
}
if (isset($mapping['cast'])) {
if (isset($mapping['explicit']) || $mapping['type'] == self::TYPE_CHOICE) {
- $value = chr($tag) . $this->_encodeLength(strlen($value)) . $value;
+ $value = chr($tag) . self::encodeLength(strlen($value)) . $value;
$tag = ($mapping['class'] << 6) | 0x20 | $mapping['cast'];
} else {
$tag = ($mapping['class'] << 6) | (ord($temp[0]) & 0x20) | $mapping['cast'];
}
}
- return chr($tag) . $this->_encodeLength(strlen($value)) . $value;
- }
-
- /**
- * DER-encode the length
- *
- * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See
- * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
- *
- * @access private
- * @param int $length
- * @return string
- */
- function _encodeLength($length)
- {
- if ($length <= 0x7F) {
- return chr($length);
- }
-
- $temp = ltrim(pack('N', $length), chr(0));
- return pack('Ca*', 0x80 | strlen($temp), $temp);
+ return chr($tag) . self::encodeLength(strlen($value)) . $value;
}
/**
@@ -1162,23 +1146,24 @@ class ASN1
*
* Called by _decode_ber()
*
- * @access private
* @param string $content
* @return string
*/
- function _decodeOID($content)
+ public static function decodeOID($content)
{
+ // BigInteger's are used because of OIDs like 2.25.329800735698586629295641978511506172918
+ // https://healthcaresecprivacy.blogspot.com/2011/02/creating-and-using-unique-id-uuid-oid.html elaborates.
static $eighty;
if (!$eighty) {
$eighty = new BigInteger(80);
}
- $oid = array();
+ $oid = [];
$pos = 0;
$len = strlen($content);
// see https://github.com/openjdk/jdk/blob/2deb318c9f047ec5a4b160d66a4b52f93688ec42/src/java.base/share/classes/sun/security/util/ObjectIdentifier.java#L55
if ($len > 4096) {
- //user_error('Object Identifier size is limited to 4096 bytes');
+ //throw new \RuntimeException("Object identifier size is limited to 4096 bytes ($len bytes present)");
return false;
}
@@ -1220,11 +1205,10 @@ class ASN1
*
* Called by _encode_der()
*
- * @access private
* @param string $source
* @return string
*/
- function _encodeOID($source)
+ public static function encodeOID($source)
{
static $mask, $zero, $forty;
if (!$mask) {
@@ -1233,11 +1217,15 @@ class ASN1
$forty = new BigInteger(40);
}
- $oid = preg_match('#(?:\d+\.)+#', $source) ? $source : array_search($source, $this->oids);
+ if (!preg_match('#(?:\d+\.)+#', $source)) {
+ $oid = isset(self::$reverseOIDs[$source]) ? self::$reverseOIDs[$source] : false;
+ } else {
+ $oid = $source;
+ }
if ($oid === false) {
- user_error('Invalid OID');
- return false;
+ throw new \RuntimeException('Invalid OID');
}
+
$parts = explode('.', $oid);
$part1 = array_shift($parts);
$part2 = array_shift($parts);
@@ -1263,7 +1251,7 @@ class ASN1
}
$temp[strlen($temp) - 1] = $temp[strlen($temp) - 1] & chr(0x7F);
}
- $value.= $temp;
+ $value .= $temp;
}
return $value;
@@ -1274,12 +1262,11 @@ class ASN1
*
* Called by _decode_ber() and in the case of implicit tags asn1map().
*
- * @access private
* @param string $content
* @param int $tag
- * @return string
+ * @return \DateTime|false
*/
- function _decodeTime($content, $tag)
+ private static function decodeTime($content, $tag)
{
/* UTCTime:
http://tools.ietf.org/html/rfc5280#section-4.1.2.5.1
@@ -1301,7 +1288,7 @@ class ASN1
$prefix = substr($content, 0, 2) >= 50 ? '19' : '20';
$content = $prefix . $content;
} elseif (strpos($content, '.') !== false) {
- $format.= '.u';
+ $format .= '.u';
}
if ($content[strlen($content) - 1] == 'Z') {
@@ -1309,12 +1296,12 @@ class ASN1
}
if (strpos($content, '-') !== false || strpos($content, '+') !== false) {
- $format.= 'O';
+ $format .= 'O';
}
// error supression isn't necessary as of PHP 7.0:
// http://php.net/manual/en/migration70.other-changes.php
- return @DateTime::createFromFormat($format, $content);
+ return @\DateTime::createFromFormat($format, $content);
}
/**
@@ -1322,55 +1309,38 @@ class ASN1
*
* Sets the time / date format for asn1map().
*
- * @access public
* @param string $format
*/
- function setTimeFormat($format)
+ public static function setTimeFormat($format)
{
- $this->format = $format;
+ self::$format = $format;
}
/**
* Load OIDs
*
* Load the relevant OIDs for a particular ASN.1 semantic mapping.
+ * Previously loaded OIDs are retained.
*
- * @access public
* @param array $oids
*/
- function loadOIDs($oids)
+ public static function loadOIDs(array $oids)
{
- $this->oids = $oids;
+ self::$reverseOIDs += $oids;
+ self::$oids = array_flip(self::$reverseOIDs);
}
/**
- * Load filters
+ * Set filters
*
- * See \phpseclib\File\X509, etc, for an example.
+ * See \phpseclib3\File\X509, etc, for an example.
+ * Previously loaded filters are not retained.
*
- * @access public
* @param array $filters
*/
- function loadFilters($filters)
- {
- $this->filters = $filters;
- }
-
- /**
- * String Shift
- *
- * Inspired by array_shift
- *
- * @param string $string
- * @param int $index
- * @return string
- * @access private
- */
- function _string_shift(&$string, $index = 1)
+ public static function setFilters(array $filters)
{
- $substr = substr($string, 0, $index);
- $string = substr($string, $index);
- return $substr;
+ self::$filters = $filters;
}
/**
@@ -1383,15 +1353,15 @@ class ASN1
* @param int $from
* @param int $to
* @return string
- * @access public
*/
- function convert($in, $from = self::TYPE_UTF8_STRING, $to = self::TYPE_UTF8_STRING)
+ public static function convert($in, $from = self::TYPE_UTF8_STRING, $to = self::TYPE_UTF8_STRING)
{
- if (!isset($this->stringTypeSize[$from]) || !isset($this->stringTypeSize[$to])) {
+ // isset(self::STRING_TYPE_SIZE[$from] returns a fatal error on PHP 5.6
+ if (!array_key_exists($from, self::STRING_TYPE_SIZE) || !array_key_exists($to, self::STRING_TYPE_SIZE)) {
return false;
}
- $insize = $this->stringTypeSize[$from];
- $outsize = $this->stringTypeSize[$to];
+ $insize = self::STRING_TYPE_SIZE[$from];
+ $outsize = self::STRING_TYPE_SIZE[$to];
$inlength = strlen($in);
$out = '';
@@ -1406,8 +1376,10 @@ class ASN1
case $insize == 4:
$c = ($c << 8) | ord($in[$i++]);
$c = ($c << 8) | ord($in[$i++]);
+ // fall-through
case $insize == 2:
$c = ($c << 8) | ord($in[$i++]);
+ // fall-through
case $insize == 1:
break;
case ($c & 0x80) == 0x00:
@@ -1436,9 +1408,11 @@ class ASN1
$c >>= 8;
$v .= chr($c & 0xFF);
$c >>= 8;
+ // fall-through
case $outsize == 2:
$v .= chr($c & 0xFF);
$c >>= 8;
+ // fall-through
case $outsize == 1:
$v .= chr($c & 0xFF);
$c >>= 8;
@@ -1451,18 +1425,23 @@ class ASN1
case $c >= 0x04000000:
$v .= chr(0x80 | ($c & 0x3F));
$c = ($c >> 6) | 0x04000000;
+ // fall-through
case $c >= 0x00200000:
$v .= chr(0x80 | ($c & 0x3F));
$c = ($c >> 6) | 0x00200000;
+ // fall-through
case $c >= 0x00010000:
$v .= chr(0x80 | ($c & 0x3F));
$c = ($c >> 6) | 0x00010000;
+ // fall-through
case $c >= 0x00000800:
$v .= chr(0x80 | ($c & 0x3F));
$c = ($c >> 6) | 0x00000800;
+ // fall-through
case $c >= 0x00000080:
$v .= chr(0x80 | ($c & 0x3F));
$c = ($c >> 6) | 0x000000C0;
+ // fall-through
default:
$v .= chr($c);
break;
@@ -1471,4 +1450,77 @@ class ASN1
}
return $out;
}
+
+ /**
+ * Extract raw BER from Base64 encoding
+ *
+ * @param string $str
+ * @return string
+ */
+ public static function extractBER($str)
+ {
+ /* X.509 certs are assumed to be base64 encoded but sometimes they'll have additional things in them
+ * above and beyond the ceritificate.
+ * ie. some may have the following preceding the -----BEGIN CERTIFICATE----- line:
+ *
+ * Bag Attributes
+ * localKeyID: 01 00 00 00
+ * subject=/O=organization/OU=org unit/CN=common name
+ * issuer=/O=organization/CN=common name
+ */
+ if (strlen($str) > ini_get('pcre.backtrack_limit')) {
+ $temp = $str;
+ } else {
+ $temp = preg_replace('#.*?^-+[^-]+-+[\r\n ]*$#ms', '', $str, 1);
+ $temp = preg_replace('#-+END.*[\r\n ]*.*#ms', '', $temp, 1);
+ }
+ // remove new lines
+ $temp = str_replace(["\r", "\n", ' '], '', $temp);
+ // remove the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- stuff
+ $temp = preg_replace('#^-+[^-]+-+|-+[^-]+-+$#', '', $temp);
+ $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? Strings::base64_decode($temp) : false;
+ return $temp != false ? $temp : $str;
+ }
+
+ /**
+ * DER-encode the length
+ *
+ * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See
+ * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
+ *
+ * @param int $length
+ * @return string
+ */
+ public static function encodeLength($length)
+ {
+ if ($length <= 0x7F) {
+ return chr($length);
+ }
+
+ $temp = ltrim(pack('N', $length), chr(0));
+ return pack('Ca*', 0x80 | strlen($temp), $temp);
+ }
+
+ /**
+ * Returns the OID corresponding to a name
+ *
+ * What's returned in the associative array returned by loadX509() (or load*()) is either a name or an OID if
+ * no OID to name mapping is available. The problem with this is that what may be an unmapped OID in one version
+ * of phpseclib may not be unmapped in the next version, so apps that are looking at this OID may not be able
+ * to work from version to version.
+ *
+ * This method will return the OID if a name is passed to it and if no mapping is avialable it'll assume that
+ * what's being passed to it already is an OID and return that instead. A few examples.
+ *
+ * getOID('2.16.840.1.101.3.4.2.1') == '2.16.840.1.101.3.4.2.1'
+ * getOID('id-sha256') == '2.16.840.1.101.3.4.2.1'
+ * getOID('zzz') == 'zzz'
+ *
+ * @param string $name
+ * @return string
+ */
+ public static function getOID($name)
+ {
+ return isset(self::$reverseOIDs[$name]) ? self::$reverseOIDs[$name] : $name;
+ }
}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Element.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Element.php
index 68246e2b5..ae4b764b0 100644
--- a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Element.php
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Element.php
@@ -1,27 +1,25 @@
<?php
+
/**
- * Pure-PHP ASN.1 Parser
+ * ASN.1 Raw Element
*
* PHP version 5
*
- * @category File
- * @package ASN1
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2012 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
-namespace phpseclib\File\ASN1;
+namespace phpseclib3\File\ASN1;
/**
- * ASN.1 Element
+ * ASN.1 Raw Element
*
- * Bypass normal encoding rules in phpseclib\File\ASN1::encodeDER()
+ * An ASN.1 ANY mapping will return an ASN1\Element object. Use of this object
+ * will also bypass the normal encoding rules in ASN1::encodeDER()
*
- * @package ASN1
* @author Jim Wigginton <terrafrost@php.net>
- * @access public
*/
class Element
{
@@ -29,18 +27,16 @@ class Element
* Raw element value
*
* @var string
- * @access private
*/
- var $element;
+ public $element;
/**
* Constructor
*
* @param string $encoded
- * @return \phpseclib\File\ASN1\Element
- * @access public
+ * @return Element
*/
- function __construct($encoded)
+ public function __construct($encoded)
{
$this->element = $encoded;
}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AccessDescription.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AccessDescription.php
new file mode 100644
index 000000000..1cbc5a594
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AccessDescription.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * AccessDescription
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * AccessDescription
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class AccessDescription
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'accessMethod' => ['type' => ASN1::TYPE_OBJECT_IDENTIFIER],
+ 'accessLocation' => GeneralName::MAP
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AdministrationDomainName.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AdministrationDomainName.php
new file mode 100644
index 000000000..04183a13b
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AdministrationDomainName.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * AdministrationDomainName
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * AdministrationDomainName
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class AdministrationDomainName
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_CHOICE,
+ // if class isn't present it's assumed to be \phpseclib3\File\ASN1::CLASS_UNIVERSAL or
+ // (if constant is present) \phpseclib3\File\ASN1::CLASS_CONTEXT_SPECIFIC
+ 'class' => ASN1::CLASS_APPLICATION,
+ 'cast' => 2,
+ 'children' => [
+ 'numeric' => ['type' => ASN1::TYPE_NUMERIC_STRING],
+ 'printable' => ['type' => ASN1::TYPE_PRINTABLE_STRING]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AlgorithmIdentifier.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AlgorithmIdentifier.php
new file mode 100644
index 000000000..0da7eb102
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AlgorithmIdentifier.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * AlgorithmIdentifier
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * AlgorithmIdentifier
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class AlgorithmIdentifier
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'algorithm' => ['type' => ASN1::TYPE_OBJECT_IDENTIFIER],
+ 'parameters' => [
+ 'type' => ASN1::TYPE_ANY,
+ 'optional' => true
+ ]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AnotherName.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AnotherName.php
new file mode 100644
index 000000000..d96c170be
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AnotherName.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * AnotherName
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * AnotherName
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class AnotherName
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'type-id' => ['type' => ASN1::TYPE_OBJECT_IDENTIFIER],
+ 'value' => [
+ 'type' => ASN1::TYPE_ANY,
+ 'constant' => 0,
+ 'optional' => true,
+ 'explicit' => true
+ ]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Attribute.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Attribute.php
new file mode 100644
index 000000000..38a6aeefa
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Attribute.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * Attribute
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * Attribute
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Attribute
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'type' => AttributeType::MAP,
+ 'value' => [
+ 'type' => ASN1::TYPE_SET,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => AttributeValue::MAP
+ ]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeType.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeType.php
new file mode 100644
index 000000000..5cbc2bcc2
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeType.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * AttributeType
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * AttributeType
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class AttributeType
+{
+ const MAP = ['type' => ASN1::TYPE_OBJECT_IDENTIFIER];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeTypeAndValue.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeTypeAndValue.php
new file mode 100644
index 000000000..fe414f16b
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeTypeAndValue.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * AttributeTypeAndValue
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * AttributeTypeAndValue
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class AttributeTypeAndValue
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'type' => AttributeType::MAP,
+ 'value' => AttributeValue::MAP
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeValue.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeValue.php
new file mode 100644
index 000000000..3b3b6d2ed
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AttributeValue.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * AttributeValue
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * AttributeValue
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class AttributeValue
+{
+ const MAP = ['type' => ASN1::TYPE_ANY];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Attributes.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Attributes.php
new file mode 100644
index 000000000..cd53ecfaf
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Attributes.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * Attributes
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * Attributes
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Attributes
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SET,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => Attribute::MAP
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AuthorityInfoAccessSyntax.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AuthorityInfoAccessSyntax.php
new file mode 100644
index 000000000..3e80a55d1
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AuthorityInfoAccessSyntax.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * AuthorityInfoAccessSyntax
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * AuthorityInfoAccessSyntax
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class AuthorityInfoAccessSyntax
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => AccessDescription::MAP
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AuthorityKeyIdentifier.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AuthorityKeyIdentifier.php
new file mode 100644
index 000000000..e7ec5b28c
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/AuthorityKeyIdentifier.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * AuthorityKeyIdentifier
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * AuthorityKeyIdentifier
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class AuthorityKeyIdentifier
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'keyIdentifier' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true
+ ] + KeyIdentifier::MAP,
+ 'authorityCertIssuer' => [
+ 'constant' => 1,
+ 'optional' => true,
+ 'implicit' => true
+ ] + GeneralNames::MAP,
+ 'authorityCertSerialNumber' => [
+ 'constant' => 2,
+ 'optional' => true,
+ 'implicit' => true
+ ] + CertificateSerialNumber::MAP
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BaseDistance.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BaseDistance.php
new file mode 100644
index 000000000..e59668ab9
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BaseDistance.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * BaseDistance
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * BaseDistance
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class BaseDistance
+{
+ const MAP = ['type' => ASN1::TYPE_INTEGER];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BasicConstraints.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BasicConstraints.php
new file mode 100644
index 000000000..587ef1b0e
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BasicConstraints.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * BasicConstraints
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * BasicConstraints
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class BasicConstraints
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'cA' => [
+ 'type' => ASN1::TYPE_BOOLEAN,
+ 'optional' => true,
+ 'default' => false
+ ],
+ 'pathLenConstraint' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'optional' => true
+ ]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttribute.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttribute.php
new file mode 100644
index 000000000..e81bc78e8
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttribute.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * BuiltInDomainDefinedAttribute
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * BuiltInDomainDefinedAttribute
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class BuiltInDomainDefinedAttribute
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'type' => ['type' => ASN1::TYPE_PRINTABLE_STRING],
+ 'value' => ['type' => ASN1::TYPE_PRINTABLE_STRING]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttributes.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttributes.php
new file mode 100644
index 000000000..471e88f92
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInDomainDefinedAttributes.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * BuiltInDomainDefinedAttributes
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * BuiltInDomainDefinedAttributes
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class BuiltInDomainDefinedAttributes
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => 4, // ub-domain-defined-attributes
+ 'children' => BuiltInDomainDefinedAttribute::MAP
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInStandardAttributes.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInStandardAttributes.php
new file mode 100644
index 000000000..752f400da
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/BuiltInStandardAttributes.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * BuiltInStandardAttributes
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * BuiltInStandardAttributes
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class BuiltInStandardAttributes
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'country-name' => ['optional' => true] + CountryName::MAP,
+ 'administration-domain-name' => ['optional' => true] + AdministrationDomainName::MAP,
+ 'network-address' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true
+ ] + NetworkAddress::MAP,
+ 'terminal-identifier' => [
+ 'constant' => 1,
+ 'optional' => true,
+ 'implicit' => true
+ ] + TerminalIdentifier::MAP,
+ 'private-domain-name' => [
+ 'constant' => 2,
+ 'optional' => true,
+ 'explicit' => true
+ ] + PrivateDomainName::MAP,
+ 'organization-name' => [
+ 'constant' => 3,
+ 'optional' => true,
+ 'implicit' => true
+ ] + OrganizationName::MAP,
+ 'numeric-user-identifier' => [
+ 'constant' => 4,
+ 'optional' => true,
+ 'implicit' => true
+ ] + NumericUserIdentifier::MAP,
+ 'personal-name' => [
+ 'constant' => 5,
+ 'optional' => true,
+ 'implicit' => true
+ ] + PersonalName::MAP,
+ 'organizational-unit-names' => [
+ 'constant' => 6,
+ 'optional' => true,
+ 'implicit' => true
+ ] + OrganizationalUnitNames::MAP
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CPSuri.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CPSuri.php
new file mode 100644
index 000000000..56e58887e
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CPSuri.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * CPSuri
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * CPSuri
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class CPSuri
+{
+ const MAP = ['type' => ASN1::TYPE_IA5_STRING];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLDistributionPoints.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLDistributionPoints.php
new file mode 100644
index 000000000..79860b2fd
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLDistributionPoints.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * CRLDistributionPoints
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * CRLDistributionPoints
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class CRLDistributionPoints
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => DistributionPoint::MAP
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLNumber.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLNumber.php
new file mode 100644
index 000000000..f6cb95672
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLNumber.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * CRLNumber
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * CRLNumber
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class CRLNumber
+{
+ const MAP = ['type' => ASN1::TYPE_INTEGER];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLReason.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLReason.php
new file mode 100644
index 000000000..d37365299
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CRLReason.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * CRLReason
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * CRLReason
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class CRLReason
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_ENUMERATED,
+ 'mapping' => [
+ 'unspecified',
+ 'keyCompromise',
+ 'cACompromise',
+ 'affiliationChanged',
+ 'superseded',
+ 'cessationOfOperation',
+ 'certificateHold',
+ // Value 7 is not used.
+ 8 => 'removeFromCRL',
+ 'privilegeWithdrawn',
+ 'aACompromise'
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertPolicyId.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertPolicyId.php
new file mode 100644
index 000000000..d7e7776e8
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertPolicyId.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * CertPolicyId
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * CertPolicyId
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class CertPolicyId
+{
+ const MAP = ['type' => ASN1::TYPE_OBJECT_IDENTIFIER];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Certificate.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Certificate.php
new file mode 100644
index 000000000..01943a0d4
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Certificate.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * Certificate
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * Certificate
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Certificate
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'tbsCertificate' => TBSCertificate::MAP,
+ 'signatureAlgorithm' => AlgorithmIdentifier::MAP,
+ 'signature' => ['type' => ASN1::TYPE_BIT_STRING]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateIssuer.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateIssuer.php
new file mode 100644
index 000000000..ccd68dded
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateIssuer.php
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * CertificateIssuer
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+/**
+ * CertificateIssuer
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class CertificateIssuer
+{
+ const MAP = GeneralNames::MAP;
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateList.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateList.php
new file mode 100644
index 000000000..d54ed6d96
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateList.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * CertificateList
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * CertificateList
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class CertificateList
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'tbsCertList' => TBSCertList::MAP,
+ 'signatureAlgorithm' => AlgorithmIdentifier::MAP,
+ 'signature' => ['type' => ASN1::TYPE_BIT_STRING]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificatePolicies.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificatePolicies.php
new file mode 100644
index 000000000..ec0fa6b5d
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificatePolicies.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * CertificatePolicies
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * CertificatePolicies
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class CertificatePolicies
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => PolicyInformation::MAP
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateSerialNumber.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateSerialNumber.php
new file mode 100644
index 000000000..06ec944c4
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificateSerialNumber.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * CertificateSerialNumber
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * CertificateSerialNumber
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class CertificateSerialNumber
+{
+ const MAP = ['type' => ASN1::TYPE_INTEGER];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificationRequest.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificationRequest.php
new file mode 100644
index 000000000..2da70ed6a
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificationRequest.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * CertificationRequest
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * CertificationRequest
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class CertificationRequest
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'certificationRequestInfo' => CertificationRequestInfo::MAP,
+ 'signatureAlgorithm' => AlgorithmIdentifier::MAP,
+ 'signature' => ['type' => ASN1::TYPE_BIT_STRING]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificationRequestInfo.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificationRequestInfo.php
new file mode 100644
index 000000000..ce6dc8800
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CertificationRequestInfo.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * CertificationRequestInfo
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * CertificationRequestInfo
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class CertificationRequestInfo
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'version' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'mapping' => ['v1']
+ ],
+ 'subject' => Name::MAP,
+ 'subjectPKInfo' => SubjectPublicKeyInfo::MAP,
+ 'attributes' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true
+ ] + Attributes::MAP,
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Characteristic_two.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Characteristic_two.php
new file mode 100644
index 000000000..5bf59bb89
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Characteristic_two.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * Characteristic_two
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * Characteristic_two
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Characteristic_two
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'm' => ['type' => ASN1::TYPE_INTEGER], // field size 2**m
+ 'basis' => ['type' => ASN1::TYPE_OBJECT_IDENTIFIER],
+ 'parameters' => [
+ 'type' => ASN1::TYPE_ANY,
+ 'optional' => true
+ ]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CountryName.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CountryName.php
new file mode 100644
index 000000000..737d844d1
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/CountryName.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * CountryName
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * CountryName
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class CountryName
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_CHOICE,
+ // if class isn't present it's assumed to be \phpseclib3\File\ASN1::CLASS_UNIVERSAL or
+ // (if constant is present) \phpseclib3\File\ASN1::CLASS_CONTEXT_SPECIFIC
+ 'class' => ASN1::CLASS_APPLICATION,
+ 'cast' => 1,
+ 'children' => [
+ 'x121-dcc-code' => ['type' => ASN1::TYPE_NUMERIC_STRING],
+ 'iso-3166-alpha2-code' => ['type' => ASN1::TYPE_PRINTABLE_STRING]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Curve.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Curve.php
new file mode 100644
index 000000000..621f10355
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Curve.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * Curve
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * Curve
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Curve
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'a' => FieldElement::MAP,
+ 'b' => FieldElement::MAP,
+ 'seed' => [
+ 'type' => ASN1::TYPE_BIT_STRING,
+ 'optional' => true
+ ]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DHParameter.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DHParameter.php
new file mode 100644
index 000000000..26863dbcf
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DHParameter.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * DHParameter
+ *
+ * From: https://www.teletrust.de/fileadmin/files/oid/oid_pkcs-3v1-4.pdf#page=6
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * DHParameter
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class DHParameter
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'prime' => ['type' => ASN1::TYPE_INTEGER],
+ 'base' => ['type' => ASN1::TYPE_INTEGER],
+ 'privateValueLength' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'optional' => true
+ ]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAParams.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAParams.php
new file mode 100644
index 000000000..7af397bb0
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAParams.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * DSAParams
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * DSAParams
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class DSAParams
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'p' => ['type' => ASN1::TYPE_INTEGER],
+ 'q' => ['type' => ASN1::TYPE_INTEGER],
+ 'g' => ['type' => ASN1::TYPE_INTEGER]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAPrivateKey.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAPrivateKey.php
new file mode 100644
index 000000000..d97cd023c
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAPrivateKey.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * DSAPrivateKey
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * DSAPrivateKey
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class DSAPrivateKey
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'version' => ['type' => ASN1::TYPE_INTEGER],
+ 'p' => ['type' => ASN1::TYPE_INTEGER],
+ 'q' => ['type' => ASN1::TYPE_INTEGER],
+ 'g' => ['type' => ASN1::TYPE_INTEGER],
+ 'y' => ['type' => ASN1::TYPE_INTEGER],
+ 'x' => ['type' => ASN1::TYPE_INTEGER]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAPublicKey.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAPublicKey.php
new file mode 100644
index 000000000..f795747a2
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DSAPublicKey.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * DSAPublicKey
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * DSAPublicKey
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class DSAPublicKey
+{
+ const MAP = ['type' => ASN1::TYPE_INTEGER];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DigestInfo.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DigestInfo.php
new file mode 100644
index 000000000..b38ff3c44
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DigestInfo.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * DigestInfo
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * DigestInfo
+ *
+ * from https://tools.ietf.org/html/rfc2898#appendix-A.3
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class DigestInfo
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'digestAlgorithm' => AlgorithmIdentifier::MAP,
+ 'digest' => ['type' => ASN1::TYPE_OCTET_STRING]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DirectoryString.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DirectoryString.php
new file mode 100644
index 000000000..45218e3e6
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DirectoryString.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * DirectoryString
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * DirectoryString
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class DirectoryString
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_CHOICE,
+ 'children' => [
+ 'teletexString' => ['type' => ASN1::TYPE_TELETEX_STRING],
+ 'printableString' => ['type' => ASN1::TYPE_PRINTABLE_STRING],
+ 'universalString' => ['type' => ASN1::TYPE_UNIVERSAL_STRING],
+ 'utf8String' => ['type' => ASN1::TYPE_UTF8_STRING],
+ 'bmpString' => ['type' => ASN1::TYPE_BMP_STRING]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DisplayText.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DisplayText.php
new file mode 100644
index 000000000..a13e6a64e
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DisplayText.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * DisplayText
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * DisplayText
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class DisplayText
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_CHOICE,
+ 'children' => [
+ 'ia5String' => ['type' => ASN1::TYPE_IA5_STRING],
+ 'visibleString' => ['type' => ASN1::TYPE_VISIBLE_STRING],
+ 'bmpString' => ['type' => ASN1::TYPE_BMP_STRING],
+ 'utf8String' => ['type' => ASN1::TYPE_UTF8_STRING]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DistributionPoint.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DistributionPoint.php
new file mode 100644
index 000000000..4d9af6b59
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DistributionPoint.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * DistributionPoint
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * DistributionPoint
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class DistributionPoint
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'distributionPoint' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'explicit' => true
+ ] + DistributionPointName::MAP,
+ 'reasons' => [
+ 'constant' => 1,
+ 'optional' => true,
+ 'implicit' => true
+ ] + ReasonFlags::MAP,
+ 'cRLIssuer' => [
+ 'constant' => 2,
+ 'optional' => true,
+ 'implicit' => true
+ ] + GeneralNames::MAP
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DistributionPointName.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DistributionPointName.php
new file mode 100644
index 000000000..bc0cec8f7
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DistributionPointName.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * DistributionPointName
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * DistributionPointName
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class DistributionPointName
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_CHOICE,
+ 'children' => [
+ 'fullName' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true
+ ] + GeneralNames::MAP,
+ 'nameRelativeToCRLIssuer' => [
+ 'constant' => 1,
+ 'optional' => true,
+ 'implicit' => true
+ ] + RelativeDistinguishedName::MAP
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DssSigValue.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DssSigValue.php
new file mode 100644
index 000000000..2af740883
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/DssSigValue.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * DssSigValue
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * DssSigValue
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class DssSigValue
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'r' => ['type' => ASN1::TYPE_INTEGER],
+ 's' => ['type' => ASN1::TYPE_INTEGER]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECParameters.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECParameters.php
new file mode 100644
index 000000000..f25f6faaa
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECParameters.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * ECParameters
+ *
+ * From: https://tools.ietf.org/html/rfc5915
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * ECParameters
+ *
+ * ECParameters ::= CHOICE {
+ * namedCurve OBJECT IDENTIFIER
+ * -- implicitCurve NULL
+ * -- specifiedCurve SpecifiedECDomain
+ * }
+ * -- implicitCurve and specifiedCurve MUST NOT be used in PKIX.
+ * -- Details for SpecifiedECDomain can be found in [X9.62].
+ * -- Any future additions to this CHOICE should be coordinated
+ * -- with ANSI X9.
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class ECParameters
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_CHOICE,
+ 'children' => [
+ 'namedCurve' => ['type' => ASN1::TYPE_OBJECT_IDENTIFIER],
+ 'implicitCurve' => ['type' => ASN1::TYPE_NULL],
+ 'specifiedCurve' => SpecifiedECDomain::MAP
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECPoint.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECPoint.php
new file mode 100644
index 000000000..fb11db83f
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECPoint.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * ECPoint
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * ECPoint
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class ECPoint
+{
+ const MAP = ['type' => ASN1::TYPE_OCTET_STRING];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECPrivateKey.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECPrivateKey.php
new file mode 100644
index 000000000..7454f3874
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ECPrivateKey.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * ECPrivateKey
+ *
+ * From: https://tools.ietf.org/html/rfc5915
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * ECPrivateKey
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class ECPrivateKey
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'version' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'mapping' => [1 => 'ecPrivkeyVer1']
+ ],
+ 'privateKey' => ['type' => ASN1::TYPE_OCTET_STRING],
+ 'parameters' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'explicit' => true
+ ] + ECParameters::MAP,
+ 'publicKey' => [
+ 'type' => ASN1::TYPE_BIT_STRING,
+ 'constant' => 1,
+ 'optional' => true,
+ 'explicit' => true
+ ]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EDIPartyName.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EDIPartyName.php
new file mode 100644
index 000000000..ea7dcf194
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EDIPartyName.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * EDIPartyName
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * EDIPartyName
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class EDIPartyName
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'nameAssigner' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true
+ ] + DirectoryString::MAP,
+ // partyName is technically required but \phpseclib3\File\ASN1 doesn't currently support non-optional constants and
+ // setting it to optional gets the job done in any event.
+ 'partyName' => [
+ 'constant' => 1,
+ 'optional' => true,
+ 'implicit' => true
+ ] + DirectoryString::MAP
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EcdsaSigValue.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EcdsaSigValue.php
new file mode 100644
index 000000000..8ab9ff1eb
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EcdsaSigValue.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * EcdsaSigValue
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * EcdsaSigValue
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class EcdsaSigValue
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'r' => ['type' => ASN1::TYPE_INTEGER],
+ 's' => ['type' => ASN1::TYPE_INTEGER]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EncryptedData.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EncryptedData.php
new file mode 100644
index 000000000..8d8739e1c
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EncryptedData.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * EncryptedData
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * EncryptedData
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class EncryptedData
+{
+ const MAP = ['type' => ASN1::TYPE_OCTET_STRING];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EncryptedPrivateKeyInfo.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EncryptedPrivateKeyInfo.php
new file mode 100644
index 000000000..2c9356769
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/EncryptedPrivateKeyInfo.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * EncryptedPrivateKeyInfo
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * EncryptedPrivateKeyInfo
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class EncryptedPrivateKeyInfo
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'encryptionAlgorithm' => AlgorithmIdentifier::MAP,
+ 'encryptedData' => EncryptedData::MAP
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtKeyUsageSyntax.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtKeyUsageSyntax.php
new file mode 100644
index 000000000..f9bc5deff
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtKeyUsageSyntax.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * ExtKeyUsageSyntax
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * ExtKeyUsageSyntax
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class ExtKeyUsageSyntax
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => KeyPurposeId::MAP
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Extension.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Extension.php
new file mode 100644
index 000000000..e32668fb5
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Extension.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * Extension
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * Extension
+ *
+ * A certificate using system MUST reject the certificate if it encounters
+ * a critical extension it does not recognize; however, a non-critical
+ * extension may be ignored if it is not recognized.
+ *
+ * http://tools.ietf.org/html/rfc5280#section-4.2
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Extension
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'extnId' => ['type' => ASN1::TYPE_OBJECT_IDENTIFIER],
+ 'critical' => [
+ 'type' => ASN1::TYPE_BOOLEAN,
+ 'optional' => true,
+ 'default' => false
+ ],
+ 'extnValue' => ['type' => ASN1::TYPE_OCTET_STRING]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtensionAttribute.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtensionAttribute.php
new file mode 100644
index 000000000..565b36d39
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtensionAttribute.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * ExtensionAttribute
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * ExtensionAttribute
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class ExtensionAttribute
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'extension-attribute-type' => [
+ 'type' => ASN1::TYPE_PRINTABLE_STRING,
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true
+ ],
+ 'extension-attribute-value' => [
+ 'type' => ASN1::TYPE_ANY,
+ 'constant' => 1,
+ 'optional' => true,
+ 'explicit' => true
+ ]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtensionAttributes.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtensionAttributes.php
new file mode 100644
index 000000000..a2e9bfaec
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ExtensionAttributes.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * ExtensionAttributes
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * ExtensionAttributes
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class ExtensionAttributes
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SET,
+ 'min' => 1,
+ 'max' => 256, // ub-extension-attributes
+ 'children' => ExtensionAttribute::MAP
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Extensions.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Extensions.php
new file mode 100644
index 000000000..5015c976c
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Extensions.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * Extensions
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * Extensions
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Extensions
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ // technically, it's MAX, but we'll assume anything < 0 is MAX
+ 'max' => -1,
+ // if 'children' isn't an array then 'min' and 'max' must be defined
+ 'children' => Extension::MAP
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/FieldElement.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/FieldElement.php
new file mode 100644
index 000000000..31734078d
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/FieldElement.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * FieldElement
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * FieldElement
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class FieldElement
+{
+ const MAP = ['type' => ASN1::TYPE_OCTET_STRING];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/FieldID.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/FieldID.php
new file mode 100644
index 000000000..e32a9c03d
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/FieldID.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * FieldID
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * FieldID
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class FieldID
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'fieldType' => ['type' => ASN1::TYPE_OBJECT_IDENTIFIER],
+ 'parameters' => [
+ 'type' => ASN1::TYPE_ANY,
+ 'optional' => true
+ ]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralName.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralName.php
new file mode 100644
index 000000000..57d86da85
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralName.php
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * GeneralName
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * GeneralName
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class GeneralName
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_CHOICE,
+ 'children' => [
+ 'otherName' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true
+ ] + AnotherName::MAP,
+ 'rfc822Name' => [
+ 'type' => ASN1::TYPE_IA5_STRING,
+ 'constant' => 1,
+ 'optional' => true,
+ 'implicit' => true
+ ],
+ 'dNSName' => [
+ 'type' => ASN1::TYPE_IA5_STRING,
+ 'constant' => 2,
+ 'optional' => true,
+ 'implicit' => true
+ ],
+ 'x400Address' => [
+ 'constant' => 3,
+ 'optional' => true,
+ 'implicit' => true
+ ] + ORAddress::MAP,
+ 'directoryName' => [
+ 'constant' => 4,
+ 'optional' => true,
+ 'explicit' => true
+ ] + Name::MAP,
+ 'ediPartyName' => [
+ 'constant' => 5,
+ 'optional' => true,
+ 'implicit' => true
+ ] + EDIPartyName::MAP,
+ 'uniformResourceIdentifier' => [
+ 'type' => ASN1::TYPE_IA5_STRING,
+ 'constant' => 6,
+ 'optional' => true,
+ 'implicit' => true
+ ],
+ 'iPAddress' => [
+ 'type' => ASN1::TYPE_OCTET_STRING,
+ 'constant' => 7,
+ 'optional' => true,
+ 'implicit' => true
+ ],
+ 'registeredID' => [
+ 'type' => ASN1::TYPE_OBJECT_IDENTIFIER,
+ 'constant' => 8,
+ 'optional' => true,
+ 'implicit' => true
+ ]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralNames.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralNames.php
new file mode 100644
index 000000000..5d931532d
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralNames.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * GeneralNames
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * GeneralNames
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class GeneralNames
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => GeneralName::MAP
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralSubtree.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralSubtree.php
new file mode 100644
index 000000000..5388db559
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralSubtree.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * GeneralSubtree
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * GeneralSubtree
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class GeneralSubtree
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'base' => GeneralName::MAP,
+ 'minimum' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true,
+ 'default' => '0'
+ ] + BaseDistance::MAP,
+ 'maximum' => [
+ 'constant' => 1,
+ 'optional' => true,
+ 'implicit' => true,
+ ] + BaseDistance::MAP
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralSubtrees.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralSubtrees.php
new file mode 100644
index 000000000..27548cfec
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/GeneralSubtrees.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * GeneralSubtrees
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * GeneralSubtrees
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class GeneralSubtrees
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => GeneralSubtree::MAP
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/HashAlgorithm.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/HashAlgorithm.php
new file mode 100644
index 000000000..deb13cabe
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/HashAlgorithm.php
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * HashAglorithm
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+/**
+ * HashAglorithm
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class HashAlgorithm
+{
+ const MAP = AlgorithmIdentifier::MAP;
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/HoldInstructionCode.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/HoldInstructionCode.php
new file mode 100644
index 000000000..88d6ff3ea
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/HoldInstructionCode.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * HoldInstructionCode
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * HoldInstructionCode
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class HoldInstructionCode
+{
+ const MAP = ['type' => ASN1::TYPE_OBJECT_IDENTIFIER];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/InvalidityDate.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/InvalidityDate.php
new file mode 100644
index 000000000..f34b5f728
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/InvalidityDate.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * InvalidityDate
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * InvalidityDate
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class InvalidityDate
+{
+ const MAP = ['type' => ASN1::TYPE_GENERALIZED_TIME];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/IssuerAltName.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/IssuerAltName.php
new file mode 100644
index 000000000..e9d032448
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/IssuerAltName.php
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * IssuerAltName
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+/**
+ * IssuerAltName
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class IssuerAltName
+{
+ const MAP = GeneralNames::MAP;
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/IssuingDistributionPoint.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/IssuingDistributionPoint.php
new file mode 100644
index 000000000..415996f52
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/IssuingDistributionPoint.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * IssuingDistributionPoint
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * IssuingDistributionPoint
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class IssuingDistributionPoint
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'distributionPoint' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'explicit' => true
+ ] + DistributionPointName::MAP,
+ 'onlyContainsUserCerts' => [
+ 'type' => ASN1::TYPE_BOOLEAN,
+ 'constant' => 1,
+ 'optional' => true,
+ 'default' => false,
+ 'implicit' => true
+ ],
+ 'onlyContainsCACerts' => [
+ 'type' => ASN1::TYPE_BOOLEAN,
+ 'constant' => 2,
+ 'optional' => true,
+ 'default' => false,
+ 'implicit' => true
+ ],
+ 'onlySomeReasons' => [
+ 'constant' => 3,
+ 'optional' => true,
+ 'implicit' => true
+ ] + ReasonFlags::MAP,
+ 'indirectCRL' => [
+ 'type' => ASN1::TYPE_BOOLEAN,
+ 'constant' => 4,
+ 'optional' => true,
+ 'default' => false,
+ 'implicit' => true
+ ],
+ 'onlyContainsAttributeCerts' => [
+ 'type' => ASN1::TYPE_BOOLEAN,
+ 'constant' => 5,
+ 'optional' => true,
+ 'default' => false,
+ 'implicit' => true
+ ]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyIdentifier.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyIdentifier.php
new file mode 100644
index 000000000..82a415199
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyIdentifier.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * KeyIdentifier
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * KeyIdentifier
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class KeyIdentifier
+{
+ const MAP = ['type' => ASN1::TYPE_OCTET_STRING];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyPurposeId.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyPurposeId.php
new file mode 100644
index 000000000..b8509f196
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyPurposeId.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * KeyPurposeId
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * KeyPurposeId
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class KeyPurposeId
+{
+ const MAP = ['type' => ASN1::TYPE_OBJECT_IDENTIFIER];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyUsage.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyUsage.php
new file mode 100644
index 000000000..827ce0330
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/KeyUsage.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * KeyUsage
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * KeyUsage
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class KeyUsage
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_BIT_STRING,
+ 'mapping' => [
+ 'digitalSignature',
+ 'nonRepudiation',
+ 'keyEncipherment',
+ 'dataEncipherment',
+ 'keyAgreement',
+ 'keyCertSign',
+ 'cRLSign',
+ 'encipherOnly',
+ 'decipherOnly'
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/MaskGenAlgorithm.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/MaskGenAlgorithm.php
new file mode 100644
index 000000000..ea3f998b4
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/MaskGenAlgorithm.php
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * MaskGenAglorithm
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+/**
+ * MaskGenAglorithm
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class MaskGenAlgorithm
+{
+ const MAP = AlgorithmIdentifier::MAP;
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Name.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Name.php
new file mode 100644
index 000000000..a6a9009dc
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Name.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * Name
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * Name
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Name
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_CHOICE,
+ 'children' => [
+ 'rdnSequence' => RDNSequence::MAP
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NameConstraints.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NameConstraints.php
new file mode 100644
index 000000000..80486f94d
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NameConstraints.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * NameConstraints
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * NameConstraints
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class NameConstraints
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'permittedSubtrees' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true
+ ] + GeneralSubtrees::MAP,
+ 'excludedSubtrees' => [
+ 'constant' => 1,
+ 'optional' => true,
+ 'implicit' => true
+ ] + GeneralSubtrees::MAP
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NetworkAddress.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NetworkAddress.php
new file mode 100644
index 000000000..6c68df002
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NetworkAddress.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * NetworkAddress
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * NetworkAddress
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class NetworkAddress
+{
+ const MAP = ['type' => ASN1::TYPE_NUMERIC_STRING];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NoticeReference.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NoticeReference.php
new file mode 100644
index 000000000..9eec123a9
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NoticeReference.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * NoticeReference
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * NoticeReference
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class NoticeReference
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'organization' => DisplayText::MAP,
+ 'noticeNumbers' => [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => 200,
+ 'children' => ['type' => ASN1::TYPE_INTEGER]
+ ]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NumericUserIdentifier.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NumericUserIdentifier.php
new file mode 100644
index 000000000..635a89dcb
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/NumericUserIdentifier.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * NumericUserIdentifier
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * NumericUserIdentifier
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class NumericUserIdentifier
+{
+ const MAP = ['type' => ASN1::TYPE_NUMERIC_STRING];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ORAddress.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ORAddress.php
new file mode 100644
index 000000000..b853abe82
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ORAddress.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * ORAddress
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * ORAddress
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class ORAddress
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'built-in-standard-attributes' => BuiltInStandardAttributes::MAP,
+ 'built-in-domain-defined-attributes' => ['optional' => true] + BuiltInDomainDefinedAttributes::MAP,
+ 'extension-attributes' => ['optional' => true] + ExtensionAttributes::MAP
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OneAsymmetricKey.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OneAsymmetricKey.php
new file mode 100644
index 000000000..59530248c
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OneAsymmetricKey.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * OneAsymmetricKey
+ *
+ * See https://tools.ietf.org/html/rfc5958
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * OneAsymmetricKey
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class OneAsymmetricKey
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'version' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'mapping' => ['v1', 'v2']
+ ],
+ 'privateKeyAlgorithm' => AlgorithmIdentifier::MAP,
+ 'privateKey' => PrivateKey::MAP,
+ 'attributes' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true
+ ] + Attributes::MAP,
+ 'publicKey' => [
+ 'constant' => 1,
+ 'optional' => true,
+ 'implicit' => true
+ ] + PublicKey::MAP
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OrganizationName.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OrganizationName.php
new file mode 100644
index 000000000..b5cc9491a
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OrganizationName.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * OrganizationName
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * OrganizationName
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class OrganizationName
+{
+ const MAP = ['type' => ASN1::TYPE_PRINTABLE_STRING];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OrganizationalUnitNames.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OrganizationalUnitNames.php
new file mode 100644
index 000000000..b3e57809b
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OrganizationalUnitNames.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * OrganizationalUnitNames
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * OrganizationalUnitNames
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class OrganizationalUnitNames
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => 4, // ub-organizational-units
+ 'children' => ['type' => ASN1::TYPE_PRINTABLE_STRING]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OtherPrimeInfo.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OtherPrimeInfo.php
new file mode 100644
index 000000000..5d565605e
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OtherPrimeInfo.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * OtherPrimeInfo
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * OtherPrimeInfo
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class OtherPrimeInfo
+{
+ // version must be multi if otherPrimeInfos present
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'prime' => ['type' => ASN1::TYPE_INTEGER], // ri
+ 'exponent' => ['type' => ASN1::TYPE_INTEGER], // di
+ 'coefficient' => ['type' => ASN1::TYPE_INTEGER] // ti
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OtherPrimeInfos.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OtherPrimeInfos.php
new file mode 100644
index 000000000..9802a8089
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/OtherPrimeInfos.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * OtherPrimeInfos
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * OtherPrimeInfos
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class OtherPrimeInfos
+{
+ // version must be multi if otherPrimeInfos present
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => OtherPrimeInfo::MAP
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBEParameter.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBEParameter.php
new file mode 100644
index 000000000..8eb27cf62
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBEParameter.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * PBEParameter
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PBEParameter
+ *
+ * from https://tools.ietf.org/html/rfc2898#appendix-A.3
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PBEParameter
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'salt' => ['type' => ASN1::TYPE_OCTET_STRING],
+ 'iterationCount' => ['type' => ASN1::TYPE_INTEGER]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBES2params.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBES2params.php
new file mode 100644
index 000000000..bd31699ff
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBES2params.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * PBES2params
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PBES2params
+ *
+ * from https://tools.ietf.org/html/rfc2898#appendix-A.3
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PBES2params
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'keyDerivationFunc' => AlgorithmIdentifier::MAP,
+ 'encryptionScheme' => AlgorithmIdentifier::MAP
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBKDF2params.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBKDF2params.php
new file mode 100644
index 000000000..2dafed9ca
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBKDF2params.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * PBKDF2params
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PBKDF2params
+ *
+ * from https://tools.ietf.org/html/rfc2898#appendix-A.3
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PBKDF2params
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ // technically, this is a CHOICE in RFC2898 but the other "choice" is, currently, more of a placeholder
+ // in the RFC
+ 'salt' => ['type' => ASN1::TYPE_OCTET_STRING],
+ 'iterationCount' => ['type' => ASN1::TYPE_INTEGER],
+ 'keyLength' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'optional' => true
+ ],
+ 'prf' => AlgorithmIdentifier::MAP + ['optional' => true]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBMAC1params.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBMAC1params.php
new file mode 100644
index 000000000..91319f582
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PBMAC1params.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * PBMAC1params
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PBMAC1params
+ *
+ * from https://tools.ietf.org/html/rfc2898#appendix-A.3
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PBMAC1params
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'keyDerivationFunc' => AlgorithmIdentifier::MAP,
+ 'messageAuthScheme' => AlgorithmIdentifier::MAP
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PKCS9String.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PKCS9String.php
new file mode 100644
index 000000000..87d0862f5
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PKCS9String.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * PKCS9String
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PKCS9String
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PKCS9String
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_CHOICE,
+ 'children' => [
+ 'ia5String' => ['type' => ASN1::TYPE_IA5_STRING],
+ 'directoryString' => DirectoryString::MAP
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Pentanomial.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Pentanomial.php
new file mode 100644
index 000000000..b8c8c02fd
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Pentanomial.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * Pentanomial
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * Pentanomial
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Pentanomial
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'k1' => ['type' => ASN1::TYPE_INTEGER], // k1 > 0
+ 'k2' => ['type' => ASN1::TYPE_INTEGER], // k2 > k1
+ 'k3' => ['type' => ASN1::TYPE_INTEGER], // k3 > h2
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PersonalName.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PersonalName.php
new file mode 100644
index 000000000..14e2860e5
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PersonalName.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * PersonalName
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PersonalName
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PersonalName
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SET,
+ 'children' => [
+ 'surname' => [
+ 'type' => ASN1::TYPE_PRINTABLE_STRING,
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true
+ ],
+ 'given-name' => [
+ 'type' => ASN1::TYPE_PRINTABLE_STRING,
+ 'constant' => 1,
+ 'optional' => true,
+ 'implicit' => true
+ ],
+ 'initials' => [
+ 'type' => ASN1::TYPE_PRINTABLE_STRING,
+ 'constant' => 2,
+ 'optional' => true,
+ 'implicit' => true
+ ],
+ 'generation-qualifier' => [
+ 'type' => ASN1::TYPE_PRINTABLE_STRING,
+ 'constant' => 3,
+ 'optional' => true,
+ 'implicit' => true
+ ]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyInformation.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyInformation.php
new file mode 100644
index 000000000..1625d199a
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyInformation.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * PolicyInformation
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PolicyInformation
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PolicyInformation
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'policyIdentifier' => CertPolicyId::MAP,
+ 'policyQualifiers' => [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 0,
+ 'max' => -1,
+ 'optional' => true,
+ 'children' => PolicyQualifierInfo::MAP
+ ]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyMappings.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyMappings.php
new file mode 100644
index 000000000..d30b85235
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyMappings.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * PolicyMappings
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PolicyMappings
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PolicyMappings
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'issuerDomainPolicy' => CertPolicyId::MAP,
+ 'subjectDomainPolicy' => CertPolicyId::MAP
+ ]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyQualifierId.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyQualifierId.php
new file mode 100644
index 000000000..7b7cd6a76
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyQualifierId.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * PolicyQualifierId
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PolicyQualifierId
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PolicyQualifierId
+{
+ const MAP = ['type' => ASN1::TYPE_OBJECT_IDENTIFIER];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyQualifierInfo.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyQualifierInfo.php
new file mode 100644
index 000000000..d227702ef
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PolicyQualifierInfo.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * PolicyQualifierInfo
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PolicyQualifierInfo
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PolicyQualifierInfo
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'policyQualifierId' => PolicyQualifierId::MAP,
+ 'qualifier' => ['type' => ASN1::TYPE_ANY]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PostalAddress.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PostalAddress.php
new file mode 100644
index 000000000..142b309e4
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PostalAddress.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * PostalAddress
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PostalAddress
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PostalAddress
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'optional' => true,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => DirectoryString::MAP
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Prime_p.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Prime_p.php
new file mode 100644
index 000000000..774303448
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Prime_p.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * Prime_p
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * Prime_p
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Prime_p
+{
+ const MAP = ['type' => ASN1::TYPE_INTEGER];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateDomainName.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateDomainName.php
new file mode 100644
index 000000000..195dcaa5e
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateDomainName.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * PrivateDomainName
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PrivateDomainName
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PrivateDomainName
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_CHOICE,
+ 'children' => [
+ 'numeric' => ['type' => ASN1::TYPE_NUMERIC_STRING],
+ 'printable' => ['type' => ASN1::TYPE_PRINTABLE_STRING]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKey.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKey.php
new file mode 100644
index 000000000..3c8959411
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKey.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * PrivateKey
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PrivateKey
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PrivateKey
+{
+ const MAP = ['type' => ASN1::TYPE_OCTET_STRING];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKeyInfo.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKeyInfo.php
new file mode 100644
index 000000000..b440b78df
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKeyInfo.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * PrivateKeyInfo
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PrivateKeyInfo
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PrivateKeyInfo
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'version' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'mapping' => ['v1']
+ ],
+ 'privateKeyAlgorithm' => AlgorithmIdentifier::MAP,
+ 'privateKey' => PrivateKey::MAP,
+ 'attributes' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true
+ ] + Attributes::MAP
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKeyUsagePeriod.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKeyUsagePeriod.php
new file mode 100644
index 000000000..5b87036e6
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PrivateKeyUsagePeriod.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * PrivateKeyUsagePeriod
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PrivateKeyUsagePeriod
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PrivateKeyUsagePeriod
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'notBefore' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'implicit' => true,
+ 'type' => ASN1::TYPE_GENERALIZED_TIME],
+ 'notAfter' => [
+ 'constant' => 1,
+ 'optional' => true,
+ 'implicit' => true,
+ 'type' => ASN1::TYPE_GENERALIZED_TIME]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKey.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKey.php
new file mode 100644
index 000000000..484092042
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKey.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * PublicKey
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PublicKey
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PublicKey
+{
+ const MAP = ['type' => ASN1::TYPE_BIT_STRING];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKeyAndChallenge.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKeyAndChallenge.php
new file mode 100644
index 000000000..432581e48
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKeyAndChallenge.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * PublicKeyAndChallenge
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PublicKeyAndChallenge
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PublicKeyAndChallenge
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'spki' => SubjectPublicKeyInfo::MAP,
+ 'challenge' => ['type' => ASN1::TYPE_IA5_STRING]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKeyInfo.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKeyInfo.php
new file mode 100644
index 000000000..b39a341f0
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/PublicKeyInfo.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * PublicKeyInfo
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * PublicKeyInfo
+ *
+ * this format is not formally defined anywhere but is none-the-less the form you
+ * get when you do "openssl rsa -in private.pem -outform PEM -pubout"
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PublicKeyInfo
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'publicKeyAlgorithm' => AlgorithmIdentifier::MAP,
+ 'publicKey' => ['type' => ASN1::TYPE_BIT_STRING]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RC2CBCParameter.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RC2CBCParameter.php
new file mode 100644
index 000000000..48649abd5
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RC2CBCParameter.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * RC2CBCParameter
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * RC2CBCParameter
+ *
+ * from https://tools.ietf.org/html/rfc2898#appendix-A.3
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class RC2CBCParameter
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'rc2ParametersVersion' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'optional' => true
+ ],
+ 'iv' => ['type' => ASN1::TYPE_OCTET_STRING]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RDNSequence.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RDNSequence.php
new file mode 100644
index 000000000..04b071c27
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RDNSequence.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * RDNSequence
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * RDNSequence
+ *
+ * In practice, RDNs containing multiple name-value pairs (called "multivalued RDNs") are rare,
+ * but they can be useful at times when either there is no unique attribute in the entry or you
+ * want to ensure that the entry's DN contains some useful identifying information.
+ *
+ * - https://www.opends.org/wiki/page/DefinitionRelativeDistinguishedName
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class RDNSequence
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ // RDNSequence does not define a min or a max, which means it doesn't have one
+ 'min' => 0,
+ 'max' => -1,
+ 'children' => RelativeDistinguishedName::MAP
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSAPrivateKey.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSAPrivateKey.php
new file mode 100644
index 000000000..8c19c658e
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSAPrivateKey.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * RSAPrivateKey
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * RSAPrivateKey
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class RSAPrivateKey
+{
+ // version must be multi if otherPrimeInfos present
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'version' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'mapping' => ['two-prime', 'multi']
+ ],
+ 'modulus' => ['type' => ASN1::TYPE_INTEGER], // n
+ 'publicExponent' => ['type' => ASN1::TYPE_INTEGER], // e
+ 'privateExponent' => ['type' => ASN1::TYPE_INTEGER], // d
+ 'prime1' => ['type' => ASN1::TYPE_INTEGER], // p
+ 'prime2' => ['type' => ASN1::TYPE_INTEGER], // q
+ 'exponent1' => ['type' => ASN1::TYPE_INTEGER], // d mod (p-1)
+ 'exponent2' => ['type' => ASN1::TYPE_INTEGER], // d mod (q-1)
+ 'coefficient' => ['type' => ASN1::TYPE_INTEGER], // (inverse of q) mod p
+ 'otherPrimeInfos' => OtherPrimeInfos::MAP + ['optional' => true]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSAPublicKey.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSAPublicKey.php
new file mode 100644
index 000000000..b14c32c42
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSAPublicKey.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * RSAPublicKey
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * RSAPublicKey
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class RSAPublicKey
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'modulus' => ['type' => ASN1::TYPE_INTEGER],
+ 'publicExponent' => ['type' => ASN1::TYPE_INTEGER]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSASSA_PSS_params.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSASSA_PSS_params.php
new file mode 100644
index 000000000..1a784bf4d
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RSASSA_PSS_params.php
@@ -0,0 +1,58 @@
+<?php
+
+/**
+ * RSASSA_PSS_params
+ *
+ * As defined in https://tools.ietf.org/html/rfc4055#section-3.1
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * RSASSA_PSS_params
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class RSASSA_PSS_params
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'hashAlgorithm' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'explicit' => true,
+ //'default' => 'sha1Identifier'
+ ] + HashAlgorithm::MAP,
+ 'maskGenAlgorithm' => [
+ 'constant' => 1,
+ 'optional' => true,
+ 'explicit' => true,
+ //'default' => 'mgf1SHA1Identifier'
+ ] + MaskGenAlgorithm::MAP,
+ 'saltLength' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'constant' => 2,
+ 'optional' => true,
+ 'explicit' => true,
+ 'default' => 20
+ ],
+ 'trailerField' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'constant' => 3,
+ 'optional' => true,
+ 'explicit' => true,
+ 'default' => 1
+ ]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ReasonFlags.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ReasonFlags.php
new file mode 100644
index 000000000..2e62fcdb3
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/ReasonFlags.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * ReasonFlags
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * ReasonFlags
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class ReasonFlags
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_BIT_STRING,
+ 'mapping' => [
+ 'unused',
+ 'keyCompromise',
+ 'cACompromise',
+ 'affiliationChanged',
+ 'superseded',
+ 'cessationOfOperation',
+ 'certificateHold',
+ 'privilegeWithdrawn',
+ 'aACompromise'
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RelativeDistinguishedName.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RelativeDistinguishedName.php
new file mode 100644
index 000000000..a0421f731
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RelativeDistinguishedName.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * RelativeDistinguishedName
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * RelativeDistinguishedName
+ *
+ * In practice, RDNs containing multiple name-value pairs (called "multivalued RDNs") are rare,
+ * but they can be useful at times when either there is no unique attribute in the entry or you
+ * want to ensure that the entry's DN contains some useful identifying information.
+ *
+ * - https://www.opends.org/wiki/page/DefinitionRelativeDistinguishedName
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class RelativeDistinguishedName
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SET,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => AttributeTypeAndValue::MAP
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RevokedCertificate.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RevokedCertificate.php
new file mode 100644
index 000000000..ff759eb73
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/RevokedCertificate.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * RevokedCertificate
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * RevokedCertificate
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class RevokedCertificate
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'userCertificate' => CertificateSerialNumber::MAP,
+ 'revocationDate' => Time::MAP,
+ 'crlEntryExtensions' => [
+ 'optional' => true
+ ] + Extensions::MAP
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SignedPublicKeyAndChallenge.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SignedPublicKeyAndChallenge.php
new file mode 100644
index 000000000..0f482a261
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SignedPublicKeyAndChallenge.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * SignedPublicKeyAndChallenge
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * SignedPublicKeyAndChallenge
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class SignedPublicKeyAndChallenge
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'publicKeyAndChallenge' => PublicKeyAndChallenge::MAP,
+ 'signatureAlgorithm' => AlgorithmIdentifier::MAP,
+ 'signature' => ['type' => ASN1::TYPE_BIT_STRING]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SpecifiedECDomain.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SpecifiedECDomain.php
new file mode 100644
index 000000000..7408a5637
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SpecifiedECDomain.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * SpecifiedECDomain
+ *
+ * From: http://www.secg.org/sec1-v2.pdf#page=109
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * SpecifiedECDomain
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class SpecifiedECDomain
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'version' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'mapping' => [1 => 'ecdpVer1', 'ecdpVer2', 'ecdpVer3']
+ ],
+ 'fieldID' => FieldID::MAP,
+ 'curve' => Curve::MAP,
+ 'base' => ECPoint::MAP,
+ 'order' => ['type' => ASN1::TYPE_INTEGER],
+ 'cofactor' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'optional' => true
+ ],
+ 'hash' => ['optional' => true] + HashAlgorithm::MAP
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectAltName.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectAltName.php
new file mode 100644
index 000000000..39138a94f
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectAltName.php
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * SubjectAltName
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+/**
+ * SubjectAltName
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class SubjectAltName
+{
+ const MAP = GeneralNames::MAP;
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectDirectoryAttributes.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectDirectoryAttributes.php
new file mode 100644
index 000000000..f2e206f6a
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectDirectoryAttributes.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * SubjectDirectoryAttributes
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * SubjectDirectoryAttributes
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class SubjectDirectoryAttributes
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => Attribute::MAP
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectInfoAccessSyntax.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectInfoAccessSyntax.php
new file mode 100644
index 000000000..1ff241f71
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectInfoAccessSyntax.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * SubjectInfoAccessSyntax
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * SubjectInfoAccessSyntax
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class SubjectInfoAccessSyntax
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'min' => 1,
+ 'max' => -1,
+ 'children' => AccessDescription::MAP
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectPublicKeyInfo.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectPublicKeyInfo.php
new file mode 100644
index 000000000..0d53d5401
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/SubjectPublicKeyInfo.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * SubjectPublicKeyInfo
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * SubjectPublicKeyInfo
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class SubjectPublicKeyInfo
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'algorithm' => AlgorithmIdentifier::MAP,
+ 'subjectPublicKey' => ['type' => ASN1::TYPE_BIT_STRING]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TBSCertList.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TBSCertList.php
new file mode 100644
index 000000000..8e00f4d85
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TBSCertList.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * TBSCertList
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * TBSCertList
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class TBSCertList
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'version' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'mapping' => ['v1', 'v2'],
+ 'optional' => true,
+ 'default' => 'v1'
+ ],
+ 'signature' => AlgorithmIdentifier::MAP,
+ 'issuer' => Name::MAP,
+ 'thisUpdate' => Time::MAP,
+ 'nextUpdate' => [
+ 'optional' => true
+ ] + Time::MAP,
+ 'revokedCertificates' => [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'optional' => true,
+ 'min' => 0,
+ 'max' => -1,
+ 'children' => RevokedCertificate::MAP
+ ],
+ 'crlExtensions' => [
+ 'constant' => 0,
+ 'optional' => true,
+ 'explicit' => true
+ ] + Extensions::MAP
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TBSCertificate.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TBSCertificate.php
new file mode 100644
index 000000000..007360c97
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TBSCertificate.php
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * TBSCertificate
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * TBSCertificate
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class TBSCertificate
+{
+ // assert($TBSCertificate['children']['signature'] == $Certificate['children']['signatureAlgorithm'])
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ // technically, default implies optional, but we'll define it as being optional, none-the-less, just to
+ // reenforce that fact
+ 'version' => [
+ 'type' => ASN1::TYPE_INTEGER,
+ 'constant' => 0,
+ 'optional' => true,
+ 'explicit' => true,
+ 'mapping' => ['v1', 'v2', 'v3'],
+ 'default' => 'v1'
+ ],
+ 'serialNumber' => CertificateSerialNumber::MAP,
+ 'signature' => AlgorithmIdentifier::MAP,
+ 'issuer' => Name::MAP,
+ 'validity' => Validity::MAP,
+ 'subject' => Name::MAP,
+ 'subjectPublicKeyInfo' => SubjectPublicKeyInfo::MAP,
+ // implicit means that the T in the TLV structure is to be rewritten, regardless of the type
+ 'issuerUniqueID' => [
+ 'constant' => 1,
+ 'optional' => true,
+ 'implicit' => true
+ ] + UniqueIdentifier::MAP,
+ 'subjectUniqueID' => [
+ 'constant' => 2,
+ 'optional' => true,
+ 'implicit' => true
+ ] + UniqueIdentifier::MAP,
+ // <http://tools.ietf.org/html/rfc2459#page-74> doesn't use the EXPLICIT keyword but if
+ // it's not IMPLICIT, it's EXPLICIT
+ 'extensions' => [
+ 'constant' => 3,
+ 'optional' => true,
+ 'explicit' => true
+ ] + Extensions::MAP
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TerminalIdentifier.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TerminalIdentifier.php
new file mode 100644
index 000000000..7f6d9d2e9
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/TerminalIdentifier.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * TerminalIdentifier
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * TerminalIdentifier
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class TerminalIdentifier
+{
+ const MAP = ['type' => ASN1::TYPE_PRINTABLE_STRING];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Time.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Time.php
new file mode 100644
index 000000000..744ee7049
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Time.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * Time
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * Time
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Time
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_CHOICE,
+ 'children' => [
+ 'utcTime' => ['type' => ASN1::TYPE_UTC_TIME],
+ 'generalTime' => ['type' => ASN1::TYPE_GENERALIZED_TIME]
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Trinomial.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Trinomial.php
new file mode 100644
index 000000000..33baa91e6
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Trinomial.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * Trinomial
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * Trinomial
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Trinomial
+{
+ const MAP = ['type' => ASN1::TYPE_INTEGER];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/UniqueIdentifier.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/UniqueIdentifier.php
new file mode 100644
index 000000000..f4c954bbc
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/UniqueIdentifier.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * UniqueIdentifier
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * UniqueIdentifier
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class UniqueIdentifier
+{
+ const MAP = ['type' => ASN1::TYPE_BIT_STRING];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/UserNotice.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/UserNotice.php
new file mode 100644
index 000000000..98d527b7b
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/UserNotice.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * UserNotice
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * UserNotice
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class UserNotice
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'noticeRef' => [
+ 'optional' => true,
+ 'implicit' => true
+ ] + NoticeReference::MAP,
+ 'explicitText' => [
+ 'optional' => true,
+ 'implicit' => true
+ ] + DisplayText::MAP
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Validity.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Validity.php
new file mode 100644
index 000000000..8ef64cf5d
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/Validity.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * Validity
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * Validity
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Validity
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_SEQUENCE,
+ 'children' => [
+ 'notBefore' => Time::MAP,
+ 'notAfter' => Time::MAP
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_ca_policy_url.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_ca_policy_url.php
new file mode 100644
index 000000000..2ab157287
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_ca_policy_url.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * netscape_ca_policy_url
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * netscape_ca_policy_url
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class netscape_ca_policy_url
+{
+ const MAP = ['type' => ASN1::TYPE_IA5_STRING];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_cert_type.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_cert_type.php
new file mode 100644
index 000000000..49e8da4b9
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_cert_type.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * netscape_cert_type
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * netscape_cert_type
+ *
+ * mapping is from <http://www.mozilla.org/projects/security/pki/nss/tech-notes/tn3.html>
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class netscape_cert_type
+{
+ const MAP = [
+ 'type' => ASN1::TYPE_BIT_STRING,
+ 'mapping' => [
+ 'SSLClient',
+ 'SSLServer',
+ 'Email',
+ 'ObjectSigning',
+ 'Reserved',
+ 'SSLCA',
+ 'EmailCA',
+ 'ObjectSigningCA'
+ ]
+ ];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_comment.php b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_comment.php
new file mode 100644
index 000000000..d3ff4ddfb
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/ASN1/Maps/netscape_comment.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * netscape_comment
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2016 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\File\ASN1\Maps;
+
+use phpseclib3\File\ASN1;
+
+/**
+ * netscape_comment
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class netscape_comment
+{
+ const MAP = ['type' => ASN1::TYPE_IA5_STRING];
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/File/X509.php b/vendor/phpseclib/phpseclib/phpseclib/File/X509.php
index 64e22655c..80e0bab10 100644
--- a/vendor/phpseclib/phpseclib/phpseclib/File/X509.php
+++ b/vendor/phpseclib/phpseclib/phpseclib/File/X509.php
@@ -16,30 +16,33 @@
* be encoded. It can be encoded explicitly or left out all together. This would effect the signature value and thus may invalidate the
* the certificate all together unless the certificate is re-signed.
*
- * @category File
- * @package X509
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2012 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
-namespace phpseclib\File;
-
-use phpseclib\Crypt\Hash;
-use phpseclib\Crypt\Random;
-use phpseclib\Crypt\RSA;
-use phpseclib\File\ASN1\Element;
-use phpseclib\Math\BigInteger;
-use DateTime;
-use DateTimeZone;
+namespace phpseclib3\File;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\PrivateKey;
+use phpseclib3\Crypt\Common\PublicKey;
+use phpseclib3\Crypt\DSA;
+use phpseclib3\Crypt\EC;
+use phpseclib3\Crypt\Hash;
+use phpseclib3\Crypt\PublicKeyLoader;
+use phpseclib3\Crypt\Random;
+use phpseclib3\Crypt\RSA;
+use phpseclib3\Crypt\RSA\Formats\Keys\PSS;
+use phpseclib3\Exception\UnsupportedAlgorithmException;
+use phpseclib3\File\ASN1\Element;
+use phpseclib3\File\ASN1\Maps;
+use phpseclib3\Math\BigInteger;
/**
* Pure-PHP X.509 Parser
*
- * @package X509
* @author Jim Wigginton <terrafrost@php.net>
- * @access public
*/
class X509
{
@@ -48,59 +51,71 @@ class X509
*
* Not really used anymore but retained all the same to suppress E_NOTICEs from old installs
*
- * @access public
*/
const VALIDATE_SIGNATURE_BY_CA = 1;
- /**#@+
- * @access public
- * @see \phpseclib\File\X509::getDN()
- */
/**
* Return internal array representation
+ *
+ * @see \phpseclib3\File\X509::getDN()
*/
const DN_ARRAY = 0;
/**
* Return string
+ *
+ * @see \phpseclib3\File\X509::getDN()
*/
const DN_STRING = 1;
/**
* Return ASN.1 name string
+ *
+ * @see \phpseclib3\File\X509::getDN()
*/
const DN_ASN1 = 2;
/**
* Return OpenSSL compatible array
+ *
+ * @see \phpseclib3\File\X509::getDN()
*/
const DN_OPENSSL = 3;
/**
* Return canonical ASN.1 RDNs string
+ *
+ * @see \phpseclib3\File\X509::getDN()
*/
const DN_CANON = 4;
/**
* Return name hash for file indexing
+ *
+ * @see \phpseclib3\File\X509::getDN()
*/
const DN_HASH = 5;
- /**#@-*/
-
- /**#@+
- * @access public
- * @see \phpseclib\File\X509::saveX509()
- * @see \phpseclib\File\X509::saveCSR()
- * @see \phpseclib\File\X509::saveCRL()
- */
+
/**
* Save as PEM
*
* ie. a base64-encoded PEM with a header and a footer
+ *
+ * @see \phpseclib3\File\X509::saveX509()
+ * @see \phpseclib3\File\X509::saveCSR()
+ * @see \phpseclib3\File\X509::saveCRL()
*/
const FORMAT_PEM = 0;
/**
* Save as DER
+ *
+ * @see \phpseclib3\File\X509::saveX509()
+ * @see \phpseclib3\File\X509::saveCSR()
+ * @see \phpseclib3\File\X509::saveCRL()
*/
const FORMAT_DER = 1;
/**
* Save as a SPKAC
*
+ * @see \phpseclib3\File\X509::saveX509()
+ * @see \phpseclib3\File\X509::saveCSR()
+ * @see \phpseclib3\File\X509::saveCRL()
+ *
* Only works on CSRs. Not currently supported.
*/
const FORMAT_SPKAC = 2;
@@ -108,9 +123,12 @@ class X509
* Auto-detect the format
*
* Used only by the load*() functions
+ *
+ * @see \phpseclib3\File\X509::saveX509()
+ * @see \phpseclib3\File\X509::saveCSR()
+ * @see \phpseclib3\File\X509::saveCRL()
*/
const FORMAT_AUTO_DETECT = 3;
- /**#@-*/
/**
* Attribute value disposition.
@@ -121,163 +139,70 @@ class X509
const ATTR_REPLACE = -3; // Clear first, then add a value.
/**
- * ASN.1 syntax for X.509 certificates
- *
- * @var array
- * @access private
- */
- var $Certificate;
-
- /**#@+
- * ASN.1 syntax for various extensions
- *
- * @access private
- */
- var $DirectoryString;
- var $PKCS9String;
- var $AttributeValue;
- var $Extensions;
- var $KeyUsage;
- var $ExtKeyUsageSyntax;
- var $BasicConstraints;
- var $KeyIdentifier;
- var $CRLDistributionPoints;
- var $AuthorityKeyIdentifier;
- var $CertificatePolicies;
- var $AuthorityInfoAccessSyntax;
- var $SubjectInfoAccessSyntax;
- var $SubjectAltName;
- var $SubjectDirectoryAttributes;
- var $PrivateKeyUsagePeriod;
- var $IssuerAltName;
- var $PolicyMappings;
- var $NameConstraints;
-
- var $CPSuri;
- var $UserNotice;
-
- var $netscape_cert_type;
- var $netscape_comment;
- var $netscape_ca_policy_url;
-
- var $Name;
- var $RelativeDistinguishedName;
- var $CRLNumber;
- var $CRLReason;
- var $IssuingDistributionPoint;
- var $InvalidityDate;
- var $CertificateIssuer;
- var $HoldInstructionCode;
- var $SignedPublicKeyAndChallenge;
- /**#@-*/
-
- /**#@+
- * ASN.1 syntax for various DN attributes
- *
- * @access private
- */
- var $PostalAddress;
- /**#@-*/
-
- /**
- * ASN.1 syntax for Certificate Signing Requests (RFC2986)
- *
- * @var array
- * @access private
- */
- var $CertificationRequest;
-
- /**
- * ASN.1 syntax for Certificate Revocation Lists (RFC5280)
- *
- * @var array
- * @access private
- */
- var $CertificateList;
-
- /**
* Distinguished Name
*
* @var array
- * @access private
*/
- var $dn;
+ private $dn;
/**
* Public key
*
- * @var string
- * @access private
+ * @var string|PublicKey
*/
- var $publicKey;
+ private $publicKey;
/**
* Private key
*
- * @var string
- * @access private
- */
- var $privateKey;
-
- /**
- * Object identifiers for X.509 certificates
- *
- * @var array
- * @access private
- * @link http://en.wikipedia.org/wiki/Object_identifier
+ * @var string|PrivateKey
*/
- var $oids;
+ private $privateKey;
/**
* The certificate authorities
*
* @var array
- * @access private
*/
- var $CAs;
+ private $CAs = [];
/**
* The currently loaded certificate
*
* @var array
- * @access private
*/
- var $currentCert;
+ private $currentCert;
/**
* The signature subject
*
- * There's no guarantee \phpseclib\File\X509 is going to re-encode an X.509 cert in the same way it was originally
+ * There's no guarantee \phpseclib3\File\X509 is going to re-encode an X.509 cert in the same way it was originally
* encoded so we take save the portion of the original cert that the signature would have made for.
*
* @var string
- * @access private
*/
- var $signatureSubject;
+ private $signatureSubject;
/**
* Certificate Start Date
*
* @var string
- * @access private
*/
- var $startDate;
+ private $startDate;
/**
* Certificate End Date
*
- * @var string
- * @access private
+ * @var string|Element
*/
- var $endDate;
+ private $endDate;
/**
* Serial Number
*
* @var string
- * @access private
*/
- var $serialNumber;
+ private $serialNumber;
/**
* Key Identifier
@@ -286,1175 +211,213 @@ class X509
* {@link http://tools.ietf.org/html/rfc5280#section-4.2.1.2 RFC5280#section-4.2.1.2}.
*
* @var string
- * @access private
*/
- var $currentKeyIdentifier;
+ private $currentKeyIdentifier;
/**
* CA Flag
*
* @var bool
- * @access private
*/
- var $caFlag = false;
+ private $caFlag = false;
/**
* SPKAC Challenge
*
* @var string
- * @access private
*/
- var $challenge;
+ private $challenge;
+
+ /**
+ * @var array
+ */
+ private $extensionValues = [];
+
+ /**
+ * OIDs loaded
+ *
+ * @var bool
+ */
+ private static $oidsLoaded = false;
/**
* Recursion Limit
*
* @var int
- * @access private
*/
- static $recur_limit = 5;
+ private static $recur_limit = 5;
/**
* URL fetch flag
*
* @var bool
- * @access private
*/
- static $disable_url_fetch = false;
+ private static $disable_url_fetch = false;
+
+ /**
+ * @var array
+ */
+ private static $extensions = [];
+
+ /**
+ * @var ?array
+ */
+ private $ipAddresses = null;
+
+ /**
+ * @var ?array
+ */
+ private $domains = null;
/**
* Default Constructor.
*
- * @return \phpseclib\File\X509
- * @access public
+ * @return X509
*/
- function __construct()
+ public function __construct()
{
// Explicitly Tagged Module, 1988 Syntax
// http://tools.ietf.org/html/rfc5280#appendix-A.1
- $this->DirectoryString = array(
- 'type' => ASN1::TYPE_CHOICE,
- 'children' => array(
- 'teletexString' => array('type' => ASN1::TYPE_TELETEX_STRING),
- 'printableString' => array('type' => ASN1::TYPE_PRINTABLE_STRING),
- 'universalString' => array('type' => ASN1::TYPE_UNIVERSAL_STRING),
- 'utf8String' => array('type' => ASN1::TYPE_UTF8_STRING),
- 'bmpString' => array('type' => ASN1::TYPE_BMP_STRING)
- )
- );
-
- $this->PKCS9String = array(
- 'type' => ASN1::TYPE_CHOICE,
- 'children' => array(
- 'ia5String' => array('type' => ASN1::TYPE_IA5_STRING),
- 'directoryString' => $this->DirectoryString
- )
- );
-
- $this->AttributeValue = array('type' => ASN1::TYPE_ANY);
-
- $AttributeType = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER);
-
- $AttributeTypeAndValue = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'type' => $AttributeType,
- 'value'=> $this->AttributeValue
- )
- );
-
- /*
- In practice, RDNs containing multiple name-value pairs (called "multivalued RDNs") are rare,
- but they can be useful at times when either there is no unique attribute in the entry or you
- want to ensure that the entry's DN contains some useful identifying information.
-
- - https://www.opends.org/wiki/page/DefinitionRelativeDistinguishedName
- */
- $this->RelativeDistinguishedName = array(
- 'type' => ASN1::TYPE_SET,
- 'min' => 1,
- 'max' => -1,
- 'children' => $AttributeTypeAndValue
- );
-
- // http://tools.ietf.org/html/rfc5280#section-4.1.2.4
- $RDNSequence = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- // RDNSequence does not define a min or a max, which means it doesn't have one
- 'min' => 0,
- 'max' => -1,
- 'children' => $this->RelativeDistinguishedName
- );
-
- $this->Name = array(
- 'type' => ASN1::TYPE_CHOICE,
- 'children' => array(
- 'rdnSequence' => $RDNSequence
- )
- );
-
- // http://tools.ietf.org/html/rfc5280#section-4.1.1.2
- $AlgorithmIdentifier = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'algorithm' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER),
- 'parameters' => array(
- 'type' => ASN1::TYPE_ANY,
- 'optional' => true
- )
- )
- );
-
- /*
- A certificate using system MUST reject the certificate if it encounters
- a critical extension it does not recognize; however, a non-critical
- extension may be ignored if it is not recognized.
-
- http://tools.ietf.org/html/rfc5280#section-4.2
- */
- $Extension = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'extnId' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER),
- 'critical' => array(
- 'type' => ASN1::TYPE_BOOLEAN,
- 'optional' => true,
- 'default' => false
- ),
- 'extnValue' => array('type' => ASN1::TYPE_OCTET_STRING)
- )
- );
-
- $this->Extensions = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'min' => 1,
- // technically, it's MAX, but we'll assume anything < 0 is MAX
- 'max' => -1,
- // if 'children' isn't an array then 'min' and 'max' must be defined
- 'children' => $Extension
- );
-
- $SubjectPublicKeyInfo = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'algorithm' => $AlgorithmIdentifier,
- 'subjectPublicKey' => array('type' => ASN1::TYPE_BIT_STRING)
- )
- );
-
- $UniqueIdentifier = array('type' => ASN1::TYPE_BIT_STRING);
-
- $Time = array(
- 'type' => ASN1::TYPE_CHOICE,
- 'children' => array(
- 'utcTime' => array('type' => ASN1::TYPE_UTC_TIME),
- 'generalTime' => array('type' => ASN1::TYPE_GENERALIZED_TIME)
- )
- );
-
- // http://tools.ietf.org/html/rfc5280#section-4.1.2.5
- $Validity = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'notBefore' => $Time,
- 'notAfter' => $Time
- )
- );
-
- $CertificateSerialNumber = array('type' => ASN1::TYPE_INTEGER);
-
- $Version = array(
- 'type' => ASN1::TYPE_INTEGER,
- 'mapping' => array('v1', 'v2', 'v3')
- );
-
- // assert($TBSCertificate['children']['signature'] == $Certificate['children']['signatureAlgorithm'])
- $TBSCertificate = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- // technically, default implies optional, but we'll define it as being optional, none-the-less, just to
- // reenforce that fact
- 'version' => array(
- 'constant' => 0,
- 'optional' => true,
- 'explicit' => true,
- 'default' => 'v1'
- ) + $Version,
- 'serialNumber' => $CertificateSerialNumber,
- 'signature' => $AlgorithmIdentifier,
- 'issuer' => $this->Name,
- 'validity' => $Validity,
- 'subject' => $this->Name,
- 'subjectPublicKeyInfo' => $SubjectPublicKeyInfo,
- // implicit means that the T in the TLV structure is to be rewritten, regardless of the type
- 'issuerUniqueID' => array(
- 'constant' => 1,
- 'optional' => true,
- 'implicit' => true
- ) + $UniqueIdentifier,
- 'subjectUniqueID' => array(
- 'constant' => 2,
- 'optional' => true,
- 'implicit' => true
- ) + $UniqueIdentifier,
- // <http://tools.ietf.org/html/rfc2459#page-74> doesn't use the EXPLICIT keyword but if
- // it's not IMPLICIT, it's EXPLICIT
- 'extensions' => array(
- 'constant' => 3,
- 'optional' => true,
- 'explicit' => true
- ) + $this->Extensions
- )
- );
-
- $this->Certificate = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'tbsCertificate' => $TBSCertificate,
- 'signatureAlgorithm' => $AlgorithmIdentifier,
- 'signature' => array('type' => ASN1::TYPE_BIT_STRING)
- )
- );
-
- $this->KeyUsage = array(
- 'type' => ASN1::TYPE_BIT_STRING,
- 'mapping' => array(
- 'digitalSignature',
- 'nonRepudiation',
- 'keyEncipherment',
- 'dataEncipherment',
- 'keyAgreement',
- 'keyCertSign',
- 'cRLSign',
- 'encipherOnly',
- 'decipherOnly'
- )
- );
-
- $this->BasicConstraints = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'cA' => array(
- 'type' => ASN1::TYPE_BOOLEAN,
- 'optional' => true,
- 'default' => false
- ),
- 'pathLenConstraint' => array(
- 'type' => ASN1::TYPE_INTEGER,
- 'optional' => true
- )
- )
- );
-
- $this->KeyIdentifier = array('type' => ASN1::TYPE_OCTET_STRING);
-
- $OrganizationalUnitNames = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'min' => 1,
- 'max' => 4, // ub-organizational-units
- 'children' => array('type' => ASN1::TYPE_PRINTABLE_STRING)
- );
-
- $PersonalName = array(
- 'type' => ASN1::TYPE_SET,
- 'children' => array(
- 'surname' => array(
- 'type' => ASN1::TYPE_PRINTABLE_STRING,
- 'constant' => 0,
- 'optional' => true,
- 'implicit' => true
- ),
- 'given-name' => array(
- 'type' => ASN1::TYPE_PRINTABLE_STRING,
- 'constant' => 1,
- 'optional' => true,
- 'implicit' => true
- ),
- 'initials' => array(
- 'type' => ASN1::TYPE_PRINTABLE_STRING,
- 'constant' => 2,
- 'optional' => true,
- 'implicit' => true
- ),
- 'generation-qualifier' => array(
- 'type' => ASN1::TYPE_PRINTABLE_STRING,
- 'constant' => 3,
- 'optional' => true,
- 'implicit' => true
- )
- )
- );
-
- $NumericUserIdentifier = array('type' => ASN1::TYPE_NUMERIC_STRING);
-
- $OrganizationName = array('type' => ASN1::TYPE_PRINTABLE_STRING);
-
- $PrivateDomainName = array(
- 'type' => ASN1::TYPE_CHOICE,
- 'children' => array(
- 'numeric' => array('type' => ASN1::TYPE_NUMERIC_STRING),
- 'printable' => array('type' => ASN1::TYPE_PRINTABLE_STRING)
- )
- );
-
- $TerminalIdentifier = array('type' => ASN1::TYPE_PRINTABLE_STRING);
-
- $NetworkAddress = array('type' => ASN1::TYPE_NUMERIC_STRING);
-
- $AdministrationDomainName = array(
- 'type' => ASN1::TYPE_CHOICE,
- // if class isn't present it's assumed to be \phpseclib\File\ASN1::CLASS_UNIVERSAL or
- // (if constant is present) \phpseclib\File\ASN1::CLASS_CONTEXT_SPECIFIC
- 'class' => ASN1::CLASS_APPLICATION,
- 'cast' => 2,
- 'children' => array(
- 'numeric' => array('type' => ASN1::TYPE_NUMERIC_STRING),
- 'printable' => array('type' => ASN1::TYPE_PRINTABLE_STRING)
- )
- );
-
- $CountryName = array(
- 'type' => ASN1::TYPE_CHOICE,
- // if class isn't present it's assumed to be \phpseclib\File\ASN1::CLASS_UNIVERSAL or
- // (if constant is present) \phpseclib\File\ASN1::CLASS_CONTEXT_SPECIFIC
- 'class' => ASN1::CLASS_APPLICATION,
- 'cast' => 1,
- 'children' => array(
- 'x121-dcc-code' => array('type' => ASN1::TYPE_NUMERIC_STRING),
- 'iso-3166-alpha2-code' => array('type' => ASN1::TYPE_PRINTABLE_STRING)
- )
- );
-
- $AnotherName = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'type-id' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER),
- 'value' => array(
- 'type' => ASN1::TYPE_ANY,
- 'constant' => 0,
- 'optional' => true,
- 'explicit' => true
- )
- )
- );
-
- $ExtensionAttribute = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'extension-attribute-type' => array(
- 'type' => ASN1::TYPE_PRINTABLE_STRING,
- 'constant' => 0,
- 'optional' => true,
- 'implicit' => true
- ),
- 'extension-attribute-value' => array(
- 'type' => ASN1::TYPE_ANY,
- 'constant' => 1,
- 'optional' => true,
- 'explicit' => true
- )
- )
- );
-
- $ExtensionAttributes = array(
- 'type' => ASN1::TYPE_SET,
- 'min' => 1,
- 'max' => 256, // ub-extension-attributes
- 'children' => $ExtensionAttribute
- );
-
- $BuiltInDomainDefinedAttribute = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'type' => array('type' => ASN1::TYPE_PRINTABLE_STRING),
- 'value' => array('type' => ASN1::TYPE_PRINTABLE_STRING)
- )
- );
-
- $BuiltInDomainDefinedAttributes = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'min' => 1,
- 'max' => 4, // ub-domain-defined-attributes
- 'children' => $BuiltInDomainDefinedAttribute
- );
-
- $BuiltInStandardAttributes = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'country-name' => array('optional' => true) + $CountryName,
- 'administration-domain-name' => array('optional' => true) + $AdministrationDomainName,
- 'network-address' => array(
- 'constant' => 0,
- 'optional' => true,
- 'implicit' => true
- ) + $NetworkAddress,
- 'terminal-identifier' => array(
- 'constant' => 1,
- 'optional' => true,
- 'implicit' => true
- ) + $TerminalIdentifier,
- 'private-domain-name' => array(
- 'constant' => 2,
- 'optional' => true,
- 'explicit' => true
- ) + $PrivateDomainName,
- 'organization-name' => array(
- 'constant' => 3,
- 'optional' => true,
- 'implicit' => true
- ) + $OrganizationName,
- 'numeric-user-identifier' => array(
- 'constant' => 4,
- 'optional' => true,
- 'implicit' => true
- ) + $NumericUserIdentifier,
- 'personal-name' => array(
- 'constant' => 5,
- 'optional' => true,
- 'implicit' => true
- ) + $PersonalName,
- 'organizational-unit-names' => array(
- 'constant' => 6,
- 'optional' => true,
- 'implicit' => true
- ) + $OrganizationalUnitNames
- )
- );
-
- $ORAddress = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'built-in-standard-attributes' => $BuiltInStandardAttributes,
- 'built-in-domain-defined-attributes' => array('optional' => true) + $BuiltInDomainDefinedAttributes,
- 'extension-attributes' => array('optional' => true) + $ExtensionAttributes
- )
- );
-
- $EDIPartyName = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'nameAssigner' => array(
- 'constant' => 0,
- 'optional' => true,
- 'implicit' => true
- ) + $this->DirectoryString,
- // partyName is technically required but \phpseclib\File\ASN1 doesn't currently support non-optional constants and
- // setting it to optional gets the job done in any event.
- 'partyName' => array(
- 'constant' => 1,
- 'optional' => true,
- 'implicit' => true
- ) + $this->DirectoryString
- )
- );
-
- $GeneralName = array(
- 'type' => ASN1::TYPE_CHOICE,
- 'children' => array(
- 'otherName' => array(
- 'constant' => 0,
- 'optional' => true,
- 'implicit' => true
- ) + $AnotherName,
- 'rfc822Name' => array(
- 'type' => ASN1::TYPE_IA5_STRING,
- 'constant' => 1,
- 'optional' => true,
- 'implicit' => true
- ),
- 'dNSName' => array(
- 'type' => ASN1::TYPE_IA5_STRING,
- 'constant' => 2,
- 'optional' => true,
- 'implicit' => true
- ),
- 'x400Address' => array(
- 'constant' => 3,
- 'optional' => true,
- 'implicit' => true
- ) + $ORAddress,
- 'directoryName' => array(
- 'constant' => 4,
- 'optional' => true,
- 'explicit' => true
- ) + $this->Name,
- 'ediPartyName' => array(
- 'constant' => 5,
- 'optional' => true,
- 'implicit' => true
- ) + $EDIPartyName,
- 'uniformResourceIdentifier' => array(
- 'type' => ASN1::TYPE_IA5_STRING,
- 'constant' => 6,
- 'optional' => true,
- 'implicit' => true
- ),
- 'iPAddress' => array(
- 'type' => ASN1::TYPE_OCTET_STRING,
- 'constant' => 7,
- 'optional' => true,
- 'implicit' => true
- ),
- 'registeredID' => array(
- 'type' => ASN1::TYPE_OBJECT_IDENTIFIER,
- 'constant' => 8,
- 'optional' => true,
- 'implicit' => true
- )
- )
- );
-
- $GeneralNames = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'min' => 1,
- 'max' => -1,
- 'children' => $GeneralName
- );
-
- $this->IssuerAltName = $GeneralNames;
-
- $ReasonFlags = array(
- 'type' => ASN1::TYPE_BIT_STRING,
- 'mapping' => array(
- 'unused',
- 'keyCompromise',
- 'cACompromise',
- 'affiliationChanged',
- 'superseded',
- 'cessationOfOperation',
- 'certificateHold',
- 'privilegeWithdrawn',
- 'aACompromise'
- )
- );
-
- $DistributionPointName = array(
- 'type' => ASN1::TYPE_CHOICE,
- 'children' => array(
- 'fullName' => array(
- 'constant' => 0,
- 'optional' => true,
- 'implicit' => true
- ) + $GeneralNames,
- 'nameRelativeToCRLIssuer' => array(
- 'constant' => 1,
- 'optional' => true,
- 'implicit' => true
- ) + $this->RelativeDistinguishedName
- )
- );
-
- $DistributionPoint = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'distributionPoint' => array(
- 'constant' => 0,
- 'optional' => true,
- 'explicit' => true
- ) + $DistributionPointName,
- 'reasons' => array(
- 'constant' => 1,
- 'optional' => true,
- 'implicit' => true
- ) + $ReasonFlags,
- 'cRLIssuer' => array(
- 'constant' => 2,
- 'optional' => true,
- 'implicit' => true
- ) + $GeneralNames
- )
- );
-
- $this->CRLDistributionPoints = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'min' => 1,
- 'max' => -1,
- 'children' => $DistributionPoint
- );
-
- $this->AuthorityKeyIdentifier = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'keyIdentifier' => array(
- 'constant' => 0,
- 'optional' => true,
- 'implicit' => true
- ) + $this->KeyIdentifier,
- 'authorityCertIssuer' => array(
- 'constant' => 1,
- 'optional' => true,
- 'implicit' => true
- ) + $GeneralNames,
- 'authorityCertSerialNumber' => array(
- 'constant' => 2,
- 'optional' => true,
- 'implicit' => true
- ) + $CertificateSerialNumber
- )
- );
-
- $PolicyQualifierId = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER);
-
- $PolicyQualifierInfo = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'policyQualifierId' => $PolicyQualifierId,
- 'qualifier' => array('type' => ASN1::TYPE_ANY)
- )
- );
-
- $CertPolicyId = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER);
-
- $PolicyInformation = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'policyIdentifier' => $CertPolicyId,
- 'policyQualifiers' => array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'min' => 0,
- 'max' => -1,
- 'optional' => true,
- 'children' => $PolicyQualifierInfo
- )
- )
- );
-
- $this->CertificatePolicies = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'min' => 1,
- 'max' => -1,
- 'children' => $PolicyInformation
- );
-
- $this->PolicyMappings = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'min' => 1,
- 'max' => -1,
- 'children' => array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'issuerDomainPolicy' => $CertPolicyId,
- 'subjectDomainPolicy' => $CertPolicyId
- )
- )
- );
-
- $KeyPurposeId = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER);
-
- $this->ExtKeyUsageSyntax = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'min' => 1,
- 'max' => -1,
- 'children' => $KeyPurposeId
- );
-
- $AccessDescription = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'accessMethod' => array('type' => ASN1::TYPE_OBJECT_IDENTIFIER),
- 'accessLocation' => $GeneralName
- )
- );
-
- $this->AuthorityInfoAccessSyntax = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'min' => 1,
- 'max' => -1,
- 'children' => $AccessDescription
- );
-
- $this->SubjectInfoAccessSyntax = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'min' => 1,
- 'max' => -1,
- 'children' => $AccessDescription
- );
-
- $this->SubjectAltName = $GeneralNames;
-
- $this->PrivateKeyUsagePeriod = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'notBefore' => array(
- 'constant' => 0,
- 'optional' => true,
- 'implicit' => true,
- 'type' => ASN1::TYPE_GENERALIZED_TIME),
- 'notAfter' => array(
- 'constant' => 1,
- 'optional' => true,
- 'implicit' => true,
- 'type' => ASN1::TYPE_GENERALIZED_TIME)
- )
- );
-
- $BaseDistance = array('type' => ASN1::TYPE_INTEGER);
-
- $GeneralSubtree = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'base' => $GeneralName,
- 'minimum' => array(
- 'constant' => 0,
- 'optional' => true,
- 'implicit' => true,
- 'default' => new BigInteger(0)
- ) + $BaseDistance,
- 'maximum' => array(
- 'constant' => 1,
- 'optional' => true,
- 'implicit' => true,
- ) + $BaseDistance
- )
- );
-
- $GeneralSubtrees = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'min' => 1,
- 'max' => -1,
- 'children' => $GeneralSubtree
- );
-
- $this->NameConstraints = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'permittedSubtrees' => array(
- 'constant' => 0,
- 'optional' => true,
- 'implicit' => true
- ) + $GeneralSubtrees,
- 'excludedSubtrees' => array(
- 'constant' => 1,
- 'optional' => true,
- 'implicit' => true
- ) + $GeneralSubtrees
- )
- );
-
- $this->CPSuri = array('type' => ASN1::TYPE_IA5_STRING);
-
- $DisplayText = array(
- 'type' => ASN1::TYPE_CHOICE,
- 'children' => array(
- 'ia5String' => array('type' => ASN1::TYPE_IA5_STRING),
- 'visibleString' => array('type' => ASN1::TYPE_VISIBLE_STRING),
- 'bmpString' => array('type' => ASN1::TYPE_BMP_STRING),
- 'utf8String' => array('type' => ASN1::TYPE_UTF8_STRING)
- )
- );
-
- $NoticeReference = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'organization' => $DisplayText,
- 'noticeNumbers' => array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'min' => 1,
- 'max' => 200,
- 'children' => array('type' => ASN1::TYPE_INTEGER)
- )
- )
- );
-
- $this->UserNotice = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'noticeRef' => array(
- 'optional' => true,
- 'implicit' => true
- ) + $NoticeReference,
- 'explicitText' => array(
- 'optional' => true,
- 'implicit' => true
- ) + $DisplayText
- )
- );
-
- // mapping is from <http://www.mozilla.org/projects/security/pki/nss/tech-notes/tn3.html>
- $this->netscape_cert_type = array(
- 'type' => ASN1::TYPE_BIT_STRING,
- 'mapping' => array(
- 'SSLClient',
- 'SSLServer',
- 'Email',
- 'ObjectSigning',
- 'Reserved',
- 'SSLCA',
- 'EmailCA',
- 'ObjectSigningCA'
- )
- );
-
- $this->netscape_comment = array('type' => ASN1::TYPE_IA5_STRING);
- $this->netscape_ca_policy_url = array('type' => ASN1::TYPE_IA5_STRING);
-
- // attribute is used in RFC2986 but we're using the RFC5280 definition
-
- $Attribute = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'type' => $AttributeType,
- 'value'=> array(
- 'type' => ASN1::TYPE_SET,
- 'min' => 1,
- 'max' => -1,
- 'children' => $this->AttributeValue
- )
- )
- );
-
- $this->SubjectDirectoryAttributes = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'min' => 1,
- 'max' => -1,
- 'children' => $Attribute
- );
-
- // adapted from <http://tools.ietf.org/html/rfc2986>
-
- $Attributes = array(
- 'type' => ASN1::TYPE_SET,
- 'min' => 1,
- 'max' => -1,
- 'children' => $Attribute
- );
-
- $CertificationRequestInfo = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'version' => array(
- 'type' => ASN1::TYPE_INTEGER,
- 'mapping' => array('v1')
- ),
- 'subject' => $this->Name,
- 'subjectPKInfo' => $SubjectPublicKeyInfo,
- 'attributes' => array(
- 'constant' => 0,
- 'optional' => true,
- 'implicit' => true
- ) + $Attributes,
- )
- );
-
- $this->CertificationRequest = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'certificationRequestInfo' => $CertificationRequestInfo,
- 'signatureAlgorithm' => $AlgorithmIdentifier,
- 'signature' => array('type' => ASN1::TYPE_BIT_STRING)
- )
- );
-
- $RevokedCertificate = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'userCertificate' => $CertificateSerialNumber,
- 'revocationDate' => $Time,
- 'crlEntryExtensions' => array(
- 'optional' => true
- ) + $this->Extensions
- )
- );
-
- $TBSCertList = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'version' => array(
- 'optional' => true,
- 'default' => 'v1'
- ) + $Version,
- 'signature' => $AlgorithmIdentifier,
- 'issuer' => $this->Name,
- 'thisUpdate' => $Time,
- 'nextUpdate' => array(
- 'optional' => true
- ) + $Time,
- 'revokedCertificates' => array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'optional' => true,
- 'min' => 0,
- 'max' => -1,
- 'children' => $RevokedCertificate
- ),
- 'crlExtensions' => array(
- 'constant' => 0,
- 'optional' => true,
- 'explicit' => true
- ) + $this->Extensions
- )
- );
-
- $this->CertificateList = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'tbsCertList' => $TBSCertList,
- 'signatureAlgorithm' => $AlgorithmIdentifier,
- 'signature' => array('type' => ASN1::TYPE_BIT_STRING)
- )
- );
-
- $this->CRLNumber = array('type' => ASN1::TYPE_INTEGER);
-
- $this->CRLReason = array('type' => ASN1::TYPE_ENUMERATED,
- 'mapping' => array(
- 'unspecified',
- 'keyCompromise',
- 'cACompromise',
- 'affiliationChanged',
- 'superseded',
- 'cessationOfOperation',
- 'certificateHold',
- // Value 7 is not used.
- 8 => 'removeFromCRL',
- 'privilegeWithdrawn',
- 'aACompromise'
- )
- );
-
- $this->IssuingDistributionPoint = array('type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'distributionPoint' => array(
- 'constant' => 0,
- 'optional' => true,
- 'explicit' => true
- ) + $DistributionPointName,
- 'onlyContainsUserCerts' => array(
- 'type' => ASN1::TYPE_BOOLEAN,
- 'constant' => 1,
- 'optional' => true,
- 'default' => false,
- 'implicit' => true
- ),
- 'onlyContainsCACerts' => array(
- 'type' => ASN1::TYPE_BOOLEAN,
- 'constant' => 2,
- 'optional' => true,
- 'default' => false,
- 'implicit' => true
- ),
- 'onlySomeReasons' => array(
- 'constant' => 3,
- 'optional' => true,
- 'implicit' => true
- ) + $ReasonFlags,
- 'indirectCRL' => array(
- 'type' => ASN1::TYPE_BOOLEAN,
- 'constant' => 4,
- 'optional' => true,
- 'default' => false,
- 'implicit' => true
- ),
- 'onlyContainsAttributeCerts' => array(
- 'type' => ASN1::TYPE_BOOLEAN,
- 'constant' => 5,
- 'optional' => true,
- 'default' => false,
- 'implicit' => true
- )
- )
- );
-
- $this->InvalidityDate = array('type' => ASN1::TYPE_GENERALIZED_TIME);
-
- $this->CertificateIssuer = $GeneralNames;
-
- $this->HoldInstructionCode = array('type' => ASN1::TYPE_OBJECT_IDENTIFIER);
-
- $PublicKeyAndChallenge = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'spki' => $SubjectPublicKeyInfo,
- 'challenge' => array('type' => ASN1::TYPE_IA5_STRING)
- )
- );
-
- $this->SignedPublicKeyAndChallenge = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'children' => array(
- 'publicKeyAndChallenge' => $PublicKeyAndChallenge,
- 'signatureAlgorithm' => $AlgorithmIdentifier,
- 'signature' => array('type' => ASN1::TYPE_BIT_STRING)
- )
- );
-
- $this->PostalAddress = array(
- 'type' => ASN1::TYPE_SEQUENCE,
- 'optional' => true,
- 'min' => 1,
- 'max' => -1,
- 'children' => $this->DirectoryString
- );
-
- // OIDs from RFC5280 and those RFCs mentioned in RFC5280#section-4.1.1.2
- $this->oids = array(
- '1.3.6.1.5.5.7' => 'id-pkix',
- '1.3.6.1.5.5.7.1' => 'id-pe',
- '1.3.6.1.5.5.7.2' => 'id-qt',
- '1.3.6.1.5.5.7.3' => 'id-kp',
- '1.3.6.1.5.5.7.48' => 'id-ad',
- '1.3.6.1.5.5.7.2.1' => 'id-qt-cps',
- '1.3.6.1.5.5.7.2.2' => 'id-qt-unotice',
- '1.3.6.1.5.5.7.48.1' =>'id-ad-ocsp',
- '1.3.6.1.5.5.7.48.2' => 'id-ad-caIssuers',
- '1.3.6.1.5.5.7.48.3' => 'id-ad-timeStamping',
- '1.3.6.1.5.5.7.48.5' => 'id-ad-caRepository',
- '2.5.4' => 'id-at',
- '2.5.4.41' => 'id-at-name',
- '2.5.4.4' => 'id-at-surname',
- '2.5.4.42' => 'id-at-givenName',
- '2.5.4.43' => 'id-at-initials',
- '2.5.4.44' => 'id-at-generationQualifier',
- '2.5.4.3' => 'id-at-commonName',
- '2.5.4.7' => 'id-at-localityName',
- '2.5.4.8' => 'id-at-stateOrProvinceName',
- '2.5.4.10' => 'id-at-organizationName',
- '2.5.4.11' => 'id-at-organizationalUnitName',
- '2.5.4.12' => 'id-at-title',
- '2.5.4.13' => 'id-at-description',
- '2.5.4.46' => 'id-at-dnQualifier',
- '2.5.4.6' => 'id-at-countryName',
- '2.5.4.5' => 'id-at-serialNumber',
- '2.5.4.65' => 'id-at-pseudonym',
- '2.5.4.17' => 'id-at-postalCode',
- '2.5.4.9' => 'id-at-streetAddress',
- '2.5.4.45' => 'id-at-uniqueIdentifier',
- '2.5.4.72' => 'id-at-role',
- '2.5.4.16' => 'id-at-postalAddress',
- '1.3.6.1.4.1.311.60.2.1.3' => 'jurisdictionOfIncorporationCountryName',
- '1.3.6.1.4.1.311.60.2.1.2' => 'jurisdictionOfIncorporationStateOrProvinceName',
- '1.3.6.1.4.1.311.60.2.1.1' => 'jurisdictionLocalityName',
- '2.5.4.15' => 'id-at-businessCategory',
-
- '0.9.2342.19200300.100.1.25' => 'id-domainComponent',
- '1.2.840.113549.1.9' => 'pkcs-9',
- '1.2.840.113549.1.9.1' => 'pkcs-9-at-emailAddress',
- '2.5.29' => 'id-ce',
- '2.5.29.35' => 'id-ce-authorityKeyIdentifier',
- '2.5.29.14' => 'id-ce-subjectKeyIdentifier',
- '2.5.29.15' => 'id-ce-keyUsage',
- '2.5.29.16' => 'id-ce-privateKeyUsagePeriod',
- '2.5.29.32' => 'id-ce-certificatePolicies',
- '2.5.29.32.0' => 'anyPolicy',
-
- '2.5.29.33' => 'id-ce-policyMappings',
- '2.5.29.17' => 'id-ce-subjectAltName',
- '2.5.29.18' => 'id-ce-issuerAltName',
- '2.5.29.9' => 'id-ce-subjectDirectoryAttributes',
- '2.5.29.19' => 'id-ce-basicConstraints',
- '2.5.29.30' => 'id-ce-nameConstraints',
- '2.5.29.36' => 'id-ce-policyConstraints',
- '2.5.29.31' => 'id-ce-cRLDistributionPoints',
- '2.5.29.37' => 'id-ce-extKeyUsage',
- '2.5.29.37.0' => 'anyExtendedKeyUsage',
- '1.3.6.1.5.5.7.3.1' => 'id-kp-serverAuth',
- '1.3.6.1.5.5.7.3.2' => 'id-kp-clientAuth',
- '1.3.6.1.5.5.7.3.3' => 'id-kp-codeSigning',
- '1.3.6.1.5.5.7.3.4' => 'id-kp-emailProtection',
- '1.3.6.1.5.5.7.3.8' => 'id-kp-timeStamping',
- '1.3.6.1.5.5.7.3.9' => 'id-kp-OCSPSigning',
- '2.5.29.54' => 'id-ce-inhibitAnyPolicy',
- '2.5.29.46' => 'id-ce-freshestCRL',
- '1.3.6.1.5.5.7.1.1' => 'id-pe-authorityInfoAccess',
- '1.3.6.1.5.5.7.1.11' => 'id-pe-subjectInfoAccess',
- '2.5.29.20' => 'id-ce-cRLNumber',
- '2.5.29.28' => 'id-ce-issuingDistributionPoint',
- '2.5.29.27' => 'id-ce-deltaCRLIndicator',
- '2.5.29.21' => 'id-ce-cRLReasons',
- '2.5.29.29' => 'id-ce-certificateIssuer',
- '2.5.29.23' => 'id-ce-holdInstructionCode',
- '1.2.840.10040.2' => 'holdInstruction',
- '1.2.840.10040.2.1' => 'id-holdinstruction-none',
- '1.2.840.10040.2.2' => 'id-holdinstruction-callissuer',
- '1.2.840.10040.2.3' => 'id-holdinstruction-reject',
- '2.5.29.24' => 'id-ce-invalidityDate',
-
- '1.2.840.113549.2.2' => 'md2',
- '1.2.840.113549.2.5' => 'md5',
- '1.3.14.3.2.26' => 'id-sha1',
- '1.2.840.10040.4.1' => 'id-dsa',
- '1.2.840.10040.4.3' => 'id-dsa-with-sha1',
- '1.2.840.113549.1.1' => 'pkcs-1',
- '1.2.840.113549.1.1.1' => 'rsaEncryption',
- '1.2.840.113549.1.1.2' => 'md2WithRSAEncryption',
- '1.2.840.113549.1.1.4' => 'md5WithRSAEncryption',
- '1.2.840.113549.1.1.5' => 'sha1WithRSAEncryption',
- '1.2.840.10046.2.1' => 'dhpublicnumber',
- '2.16.840.1.101.2.1.1.22' => 'id-keyExchangeAlgorithm',
- '1.2.840.10045' => 'ansi-X9-62',
- '1.2.840.10045.4' => 'id-ecSigType',
- '1.2.840.10045.4.1' => 'ecdsa-with-SHA1',
- '1.2.840.10045.1' => 'id-fieldType',
- '1.2.840.10045.1.1' => 'prime-field',
- '1.2.840.10045.1.2' => 'characteristic-two-field',
- '1.2.840.10045.1.2.3' => 'id-characteristic-two-basis',
- '1.2.840.10045.1.2.3.1' => 'gnBasis',
- '1.2.840.10045.1.2.3.2' => 'tpBasis',
- '1.2.840.10045.1.2.3.3' => 'ppBasis',
- '1.2.840.10045.2' => 'id-publicKeyType',
- '1.2.840.10045.2.1' => 'id-ecPublicKey',
- '1.2.840.10045.3' => 'ellipticCurve',
- '1.2.840.10045.3.0' => 'c-TwoCurve',
- '1.2.840.10045.3.0.1' => 'c2pnb163v1',
- '1.2.840.10045.3.0.2' => 'c2pnb163v2',
- '1.2.840.10045.3.0.3' => 'c2pnb163v3',
- '1.2.840.10045.3.0.4' => 'c2pnb176w1',
- '1.2.840.10045.3.0.5' => 'c2pnb191v1',
- '1.2.840.10045.3.0.6' => 'c2pnb191v2',
- '1.2.840.10045.3.0.7' => 'c2pnb191v3',
- '1.2.840.10045.3.0.8' => 'c2pnb191v4',
- '1.2.840.10045.3.0.9' => 'c2pnb191v5',
- '1.2.840.10045.3.0.10' => 'c2pnb208w1',
- '1.2.840.10045.3.0.11' => 'c2pnb239v1',
- '1.2.840.10045.3.0.12' => 'c2pnb239v2',
- '1.2.840.10045.3.0.13' => 'c2pnb239v3',
- '1.2.840.10045.3.0.14' => 'c2pnb239v4',
- '1.2.840.10045.3.0.15' => 'c2pnb239v5',
- '1.2.840.10045.3.0.16' => 'c2pnb272w1',
- '1.2.840.10045.3.0.17' => 'c2pnb304w1',
- '1.2.840.10045.3.0.18' => 'c2pnb359v1',
- '1.2.840.10045.3.0.19' => 'c2pnb368w1',
- '1.2.840.10045.3.0.20' => 'c2pnb431r1',
- '1.2.840.10045.3.1' => 'primeCurve',
- '1.2.840.10045.3.1.1' => 'prime192v1',
- '1.2.840.10045.3.1.2' => 'prime192v2',
- '1.2.840.10045.3.1.3' => 'prime192v3',
- '1.2.840.10045.3.1.4' => 'prime239v1',
- '1.2.840.10045.3.1.5' => 'prime239v2',
- '1.2.840.10045.3.1.6' => 'prime239v3',
- '1.2.840.10045.3.1.7' => 'prime256v1',
- '1.2.840.113549.1.1.7' => 'id-RSAES-OAEP',
- '1.2.840.113549.1.1.9' => 'id-pSpecified',
- '1.2.840.113549.1.1.10' => 'id-RSASSA-PSS',
- '1.2.840.113549.1.1.8' => 'id-mgf1',
- '1.2.840.113549.1.1.14' => 'sha224WithRSAEncryption',
- '1.2.840.113549.1.1.11' => 'sha256WithRSAEncryption',
- '1.2.840.113549.1.1.12' => 'sha384WithRSAEncryption',
- '1.2.840.113549.1.1.13' => 'sha512WithRSAEncryption',
- '2.16.840.1.101.3.4.2.4' => 'id-sha224',
- '2.16.840.1.101.3.4.2.1' => 'id-sha256',
- '2.16.840.1.101.3.4.2.2' => 'id-sha384',
- '2.16.840.1.101.3.4.2.3' => 'id-sha512',
- '1.2.643.2.2.4' => 'id-GostR3411-94-with-GostR3410-94',
- '1.2.643.2.2.3' => 'id-GostR3411-94-with-GostR3410-2001',
- '1.2.643.2.2.20' => 'id-GostR3410-2001',
- '1.2.643.2.2.19' => 'id-GostR3410-94',
- // Netscape Object Identifiers from "Netscape Certificate Extensions"
- '2.16.840.1.113730' => 'netscape',
- '2.16.840.1.113730.1' => 'netscape-cert-extension',
- '2.16.840.1.113730.1.1' => 'netscape-cert-type',
- '2.16.840.1.113730.1.13' => 'netscape-comment',
- '2.16.840.1.113730.1.8' => 'netscape-ca-policy-url',
- // the following are X.509 extensions not supported by phpseclib
- '1.3.6.1.5.5.7.1.12' => 'id-pe-logotype',
- '1.2.840.113533.7.65.0' => 'entrustVersInfo',
- '2.16.840.1.113733.1.6.9' => 'verisignPrivate',
- // for Certificate Signing Requests
- // see http://tools.ietf.org/html/rfc2985
- '1.2.840.113549.1.9.2' => 'pkcs-9-at-unstructuredName', // PKCS #9 unstructured name
- '1.2.840.113549.1.9.7' => 'pkcs-9-at-challengePassword', // Challenge password for certificate revocations
- '1.2.840.113549.1.9.14' => 'pkcs-9-at-extensionRequest' // Certificate extension request
- );
+ if (!self::$oidsLoaded) {
+ // OIDs from RFC5280 and those RFCs mentioned in RFC5280#section-4.1.1.2
+ ASN1::loadOIDs([
+ //'id-pkix' => '1.3.6.1.5.5.7',
+ //'id-pe' => '1.3.6.1.5.5.7.1',
+ //'id-qt' => '1.3.6.1.5.5.7.2',
+ //'id-kp' => '1.3.6.1.5.5.7.3',
+ //'id-ad' => '1.3.6.1.5.5.7.48',
+ 'id-qt-cps' => '1.3.6.1.5.5.7.2.1',
+ 'id-qt-unotice' => '1.3.6.1.5.5.7.2.2',
+ 'id-ad-ocsp' => '1.3.6.1.5.5.7.48.1',
+ 'id-ad-caIssuers' => '1.3.6.1.5.5.7.48.2',
+ 'id-ad-timeStamping' => '1.3.6.1.5.5.7.48.3',
+ 'id-ad-caRepository' => '1.3.6.1.5.5.7.48.5',
+ //'id-at' => '2.5.4',
+ 'id-at-name' => '2.5.4.41',
+ 'id-at-surname' => '2.5.4.4',
+ 'id-at-givenName' => '2.5.4.42',
+ 'id-at-initials' => '2.5.4.43',
+ 'id-at-generationQualifier' => '2.5.4.44',
+ 'id-at-commonName' => '2.5.4.3',
+ 'id-at-localityName' => '2.5.4.7',
+ 'id-at-stateOrProvinceName' => '2.5.4.8',
+ 'id-at-organizationName' => '2.5.4.10',
+ 'id-at-organizationalUnitName' => '2.5.4.11',
+ 'id-at-title' => '2.5.4.12',
+ 'id-at-description' => '2.5.4.13',
+ 'id-at-dnQualifier' => '2.5.4.46',
+ 'id-at-countryName' => '2.5.4.6',
+ 'id-at-serialNumber' => '2.5.4.5',
+ 'id-at-pseudonym' => '2.5.4.65',
+ 'id-at-postalCode' => '2.5.4.17',
+ 'id-at-streetAddress' => '2.5.4.9',
+ 'id-at-uniqueIdentifier' => '2.5.4.45',
+ 'id-at-role' => '2.5.4.72',
+ 'id-at-postalAddress' => '2.5.4.16',
+ 'jurisdictionOfIncorporationCountryName' => '1.3.6.1.4.1.311.60.2.1.3',
+ 'jurisdictionOfIncorporationStateOrProvinceName' => '1.3.6.1.4.1.311.60.2.1.2',
+ 'jurisdictionLocalityName' => '1.3.6.1.4.1.311.60.2.1.1',
+ 'id-at-businessCategory' => '2.5.4.15',
+
+ //'id-domainComponent' => '0.9.2342.19200300.100.1.25',
+ //'pkcs-9' => '1.2.840.113549.1.9',
+ 'pkcs-9-at-emailAddress' => '1.2.840.113549.1.9.1',
+ //'id-ce' => '2.5.29',
+ 'id-ce-authorityKeyIdentifier' => '2.5.29.35',
+ 'id-ce-subjectKeyIdentifier' => '2.5.29.14',
+ 'id-ce-keyUsage' => '2.5.29.15',
+ 'id-ce-privateKeyUsagePeriod' => '2.5.29.16',
+ 'id-ce-certificatePolicies' => '2.5.29.32',
+ //'anyPolicy' => '2.5.29.32.0',
+
+ 'id-ce-policyMappings' => '2.5.29.33',
+
+ 'id-ce-subjectAltName' => '2.5.29.17',
+ 'id-ce-issuerAltName' => '2.5.29.18',
+ 'id-ce-subjectDirectoryAttributes' => '2.5.29.9',
+ 'id-ce-basicConstraints' => '2.5.29.19',
+ 'id-ce-nameConstraints' => '2.5.29.30',
+ 'id-ce-policyConstraints' => '2.5.29.36',
+ 'id-ce-cRLDistributionPoints' => '2.5.29.31',
+ 'id-ce-extKeyUsage' => '2.5.29.37',
+ //'anyExtendedKeyUsage' => '2.5.29.37.0',
+ 'id-kp-serverAuth' => '1.3.6.1.5.5.7.3.1',
+ 'id-kp-clientAuth' => '1.3.6.1.5.5.7.3.2',
+ 'id-kp-codeSigning' => '1.3.6.1.5.5.7.3.3',
+ 'id-kp-emailProtection' => '1.3.6.1.5.5.7.3.4',
+ 'id-kp-timeStamping' => '1.3.6.1.5.5.7.3.8',
+ 'id-kp-OCSPSigning' => '1.3.6.1.5.5.7.3.9',
+ 'id-ce-inhibitAnyPolicy' => '2.5.29.54',
+ 'id-ce-freshestCRL' => '2.5.29.46',
+ 'id-pe-authorityInfoAccess' => '1.3.6.1.5.5.7.1.1',
+ 'id-pe-subjectInfoAccess' => '1.3.6.1.5.5.7.1.11',
+ 'id-ce-cRLNumber' => '2.5.29.20',
+ 'id-ce-issuingDistributionPoint' => '2.5.29.28',
+ 'id-ce-deltaCRLIndicator' => '2.5.29.27',
+ 'id-ce-cRLReasons' => '2.5.29.21',
+ 'id-ce-certificateIssuer' => '2.5.29.29',
+ 'id-ce-holdInstructionCode' => '2.5.29.23',
+ //'holdInstruction' => '1.2.840.10040.2',
+ 'id-holdinstruction-none' => '1.2.840.10040.2.1',
+ 'id-holdinstruction-callissuer' => '1.2.840.10040.2.2',
+ 'id-holdinstruction-reject' => '1.2.840.10040.2.3',
+ 'id-ce-invalidityDate' => '2.5.29.24',
+
+ 'rsaEncryption' => '1.2.840.113549.1.1.1',
+ 'md2WithRSAEncryption' => '1.2.840.113549.1.1.2',
+ 'md5WithRSAEncryption' => '1.2.840.113549.1.1.4',
+ 'sha1WithRSAEncryption' => '1.2.840.113549.1.1.5',
+ 'sha224WithRSAEncryption' => '1.2.840.113549.1.1.14',
+ 'sha256WithRSAEncryption' => '1.2.840.113549.1.1.11',
+ 'sha384WithRSAEncryption' => '1.2.840.113549.1.1.12',
+ 'sha512WithRSAEncryption' => '1.2.840.113549.1.1.13',
+
+ 'id-ecPublicKey' => '1.2.840.10045.2.1',
+ 'ecdsa-with-SHA1' => '1.2.840.10045.4.1',
+ // from https://tools.ietf.org/html/rfc5758#section-3.2
+ 'ecdsa-with-SHA224' => '1.2.840.10045.4.3.1',
+ 'ecdsa-with-SHA256' => '1.2.840.10045.4.3.2',
+ 'ecdsa-with-SHA384' => '1.2.840.10045.4.3.3',
+ 'ecdsa-with-SHA512' => '1.2.840.10045.4.3.4',
+
+ 'id-dsa' => '1.2.840.10040.4.1',
+ 'id-dsa-with-sha1' => '1.2.840.10040.4.3',
+ // from https://tools.ietf.org/html/rfc5758#section-3.1
+ 'id-dsa-with-sha224' => '2.16.840.1.101.3.4.3.1',
+ 'id-dsa-with-sha256' => '2.16.840.1.101.3.4.3.2',
+
+ // from https://tools.ietf.org/html/rfc8410:
+ 'id-Ed25519' => '1.3.101.112',
+ 'id-Ed448' => '1.3.101.113',
+
+ 'id-RSASSA-PSS' => '1.2.840.113549.1.1.10',
+
+ //'id-sha224' => '2.16.840.1.101.3.4.2.4',
+ //'id-sha256' => '2.16.840.1.101.3.4.2.1',
+ //'id-sha384' => '2.16.840.1.101.3.4.2.2',
+ //'id-sha512' => '2.16.840.1.101.3.4.2.3',
+ //'id-GostR3411-94-with-GostR3410-94' => '1.2.643.2.2.4',
+ //'id-GostR3411-94-with-GostR3410-2001' => '1.2.643.2.2.3',
+ //'id-GostR3410-2001' => '1.2.643.2.2.20',
+ //'id-GostR3410-94' => '1.2.643.2.2.19',
+ // Netscape Object Identifiers from "Netscape Certificate Extensions"
+ 'netscape' => '2.16.840.1.113730',
+ 'netscape-cert-extension' => '2.16.840.1.113730.1',
+ 'netscape-cert-type' => '2.16.840.1.113730.1.1',
+ 'netscape-comment' => '2.16.840.1.113730.1.13',
+ 'netscape-ca-policy-url' => '2.16.840.1.113730.1.8',
+ // the following are X.509 extensions not supported by phpseclib
+ 'id-pe-logotype' => '1.3.6.1.5.5.7.1.12',
+ 'entrustVersInfo' => '1.2.840.113533.7.65.0',
+ 'verisignPrivate' => '2.16.840.1.113733.1.6.9',
+ // for Certificate Signing Requests
+ // see http://tools.ietf.org/html/rfc2985
+ 'pkcs-9-at-unstructuredName' => '1.2.840.113549.1.9.2', // PKCS #9 unstructured name
+ 'pkcs-9-at-challengePassword' => '1.2.840.113549.1.9.7', // Challenge password for certificate revocations
+ 'pkcs-9-at-extensionRequest' => '1.2.840.113549.1.9.14' // Certificate extension request
+ ]);
+ }
}
/**
@@ -1462,12 +425,11 @@ class X509
*
* Returns an associative array describing the X.509 cert or a false if the cert failed to load
*
- * @param string $cert
+ * @param array|string $cert
* @param int $mode
- * @access public
* @return mixed
*/
- function loadX509($cert, $mode = self::FORMAT_AUTO_DETECT)
+ public function loadX509($cert, $mode = self::FORMAT_AUTO_DETECT)
{
if (is_array($cert) && isset($cert['tbsCertificate'])) {
unset($this->currentCert);
@@ -1486,10 +448,8 @@ class X509
return $cert;
}
- $asn1 = new ASN1();
-
if ($mode != self::FORMAT_DER) {
- $newcert = $this->_extractBER($cert);
+ $newcert = ASN1::extractBER($cert);
if ($mode == self::FORMAT_PEM && $cert == $newcert) {
return false;
}
@@ -1501,11 +461,10 @@ class X509
return false;
}
- $asn1->loadOIDs($this->oids);
- $decoded = $asn1->decodeBER($cert);
+ $decoded = ASN1::decodeBER($cert);
- if (!empty($decoded)) {
- $x509 = $asn1->asn1map($decoded[0], $this->Certificate);
+ if ($decoded) {
+ $x509 = ASN1::asn1map($decoded[0], Maps\Certificate::MAP);
}
if (!isset($x509) || $x509 === false) {
$this->currentCert = false;
@@ -1514,14 +473,18 @@ class X509
$this->signatureSubject = substr($cert, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
- if ($this->_isSubArrayValid($x509, 'tbsCertificate/extensions')) {
- $this->_mapInExtensions($x509, 'tbsCertificate/extensions', $asn1);
+ if ($this->isSubArrayValid($x509, 'tbsCertificate/extensions')) {
+ $this->mapInExtensions($x509, 'tbsCertificate/extensions');
}
- $this->_mapInDNs($x509, 'tbsCertificate/issuer/rdnSequence', $asn1);
- $this->_mapInDNs($x509, 'tbsCertificate/subject/rdnSequence', $asn1);
+ $this->mapInDNs($x509, 'tbsCertificate/issuer/rdnSequence');
+ $this->mapInDNs($x509, 'tbsCertificate/subject/rdnSequence');
- $key = &$x509['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'];
- $key = $this->_reformatKey($x509['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'], $key);
+ $key = $x509['tbsCertificate']['subjectPublicKeyInfo'];
+ $key = ASN1::encodeDER($key, Maps\SubjectPublicKeyInfo::MAP);
+ $x509['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'] =
+ "-----BEGIN PUBLIC KEY-----\r\n" .
+ chunk_split(base64_encode($key), 64) .
+ "-----END PUBLIC KEY-----";
$this->currentCert = $x509;
$this->dn = $x509['tbsCertificate']['subject'];
@@ -1537,10 +500,9 @@ class X509
*
* @param array $cert
* @param int $format optional
- * @access public
* @return string
*/
- function saveX509($cert, $format = self::FORMAT_PEM)
+ public function saveX509(array $cert, $format = self::FORMAT_PEM)
{
if (!is_array($cert) || !isset($cert['tbsCertificate'])) {
return false;
@@ -1548,32 +510,17 @@ class X509
switch (true) {
// "case !$a: case !$b: break; default: whatever();" is the same thing as "if ($a && $b) whatever()"
- case !($algorithm = $this->_subArray($cert, 'tbsCertificate/subjectPublicKeyInfo/algorithm/algorithm')):
+ case !($algorithm = $this->subArray($cert, 'tbsCertificate/subjectPublicKeyInfo/algorithm/algorithm')):
case is_object($cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']):
break;
default:
- switch ($algorithm) {
- case 'rsaEncryption':
- $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']
- = base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'])));
- /* "[For RSA keys] the parameters field MUST have ASN.1 type NULL for this algorithm identifier."
- -- https://tools.ietf.org/html/rfc3279#section-2.3.1
-
- given that and the fact that RSA keys appear ot be the only key type for which the parameters field can be blank,
- it seems like perhaps the ASN.1 description ought not say the parameters field is OPTIONAL, but whatever.
- */
- $cert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['parameters'] = null;
- // https://tools.ietf.org/html/rfc3279#section-2.2.1
- $cert['signatureAlgorithm']['parameters'] = null;
- $cert['tbsCertificate']['signature']['parameters'] = null;
- }
+ $cert['tbsCertificate']['subjectPublicKeyInfo'] = new Element(
+ base64_decode(preg_replace('#-.+-|[\r\n]#', '', $cert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey']))
+ );
}
- $asn1 = new ASN1();
- $asn1->loadOIDs($this->oids);
-
- $filters = array();
- $type_utf8_string = array('type' => ASN1::TYPE_UTF8_STRING);
+ $filters = [];
+ $type_utf8_string = ['type' => ASN1::TYPE_UTF8_STRING];
$filters['tbsCertificate']['signature']['parameters'] = $type_utf8_string;
$filters['tbsCertificate']['signature']['issuer']['rdnSequence']['value'] = $type_utf8_string;
$filters['tbsCertificate']['issuer']['rdnSequence']['value'] = $type_utf8_string;
@@ -1585,27 +532,31 @@ class X509
$filters['distributionPoint']['fullName']['directoryName']['rdnSequence']['value'] = $type_utf8_string;
$filters['directoryName']['rdnSequence']['value'] = $type_utf8_string;
- /* in the case of policyQualifiers/qualifier, the type has to be \phpseclib\File\ASN1::TYPE_IA5_STRING.
- \phpseclib\File\ASN1::TYPE_PRINTABLE_STRING will cause OpenSSL's X.509 parser to spit out random
+ foreach (self::$extensions as $extension) {
+ $filters['tbsCertificate']['extensions'][] = $extension;
+ }
+
+ /* in the case of policyQualifiers/qualifier, the type has to be \phpseclib3\File\ASN1::TYPE_IA5_STRING.
+ \phpseclib3\File\ASN1::TYPE_PRINTABLE_STRING will cause OpenSSL's X.509 parser to spit out random
characters.
*/
$filters['policyQualifiers']['qualifier']
- = array('type' => ASN1::TYPE_IA5_STRING);
+ = ['type' => ASN1::TYPE_IA5_STRING];
- $asn1->loadFilters($filters);
+ ASN1::setFilters($filters);
- $this->_mapOutExtensions($cert, 'tbsCertificate/extensions', $asn1);
- $this->_mapOutDNs($cert, 'tbsCertificate/issuer/rdnSequence', $asn1);
- $this->_mapOutDNs($cert, 'tbsCertificate/subject/rdnSequence', $asn1);
+ $this->mapOutExtensions($cert, 'tbsCertificate/extensions');
+ $this->mapOutDNs($cert, 'tbsCertificate/issuer/rdnSequence');
+ $this->mapOutDNs($cert, 'tbsCertificate/subject/rdnSequence');
- $cert = $asn1->encodeDER($cert, $this->Certificate);
+ $cert = ASN1::encodeDER($cert, Maps\Certificate::MAP);
switch ($format) {
case self::FORMAT_DER:
return $cert;
// case self::FORMAT_PEM:
default:
- return "-----BEGIN CERTIFICATE-----\r\n" . chunk_split(base64_encode($cert), 64) . '-----END CERTIFICATE-----';
+ return "-----BEGIN CERTIFICATE-----\r\n" . chunk_split(Strings::base64_encode($cert), 64) . '-----END CERTIFICATE-----';
}
}
@@ -1615,27 +566,27 @@ class X509
*
* @param array $root (by reference)
* @param string $path
- * @param object $asn1
- * @access private
*/
- function _mapInExtensions(&$root, $path, $asn1)
+ private function mapInExtensions(array &$root, $path)
{
- $extensions = &$this->_subArrayUnchecked($root, $path);
+ $extensions = &$this->subArrayUnchecked($root, $path);
if ($extensions) {
for ($i = 0; $i < count($extensions); $i++) {
$id = $extensions[$i]['extnId'];
$value = &$extensions[$i]['extnValue'];
- $value = base64_decode($value);
/* [extnValue] contains the DER encoding of an ASN.1 value
corresponding to the extension type identified by extnID */
- $map = $this->_getMapping($id);
+ $map = $this->getMapping($id);
if (!is_bool($map)) {
$decoder = $id == 'id-ce-nameConstraints' ?
- array($this, '_decodeNameConstraintIP') :
- array($this, '_decodeIP');
- $decoded = $asn1->decodeBER($value);
- $mapped = $asn1->asn1map($decoded[0], $map, array('iPAddress' => $decoder));
+ [static::class, 'decodeNameConstraintIP'] :
+ [static::class, 'decodeIP'];
+ $decoded = ASN1::decodeBER($value);
+ if (!$decoded) {
+ continue;
+ }
+ $mapped = ASN1::asn1map($decoded[0], $map, ['iPAddress' => $decoder]);
$value = $mapped === false ? $decoded[0] : $mapped;
if ($id == 'id-ce-certificatePolicies') {
@@ -1645,18 +596,19 @@ class X509
}
for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++) {
$subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId'];
- $map = $this->_getMapping($subid);
+ $map = $this->getMapping($subid);
$subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier'];
if ($map !== false) {
- $decoded = $asn1->decodeBER($subvalue);
- $mapped = $asn1->asn1map($decoded[0], $map);
+ $decoded = ASN1::decodeBER($subvalue);
+ if (!$decoded) {
+ continue;
+ }
+ $mapped = ASN1::asn1map($decoded[0], $map);
$subvalue = $mapped === false ? $decoded[0] : $mapped;
}
}
}
}
- } else {
- $value = base64_encode($value);
}
}
}
@@ -1668,12 +620,30 @@ class X509
*
* @param array $root (by reference)
* @param string $path
- * @param object $asn1
- * @access private
*/
- function _mapOutExtensions(&$root, $path, $asn1)
- {
- $extensions = &$this->_subArray($root, $path);
+ private function mapOutExtensions(array &$root, $path)
+ {
+ $extensions = &$this->subArray($root, $path, !empty($this->extensionValues));
+
+ foreach ($this->extensionValues as $id => $data) {
+ $critical = $data['critical'];
+ $replace = $data['replace'];
+ $value = $data['value'];
+ $newext = [
+ 'extnId' => $id,
+ 'extnValue' => $value,
+ 'critical' => $critical
+ ];
+ if ($replace) {
+ foreach ($extensions as $key => $value) {
+ if ($value['extnId'] == $id) {
+ $extensions[$key] = $newext;
+ continue 2;
+ }
+ }
+ }
+ $extensions[] = $newext;
+ }
if (is_array($extensions)) {
$size = count($extensions);
@@ -1693,12 +663,12 @@ class X509
}
for ($k = 0; $k < count($value[$j]['policyQualifiers']); $k++) {
$subid = $value[$j]['policyQualifiers'][$k]['policyQualifierId'];
- $map = $this->_getMapping($subid);
+ $map = $this->getMapping($subid);
$subvalue = &$value[$j]['policyQualifiers'][$k]['qualifier'];
if ($map !== false) {
- // by default \phpseclib\File\ASN1 will try to render qualifier as a \phpseclib\File\ASN1::TYPE_IA5_STRING since it's
- // actual type is \phpseclib\File\ASN1::TYPE_ANY
- $subvalue = new Element($asn1->encodeDER($subvalue, $map));
+ // by default \phpseclib3\File\ASN1 will try to render qualifier as a \phpseclib3\File\ASN1::TYPE_IA5_STRING since it's
+ // actual type is \phpseclib3\File\ASN1::TYPE_ANY
+ $subvalue = new Element(ASN1::encodeDER($subvalue, $map));
}
}
}
@@ -1714,15 +684,14 @@ class X509
/* [extnValue] contains the DER encoding of an ASN.1 value
corresponding to the extension type identified by extnID */
- $map = $this->_getMapping($id);
+ $map = $this->getMapping($id);
if (is_bool($map)) {
if (!$map) {
- user_error($id . ' is not a currently supported extension');
+ //user_error($id . ' is not a currently supported extension');
unset($extensions[$i]);
}
} else {
- $temp = $asn1->encodeDER($value, $map, array('iPAddress' => array($this, '_encodeIP')));
- $value = base64_encode($temp);
+ $value = ASN1::encodeDER($value, $map, ['iPAddress' => [static::class, 'encodeIP']]);
}
}
}
@@ -1734,34 +703,35 @@ class X509
*
* @param array $root (by reference)
* @param string $path
- * @param object $asn1
- * @access private
*/
- function _mapInAttributes(&$root, $path, $asn1)
+ private function mapInAttributes(&$root, $path)
{
- $attributes = &$this->_subArray($root, $path);
+ $attributes = &$this->subArray($root, $path);
if (is_array($attributes)) {
for ($i = 0; $i < count($attributes); $i++) {
$id = $attributes[$i]['type'];
/* $value contains the DER encoding of an ASN.1 value
corresponding to the attribute type identified by type */
- $map = $this->_getMapping($id);
+ $map = $this->getMapping($id);
if (is_array($attributes[$i]['value'])) {
$values = &$attributes[$i]['value'];
for ($j = 0; $j < count($values); $j++) {
- $value = $asn1->encodeDER($values[$j], $this->AttributeValue);
- $decoded = $asn1->decodeBER($value);
+ $value = ASN1::encodeDER($values[$j], Maps\AttributeValue::MAP);
+ $decoded = ASN1::decodeBER($value);
if (!is_bool($map)) {
- $mapped = $asn1->asn1map($decoded[0], $map);
+ if (!$decoded) {
+ continue;
+ }
+ $mapped = ASN1::asn1map($decoded[0], $map);
if ($mapped !== false) {
$values[$j] = $mapped;
}
- if ($id == 'pkcs-9-at-extensionRequest' && $this->_isSubArrayValid($values, $j)) {
- $this->_mapInExtensions($values, $j, $asn1);
+ if ($id == 'pkcs-9-at-extensionRequest' && $this->isSubArrayValid($values, $j)) {
+ $this->mapInExtensions($values, $j);
}
} elseif ($map) {
- $values[$j] = base64_encode($value);
+ $values[$j] = $value;
}
}
}
@@ -1775,12 +745,10 @@ class X509
*
* @param array $root (by reference)
* @param string $path
- * @param object $asn1
- * @access private
*/
- function _mapOutAttributes(&$root, $path, $asn1)
+ private function mapOutAttributes(&$root, $path)
{
- $attributes = &$this->_subArray($root, $path);
+ $attributes = &$this->subArray($root, $path);
if (is_array($attributes)) {
$size = count($attributes);
@@ -1788,23 +756,26 @@ class X509
/* [value] contains the DER encoding of an ASN.1 value
corresponding to the attribute type identified by type */
$id = $attributes[$i]['type'];
- $map = $this->_getMapping($id);
+ $map = $this->getMapping($id);
if ($map === false) {
- user_error($id . ' is not a currently supported attribute', E_USER_NOTICE);
+ //user_error($id . ' is not a currently supported attribute', E_USER_NOTICE);
unset($attributes[$i]);
} elseif (is_array($attributes[$i]['value'])) {
$values = &$attributes[$i]['value'];
for ($j = 0; $j < count($values); $j++) {
switch ($id) {
case 'pkcs-9-at-extensionRequest':
- $this->_mapOutExtensions($values, $j, $asn1);
+ $this->mapOutExtensions($values, $j);
break;
}
if (!is_bool($map)) {
- $temp = $asn1->encodeDER($values[$j], $map);
- $decoded = $asn1->decodeBER($temp);
- $values[$j] = $asn1->asn1map($decoded[0], $this->AttributeValue);
+ $temp = ASN1::encodeDER($values[$j], $map);
+ $decoded = ASN1::decodeBER($temp);
+ if (!$decoded) {
+ continue;
+ }
+ $values[$j] = ASN1::asn1map($decoded[0], Maps\AttributeValue::MAP);
}
}
}
@@ -1818,12 +789,10 @@ class X509
*
* @param array $root (by reference)
* @param string $path
- * @param object $asn1
- * @access private
*/
- function _mapInDNs(&$root, $path, $asn1)
+ private function mapInDNs(array &$root, $path)
{
- $dns = &$this->_subArray($root, $path);
+ $dns = &$this->subArray($root, $path);
if (is_array($dns)) {
for ($i = 0; $i < count($dns); $i++) {
@@ -1831,10 +800,13 @@ class X509
$type = $dns[$i][$j]['type'];
$value = &$dns[$i][$j]['value'];
if (is_object($value) && $value instanceof Element) {
- $map = $this->_getMapping($type);
+ $map = $this->getMapping($type);
if (!is_bool($map)) {
- $decoded = $asn1->decodeBER($value);
- $value = $asn1->asn1map($decoded[0], $map);
+ $decoded = ASN1::decodeBER($value);
+ if (!$decoded) {
+ continue;
+ }
+ $value = ASN1::asn1map($decoded[0], $map);
}
}
}
@@ -1848,12 +820,10 @@ class X509
*
* @param array $root (by reference)
* @param string $path
- * @param object $asn1
- * @access private
*/
- function _mapOutDNs(&$root, $path, $asn1)
+ private function mapOutDNs(array &$root, $path)
{
- $dns = &$this->_subArray($root, $path);
+ $dns = &$this->subArray($root, $path);
if (is_array($dns)) {
$size = count($dns);
@@ -1865,9 +835,9 @@ class X509
continue;
}
- $map = $this->_getMapping($type);
+ $map = $this->getMapping($type);
if (!is_bool($map)) {
- $value = new Element($asn1->encodeDER($value, $map));
+ $value = new Element(ASN1::encodeDER($value, $map));
}
}
}
@@ -1878,60 +848,61 @@ class X509
* Associate an extension ID to an extension mapping
*
* @param string $extnId
- * @access private
* @return mixed
*/
- function _getMapping($extnId)
+ private function getMapping($extnId)
{
- if (!is_string($extnId)) { // eg. if it's a \phpseclib\File\ASN1\Element object
+ if (!is_string($extnId)) { // eg. if it's a \phpseclib3\File\ASN1\Element object
return true;
}
+ if (isset(self::$extensions[$extnId])) {
+ return self::$extensions[$extnId];
+ }
+
switch ($extnId) {
case 'id-ce-keyUsage':
- return $this->KeyUsage;
+ return Maps\KeyUsage::MAP;
case 'id-ce-basicConstraints':
- return $this->BasicConstraints;
+ return Maps\BasicConstraints::MAP;
case 'id-ce-subjectKeyIdentifier':
- return $this->KeyIdentifier;
+ return Maps\KeyIdentifier::MAP;
case 'id-ce-cRLDistributionPoints':
- return $this->CRLDistributionPoints;
+ return Maps\CRLDistributionPoints::MAP;
case 'id-ce-authorityKeyIdentifier':
- return $this->AuthorityKeyIdentifier;
+ return Maps\AuthorityKeyIdentifier::MAP;
case 'id-ce-certificatePolicies':
- return $this->CertificatePolicies;
+ return Maps\CertificatePolicies::MAP;
case 'id-ce-extKeyUsage':
- return $this->ExtKeyUsageSyntax;
+ return Maps\ExtKeyUsageSyntax::MAP;
case 'id-pe-authorityInfoAccess':
- return $this->AuthorityInfoAccessSyntax;
- case 'id-pe-subjectInfoAccess':
- return $this->SubjectInfoAccessSyntax;
+ return Maps\AuthorityInfoAccessSyntax::MAP;
case 'id-ce-subjectAltName':
- return $this->SubjectAltName;
+ return Maps\SubjectAltName::MAP;
case 'id-ce-subjectDirectoryAttributes':
- return $this->SubjectDirectoryAttributes;
+ return Maps\SubjectDirectoryAttributes::MAP;
case 'id-ce-privateKeyUsagePeriod':
- return $this->PrivateKeyUsagePeriod;
+ return Maps\PrivateKeyUsagePeriod::MAP;
case 'id-ce-issuerAltName':
- return $this->IssuerAltName;
+ return Maps\IssuerAltName::MAP;
case 'id-ce-policyMappings':
- return $this->PolicyMappings;
+ return Maps\PolicyMappings::MAP;
case 'id-ce-nameConstraints':
- return $this->NameConstraints;
+ return Maps\NameConstraints::MAP;
case 'netscape-cert-type':
- return $this->netscape_cert_type;
+ return Maps\netscape_cert_type::MAP;
case 'netscape-comment':
- return $this->netscape_comment;
+ return Maps\netscape_comment::MAP;
case 'netscape-ca-policy-url':
- return $this->netscape_ca_policy_url;
+ return Maps\netscape_ca_policy_url::MAP;
// since id-qt-cps isn't a constructed type it will have already been decoded as a string by the time it gets
// back around to asn1map() and we don't want it decoded again.
//case 'id-qt-cps':
- // return $this->CPSuri;
+ // return Maps\CPSuri::MAP;
case 'id-qt-unotice':
- return $this->UserNotice;
+ return Maps\UserNotice::MAP;
// the following OIDs are unsupported but we don't want them to give notices when calling saveX509().
case 'id-pe-logotype': // http://www.ietf.org/rfc/rfc3709.txt
@@ -1952,31 +923,31 @@ class X509
// CSR attributes
case 'pkcs-9-at-unstructuredName':
- return $this->PKCS9String;
+ return Maps\PKCS9String::MAP;
case 'pkcs-9-at-challengePassword':
- return $this->DirectoryString;
+ return Maps\DirectoryString::MAP;
case 'pkcs-9-at-extensionRequest':
- return $this->Extensions;
+ return Maps\Extensions::MAP;
// CRL extensions.
case 'id-ce-cRLNumber':
- return $this->CRLNumber;
+ return Maps\CRLNumber::MAP;
case 'id-ce-deltaCRLIndicator':
- return $this->CRLNumber;
+ return Maps\CRLNumber::MAP;
case 'id-ce-issuingDistributionPoint':
- return $this->IssuingDistributionPoint;
+ return Maps\IssuingDistributionPoint::MAP;
case 'id-ce-freshestCRL':
- return $this->CRLDistributionPoints;
+ return Maps\CRLDistributionPoints::MAP;
case 'id-ce-cRLReasons':
- return $this->CRLReason;
+ return Maps\CRLReason::MAP;
case 'id-ce-invalidityDate':
- return $this->InvalidityDate;
+ return Maps\InvalidityDate::MAP;
case 'id-ce-certificateIssuer':
- return $this->CertificateIssuer;
+ return Maps\CertificateIssuer::MAP;
case 'id-ce-holdInstructionCode':
- return $this->HoldInstructionCode;
+ return Maps\HoldInstructionCode::MAP;
case 'id-at-postalAddress':
- return $this->PostalAddress;
+ return Maps\PostalAddress::MAP;
}
return false;
@@ -1986,10 +957,9 @@ class X509
* Load an X.509 certificate as a certificate authority
*
* @param string $cert
- * @access public
* @return bool
*/
- function loadCA($cert)
+ public function loadCA($cert)
{
$olddn = $this->dn;
$oldcert = $this->currentCert;
@@ -2053,10 +1023,9 @@ class X509
* not bar.foo.a.com. f*.com matches foo.com but not bar.com.
*
* @param string $url
- * @access public
* @return bool
*/
- function validateURL($url)
+ public function validateURL($url)
{
if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
return false;
@@ -2101,8 +1070,8 @@ class X509
}
if ($value = $this->getDNProp('id-at-commonName')) {
- $value = str_replace(array('.', '*'), array('\.', '[^.]*'), $value[0]);
- return preg_match('#^' . $value . '$#', $components['host']);
+ $value = str_replace(['.', '*'], ['\.', '[^.]*'], $value[0]);
+ return preg_match('#^' . $value . '$#', $components['host']) === 1;
}
return false;
@@ -2113,17 +1082,17 @@ class X509
*
* If $date isn't defined it is assumed to be the current date.
*
- * @param \DateTime|string $date optional
- * @access public
+ * @param \DateTimeInterface|string $date optional
+ * @return bool
*/
- function validateDate($date = null)
+ public function validateDate($date = null)
{
if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
return false;
}
if (!isset($date)) {
- $date = new DateTime(null, new DateTimeZone(@date_default_timezone_get()));
+ $date = new \DateTimeImmutable('now', new \DateTimeZone(@date_default_timezone_get()));
}
$notBefore = $this->currentCert['tbsCertificate']['validity']['notBefore'];
@@ -2133,29 +1102,22 @@ class X509
$notAfter = isset($notAfter['generalTime']) ? $notAfter['generalTime'] : $notAfter['utcTime'];
if (is_string($date)) {
- $date = new DateTime($date, new DateTimeZone(@date_default_timezone_get()));
+ $date = new \DateTimeImmutable($date, new \DateTimeZone(@date_default_timezone_get()));
}
- $notBefore = new DateTime($notBefore, new DateTimeZone(@date_default_timezone_get()));
- $notAfter = new DateTime($notAfter, new DateTimeZone(@date_default_timezone_get()));
-
- switch (true) {
- case $date < $notBefore:
- case $date > $notAfter:
- return false;
- }
+ $notBefore = new \DateTimeImmutable($notBefore, new \DateTimeZone(@date_default_timezone_get()));
+ $notAfter = new \DateTimeImmutable($notAfter, new \DateTimeZone(@date_default_timezone_get()));
- return true;
+ return $date >= $notBefore && $date <= $notAfter;
}
/**
* Fetches a URL
*
* @param string $url
- * @access private
* @return bool|string
*/
- static function _fetchURL($url)
+ private static function fetchURL($url)
{
if (self::$disable_url_fetch) {
return false;
@@ -2171,7 +1133,7 @@ class X509
}
$path = $parts['path'];
if (isset($parts['query'])) {
- $path.= '?' . $parts['query'];
+ $path .= '?' . $parts['query'];
}
fputs($fsock, "GET $path HTTP/1.0\r\n");
fputs($fsock, "Host: $parts[host]\r\n\r\n");
@@ -2193,7 +1155,7 @@ class X509
if ($temp === false) {
return false;
}
- $data.= $temp;
+ $data .= $temp;
}
break;
@@ -2212,10 +1174,9 @@ class X509
*
* @param bool $caonly
* @param int $count
- * @access private
* @return bool
*/
- function _testForIntermediate($caonly, $count)
+ private function testForIntermediate($caonly, $count)
{
$opts = $this->getExtension('id-pe-authorityInfoAccess');
if (!is_array($opts)) {
@@ -2237,7 +1198,7 @@ class X509
return false;
}
- $cert = static::_fetchURL($url);
+ $cert = static::fetchURL($url);
if (!is_string($cert)) {
return false;
}
@@ -2257,7 +1218,7 @@ class X509
return false;
}
- if (!$parent->_validateSignatureCountable($caonly, ++$count)) {
+ if (!$parent->validateSignatureCountable($caonly, ++$count)) {
return false;
}
@@ -2279,12 +1240,11 @@ class X509
* The behavior of this function is inspired by {@link http://php.net/openssl-verify openssl_verify}.
*
* @param bool $caonly optional
- * @access public
* @return mixed
*/
- function validateSignature($caonly = true)
+ public function validateSignature($caonly = true)
{
- return $this->_validateSignatureCountable($caonly, 0);
+ return $this->validateSignatureCountable($caonly, 0);
}
/**
@@ -2294,10 +1254,9 @@ class X509
*
* @param bool $caonly
* @param int $count
- * @access private
* @return mixed
*/
- function _validateSignatureCountable($caonly, $count)
+ private function validateSignatureCountable($caonly, $count)
{
if (!is_array($this->currentCert) || !isset($this->signatureSubject)) {
return null;
@@ -2352,32 +1311,32 @@ class X509
}
}
if (count($this->CAs) == $i && $caonly) {
- return $this->_testForIntermediate($caonly, $count) && $this->validateSignature($caonly);
+ return $this->testForIntermediate($caonly, $count) && $this->validateSignature($caonly);
}
} elseif (!isset($signingCert) || $caonly) {
- return $this->_testForIntermediate($caonly, $count) && $this->validateSignature($caonly);
+ return $this->testForIntermediate($caonly, $count) && $this->validateSignature($caonly);
}
- return $this->_validateSignature(
+ return $this->validateSignatureHelper(
$signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'],
$signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'],
$this->currentCert['signatureAlgorithm']['algorithm'],
- substr(base64_decode($this->currentCert['signature']), 1),
+ substr($this->currentCert['signature'], 1),
$this->signatureSubject
);
case isset($this->currentCert['certificationRequestInfo']):
- return $this->_validateSignature(
+ return $this->validateSignatureHelper(
$this->currentCert['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm'],
$this->currentCert['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'],
$this->currentCert['signatureAlgorithm']['algorithm'],
- substr(base64_decode($this->currentCert['signature']), 1),
+ substr($this->currentCert['signature'], 1),
$this->signatureSubject
);
case isset($this->currentCert['publicKeyAndChallenge']):
- return $this->_validateSignature(
+ return $this->validateSignatureHelper(
$this->currentCert['publicKeyAndChallenge']['spki']['algorithm']['algorithm'],
$this->currentCert['publicKeyAndChallenge']['spki']['subjectPublicKey'],
$this->currentCert['signatureAlgorithm']['algorithm'],
- substr(base64_decode($this->currentCert['signature']), 1),
+ substr($this->currentCert['signature'], 1),
$this->signatureSubject
);
case isset($this->currentCert['tbsCertList']):
@@ -2405,11 +1364,11 @@ class X509
if (!isset($signingCert)) {
return false;
}
- return $this->_validateSignature(
+ return $this->validateSignatureHelper(
$signingCert['tbsCertificate']['subjectPublicKeyInfo']['algorithm']['algorithm'],
$signingCert['tbsCertificate']['subjectPublicKeyInfo']['subjectPublicKey'],
$this->currentCert['signatureAlgorithm']['algorithm'],
- substr(base64_decode($this->currentCert['signature']), 1),
+ substr($this->currentCert['signature'], 1),
$this->signatureSubject
);
default:
@@ -2420,24 +1379,28 @@ class X509
/**
* Validates a signature
*
- * Returns true if the signature is verified, false if it is not correct or null on error
+ * Returns true if the signature is verified and false if it is not correct.
+ * If the algorithms are unsupposed an exception is thrown.
*
* @param string $publicKeyAlgorithm
* @param string $publicKey
* @param string $signatureAlgorithm
* @param string $signature
* @param string $signatureSubject
- * @access private
- * @return int
+ * @throws UnsupportedAlgorithmException if the algorithm is unsupported
+ * @return bool
*/
- function _validateSignature($publicKeyAlgorithm, $publicKey, $signatureAlgorithm, $signature, $signatureSubject)
+ private function validateSignatureHelper($publicKeyAlgorithm, $publicKey, $signatureAlgorithm, $signature, $signatureSubject)
{
switch ($publicKeyAlgorithm) {
+ case 'id-RSASSA-PSS':
+ $key = RSA::loadFormat('PSS', $publicKey);
+ break;
case 'rsaEncryption':
- $rsa = new RSA();
- $rsa->loadKey($publicKey);
-
+ $key = RSA::loadFormat('PKCS8', $publicKey);
switch ($signatureAlgorithm) {
+ case 'id-RSASSA-PSS':
+ break;
case 'md2WithRSAEncryption':
case 'md5WithRSAEncryption':
case 'sha1WithRSAEncryption':
@@ -2445,21 +1408,51 @@ class X509
case 'sha256WithRSAEncryption':
case 'sha384WithRSAEncryption':
case 'sha512WithRSAEncryption':
- $rsa->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm));
- $rsa->setSignatureMode(RSA::SIGNATURE_PKCS1);
- if (!@$rsa->verify($signatureSubject, $signature)) {
- return false;
- }
+ $key = $key
+ ->withHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm))
+ ->withPadding(RSA::SIGNATURE_PKCS1);
+ break;
+ default:
+ throw new UnsupportedAlgorithmException('Signature algorithm unsupported');
+ }
+ break;
+ case 'id-Ed25519':
+ case 'id-Ed448':
+ $key = EC::loadFormat('PKCS8', $publicKey);
+ break;
+ case 'id-ecPublicKey':
+ $key = EC::loadFormat('PKCS8', $publicKey);
+ switch ($signatureAlgorithm) {
+ case 'ecdsa-with-SHA1':
+ case 'ecdsa-with-SHA224':
+ case 'ecdsa-with-SHA256':
+ case 'ecdsa-with-SHA384':
+ case 'ecdsa-with-SHA512':
+ $key = $key
+ ->withHash(preg_replace('#^ecdsa-with-#', '', strtolower($signatureAlgorithm)));
break;
default:
- return null;
+ throw new UnsupportedAlgorithmException('Signature algorithm unsupported');
+ }
+ break;
+ case 'id-dsa':
+ $key = DSA::loadFormat('PKCS8', $publicKey);
+ switch ($signatureAlgorithm) {
+ case 'id-dsa-with-sha1':
+ case 'id-dsa-with-sha224':
+ case 'id-dsa-with-sha256':
+ $key = $key
+ ->withHash(preg_replace('#^id-dsa-with-#', '', strtolower($signatureAlgorithm)));
+ break;
+ default:
+ throw new UnsupportedAlgorithmException('Signature algorithm unsupported');
}
break;
default:
- return null;
+ throw new UnsupportedAlgorithmException('Public key algorithm unsupported');
}
- return true;
+ return $key->verify($signatureSubject, $signature);
}
/**
@@ -2470,9 +1463,8 @@ class X509
* that we set a recursion limit. A negative number means that there is no recursion limit.
*
* @param int $count
- * @access public
*/
- static function setRecurLimit($count)
+ public static function setRecurLimit($count)
{
self::$recur_limit = $count;
}
@@ -2480,9 +1472,8 @@ class X509
/**
* Prevents URIs from being automatically retrieved
*
- * @access public
*/
- static function disableURLFetch()
+ public static function disableURLFetch()
{
self::$disable_url_fetch = true;
}
@@ -2490,51 +1481,23 @@ class X509
/**
* Allows URIs to be automatically retrieved
*
- * @access public
*/
- static function enableURLFetch()
+ public static function enableURLFetch()
{
self::$disable_url_fetch = false;
}
/**
- * Reformat public keys
- *
- * Reformats a public key to a format supported by phpseclib (if applicable)
- *
- * @param string $algorithm
- * @param string $key
- * @access private
- * @return string
- */
- function _reformatKey($algorithm, $key)
- {
- switch ($algorithm) {
- case 'rsaEncryption':
- return
- "-----BEGIN RSA PUBLIC KEY-----\r\n" .
- // subjectPublicKey is stored as a bit string in X.509 certs. the first byte of a bit string represents how many bits
- // in the last byte should be ignored. the following only supports non-zero stuff but as none of the X.509 certs Firefox
- // uses as a cert authority actually use a non-zero bit I think it's safe to assume that none do.
- chunk_split(base64_encode(substr(base64_decode($key), 1)), 64) .
- '-----END RSA PUBLIC KEY-----';
- default:
- return $key;
- }
- }
-
- /**
* Decodes an IP address
*
* Takes in a base64 encoded "blob" and returns a human readable IP address
*
* @param string $ip
- * @access private
* @return string
*/
- function _decodeIP($ip)
+ public static function decodeIP($ip)
{
- return inet_ntop(base64_decode($ip));
+ return inet_ntop($ip);
}
/**
@@ -2543,16 +1506,14 @@ class X509
* Takes in a base64 encoded "blob" and returns a human readable IP address / mask
*
* @param string $ip
- * @access private
* @return array
*/
- function _decodeNameConstraintIP($ip)
+ public static function decodeNameConstraintIP($ip)
{
- $ip = base64_decode($ip);
$size = strlen($ip) >> 1;
$mask = substr($ip, $size);
$ip = substr($ip, 0, $size);
- return array(inet_ntop($ip), inet_ntop($mask));
+ return [inet_ntop($ip), inet_ntop($mask)];
}
/**
@@ -2561,24 +1522,22 @@ class X509
* Takes a human readable IP address into a base64-encoded "blob"
*
* @param string|array $ip
- * @access private
* @return string
*/
- function _encodeIP($ip)
+ public static function encodeIP($ip)
{
return is_string($ip) ?
- base64_encode(inet_pton($ip)) :
- base64_encode(inet_pton($ip[0]) . inet_pton($ip[1]));
+ inet_pton($ip) :
+ inet_pton($ip[0]) . inet_pton($ip[1]);
}
/**
* "Normalizes" a Distinguished Name property
*
* @param string $propName
- * @access private
* @return mixed
*/
- function _translateDNProp($propName)
+ private function translateDNProp($propName)
{
switch (strtolower($propName)) {
case 'jurisdictionofincorporationcountryname':
@@ -2683,29 +1642,28 @@ class X509
* @param string $propName
* @param mixed $propValue
* @param string $type optional
- * @access public
* @return bool
*/
- function setDNProp($propName, $propValue, $type = 'utf8String')
+ public function setDNProp($propName, $propValue, $type = 'utf8String')
{
if (empty($this->dn)) {
- $this->dn = array('rdnSequence' => array());
+ $this->dn = ['rdnSequence' => []];
}
- if (($propName = $this->_translateDNProp($propName)) === false) {
+ if (($propName = $this->translateDNProp($propName)) === false) {
return false;
}
foreach ((array) $propValue as $v) {
if (!is_array($v) && isset($type)) {
- $v = array($type => $v);
+ $v = [$type => $v];
}
- $this->dn['rdnSequence'][] = array(
- array(
+ $this->dn['rdnSequence'][] = [
+ [
'type' => $propName,
- 'value'=> $v
- )
- );
+ 'value' => $v
+ ]
+ ];
}
return true;
@@ -2715,15 +1673,14 @@ class X509
* Remove Distinguished Name properties
*
* @param string $propName
- * @access public
*/
- function removeDNProp($propName)
+ public function removeDNProp($propName)
{
if (empty($this->dn)) {
return;
}
- if (($propName = $this->_translateDNProp($propName)) === false) {
+ if (($propName = $this->translateDNProp($propName)) === false) {
return;
}
@@ -2749,9 +1706,8 @@ class X509
* @param array $dn optional
* @param bool $withType optional
* @return mixed
- * @access public
*/
- function getDNProp($propName, $dn = null, $withType = false)
+ public function getDNProp($propName, $dn = null, $withType = false)
{
if (!isset($dn)) {
$dn = $this->dn;
@@ -2761,27 +1717,25 @@ class X509
return false;
}
- if (($propName = $this->_translateDNProp($propName)) === false) {
+ if (($propName = $this->translateDNProp($propName)) === false) {
return false;
}
- $asn1 = new ASN1();
- $asn1->loadOIDs($this->oids);
- $filters = array();
- $filters['value'] = array('type' => ASN1::TYPE_UTF8_STRING);
- $asn1->loadFilters($filters);
- $this->_mapOutDNs($dn, 'rdnSequence', $asn1);
+ $filters = [];
+ $filters['value'] = ['type' => ASN1::TYPE_UTF8_STRING];
+ ASN1::setFilters($filters);
+ $this->mapOutDNs($dn, 'rdnSequence');
$dn = $dn['rdnSequence'];
- $result = array();
+ $result = [];
for ($i = 0; $i < count($dn); $i++) {
if ($dn[$i][0]['type'] == $propName) {
$v = $dn[$i][0]['value'];
if (!$withType) {
if (is_array($v)) {
foreach ($v as $type => $s) {
- $type = array_search($type, $asn1->ANYmap, true);
- if ($type !== false && isset($asn1->stringTypeSize[$type])) {
- $s = $asn1->convert($s, $type);
+ $type = array_search($type, ASN1::ANY_MAP);
+ if ($type !== false && array_key_exists($type, ASN1::STRING_TYPE_SIZE)) {
+ $s = ASN1::convert($s, $type);
if ($s !== false) {
$v = $s;
break;
@@ -2792,10 +1746,13 @@ class X509
$v = array_pop($v); // Always strip data type.
}
} elseif (is_object($v) && $v instanceof Element) {
- $map = $this->_getMapping($propName);
+ $map = $this->getMapping($propName);
if (!is_bool($map)) {
- $decoded = $asn1->decodeBER($v);
- $v = $asn1->asn1map($decoded[0], $map);
+ $decoded = ASN1::decodeBER($v);
+ if (!$decoded) {
+ return false;
+ }
+ $v = ASN1::asn1map($decoded[0], $map);
}
}
}
@@ -2812,10 +1769,9 @@ class X509
* @param mixed $dn
* @param bool $merge optional
* @param string $type optional
- * @access public
* @return bool
*/
- function setDN($dn, $merge = false, $type = 'utf8String')
+ public function setDN($dn, $merge = false, $type = 'utf8String')
{
if (!$merge) {
$this->dn = null;
@@ -2838,7 +1794,7 @@ class X509
// handles everything else
$results = preg_split('#((?:^|, *|/)(?:C=|O=|OU=|CN=|L=|ST=|SN=|postalCode=|streetAddress=|emailAddress=|serialNumber=|organizationalUnitName=|title=|description=|role=|x500UniqueIdentifier=|postalAddress=))#', $dn, -1, PREG_SPLIT_DELIM_CAPTURE);
- for ($i = 1; $i < count($results); $i+=2) {
+ for ($i = 1; $i < count($results); $i += 2) {
$prop = trim($results[$i], ', =/');
$value = $results[$i + 1];
if (!$this->setDNProp($prop, $value, $type)) {
@@ -2854,10 +1810,9 @@ class X509
*
* @param mixed $format optional
* @param array $dn optional
- * @access public
- * @return bool
+ * @return array|bool|string
*/
- function getDN($format = self::DN_ARRAY, $dn = null)
+ public function getDN($format = self::DN_ARRAY, $dn = null)
{
if (!isset($dn)) {
$dn = isset($this->currentCert['tbsCertList']) ? $this->currentCert['tbsCertList']['issuer'] : $this->dn;
@@ -2867,32 +1822,28 @@ class X509
case self::DN_ARRAY:
return $dn;
case self::DN_ASN1:
- $asn1 = new ASN1();
- $asn1->loadOIDs($this->oids);
- $filters = array();
- $filters['rdnSequence']['value'] = array('type' => ASN1::TYPE_UTF8_STRING);
- $asn1->loadFilters($filters);
- $this->_mapOutDNs($dn, 'rdnSequence', $asn1);
- return $asn1->encodeDER($dn, $this->Name);
+ $filters = [];
+ $filters['rdnSequence']['value'] = ['type' => ASN1::TYPE_UTF8_STRING];
+ ASN1::setFilters($filters);
+ $this->mapOutDNs($dn, 'rdnSequence');
+ return ASN1::encodeDER($dn, Maps\Name::MAP);
case self::DN_CANON:
// No SEQUENCE around RDNs and all string values normalized as
// trimmed lowercase UTF-8 with all spacing as one blank.
// constructed RDNs will not be canonicalized
- $asn1 = new ASN1();
- $asn1->loadOIDs($this->oids);
- $filters = array();
- $filters['value'] = array('type' => ASN1::TYPE_UTF8_STRING);
- $asn1->loadFilters($filters);
+ $filters = [];
+ $filters['value'] = ['type' => ASN1::TYPE_UTF8_STRING];
+ ASN1::setFilters($filters);
$result = '';
- $this->_mapOutDNs($dn, 'rdnSequence', $asn1);
+ $this->mapOutDNs($dn, 'rdnSequence');
foreach ($dn['rdnSequence'] as $rdn) {
foreach ($rdn as $i => $attr) {
$attr = &$rdn[$i];
if (is_array($attr['value'])) {
foreach ($attr['value'] as $type => $v) {
- $type = array_search($type, $asn1->ANYmap, true);
- if ($type !== false && isset($asn1->stringTypeSize[$type])) {
- $v = $asn1->convert($v, $type);
+ $type = array_search($type, ASN1::ANY_MAP, true);
+ if ($type !== false && array_key_exists($type, ASN1::STRING_TYPE_SIZE)) {
+ $v = ASN1::convert($v, $type);
if ($v !== false) {
$v = preg_replace('/\s+/', ' ', $v);
$attr['value'] = strtolower(trim($v));
@@ -2902,28 +1853,26 @@ class X509
}
}
}
- $result .= $asn1->encodeDER($rdn, $this->RelativeDistinguishedName);
+ $result .= ASN1::encodeDER($rdn, Maps\RelativeDistinguishedName::MAP);
}
return $result;
case self::DN_HASH:
$dn = $this->getDN(self::DN_CANON, $dn);
$hash = new Hash('sha1');
$hash = $hash->hash($dn);
- extract(unpack('Vhash', $hash));
- return strtolower(bin2hex(pack('N', $hash)));
+ $hash = unpack('Vhash', $hash)['hash'];
+ return strtolower(Strings::bin2hex(pack('N', $hash)));
}
// Default is to return a string.
$start = true;
$output = '';
- $result = array();
- $asn1 = new ASN1();
- $asn1->loadOIDs($this->oids);
- $filters = array();
- $filters['rdnSequence']['value'] = array('type' => ASN1::TYPE_UTF8_STRING);
- $asn1->loadFilters($filters);
- $this->_mapOutDNs($dn, 'rdnSequence', $asn1);
+ $result = [];
+ $filters = [];
+ $filters['rdnSequence']['value'] = ['type' => ASN1::TYPE_UTF8_STRING];
+ ASN1::setFilters($filters);
+ $this->mapOutDNs($dn, 'rdnSequence');
foreach ($dn['rdnSequence'] as $field) {
$prop = $field[0]['type'];
@@ -2966,13 +1915,13 @@ class X509
}
if (!$start) {
- $output.= $delim;
+ $output .= $delim;
}
if (is_array($value)) {
foreach ($value as $type => $v) {
- $type = array_search($type, $asn1->ANYmap, true);
- if ($type !== false && isset($asn1->stringTypeSize[$type])) {
- $v = $asn1->convert($v, $type);
+ $type = array_search($type, ASN1::ANY_MAP, true);
+ if ($type !== false && array_key_exists($type, ASN1::STRING_TYPE_SIZE)) {
+ $v = ASN1::convert($v, $type);
if ($v !== false) {
$value = $v;
break;
@@ -2984,13 +1933,13 @@ class X509
}
} elseif (is_object($value) && $value instanceof Element) {
$callback = function ($x) {
- return "\x" . bin2hex($x[0]);
+ return '\x' . bin2hex($x[0]);
};
$value = strtoupper(preg_replace_callback('#[^\x20-\x7E]#', $callback, $value->element));
}
- $output.= $desc . '=' . $value;
+ $output .= $desc . '=' . $value;
$result[$desc] = isset($result[$desc]) ?
- array_merge((array) $result[$desc], array($value)) :
+ array_merge((array) $result[$desc], [$value]) :
$value;
$start = false;
}
@@ -3002,10 +1951,9 @@ class X509
* Get the Distinguished Name for a certificate/crl issuer
*
* @param int $format optional
- * @access public
* @return mixed
*/
- function getIssuerDN($format = self::DN_ARRAY)
+ public function getIssuerDN($format = self::DN_ARRAY)
{
switch (true) {
case !isset($this->currentCert) || !is_array($this->currentCert):
@@ -3024,10 +1972,9 @@ class X509
* Alias of getDN()
*
* @param int $format optional
- * @access public
* @return mixed
*/
- function getSubjectDN($format = self::DN_ARRAY)
+ public function getSubjectDN($format = self::DN_ARRAY)
{
switch (true) {
case !empty($this->dn):
@@ -3048,10 +1995,9 @@ class X509
*
* @param string $propName
* @param bool $withType optional
- * @access public
* @return mixed
*/
- function getIssuerDNProp($propName, $withType = false)
+ public function getIssuerDNProp($propName, $withType = false)
{
switch (true) {
case !isset($this->currentCert) || !is_array($this->currentCert):
@@ -3070,10 +2016,9 @@ class X509
*
* @param string $propName
* @param bool $withType optional
- * @access public
* @return mixed
*/
- function getSubjectDNProp($propName, $withType = false)
+ public function getSubjectDNProp($propName, $withType = false)
{
switch (true) {
case !empty($this->dn):
@@ -3092,19 +2037,15 @@ class X509
/**
* Get the certificate chain for the current cert
*
- * @access public
* @return mixed
*/
- function getChain()
+ public function getChain()
{
- $chain = array($this->currentCert);
+ $chain = [$this->currentCert];
if (!is_array($this->currentCert) || !isset($this->currentCert['tbsCertificate'])) {
return false;
}
- if (empty($this->CAs)) {
- return $chain;
- }
while (true) {
$currentCert = $chain[count($chain) - 1];
for ($i = 0; $i < count($this->CAs); $i++) {
@@ -3135,29 +2076,36 @@ class X509
}
/**
+ * Returns the current cert
+ *
+ * @return array|bool
+ */
+ public function &getCurrentCert()
+ {
+ return $this->currentCert;
+ }
+
+ /**
* Set public key
*
- * Key needs to be a \phpseclib\Crypt\RSA object
+ * Key needs to be a \phpseclib3\Crypt\RSA object
*
- * @param object $key
- * @access public
- * @return bool
+ * @param PublicKey $key
+ * @return void
*/
- function setPublicKey($key)
+ public function setPublicKey(PublicKey $key)
{
- $key->setPublicKey();
$this->publicKey = $key;
}
/**
* Set private key
*
- * Key needs to be a \phpseclib\Crypt\RSA object
+ * Key needs to be a \phpseclib3\Crypt\RSA object
*
- * @param object $key
- * @access public
+ * @param PrivateKey $key
*/
- function setPrivateKey($key)
+ public function setPrivateKey(PrivateKey $key)
{
$this->privateKey = $key;
}
@@ -3168,9 +2116,8 @@ class X509
* Used for SPKAC CSR's
*
* @param string $challenge
- * @access public
*/
- function setChallenge($challenge)
+ public function setChallenge($challenge)
{
$this->challenge = $challenge;
}
@@ -3178,20 +2125,24 @@ class X509
/**
* Gets the public key
*
- * Returns a \phpseclib\Crypt\RSA object or a false.
+ * Returns a \phpseclib3\Crypt\RSA object or a false.
*
- * @access public
* @return mixed
*/
- function getPublicKey()
+ public function getPublicKey()
{
if (isset($this->publicKey)) {
return $this->publicKey;
}
if (isset($this->currentCert) && is_array($this->currentCert)) {
- foreach (array('tbsCertificate/subjectPublicKeyInfo', 'certificationRequestInfo/subjectPKInfo') as $path) {
- $keyinfo = $this->_subArray($this->currentCert, $path);
+ $paths = [
+ 'tbsCertificate/subjectPublicKeyInfo',
+ 'certificationRequestInfo/subjectPKInfo',
+ 'publicKeyAndChallenge/spki'
+ ];
+ foreach ($paths as $path) {
+ $keyinfo = $this->subArray($this->currentCert, $path);
if (!empty($keyinfo)) {
break;
}
@@ -3204,27 +2155,29 @@ class X509
$key = $keyinfo['subjectPublicKey'];
switch ($keyinfo['algorithm']['algorithm']) {
+ case 'id-RSASSA-PSS':
+ return RSA::loadFormat('PSS', $key);
case 'rsaEncryption':
- $publicKey = new RSA();
- $publicKey->loadKey($key);
- $publicKey->setPublicKey();
- break;
- default:
- return false;
+ return RSA::loadFormat('PKCS8', $key)->withPadding(RSA::SIGNATURE_PKCS1);
+ case 'id-ecPublicKey':
+ case 'id-Ed25519':
+ case 'id-Ed448':
+ return EC::loadFormat('PKCS8', $key);
+ case 'id-dsa':
+ return DSA::loadFormat('PKCS8', $key);
}
- return $publicKey;
+ return false;
}
/**
* Load a Certificate Signing Request
*
- * @param string|array $csr
+ * @param string $csr
* @param int $mode
- * @access public
* @return mixed
*/
- function loadCSR($csr, $mode = self::FORMAT_AUTO_DETECT)
+ public function loadCSR($csr, $mode = self::FORMAT_AUTO_DETECT)
{
if (is_array($csr) && isset($csr['certificationRequestInfo'])) {
unset($this->currentCert);
@@ -3241,10 +2194,8 @@ class X509
// see http://tools.ietf.org/html/rfc2986
- $asn1 = new ASN1();
-
if ($mode != self::FORMAT_DER) {
- $newcsr = $this->_extractBER($csr);
+ $newcsr = ASN1::extractBER($csr);
if ($mode == self::FORMAT_PEM && $csr == $newcsr) {
return false;
}
@@ -3257,44 +2208,39 @@ class X509
return false;
}
- $asn1->loadOIDs($this->oids);
- $decoded = $asn1->decodeBER($csr);
+ $decoded = ASN1::decodeBER($csr);
- if (empty($decoded)) {
+ if (!$decoded) {
$this->currentCert = false;
return false;
}
- $csr = $asn1->asn1map($decoded[0], $this->CertificationRequest);
+ $csr = ASN1::asn1map($decoded[0], Maps\CertificationRequest::MAP);
if (!isset($csr) || $csr === false) {
$this->currentCert = false;
return false;
}
- $this->_mapInAttributes($csr, 'certificationRequestInfo/attributes', $asn1);
- $this->_mapInDNs($csr, 'certificationRequestInfo/subject/rdnSequence', $asn1);
+ $this->mapInAttributes($csr, 'certificationRequestInfo/attributes');
+ $this->mapInDNs($csr, 'certificationRequestInfo/subject/rdnSequence');
$this->dn = $csr['certificationRequestInfo']['subject'];
$this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
- $algorithm = &$csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['algorithm'];
- $key = &$csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'];
- $key = $this->_reformatKey($algorithm, $key);
-
- switch ($algorithm) {
- case 'rsaEncryption':
- $this->publicKey = new RSA();
- $this->publicKey->loadKey($key);
- $this->publicKey->setPublicKey();
- break;
- default:
- $this->publicKey = null;
- }
+ $key = $csr['certificationRequestInfo']['subjectPKInfo'];
+ $key = ASN1::encodeDER($key, Maps\SubjectPublicKeyInfo::MAP);
+ $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'] =
+ "-----BEGIN PUBLIC KEY-----\r\n" .
+ chunk_split(base64_encode($key), 64) .
+ "-----END PUBLIC KEY-----";
$this->currentKeyIdentifier = null;
$this->currentCert = $csr;
+ $this->publicKey = null;
+ $this->publicKey = $this->getPublicKey();
+
return $csr;
}
@@ -3303,50 +2249,40 @@ class X509
*
* @param array $csr
* @param int $format optional
- * @access public
* @return string
*/
- function saveCSR($csr, $format = self::FORMAT_PEM)
+ public function saveCSR(array $csr, $format = self::FORMAT_PEM)
{
if (!is_array($csr) || !isset($csr['certificationRequestInfo'])) {
return false;
}
switch (true) {
- case !($algorithm = $this->_subArray($csr, 'certificationRequestInfo/subjectPKInfo/algorithm/algorithm')):
+ case !($algorithm = $this->subArray($csr, 'certificationRequestInfo/subjectPKInfo/algorithm/algorithm')):
case is_object($csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']):
break;
default:
- switch ($algorithm) {
- case 'rsaEncryption':
- $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']
- = base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey'])));
- $csr['certificationRequestInfo']['subjectPKInfo']['algorithm']['parameters'] = null;
- $csr['signatureAlgorithm']['parameters'] = null;
- $csr['certificationRequestInfo']['signature']['parameters'] = null;
- }
+ $csr['certificationRequestInfo']['subjectPKInfo'] = new Element(
+ base64_decode(preg_replace('#-.+-|[\r\n]#', '', $csr['certificationRequestInfo']['subjectPKInfo']['subjectPublicKey']))
+ );
}
- $asn1 = new ASN1();
-
- $asn1->loadOIDs($this->oids);
-
- $filters = array();
+ $filters = [];
$filters['certificationRequestInfo']['subject']['rdnSequence']['value']
- = array('type' => ASN1::TYPE_UTF8_STRING);
+ = ['type' => ASN1::TYPE_UTF8_STRING];
- $asn1->loadFilters($filters);
+ ASN1::setFilters($filters);
- $this->_mapOutDNs($csr, 'certificationRequestInfo/subject/rdnSequence', $asn1);
- $this->_mapOutAttributes($csr, 'certificationRequestInfo/attributes', $asn1);
- $csr = $asn1->encodeDER($csr, $this->CertificationRequest);
+ $this->mapOutDNs($csr, 'certificationRequestInfo/subject/rdnSequence');
+ $this->mapOutAttributes($csr, 'certificationRequestInfo/attributes');
+ $csr = ASN1::encodeDER($csr, Maps\CertificationRequest::MAP);
switch ($format) {
case self::FORMAT_DER:
return $csr;
// case self::FORMAT_PEM:
default:
- return "-----BEGIN CERTIFICATE REQUEST-----\r\n" . chunk_split(base64_encode($csr), 64) . '-----END CERTIFICATE REQUEST-----';
+ return "-----BEGIN CERTIFICATE REQUEST-----\r\n" . chunk_split(Strings::base64_encode($csr), 64) . '-----END CERTIFICATE REQUEST-----';
}
}
@@ -3357,11 +2293,10 @@ class X509
*
* https://developer.mozilla.org/en-US/docs/HTML/Element/keygen
*
- * @param string|array $spkac
- * @access public
+ * @param string $spkac
* @return mixed
*/
- function loadSPKAC($spkac)
+ public function loadSPKAC($spkac)
{
if (is_array($spkac) && isset($spkac['publicKeyAndChallenge'])) {
unset($this->currentCert);
@@ -3373,11 +2308,9 @@ class X509
// see http://www.w3.org/html/wg/drafts/html/master/forms.html#signedpublickeyandchallenge
- $asn1 = new ASN1();
-
// OpenSSL produces SPKAC's that are preceded by the string SPKAC=
$temp = preg_replace('#(?:SPKAC=)|[ \r\n\\\]#', '', $spkac);
- $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false;
+ $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? Strings::base64_decode($temp) : false;
if ($temp != false) {
$spkac = $temp;
}
@@ -3388,74 +2321,63 @@ class X509
return false;
}
- $asn1->loadOIDs($this->oids);
- $decoded = $asn1->decodeBER($spkac);
+ $decoded = ASN1::decodeBER($spkac);
- if (empty($decoded)) {
+ if (!$decoded) {
$this->currentCert = false;
return false;
}
- $spkac = $asn1->asn1map($decoded[0], $this->SignedPublicKeyAndChallenge);
+ $spkac = ASN1::asn1map($decoded[0], Maps\SignedPublicKeyAndChallenge::MAP);
- if (!isset($spkac) || $spkac === false) {
+ if (!isset($spkac) || !is_array($spkac)) {
$this->currentCert = false;
return false;
}
$this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
- $algorithm = &$spkac['publicKeyAndChallenge']['spki']['algorithm']['algorithm'];
- $key = &$spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'];
- $key = $this->_reformatKey($algorithm, $key);
-
- switch ($algorithm) {
- case 'rsaEncryption':
- $this->publicKey = new RSA();
- $this->publicKey->loadKey($key);
- $this->publicKey->setPublicKey();
- break;
- default:
- $this->publicKey = null;
- }
+ $key = $spkac['publicKeyAndChallenge']['spki'];
+ $key = ASN1::encodeDER($key, Maps\SubjectPublicKeyInfo::MAP);
+ $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'] =
+ "-----BEGIN PUBLIC KEY-----\r\n" .
+ chunk_split(base64_encode($key), 64) .
+ "-----END PUBLIC KEY-----";
$this->currentKeyIdentifier = null;
$this->currentCert = $spkac;
+ $this->publicKey = null;
+ $this->publicKey = $this->getPublicKey();
+
return $spkac;
}
/**
* Save a SPKAC CSR request
*
- * @param string|array $spkac
+ * @param array $spkac
* @param int $format optional
- * @access public
* @return string
*/
- function saveSPKAC($spkac, $format = self::FORMAT_PEM)
+ public function saveSPKAC(array $spkac, $format = self::FORMAT_PEM)
{
if (!is_array($spkac) || !isset($spkac['publicKeyAndChallenge'])) {
return false;
}
- $algorithm = $this->_subArray($spkac, 'publicKeyAndChallenge/spki/algorithm/algorithm');
+ $algorithm = $this->subArray($spkac, 'publicKeyAndChallenge/spki/algorithm/algorithm');
switch (true) {
case !$algorithm:
case is_object($spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']):
break;
default:
- switch ($algorithm) {
- case 'rsaEncryption':
- $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']
- = base64_encode("\0" . base64_decode(preg_replace('#-.+-|[\r\n]#', '', $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey'])));
- }
+ $spkac['publicKeyAndChallenge']['spki'] = new Element(
+ base64_decode(preg_replace('#-.+-|[\r\n]#', '', $spkac['publicKeyAndChallenge']['spki']['subjectPublicKey']))
+ );
}
- $asn1 = new ASN1();
-
- $asn1->loadOIDs($this->oids);
- $spkac = $asn1->encodeDER($spkac, $this->SignedPublicKeyAndChallenge);
+ $spkac = ASN1::encodeDER($spkac, Maps\SignedPublicKeyAndChallenge::MAP);
switch ($format) {
case self::FORMAT_DER:
@@ -3464,7 +2386,7 @@ class X509
default:
// OpenSSL's implementation of SPKAC requires the SPKAC be preceded by SPKAC= and since there are pretty much
// no other SPKAC decoders phpseclib will use that same format
- return 'SPKAC=' . base64_encode($spkac);
+ return 'SPKAC=' . Strings::base64_encode($spkac);
}
}
@@ -3473,10 +2395,9 @@ class X509
*
* @param string $crl
* @param int $mode
- * @access public
* @return mixed
*/
- function loadCRL($crl, $mode = self::FORMAT_AUTO_DETECT)
+ public function loadCRL($crl, $mode = self::FORMAT_AUTO_DETECT)
{
if (is_array($crl) && isset($crl['tbsCertList'])) {
$this->currentCert = $crl;
@@ -3484,10 +2405,8 @@ class X509
return $crl;
}
- $asn1 = new ASN1();
-
if ($mode != self::FORMAT_DER) {
- $newcrl = $this->_extractBER($crl);
+ $newcrl = ASN1::extractBER($crl);
if ($mode == self::FORMAT_PEM && $crl == $newcrl) {
return false;
}
@@ -3500,15 +2419,14 @@ class X509
return false;
}
- $asn1->loadOIDs($this->oids);
- $decoded = $asn1->decodeBER($crl);
+ $decoded = ASN1::decodeBER($crl);
- if (empty($decoded)) {
+ if (!$decoded) {
$this->currentCert = false;
return false;
}
- $crl = $asn1->asn1map($decoded[0], $this->CertificateList);
+ $crl = ASN1::asn1map($decoded[0], Maps\CertificateList::MAP);
if (!isset($crl) || $crl === false) {
$this->currentCert = false;
return false;
@@ -3516,17 +2434,17 @@ class X509
$this->signatureSubject = substr($orig, $decoded[0]['content'][0]['start'], $decoded[0]['content'][0]['length']);
- $this->_mapInDNs($crl, 'tbsCertList/issuer/rdnSequence', $asn1);
- if ($this->_isSubArrayValid($crl, 'tbsCertList/crlExtensions')) {
- $this->_mapInExtensions($crl, 'tbsCertList/crlExtensions', $asn1);
+ $this->mapInDNs($crl, 'tbsCertList/issuer/rdnSequence');
+ if ($this->isSubArrayValid($crl, 'tbsCertList/crlExtensions')) {
+ $this->mapInExtensions($crl, 'tbsCertList/crlExtensions');
}
- if ($this->_isSubArrayValid($crl, 'tbsCertList/revokedCertificates')) {
- $rclist_ref = &$this->_subArrayUnchecked($crl, 'tbsCertList/revokedCertificates');
+ if ($this->isSubArrayValid($crl, 'tbsCertList/revokedCertificates')) {
+ $rclist_ref = &$this->subArrayUnchecked($crl, 'tbsCertList/revokedCertificates');
if ($rclist_ref) {
$rclist = $crl['tbsCertList']['revokedCertificates'];
foreach ($rclist as $i => $extension) {
- if ($this->_isSubArrayValid($rclist, "$i/crlEntryExtensions", $asn1)) {
- $this->_mapInExtensions($rclist_ref, "$i/crlEntryExtensions", $asn1);
+ if ($this->isSubArrayValid($rclist, "$i/crlEntryExtensions")) {
+ $this->mapInExtensions($rclist_ref, "$i/crlEntryExtensions");
}
}
}
@@ -3543,56 +2461,51 @@ class X509
*
* @param array $crl
* @param int $format optional
- * @access public
* @return string
*/
- function saveCRL($crl, $format = self::FORMAT_PEM)
+ public function saveCRL(array $crl, $format = self::FORMAT_PEM)
{
if (!is_array($crl) || !isset($crl['tbsCertList'])) {
return false;
}
- $asn1 = new ASN1();
-
- $asn1->loadOIDs($this->oids);
-
- $filters = array();
+ $filters = [];
$filters['tbsCertList']['issuer']['rdnSequence']['value']
- = array('type' => ASN1::TYPE_UTF8_STRING);
+ = ['type' => ASN1::TYPE_UTF8_STRING];
$filters['tbsCertList']['signature']['parameters']
- = array('type' => ASN1::TYPE_UTF8_STRING);
+ = ['type' => ASN1::TYPE_UTF8_STRING];
$filters['signatureAlgorithm']['parameters']
- = array('type' => ASN1::TYPE_UTF8_STRING);
+ = ['type' => ASN1::TYPE_UTF8_STRING];
if (empty($crl['tbsCertList']['signature']['parameters'])) {
$filters['tbsCertList']['signature']['parameters']
- = array('type' => ASN1::TYPE_NULL);
+ = ['type' => ASN1::TYPE_NULL];
}
if (empty($crl['signatureAlgorithm']['parameters'])) {
$filters['signatureAlgorithm']['parameters']
- = array('type' => ASN1::TYPE_NULL);
+ = ['type' => ASN1::TYPE_NULL];
}
- $asn1->loadFilters($filters);
+ ASN1::setFilters($filters);
- $this->_mapOutDNs($crl, 'tbsCertList/issuer/rdnSequence', $asn1);
- $this->_mapOutExtensions($crl, 'tbsCertList/crlExtensions', $asn1);
- $rclist = &$this->_subArray($crl, 'tbsCertList/revokedCertificates');
+ $this->mapOutDNs($crl, 'tbsCertList/issuer/rdnSequence');
+ $this->mapOutExtensions($crl, 'tbsCertList/crlExtensions');
+ $rclist = &$this->subArray($crl, 'tbsCertList/revokedCertificates');
if (is_array($rclist)) {
foreach ($rclist as $i => $extension) {
- $this->_mapOutExtensions($rclist, "$i/crlEntryExtensions", $asn1);
+ $this->mapOutExtensions($rclist, "$i/crlEntryExtensions");
}
}
- $crl = $asn1->encodeDER($crl, $this->CertificateList);
+ $crl = ASN1::encodeDER($crl, Maps\CertificateList::MAP);
switch ($format) {
case self::FORMAT_DER:
return $crl;
// case self::FORMAT_PEM:
default:
- return "-----BEGIN X509 CRL-----\r\n" . chunk_split(base64_encode($crl), 64) . '-----END X509 CRL-----';
+ return "-----BEGIN X509 CRL-----\r\n" . chunk_split(Strings::base64_encode($crl), 64) . '-----END X509 CRL-----';
}
}
@@ -3605,20 +2518,19 @@ class X509
* by choosing utcTime iff year of date given is before 2050 and generalTime else.
*
* @param string $date in format date('D, d M Y H:i:s O')
- * @access private
- * @return array
+ * @return array|Element
*/
- function _timeField($date)
+ private function timeField($date)
{
if ($date instanceof Element) {
return $date;
}
- $dateObj = new DateTime($date, new DateTimeZone('GMT'));
+ $dateObj = new \DateTimeImmutable($date, new \DateTimeZone('GMT'));
$year = $dateObj->format('Y'); // the same way ASN1.php parses this
if ($year < 2050) {
- return array('utcTime' => $date);
+ return ['utcTime' => $date];
} else {
- return array('generalTime' => $date);
+ return ['generalTime' => $date];
}
}
@@ -3629,35 +2541,32 @@ class X509
* $subject can be either an existing X.509 cert (if you want to resign it),
* a CSR or something with the DN and public key explicitly set.
*
- * @param \phpseclib\File\X509 $issuer
- * @param \phpseclib\File\X509 $subject
- * @param string $signatureAlgorithm optional
- * @access public
* @return mixed
*/
- function sign($issuer, $subject, $signatureAlgorithm = 'sha1WithRSAEncryption')
+ public function sign(X509 $issuer, X509 $subject)
{
if (!is_object($issuer->privateKey) || empty($issuer->dn)) {
return false;
}
- if (isset($subject->publicKey) && !($subjectPublicKey = $subject->_formatSubjectPublicKey())) {
+ if (isset($subject->publicKey) && !($subjectPublicKey = $subject->formatSubjectPublicKey())) {
return false;
}
$currentCert = isset($this->currentCert) ? $this->currentCert : null;
- $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null;
+ $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null;
+ $signatureAlgorithm = self::identifySignatureAlgorithm($issuer->privateKey);
if (isset($subject->currentCert) && is_array($subject->currentCert) && isset($subject->currentCert['tbsCertificate'])) {
$this->currentCert = $subject->currentCert;
- $this->currentCert['tbsCertificate']['signature']['algorithm'] = $signatureAlgorithm;
- $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
+ $this->currentCert['tbsCertificate']['signature'] = $signatureAlgorithm;
+ $this->currentCert['signatureAlgorithm'] = $signatureAlgorithm;
if (!empty($this->startDate)) {
- $this->currentCert['tbsCertificate']['validity']['notBefore'] = $this->_timeField($this->startDate);
+ $this->currentCert['tbsCertificate']['validity']['notBefore'] = $this->timeField($this->startDate);
}
if (!empty($this->endDate)) {
- $this->currentCert['tbsCertificate']['validity']['notAfter'] = $this->_timeField($this->endDate);
+ $this->currentCert['tbsCertificate']['validity']['notAfter'] = $this->timeField($this->endDate);
}
if (!empty($this->serialNumber)) {
$this->currentCert['tbsCertificate']['serialNumber'] = $this->serialNumber;
@@ -3679,10 +2588,10 @@ class X509
return false;
}
- $startDate = new DateTime('now', new DateTimeZone(@date_default_timezone_get()));
+ $startDate = new \DateTimeImmutable('now', new \DateTimeZone(@date_default_timezone_get()));
$startDate = !empty($this->startDate) ? $this->startDate : $startDate->format('D, d M Y H:i:s O');
- $endDate = new DateTime('+1 year', new DateTimeZone(@date_default_timezone_get()));
+ $endDate = new \DateTimeImmutable('+1 year', new \DateTimeZone(@date_default_timezone_get()));
$endDate = !empty($this->endDate) ? $this->endDate : $endDate->format('D, d M Y H:i:s O');
/* "The serial number MUST be a positive integer"
@@ -3696,23 +2605,23 @@ class X509
$this->serialNumber :
new BigInteger(Random::string(20) & ("\x7F" . str_repeat("\xFF", 19)), 256);
- $this->currentCert = array(
+ $this->currentCert = [
'tbsCertificate' =>
- array(
+ [
'version' => 'v3',
'serialNumber' => $serialNumber, // $this->setSerialNumber()
- 'signature' => array('algorithm' => $signatureAlgorithm),
+ 'signature' => $signatureAlgorithm,
'issuer' => false, // this is going to be overwritten later
- 'validity' => array(
- 'notBefore' => $this->_timeField($startDate), // $this->setStartDate()
- 'notAfter' => $this->_timeField($endDate) // $this->setEndDate()
- ),
+ 'validity' => [
+ 'notBefore' => $this->timeField($startDate), // $this->setStartDate()
+ 'notAfter' => $this->timeField($endDate) // $this->setEndDate()
+ ],
'subject' => $subject->dn,
'subjectPublicKeyInfo' => $subjectPublicKey
- ),
- 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
+ ],
+ 'signatureAlgorithm' => $signatureAlgorithm,
'signature' => false // this is going to be overwritten later
- );
+ ];
// Copy extensions from CSR.
$csrexts = $subject->getAttribute('pkcs-9-at-extensionRequest', 0);
@@ -3725,14 +2634,14 @@ class X509
$this->currentCert['tbsCertificate']['issuer'] = $issuer->dn;
if (isset($issuer->currentKeyIdentifier)) {
- $this->setExtension('id-ce-authorityKeyIdentifier', array(
+ $this->setExtension('id-ce-authorityKeyIdentifier', [
//'authorityCertIssuer' => array(
// array(
// 'directoryName' => $issuer->dn
// )
//),
'keyIdentifier' => $issuer->currentKeyIdentifier
- ));
+ ]);
//$extensions = &$this->currentCert['tbsCertificate']['extensions'];
//if (isset($issuer->serialNumber)) {
// $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber;
@@ -3744,18 +2653,18 @@ class X509
$this->setExtension('id-ce-subjectKeyIdentifier', $subject->currentKeyIdentifier);
}
- $altName = array();
+ $altName = [];
if (isset($subject->domains) && count($subject->domains)) {
- $altName = array_map(array('\phpseclib\File\X509', '_dnsName'), $subject->domains);
+ $altName = array_map(['\phpseclib3\File\X509', 'dnsName'], $subject->domains);
}
if (isset($subject->ipAddresses) && count($subject->ipAddresses)) {
// should an IP address appear as the CN if no domain name is specified? idk
//$ips = count($subject->domains) ? $subject->ipAddresses : array_slice($subject->ipAddresses, 1);
- $ipAddresses = array();
+ $ipAddresses = [];
foreach ($subject->ipAddresses as $ipAddress) {
- $encoded = $subject->_ipAddress($ipAddress);
+ $encoded = $subject->ipAddress($ipAddress);
if ($encoded !== false) {
$ipAddresses[] = $encoded;
}
@@ -3772,36 +2681,37 @@ class X509
if ($this->caFlag) {
$keyUsage = $this->getExtension('id-ce-keyUsage');
if (!$keyUsage) {
- $keyUsage = array();
+ $keyUsage = [];
}
$this->setExtension(
'id-ce-keyUsage',
- array_values(array_unique(array_merge($keyUsage, array('cRLSign', 'keyCertSign'))))
+ array_values(array_unique(array_merge($keyUsage, ['cRLSign', 'keyCertSign'])))
);
$basicConstraints = $this->getExtension('id-ce-basicConstraints');
if (!$basicConstraints) {
- $basicConstraints = array();
+ $basicConstraints = [];
}
$this->setExtension(
'id-ce-basicConstraints',
- array_unique(array_merge(array('cA' => true), $basicConstraints)),
+ array_merge(['cA' => true], $basicConstraints),
true
);
if (!isset($subject->currentKeyIdentifier)) {
- $this->setExtension('id-ce-subjectKeyIdentifier', base64_encode($this->computeKeyIdentifier($this->currentCert)), false, false);
+ $this->setExtension('id-ce-subjectKeyIdentifier', $this->computeKeyIdentifier($this->currentCert), false, false);
}
}
// resync $this->signatureSubject
- // save $tbsCertificate in case there are any \phpseclib\File\ASN1\Element objects in it
+ // save $tbsCertificate in case there are any \phpseclib3\File\ASN1\Element objects in it
$tbsCertificate = $this->currentCert['tbsCertificate'];
$this->loadX509($this->saveX509($this->currentCert));
- $result = $this->_sign($issuer->privateKey, $signatureAlgorithm);
+ $result = $this->currentCert;
+ $this->currentCert['signature'] = $result['signature'] = "\0" . $issuer->privateKey->sign($this->signatureSubject);
$result['tbsCertificate'] = $tbsCertificate;
$this->currentCert = $currentCert;
@@ -3813,54 +2723,50 @@ class X509
/**
* Sign a CSR
*
- * @access public
* @return mixed
*/
- function signCSR($signatureAlgorithm = 'sha1WithRSAEncryption')
+ public function signCSR()
{
if (!is_object($this->privateKey) || empty($this->dn)) {
return false;
}
$origPublicKey = $this->publicKey;
- $class = get_class($this->privateKey);
- $this->publicKey = new $class();
- $this->publicKey->loadKey($this->privateKey->getPublicKey());
- $this->publicKey->setPublicKey();
- if (!($publicKey = $this->_formatSubjectPublicKey())) {
- return false;
- }
+ $this->publicKey = $this->privateKey->getPublicKey();
+ $publicKey = $this->formatSubjectPublicKey();
$this->publicKey = $origPublicKey;
$currentCert = isset($this->currentCert) ? $this->currentCert : null;
- $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null;
+ $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null;
+ $signatureAlgorithm = self::identifySignatureAlgorithm($this->privateKey);
if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['certificationRequestInfo'])) {
- $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
+ $this->currentCert['signatureAlgorithm'] = $signatureAlgorithm;
if (!empty($this->dn)) {
$this->currentCert['certificationRequestInfo']['subject'] = $this->dn;
}
$this->currentCert['certificationRequestInfo']['subjectPKInfo'] = $publicKey;
} else {
- $this->currentCert = array(
+ $this->currentCert = [
'certificationRequestInfo' =>
- array(
+ [
'version' => 'v1',
'subject' => $this->dn,
'subjectPKInfo' => $publicKey,
- 'attributes' => array()
- ),
- 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
+ 'attributes' => []
+ ],
+ 'signatureAlgorithm' => $signatureAlgorithm,
'signature' => false // this is going to be overwritten later
- );
+ ];
}
// resync $this->signatureSubject
- // save $certificationRequestInfo in case there are any \phpseclib\File\ASN1\Element objects in it
+ // save $certificationRequestInfo in case there are any \phpseclib3\File\ASN1\Element objects in it
$certificationRequestInfo = $this->currentCert['certificationRequestInfo'];
$this->loadCSR($this->saveCSR($this->currentCert));
- $result = $this->_sign($this->privateKey, $signatureAlgorithm);
+ $result = $this->currentCert;
+ $this->currentCert['signature'] = $result['signature'] = "\0" . $this->privateKey->sign($this->signatureSubject);
$result['certificationRequestInfo'] = $certificationRequestInfo;
$this->currentCert = $currentCert;
@@ -3872,41 +2778,35 @@ class X509
/**
* Sign a SPKAC
*
- * @access public
* @return mixed
*/
- function signSPKAC($signatureAlgorithm = 'sha1WithRSAEncryption')
+ public function signSPKAC()
{
if (!is_object($this->privateKey)) {
return false;
}
$origPublicKey = $this->publicKey;
- $class = get_class($this->privateKey);
- $this->publicKey = new $class();
- $this->publicKey->loadKey($this->privateKey->getPublicKey());
- $this->publicKey->setPublicKey();
- $publicKey = $this->_formatSubjectPublicKey();
- if (!$publicKey) {
- return false;
- }
+ $this->publicKey = $this->privateKey->getPublicKey();
+ $publicKey = $this->formatSubjectPublicKey();
$this->publicKey = $origPublicKey;
$currentCert = isset($this->currentCert) ? $this->currentCert : null;
- $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject: null;
+ $signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null;
+ $signatureAlgorithm = self::identifySignatureAlgorithm($this->privateKey);
// re-signing a SPKAC seems silly but since everything else supports re-signing why not?
if (isset($this->currentCert) && is_array($this->currentCert) && isset($this->currentCert['publicKeyAndChallenge'])) {
- $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
+ $this->currentCert['signatureAlgorithm'] = $signatureAlgorithm;
$this->currentCert['publicKeyAndChallenge']['spki'] = $publicKey;
if (!empty($this->challenge)) {
// the bitwise AND ensures that the output is a valid IA5String
$this->currentCert['publicKeyAndChallenge']['challenge'] = $this->challenge & str_repeat("\x7F", strlen($this->challenge));
}
} else {
- $this->currentCert = array(
+ $this->currentCert = [
'publicKeyAndChallenge' =>
- array(
+ [
'spki' => $publicKey,
// quoting <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/keygen>,
// "A challenge string that is submitted along with the public key. Defaults to an empty string if not specified."
@@ -3914,18 +2814,19 @@ class X509
// we could alternatively do this instead if we ignored the specs:
// Random::string(8) & str_repeat("\x7F", 8)
'challenge' => !empty($this->challenge) ? $this->challenge : ''
- ),
- 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
+ ],
+ 'signatureAlgorithm' => $signatureAlgorithm,
'signature' => false // this is going to be overwritten later
- );
+ ];
}
// resync $this->signatureSubject
- // save $publicKeyAndChallenge in case there are any \phpseclib\File\ASN1\Element objects in it
+ // save $publicKeyAndChallenge in case there are any \phpseclib3\File\ASN1\Element objects in it
$publicKeyAndChallenge = $this->currentCert['publicKeyAndChallenge'];
$this->loadSPKAC($this->saveSPKAC($this->currentCert));
- $result = $this->_sign($this->privateKey, $signatureAlgorithm);
+ $result = $this->currentCert;
+ $this->currentCert['signature'] = $result['signature'] = "\0" . $this->privateKey->sign($this->signatureSubject);
$result['publicKeyAndChallenge'] = $publicKeyAndChallenge;
$this->currentCert = $currentCert;
@@ -3939,13 +2840,9 @@ class X509
*
* $issuer's private key needs to be loaded.
*
- * @param \phpseclib\File\X509 $issuer
- * @param \phpseclib\File\X509 $crl
- * @param string $signatureAlgorithm optional
- * @access public
* @return mixed
*/
- function signCRL($issuer, $crl, $signatureAlgorithm = 'sha1WithRSAEncryption')
+ public function signCRL(X509 $issuer, X509 $crl)
{
if (!is_object($issuer->privateKey) || empty($issuer->dn)) {
return false;
@@ -3953,34 +2850,35 @@ class X509
$currentCert = isset($this->currentCert) ? $this->currentCert : null;
$signatureSubject = isset($this->signatureSubject) ? $this->signatureSubject : null;
+ $signatureAlgorithm = self::identifySignatureAlgorithm($issuer->privateKey);
- $thisUpdate = new DateTime('now', new DateTimeZone(@date_default_timezone_get()));
+ $thisUpdate = new \DateTimeImmutable('now', new \DateTimeZone(@date_default_timezone_get()));
$thisUpdate = !empty($this->startDate) ? $this->startDate : $thisUpdate->format('D, d M Y H:i:s O');
if (isset($crl->currentCert) && is_array($crl->currentCert) && isset($crl->currentCert['tbsCertList'])) {
$this->currentCert = $crl->currentCert;
- $this->currentCert['tbsCertList']['signature']['algorithm'] = $signatureAlgorithm;
- $this->currentCert['signatureAlgorithm']['algorithm'] = $signatureAlgorithm;
+ $this->currentCert['tbsCertList']['signature'] = $signatureAlgorithm;
+ $this->currentCert['signatureAlgorithm'] = $signatureAlgorithm;
} else {
- $this->currentCert = array(
+ $this->currentCert = [
'tbsCertList' =>
- array(
+ [
'version' => 'v2',
- 'signature' => array('algorithm' => $signatureAlgorithm),
+ 'signature' => $signatureAlgorithm,
'issuer' => false, // this is going to be overwritten later
- 'thisUpdate' => $this->_timeField($thisUpdate) // $this->setStartDate()
- ),
- 'signatureAlgorithm' => array('algorithm' => $signatureAlgorithm),
+ 'thisUpdate' => $this->timeField($thisUpdate) // $this->setStartDate()
+ ],
+ 'signatureAlgorithm' => $signatureAlgorithm,
'signature' => false // this is going to be overwritten later
- );
+ ];
}
$tbsCertList = &$this->currentCert['tbsCertList'];
$tbsCertList['issuer'] = $issuer->dn;
- $tbsCertList['thisUpdate'] = $this->_timeField($thisUpdate);
+ $tbsCertList['thisUpdate'] = $this->timeField($thisUpdate);
if (!empty($this->endDate)) {
- $tbsCertList['nextUpdate'] = $this->_timeField($this->endDate); // $this->setEndDate()
+ $tbsCertList['nextUpdate'] = $this->timeField($this->endDate); // $this->setEndDate()
} else {
unset($tbsCertList['nextUpdate']);
}
@@ -4025,14 +2923,14 @@ class X509
}
if (isset($issuer->currentKeyIdentifier)) {
- $this->setExtension('id-ce-authorityKeyIdentifier', array(
+ $this->setExtension('id-ce-authorityKeyIdentifier', [
//'authorityCertIssuer' => array(
- // array(
+ // ]
// 'directoryName' => $issuer->dn
- // )
+ // ]
//),
'keyIdentifier' => $issuer->currentKeyIdentifier
- ));
+ ]);
//$extensions = &$tbsCertList['crlExtensions'];
//if (isset($issuer->serialNumber)) {
// $extensions[count($extensions) - 1]['authorityCertSerialNumber'] = $issuer->serialNumber;
@@ -4054,11 +2952,12 @@ class X509
unset($tbsCertList);
// resync $this->signatureSubject
- // save $tbsCertList in case there are any \phpseclib\File\ASN1\Element objects in it
+ // save $tbsCertList in case there are any \phpseclib3\File\ASN1\Element objects in it
$tbsCertList = $this->currentCert['tbsCertList'];
$this->loadCRL($this->saveCRL($this->currentCert));
- $result = $this->_sign($issuer->privateKey, $signatureAlgorithm);
+ $result = $this->currentCert;
+ $this->currentCert['signature'] = $result['signature'] = "\0" . $issuer->privateKey->sign($this->signatureSubject);
$result['tbsCertList'] = $tbsCertList;
$this->currentCert = $currentCert;
@@ -4068,45 +2967,77 @@ class X509
}
/**
- * X.509 certificate signing helper function.
+ * Identify signature algorithm from key settings
*
- * @param \phpseclib\File\X509 $key
- * @param string $signatureAlgorithm
- * @access public
- * @return mixed
+ * @param PrivateKey $key
+ * @throws UnsupportedAlgorithmException if the algorithm is unsupported
+ * @return array
*/
- function _sign($key, $signatureAlgorithm)
+ private static function identifySignatureAlgorithm(PrivateKey $key)
{
if ($key instanceof RSA) {
- switch ($signatureAlgorithm) {
- case 'md2WithRSAEncryption':
- case 'md5WithRSAEncryption':
- case 'sha1WithRSAEncryption':
- case 'sha224WithRSAEncryption':
- case 'sha256WithRSAEncryption':
- case 'sha384WithRSAEncryption':
- case 'sha512WithRSAEncryption':
- $key->setHash(preg_replace('#WithRSAEncryption$#', '', $signatureAlgorithm));
- $key->setSignatureMode(RSA::SIGNATURE_PKCS1);
-
- $this->currentCert['signature'] = base64_encode("\0" . $key->sign($this->signatureSubject));
- return $this->currentCert;
+ if ($key->getPadding() & RSA::SIGNATURE_PSS) {
+ $r = PSS::load($key->withPassword()->toString('PSS'));
+ return [
+ 'algorithm' => 'id-RSASSA-PSS',
+ 'parameters' => PSS::savePSSParams($r)
+ ];
}
+ switch ($key->getHash()) {
+ case 'md2':
+ case 'md5':
+ case 'sha1':
+ case 'sha224':
+ case 'sha256':
+ case 'sha384':
+ case 'sha512':
+ return [
+ 'algorithm' => $key->getHash() . 'WithRSAEncryption',
+ 'parameters' => null
+ ];
+ }
+ throw new UnsupportedAlgorithmException('The only supported hash algorithms for RSA are: md2, md5, sha1, sha224, sha256, sha384, sha512');
}
- return false;
+ if ($key instanceof DSA) {
+ switch ($key->getHash()) {
+ case 'sha1':
+ case 'sha224':
+ case 'sha256':
+ return ['algorithm' => 'id-dsa-with-' . $key->getHash()];
+ }
+ throw new UnsupportedAlgorithmException('The only supported hash algorithms for DSA are: sha1, sha224, sha256');
+ }
+
+ if ($key instanceof EC) {
+ switch ($key->getCurve()) {
+ case 'Ed25519':
+ case 'Ed448':
+ return ['algorithm' => 'id-' . $key->getCurve()];
+ }
+ switch ($key->getHash()) {
+ case 'sha1':
+ case 'sha224':
+ case 'sha256':
+ case 'sha384':
+ case 'sha512':
+ return ['algorithm' => 'ecdsa-with-' . strtoupper($key->getHash())];
+ }
+ throw new UnsupportedAlgorithmException('The only supported hash algorithms for EC are: sha1, sha224, sha256, sha384, sha512');
+ }
+
+ throw new UnsupportedAlgorithmException('The only supported public key classes are: RSA, DSA, EC');
}
/**
* Set certificate start date
*
- * @param string $date
- * @access public
+ * @param \DateTimeInterface|string $date
*/
- function setStartDate($date)
+ public function setStartDate($date)
{
- if (!is_object($date) || !is_a($date, 'DateTime')) {
- $date = new DateTime($date, new DateTimeZone(@date_default_timezone_get()));
+ if (!is_object($date) || !($date instanceof \DateTimeInterface)) {
+ $date = new \DateTimeImmutable($date, new \DateTimeZone(@date_default_timezone_get()));
}
$this->startDate = $date->format('D, d M Y H:i:s O');
@@ -4115,10 +3046,9 @@ class X509
/**
* Set certificate end date
*
- * @param string $date
- * @access public
+ * @param \DateTimeInterface|string $date
*/
- function setEndDate($date)
+ public function setEndDate($date)
{
/*
To indicate that a certificate has no well-defined expiration date,
@@ -4127,14 +3057,13 @@ class X509
-- http://tools.ietf.org/html/rfc5280#section-4.1.2.5
*/
- if (strtolower($date) == 'lifetime') {
+ if (is_string($date) && strtolower($date) === 'lifetime') {
$temp = '99991231235959Z';
- $asn1 = new ASN1();
- $temp = chr(ASN1::TYPE_GENERALIZED_TIME) . $asn1->_encodeLength(strlen($temp)) . $temp;
+ $temp = chr(ASN1::TYPE_GENERALIZED_TIME) . ASN1::encodeLength(strlen($temp)) . $temp;
$this->endDate = new Element($temp);
} else {
- if (!is_object($date) || !is_a($date, 'DateTime')) {
- $date = new DateTime($date, new DateTimeZone(@date_default_timezone_get()));
+ if (!is_object($date) || !($date instanceof \DateTimeInterface)) {
+ $date = new \DateTimeImmutable($date, new \DateTimeZone(@date_default_timezone_get()));
}
$this->endDate = $date->format('D, d M Y H:i:s O');
@@ -4146,9 +3075,8 @@ class X509
*
* @param string $serial
* @param int $base optional
- * @access public
*/
- function setSerialNumber($serial, $base = -256)
+ public function setSerialNumber($serial, $base = -256)
{
$this->serialNumber = new BigInteger($serial, $base);
}
@@ -4156,9 +3084,8 @@ class X509
/**
* Turns the certificate into a certificate authority
*
- * @access public
*/
- function makeCA()
+ public function makeCA()
{
$this->caFlag = true;
}
@@ -4173,9 +3100,8 @@ class X509
* @param array $root
* @param string $path
* @return boolean
- * @access private
*/
- function _isSubArrayValid($root, $path)
+ private function isSubArrayValid(array $root, $path)
{
if (!is_array($root)) {
return false;
@@ -4209,10 +3135,9 @@ class X509
* @param array $root
* @param string $path absolute path with / as component separator
* @param bool $create optional
- * @access private
* @return array|false
*/
- function &_subArrayUnchecked(&$root, $path, $create = false)
+ private function &subArrayUnchecked(array &$root, $path, $create = false)
{
$false = false;
@@ -4222,7 +3147,7 @@ class X509
return $false;
}
- $root[$i] = array();
+ $root[$i] = [];
}
$root = &$root[$i];
@@ -4237,10 +3162,9 @@ class X509
* @param array $root
* @param string $path absolute path with / as component separator
* @param bool $create optional
- * @access private
* @return array|false
*/
- function &_subArray(&$root, $path, $create = false)
+ private function &subArray(&$root, $path, $create = false)
{
$false = false;
@@ -4258,7 +3182,7 @@ class X509
return $false;
}
- $root[$i] = array();
+ $root[$i] = [];
}
$root = &$root[$i];
@@ -4273,10 +3197,9 @@ class X509
* @param array $root
* @param string $path optional absolute path with / as component separator
* @param bool $create optional
- * @access private
* @return array|false
*/
- function &_extensions(&$root, $path = null, $create = false)
+ private function &extensions(&$root, $path = null, $create = false)
{
if (!isset($root)) {
$root = $this->currentCert;
@@ -4294,7 +3217,7 @@ class X509
break;
case isset($root['certificationRequestInfo']):
$pth = 'certificationRequestInfo/attributes';
- $attributes = &$this->_subArray($root, $pth, $create);
+ $attributes = &$this->subArray($root, $pth, $create);
if (is_array($attributes)) {
foreach ($attributes as $key => $value) {
@@ -4305,14 +3228,14 @@ class X509
}
if ($create) {
$key = count($attributes);
- $attributes[] = array('type' => 'pkcs-9-at-extensionRequest', 'value' => array());
+ $attributes[] = ['type' => 'pkcs-9-at-extensionRequest', 'value' => []];
$path = "$pth/$key/value/0";
}
}
break;
}
- $extensions = &$this->_subArray($root, $path, $create);
+ $extensions = &$this->subArray($root, $path, $create);
if (!is_array($extensions)) {
$false = false;
@@ -4327,12 +3250,11 @@ class X509
*
* @param string $id
* @param string $path optional
- * @access private
* @return bool
*/
- function _removeExtension($id, $path = null)
+ private function removeExtensionHelper($id, $path = null)
{
- $extensions = &$this->_extensions($this->currentCert, $path);
+ $extensions = &$this->extensions($this->currentCert, $path);
if (!is_array($extensions)) {
return false;
@@ -4362,12 +3284,11 @@ class X509
* @param string $id
* @param array $cert optional
* @param string $path optional
- * @access private
* @return mixed
*/
- function _getExtension($id, $cert = null, $path = null)
+ private function getExtensionHelper($id, $cert = null, $path = null)
{
- $extensions = $this->_extensions($cert, $path);
+ $extensions = $this->extensions($cert, $path);
if (!is_array($extensions)) {
return false;
@@ -4387,13 +3308,12 @@ class X509
*
* @param array $cert optional
* @param string $path optional
- * @access private
* @return array
*/
- function _getExtensions($cert = null, $path = null)
+ private function getExtensionsHelper($cert = null, $path = null)
{
- $exts = $this->_extensions($cert, $path);
- $extensions = array();
+ $exts = $this->extensions($cert, $path);
+ $extensions = [];
if (is_array($exts)) {
foreach ($exts as $extension) {
@@ -4412,18 +3332,17 @@ class X509
* @param bool $critical optional
* @param bool $replace optional
* @param string $path optional
- * @access private
* @return bool
*/
- function _setExtension($id, $value, $critical = false, $replace = true, $path = null)
+ private function setExtensionHelper($id, $value, $critical = false, $replace = true, $path = null)
{
- $extensions = &$this->_extensions($this->currentCert, $path, true);
+ $extensions = &$this->extensions($this->currentCert, $path, true);
if (!is_array($extensions)) {
return false;
}
- $newext = array('extnId' => $id, 'critical' => $critical, 'extnValue' => $value);
+ $newext = ['extnId' => $id, 'critical' => $critical, 'extnValue' => $value];
foreach ($extensions as $key => $value) {
if ($value['extnId'] == $id) {
@@ -4444,12 +3363,11 @@ class X509
* Remove a certificate, CSR or CRL Extension
*
* @param string $id
- * @access public
* @return bool
*/
- function removeExtension($id)
+ public function removeExtension($id)
{
- return $this->_removeExtension($id);
+ return $this->removeExtensionHelper($id);
}
/**
@@ -4459,24 +3377,24 @@ class X509
*
* @param string $id
* @param array $cert optional
- * @access public
+ * @param string $path
* @return mixed
*/
- function getExtension($id, $cert = null)
+ public function getExtension($id, $cert = null, $path = null)
{
- return $this->_getExtension($id, $cert);
+ return $this->getExtensionHelper($id, $cert, $path);
}
/**
* Returns a list of all extensions in use in certificate, CSR or CRL
*
* @param array $cert optional
- * @access public
+ * @param string $path optional
* @return array
*/
- function getExtensions($cert = null)
+ public function getExtensions($cert = null, $path = null)
{
- return $this->_getExtensions($cert);
+ return $this->getExtensionsHelper($cert, $path);
}
/**
@@ -4486,12 +3404,11 @@ class X509
* @param mixed $value
* @param bool $critical optional
* @param bool $replace optional
- * @access public
* @return bool
*/
- function setExtension($id, $value, $critical = false, $replace = true)
+ public function setExtension($id, $value, $critical = false, $replace = true)
{
- return $this->_setExtension($id, $value, $critical, $replace);
+ return $this->setExtensionHelper($id, $value, $critical, $replace);
}
/**
@@ -4499,12 +3416,11 @@ class X509
*
* @param string $id
* @param int $disposition optional
- * @access public
* @return bool
*/
- function removeAttribute($id, $disposition = self::ATTR_ALL)
+ public function removeAttribute($id, $disposition = self::ATTR_ALL)
{
- $attributes = &$this->_subArray($this->currentCert, 'certificationRequestInfo/attributes');
+ $attributes = &$this->subArray($this->currentCert, 'certificationRequestInfo/attributes');
if (!is_array($attributes)) {
return false;
@@ -4550,16 +3466,15 @@ class X509
* @param string $id
* @param int $disposition optional
* @param array $csr optional
- * @access public
* @return mixed
*/
- function getAttribute($id, $disposition = self::ATTR_ALL, $csr = null)
+ public function getAttribute($id, $disposition = self::ATTR_ALL, $csr = null)
{
if (empty($csr)) {
$csr = $this->currentCert;
}
- $attributes = $this->_subArray($csr, 'certificationRequestInfo/attributes');
+ $attributes = $this->subArray($csr, 'certificationRequestInfo/attributes');
if (!is_array($attributes)) {
return false;
@@ -4587,20 +3502,41 @@ class X509
}
/**
+ * Get all requested CSR extensions
+ *
+ * Returns the list of extensions if there are any and false if not
+ *
+ * @param array $csr optional
+ * @return mixed
+ */
+ public function getRequestedCertificateExtensions($csr = null)
+ {
+ if (empty($csr)) {
+ $csr = $this->currentCert;
+ }
+
+ $requestedExtensions = $this->getAttribute('pkcs-9-at-extensionRequest');
+ if ($requestedExtensions === false) {
+ return false;
+ }
+
+ return $this->getAttribute('pkcs-9-at-extensionRequest')[0];
+ }
+
+ /**
* Returns a list of all CSR attributes in use
*
* @param array $csr optional
- * @access public
* @return array
*/
- function getAttributes($csr = null)
+ public function getAttributes($csr = null)
{
if (empty($csr)) {
$csr = $this->currentCert;
}
- $attributes = $this->_subArray($csr, 'certificationRequestInfo/attributes');
- $attrs = array();
+ $attributes = $this->subArray($csr, 'certificationRequestInfo/attributes');
+ $attrs = [];
if (is_array($attributes)) {
foreach ($attributes as $attribute) {
@@ -4616,13 +3552,12 @@ class X509
*
* @param string $id
* @param mixed $value
- * @param bool $disposition optional
- * @access public
+ * @param int $disposition optional
* @return bool
*/
- function setAttribute($id, $value, $disposition = self::ATTR_ALL)
+ public function setAttribute($id, $value, $disposition = self::ATTR_ALL)
{
- $attributes = &$this->_subArray($this->currentCert, 'certificationRequestInfo/attributes', true);
+ $attributes = &$this->subArray($this->currentCert, 'certificationRequestInfo/attributes', true);
if (!is_array($attributes)) {
return false;
@@ -4631,6 +3566,7 @@ class X509
switch ($disposition) {
case self::ATTR_REPLACE:
$disposition = self::ATTR_APPEND;
+ // fall-through
case self::ATTR_ALL:
$this->removeAttribute($id);
break;
@@ -4660,7 +3596,7 @@ class X509
$attributes[$last]['value'][] = $value;
break;
default:
- $attributes[] = array('type' => $id, 'value' => $disposition == self::ATTR_ALL ? $value: array($value));
+ $attributes[] = ['type' => $id, 'value' => $disposition == self::ATTR_ALL ? $value : [$value]];
break;
}
@@ -4673,14 +3609,13 @@ class X509
* This is used by the id-ce-authorityKeyIdentifier and the id-ce-subjectKeyIdentifier extensions.
*
* @param string $value
- * @access public
*/
- function setKeyIdentifier($value)
+ public function setKeyIdentifier($value)
{
if (empty($value)) {
unset($this->currentKeyIdentifier);
} else {
- $this->currentKeyIdentifier = base64_encode($value);
+ $this->currentKeyIdentifier = $value;
}
}
@@ -4692,17 +3627,16 @@ class X509
* recommended methods (4.2.1.2 RFC 3280).
* Highly polymorphic: try to accept all possible forms of key:
* - Key object
- * - \phpseclib\File\X509 object with public or private key defined
+ * - \phpseclib3\File\X509 object with public or private key defined
* - Certificate or CSR array
- * - \phpseclib\File\ASN1\Element object
+ * - \phpseclib3\File\ASN1\Element object
* - PEM or DER string
*
* @param mixed $key optional
* @param int $method optional
- * @access public
* @return string binary key identifier
*/
- function computeKeyIdentifier($key = null, $method = 1)
+ public function computeKeyIdentifier($key = null, $method = 1)
{
if (is_null($key)) {
$key = $this;
@@ -4719,25 +3653,20 @@ class X509
return false;
case $key instanceof Element:
// Assume the element is a bitstring-packed key.
- $asn1 = new ASN1();
- $decoded = $asn1->decodeBER($key->element);
- if (empty($decoded)) {
+ $decoded = ASN1::decodeBER($key->element);
+ if (!$decoded) {
return false;
}
- $raw = $asn1->asn1map($decoded[0], array('type' => ASN1::TYPE_BIT_STRING));
+ $raw = ASN1::asn1map($decoded[0], ['type' => ASN1::TYPE_BIT_STRING]);
if (empty($raw)) {
return false;
}
- $raw = base64_decode($raw);
// If the key is private, compute identifier from its corresponding public key.
- $key = new RSA();
- if (!$key->loadKey($raw)) {
- return false; // Not an unencrypted RSA key.
- }
- if ($key->getPrivateKey() !== false) { // If private.
+ $key = PublicKeyLoader::load($raw);
+ if ($key instanceof PrivateKey) { // If private.
return $this->computeKeyIdentifier($key, $method);
}
- $key = $raw; // Is a public key.
+ $key = $raw; // Is a public key.
break;
case $key instanceof X509:
if (isset($key->publicKey)) {
@@ -4750,13 +3679,13 @@ class X509
return $this->computeKeyIdentifier($key->currentCert, $method);
}
return false;
- default: // Should be a key object (i.e.: \phpseclib\Crypt\RSA).
- $key = $key->getPublicKey(RSA::PUBLIC_FORMAT_PKCS1);
+ default: // Should be a key object (i.e.: \phpseclib3\Crypt\RSA).
+ $key = $key->getPublicKey();
break;
}
// If in PEM format, convert to binary.
- $key = $this->_extractBER($key);
+ $key = ASN1::extractBER($key);
// Now we have the key string: compute its sha-1 sum.
$hash = new Hash('sha1');
@@ -4773,33 +3702,39 @@ class X509
/**
* Format a public key as appropriate
*
- * @access private
- * @return array
+ * @return array|false
*/
- function _formatSubjectPublicKey()
+ private function formatSubjectPublicKey()
{
- if ($this->publicKey instanceof RSA) {
- // the following two return statements do the same thing. i dunno.. i just prefer the later for some reason.
- // the former is a good example of how to do fuzzing on the public key
- //return new Element(base64_decode(preg_replace('#-.+-|[\r\n]#', '', $this->publicKey->getPublicKey())));
- return array(
- 'algorithm' => array('algorithm' => 'rsaEncryption'),
- 'subjectPublicKey' => $this->publicKey->getPublicKey(RSA::PUBLIC_FORMAT_PKCS1)
- );
+ $format = $this->publicKey instanceof RSA && ($this->publicKey->getPadding() & RSA::SIGNATURE_PSS) ?
+ 'PSS' :
+ 'PKCS8';
+
+ $publicKey = base64_decode(preg_replace('#-.+-|[\r\n]#', '', $this->publicKey->toString($format)));
+
+ $decoded = ASN1::decodeBER($publicKey);
+ if (!$decoded) {
+ return false;
+ }
+ $mapped = ASN1::asn1map($decoded[0], Maps\SubjectPublicKeyInfo::MAP);
+ if (!is_array($mapped)) {
+ return false;
}
- return false;
+ $mapped['subjectPublicKey'] = $this->publicKey->toString($format);
+
+ return $mapped;
}
/**
* Set the domain name's which the cert is to be valid for
*
- * @access public
- * @return array
+ * @param mixed ...$domains
+ * @return void
*/
- function setDomain()
+ public function setDomain(...$domains)
{
- $this->domains = func_get_args();
+ $this->domains = $domains;
$this->removeDNProp('id-at-commonName');
$this->setDNProp('id-at-commonName', $this->domains[0]);
}
@@ -4807,11 +3742,11 @@ class X509
/**
* Set the IP Addresses's which the cert is to be valid for
*
- * @access public
+ * @param mixed[] ...$ipAddresses
*/
- function setIPAddress()
+ public function setIPAddress(...$ipAddresses)
{
- $this->ipAddresses = func_get_args();
+ $this->ipAddresses = $ipAddresses;
/*
if (!isset($this->domains)) {
$this->removeDNProp('id-at-commonName');
@@ -4823,13 +3758,12 @@ class X509
/**
* Helper function to build domain array
*
- * @access private
* @param string $domain
* @return array
*/
- function _dnsName($domain)
+ private static function dnsName($domain)
{
- return array('dNSName' => $domain);
+ return ['dNSName' => $domain];
}
/**
@@ -4837,13 +3771,12 @@ class X509
*
* (IPv6 is not currently supported)
*
- * @access private
* @param string $address
* @return array
*/
- function _iPAddress($address)
+ private function iPAddress($address)
{
- return array('iPAddress' => $address);
+ return ['iPAddress' => $address];
}
/**
@@ -4852,10 +3785,9 @@ class X509
* @param array $rclist
* @param string $serial
* @param bool $create optional
- * @access private
* @return int|false
*/
- function _revokedCertificate(&$rclist, $serial, $create = false)
+ private function revokedCertificate(array &$rclist, $serial, $create = false)
{
$serial = new BigInteger($serial);
@@ -4870,9 +3802,9 @@ class X509
}
$i = count($rclist);
- $revocationDate = new DateTime('now', new DateTimeZone(@date_default_timezone_get()));
- $rclist[] = array('userCertificate' => $serial,
- 'revocationDate' => $this->_timeField($revocationDate->format('D, d M Y H:i:s O')));
+ $revocationDate = new \DateTimeImmutable('now', new \DateTimeZone(@date_default_timezone_get()));
+ $rclist[] = ['userCertificate' => $serial,
+ 'revocationDate' => $this->timeField($revocationDate->format('D, d M Y H:i:s O'))];
return $i;
}
@@ -4881,17 +3813,16 @@ class X509
*
* @param string $serial
* @param string $date optional
- * @access public
* @return bool
*/
- function revoke($serial, $date = null)
+ public function revoke($serial, $date = null)
{
if (isset($this->currentCert['tbsCertList'])) {
- if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates', true))) {
- if ($this->_revokedCertificate($rclist, $serial) === false) { // If not yet revoked
- if (($i = $this->_revokedCertificate($rclist, $serial, true)) !== false) {
+ if (is_array($rclist = &$this->subArray($this->currentCert, 'tbsCertList/revokedCertificates', true))) {
+ if ($this->revokedCertificate($rclist, $serial) === false) { // If not yet revoked
+ if (($i = $this->revokedCertificate($rclist, $serial, true)) !== false) {
if (!empty($date)) {
- $rclist[$i]['revocationDate'] = $this->_timeField($date);
+ $rclist[$i]['revocationDate'] = $this->timeField($date);
}
return true;
@@ -4907,13 +3838,12 @@ class X509
* Unrevoke a certificate.
*
* @param string $serial
- * @access public
* @return bool
*/
- function unrevoke($serial)
+ public function unrevoke($serial)
{
- if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) {
- if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
+ if (is_array($rclist = &$this->subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) {
+ if (($i = $this->revokedCertificate($rclist, $serial)) !== false) {
unset($rclist[$i]);
$rclist = array_values($rclist);
return true;
@@ -4927,13 +3857,12 @@ class X509
* Get a revoked certificate.
*
* @param string $serial
- * @access public
* @return mixed
*/
- function getRevoked($serial)
+ public function getRevoked($serial)
{
- if (is_array($rclist = $this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) {
- if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
+ if (is_array($rclist = $this->subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) {
+ if (($i = $this->revokedCertificate($rclist, $serial)) !== false) {
return $rclist[$i];
}
}
@@ -4945,10 +3874,9 @@ class X509
* List revoked certificates
*
* @param array $crl optional
- * @access public
- * @return array
+ * @return array|bool
*/
- function listRevoked($crl = null)
+ public function listRevoked($crl = null)
{
if (!isset($crl)) {
$crl = $this->currentCert;
@@ -4958,9 +3886,9 @@ class X509
return false;
}
- $result = array();
+ $result = [];
- if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) {
+ if (is_array($rclist = $this->subArray($crl, 'tbsCertList/revokedCertificates'))) {
foreach ($rclist as $rc) {
$result[] = $rc['userCertificate']->toString();
}
@@ -4974,14 +3902,13 @@ class X509
*
* @param string $serial
* @param string $id
- * @access public
* @return bool
*/
- function removeRevokedCertificateExtension($serial, $id)
+ public function removeRevokedCertificateExtension($serial, $id)
{
- if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) {
- if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
- return $this->_removeExtension($id, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
+ if (is_array($rclist = &$this->subArray($this->currentCert, 'tbsCertList/revokedCertificates'))) {
+ if (($i = $this->revokedCertificate($rclist, $serial)) !== false) {
+ return $this->removeExtensionHelper($id, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
}
}
@@ -4996,18 +3923,17 @@ class X509
* @param string $serial
* @param string $id
* @param array $crl optional
- * @access public
* @return mixed
*/
- function getRevokedCertificateExtension($serial, $id, $crl = null)
+ public function getRevokedCertificateExtension($serial, $id, $crl = null)
{
if (!isset($crl)) {
$crl = $this->currentCert;
}
- if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) {
- if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
- return $this->_getExtension($id, $crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
+ if (is_array($rclist = $this->subArray($crl, 'tbsCertList/revokedCertificates'))) {
+ if (($i = $this->revokedCertificate($rclist, $serial)) !== false) {
+ return $this->getExtension($id, $crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
}
}
@@ -5019,18 +3945,17 @@ class X509
*
* @param string $serial
* @param array $crl optional
- * @access public
- * @return array
+ * @return array|bool
*/
- function getRevokedCertificateExtensions($serial, $crl = null)
+ public function getRevokedCertificateExtensions($serial, $crl = null)
{
if (!isset($crl)) {
$crl = $this->currentCert;
}
- if (is_array($rclist = $this->_subArray($crl, 'tbsCertList/revokedCertificates'))) {
- if (($i = $this->_revokedCertificate($rclist, $serial)) !== false) {
- return $this->_getExtensions($crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
+ if (is_array($rclist = $this->subArray($crl, 'tbsCertList/revokedCertificates'))) {
+ if (($i = $this->revokedCertificate($rclist, $serial)) !== false) {
+ return $this->getExtensions($crl, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
}
}
@@ -5045,15 +3970,14 @@ class X509
* @param mixed $value
* @param bool $critical optional
* @param bool $replace optional
- * @access public
* @return bool
*/
- function setRevokedCertificateExtension($serial, $id, $value, $critical = false, $replace = true)
+ public function setRevokedCertificateExtension($serial, $id, $value, $critical = false, $replace = true)
{
if (isset($this->currentCert['tbsCertList'])) {
- if (is_array($rclist = &$this->_subArray($this->currentCert, 'tbsCertList/revokedCertificates', true))) {
- if (($i = $this->_revokedCertificate($rclist, $serial, true)) !== false) {
- return $this->_setExtension($id, $value, $critical, $replace, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
+ if (is_array($rclist = &$this->subArray($this->currentCert, 'tbsCertList/revokedCertificates', true))) {
+ if (($i = $this->revokedCertificate($rclist, $serial, true)) !== false) {
+ return $this->setExtensionHelper($id, $value, $critical, $replace, "tbsCertList/revokedCertificates/$i/crlEntryExtensions");
}
}
}
@@ -5062,61 +3986,44 @@ class X509
}
/**
- * Extract raw BER from Base64 encoding
+ * Register the mapping for a custom/unsupported extension.
*
- * @access private
- * @param string $str
- * @return string
+ * @param string $id
+ * @param array $mapping
*/
- function _extractBER($str)
+ public static function registerExtension($id, array $mapping)
{
- /* X.509 certs are assumed to be base64 encoded but sometimes they'll have additional things in them
- * above and beyond the ceritificate.
- * ie. some may have the following preceding the -----BEGIN CERTIFICATE----- line:
- *
- * Bag Attributes
- * localKeyID: 01 00 00 00
- * subject=/O=organization/OU=org unit/CN=common name
- * issuer=/O=organization/CN=common name
- */
- if (strlen($str) > ini_get('pcre.backtrack_limit')) {
- $temp = $str;
- } else {
- $temp = preg_replace('#.*?^-+[^-]+-+[\r\n ]*$#ms', '', $str, 1);
- $temp = preg_replace('#-+END.*[\r\n ]*.*#ms', '', $temp, 1);
- }
- // remove new lines
- $temp = str_replace(array("\r", "\n", ' '), '', $temp);
- // remove the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- stuff
- $temp = preg_replace('#^-+[^-]+-+|-+[^-]+-+$#', '', $temp);
- $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false;
- return $temp != false ? $temp : $str;
+ if (isset(self::$extensions[$id]) && self::$extensions[$id] !== $mapping) {
+ throw new \RuntimeException(
+ 'Extension ' . $id . ' has already been defined with a different mapping.'
+ );
+ }
+
+ self::$extensions[$id] = $mapping;
}
/**
- * Returns the OID corresponding to a name
+ * Register the mapping for a custom/unsupported extension.
*
- * What's returned in the associative array returned by loadX509() (or load*()) is either a name or an OID if
- * no OID to name mapping is available. The problem with this is that what may be an unmapped OID in one version
- * of phpseclib may not be unmapped in the next version, so apps that are looking at this OID may not be able
- * to work from version to version.
- *
- * This method will return the OID if a name is passed to it and if no mapping is avialable it'll assume that
- * what's being passed to it already is an OID and return that instead. A few examples.
+ * @param string $id
*
- * getOID('2.16.840.1.101.3.4.2.1') == '2.16.840.1.101.3.4.2.1'
- * getOID('id-sha256') == '2.16.840.1.101.3.4.2.1'
- * getOID('zzz') == 'zzz'
+ * @return array|null
+ */
+ public static function getRegisteredExtension($id)
+ {
+ return isset(self::$extensions[$id]) ? self::$extensions[$id] : null;
+ }
+
+ /**
+ * Register the mapping for a custom/unsupported extension.
*
- * @access public
- * @return string
+ * @param string $id
+ * @param mixed $value
+ * @param bool $critical
+ * @param bool $replace
*/
- function getOID($name)
+ public function setExtensionValue($id, $value, $critical = false, $replace = false)
{
- static $reverseMap;
- if (!isset($reverseMap)) {
- $reverseMap = array_flip($this->oids);
- }
- return isset($reverseMap[$name]) ? $reverseMap[$name] : $name;
+ $this->extensionValues[$id] = compact('critical', 'replace', 'value');
}
}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger.php
index 5f2283678..965d7ff08 100644
--- a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger.php
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger.php
@@ -6,33 +6,13 @@
* Supports base-2, base-10, base-16, and base-256 numbers. Uses the GMP or BCMath extensions, if available,
* and an internal implementation, otherwise.
*
- * PHP version 5
- *
- * {@internal (all DocBlock comments regarding implementation - such as the one that follows - refer to the
- * {@link self::MODE_INTERNAL self::MODE_INTERNAL} mode)
- *
- * BigInteger uses base-2**26 to perform operations such as multiplication and division and
- * base-2**52 (ie. two base 2**26 digits) to perform addition and subtraction. Because the largest possible
- * value when multiplying two base-2**26 numbers together is a base-2**52 number, double precision floating
- * point numbers - numbers that should be supported on most hardware and whose significand is 53 bits - are
- * used. As a consequence, bitwise operators such as >> and << cannot be used, nor can the modulo operator %,
- * which only supports integers. Although this fact will slow this library down, the fact that such a high
- * base is being used should more than compensate.
- *
- * Numbers are stored in {@link http://en.wikipedia.org/wiki/Endianness little endian} format. ie.
- * (new \phpseclib\Math\BigInteger(pow(2, 26)))->value = array(0, 1)
- *
- * Useful resources are as follows:
- *
- * - {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf Handbook of Applied Cryptography (HAC)}
- * - {@link http://math.libtomcrypt.com/files/tommath.pdf Multi-Precision Math (MPM)}
- * - Java's BigInteger classes. See /j2se/src/share/classes/java/math in jdk-1_5_0-src-jrl.zip
+ * PHP version 5 and 7
*
* Here's an example of how to use this library:
* <code>
* <?php
- * $a = new \phpseclib\Math\BigInteger(2);
- * $b = new \phpseclib\Math\BigInteger(3);
+ * $a = new \phpseclib3\Math\BigInteger(2);
+ * $b = new \phpseclib3\Math\BigInteger(3);
*
* $c = $a->add($b);
*
@@ -40,1392 +20,265 @@
* ?>
* </code>
*
- * @category Math
- * @package BigInteger
* @author Jim Wigginton <terrafrost@php.net>
- * @copyright 2006 Jim Wigginton
+ * @copyright 2017 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
*/
-namespace phpseclib\Math;
+namespace phpseclib3\Math;
-use phpseclib\Crypt\Random;
+use phpseclib3\Exception\BadConfigurationException;
+use phpseclib3\Math\BigInteger\Engines\Engine;
/**
* Pure-PHP arbitrary precision integer arithmetic library. Supports base-2, base-10, base-16, and base-256
* numbers.
*
- * @package BigInteger
* @author Jim Wigginton <terrafrost@php.net>
- * @access public
*/
-class BigInteger
+class BigInteger implements \JsonSerializable
{
- /**#@+
- * Reduction constants
- *
- * @access private
- * @see BigInteger::_reduce()
- */
- /**
- * @see BigInteger::_montgomery()
- * @see BigInteger::_prepMontgomery()
- */
- const MONTGOMERY = 0;
- /**
- * @see BigInteger::_barrett()
- */
- const BARRETT = 1;
- /**
- * @see BigInteger::_mod2()
- */
- const POWEROF2 = 2;
- /**
- * @see BigInteger::_remainder()
- */
- const CLASSIC = 3;
- /**
- * @see BigInteger::__clone()
- */
- const NONE = 4;
- /**#@-*/
-
- /**#@+
- * Array constants
- *
- * Rather than create a thousands and thousands of new BigInteger objects in repeated function calls to add() and
- * multiply() or whatever, we'll just work directly on arrays, taking them in as parameters and returning them.
- *
- * @access private
- */
- /**
- * $result[self::VALUE] contains the value.
- */
- const VALUE = 0;
- /**
- * $result[self::SIGN] contains the sign.
- */
- const SIGN = 1;
- /**#@-*/
-
- /**#@+
- * @access private
- * @see BigInteger::_montgomery()
- * @see BigInteger::_barrett()
- */
- /**
- * Cache constants
- *
- * $cache[self::VARIABLE] tells us whether or not the cached data is still valid.
- */
- const VARIABLE = 0;
- /**
- * $cache[self::DATA] contains the cached data.
- */
- const DATA = 1;
- /**#@-*/
-
- /**#@+
- * Mode constants.
- *
- * @access private
- * @see BigInteger::__construct()
- */
- /**
- * To use the pure-PHP implementation
- */
- const MODE_INTERNAL = 1;
- /**
- * To use the BCMath library
- *
- * (if enabled; otherwise, the internal implementation will be used)
- */
- const MODE_BCMATH = 2;
- /**
- * To use the GMP library
- *
- * (if present; otherwise, either the BCMath or the internal implementation will be used)
- */
- const MODE_GMP = 3;
- /**#@-*/
-
/**
- * Karatsuba Cutoff
- *
- * At what point do we switch between Karatsuba multiplication and schoolbook long multiplication?
- *
- * @access private
- */
- const KARATSUBA_CUTOFF = 25;
-
- /**#@+
- * Static properties used by the pure-PHP implementation.
+ * Main Engine
*
- * @see __construct()
+ * @var class-string<Engine>
*/
- static $base;
- static $baseFull;
- static $maxDigit;
- static $msb;
+ private static $mainEngine;
/**
- * $max10 in greatest $max10Len satisfying
- * $max10 = 10**$max10Len <= 2**$base.
- */
- static $max10;
-
- /**
- * $max10Len in greatest $max10Len satisfying
- * $max10 = 10**$max10Len <= 2**$base.
- */
- static $max10Len;
- static $maxDigit2;
- /**#@-*/
-
- /**
- * Holds the BigInteger's value.
+ * Selected Engines
*
- * @var array
- * @access private
+ * @var list<string>
*/
- var $value;
+ private static $engines;
/**
- * Holds the BigInteger's magnitude.
+ * The actual BigInteger object
*
- * @var bool
- * @access private
+ * @var object
*/
- var $is_negative = false;
+ private $value;
/**
- * Precision
- *
- * @see self::setPrecision()
- * @access private
- */
- var $precision = -1;
-
- /**
- * Precision Bitmask
+ * Mode independent value used for serialization.
*
- * @see self::setPrecision()
- * @access private
+ * @see self::__sleep()
+ * @see self::__wakeup()
+ * @var string
*/
- var $bitmask = false;
+ private $hex;
/**
- * Mode independent value used for serialization.
- *
- * If the bcmath or gmp extensions are installed $this->value will be a non-serializable resource, hence the need for
- * a variable that'll be serializable regardless of whether or not extensions are being used. Unlike $this->value,
- * however, $this->hex is only calculated when $this->__sleep() is called.
+ * Precision (used only for serialization)
*
* @see self::__sleep()
* @see self::__wakeup()
- * @var string
- * @access private
+ * @var int
*/
- var $hex;
+ private $precision;
/**
- * Converts base-2, base-10, base-16, and binary strings (base-256) to BigIntegers.
+ * Sets engine type.
*
- * If the second parameter - $base - is negative, then it will be assumed that the number's are encoded using
- * two's compliment. The sole exception to this is -10, which is treated the same as 10 is.
- *
- * Here's an example:
- * <code>
- * <?php
- * $a = new \phpseclib\Math\BigInteger('0x32', 16); // 50 in base-16
- *
- * echo $a->toString(); // outputs 50
- * ?>
- * </code>
+ * Throws an exception if the type is invalid
*
- * @param int|string|resource $x base-10 number or base-$base number if $base set.
- * @param int $base
- * @return \phpseclib\Math\BigInteger
- * @access public
+ * @param string $main
+ * @param list<string> $modexps optional
+ * @return void
*/
- function __construct($x = 0, $base = 10)
+ public static function setEngine($main, array $modexps = ['DefaultEngine'])
{
- if (!defined('MATH_BIGINTEGER_MODE')) {
-
- // https://github.com/php/php-src/commit/e0a0e216a909dc4ee4ea7c113a5f41d49525f02e broke GMP
- // https://github.com/php/php-src/commit/424ba0f2ff9677d16b4e339e90885bd4bc49fcf1 fixed it
- // see https://github.com/php/php-src/issues/16870 for more info
- if (version_compare(PHP_VERSION, '8.2.26', '<')) {
- $gmpOK = true;
- } else {
- $gmpOK = !in_array(PHP_VERSION_ID, array(80226, 80314, 80400, 80401));
- }
- switch (true) {
- case extension_loaded('gmp') && $gmpOK:
- define('MATH_BIGINTEGER_MODE', self::MODE_GMP);
- break;
- case extension_loaded('bcmath'):
- define('MATH_BIGINTEGER_MODE', self::MODE_BCMATH);
- break;
- default:
- define('MATH_BIGINTEGER_MODE', self::MODE_INTERNAL);
- }
- }
-
- if (extension_loaded('openssl') && !defined('MATH_BIGINTEGER_OPENSSL_DISABLE') && !defined('MATH_BIGINTEGER_OPENSSL_ENABLED')) {
- // some versions of XAMPP have mismatched versions of OpenSSL which causes it not to work
- $versions = array();
-
- // avoid generating errors (even with suppression) when phpinfo() is disabled (common in production systems)
- if (function_exists('phpinfo')) {
- ob_start();
- @phpinfo();
- $content = ob_get_contents();
- ob_end_clean();
-
- preg_match_all('#OpenSSL (Header|Library) Version(.*)#im', $content, $matches);
-
- if (!empty($matches[1])) {
- for ($i = 0; $i < count($matches[1]); $i++) {
- $fullVersion = trim(str_replace('=>', '', strip_tags($matches[2][$i])));
-
- // Remove letter part in OpenSSL version
- if (!preg_match('/(\d+\.\d+\.\d+)/i', $fullVersion, $m)) {
- $versions[$matches[1][$i]] = $fullVersion;
- } else {
- $versions[$matches[1][$i]] = $m[0];
- }
- }
- }
- }
-
- // it doesn't appear that OpenSSL versions were reported upon until PHP 5.3+
- switch (true) {
- case !isset($versions['Header']):
- case !isset($versions['Library']):
- case $versions['Header'] == $versions['Library']:
- case version_compare($versions['Header'], '1.0.0') >= 0 && version_compare($versions['Library'], '1.0.0') >= 0:
- define('MATH_BIGINTEGER_OPENSSL_ENABLED', true);
- break;
- default:
- define('MATH_BIGINTEGER_OPENSSL_DISABLE', true);
- }
- }
+ self::$engines = [];
- if (!defined('PHP_INT_SIZE')) {
- define('PHP_INT_SIZE', 4);
+ $fqmain = 'phpseclib3\\Math\\BigInteger\\Engines\\' . $main;
+ if (!class_exists($fqmain) || !method_exists($fqmain, 'isValidEngine')) {
+ throw new \InvalidArgumentException("$main is not a valid engine");
}
-
- if (empty(self::$base) && MATH_BIGINTEGER_MODE == self::MODE_INTERNAL) {
- switch (PHP_INT_SIZE) {
- case 8: // use 64-bit integers if int size is 8 bytes
- self::$base = 31;
- self::$baseFull = 0x80000000;
- self::$maxDigit = 0x7FFFFFFF;
- self::$msb = 0x40000000;
- self::$max10 = 1000000000;
- self::$max10Len = 9;
- self::$maxDigit2 = pow(2, 62);
- break;
- //case 4: // use 64-bit floats if int size is 4 bytes
- default:
- self::$base = 26;
- self::$baseFull = 0x4000000;
- self::$maxDigit = 0x3FFFFFF;
- self::$msb = 0x2000000;
- self::$max10 = 10000000;
- self::$max10Len = 7;
- self::$maxDigit2 = pow(2, 52); // pow() prevents truncation
- }
+ if (!$fqmain::isValidEngine()) {
+ throw new BadConfigurationException("$main is not setup correctly on this system");
}
+ /** @var class-string<Engine> $fqmain */
+ self::$mainEngine = $fqmain;
- switch (MATH_BIGINTEGER_MODE) {
- case self::MODE_GMP:
- switch (true) {
- case is_resource($x) && get_resource_type($x) == 'GMP integer':
- // PHP 5.6 switched GMP from using resources to objects
- case $x instanceof \GMP:
- $this->value = $x;
- return;
- }
- $this->value = gmp_init(0);
+ $found = false;
+ foreach ($modexps as $modexp) {
+ try {
+ $fqmain::setModExpEngine($modexp);
+ $found = true;
break;
- case self::MODE_BCMATH:
- $this->value = '0';
- break;
- default:
- $this->value = array();
+ } catch (\Exception $e) {
+ }
}
- // '0' counts as empty() but when the base is 256 '0' is equal to ord('0') or 48
- // '0' is the only value like this per http://php.net/empty
- if (empty($x) && (abs($base) != 256 || $x !== '0')) {
- return;
+ if (!$found) {
+ throw new BadConfigurationException("No valid modular exponentiation engine found for $main");
}
- switch ($base) {
- case -256:
- if (ord($x[0]) & 0x80) {
- $x = ~$x;
- $this->is_negative = true;
- }
- case 256:
- switch (MATH_BIGINTEGER_MODE) {
- case self::MODE_GMP:
- $this->value = function_exists('gmp_import') ?
- gmp_import($x) :
- gmp_init('0x' . bin2hex($x));
- if ($this->is_negative) {
- $this->value = gmp_neg($this->value);
- }
- break;
- case self::MODE_BCMATH:
- // round $len to the nearest 4 (thanks, DavidMJ!)
- $len = (strlen($x) + 3) & ~3;
-
- $x = str_pad($x, $len, chr(0), STR_PAD_LEFT);
-
- for ($i = 0; $i < $len; $i+= 4) {
- $this->value = bcmul($this->value, '4294967296', 0); // 4294967296 == 2**32
- $this->value = bcadd($this->value, 0x1000000 * ord($x[$i]) + ((ord($x[$i + 1]) << 16) | (ord($x[$i + 2]) << 8) | ord($x[$i + 3])), 0);
- }
-
- if ($this->is_negative) {
- $this->value = '-' . $this->value;
- }
-
- break;
- // converts a base-2**8 (big endian / msb) number to base-2**26 (little endian / lsb)
- default:
- while (strlen($x)) {
- $this->value[] = $this->_bytes2int($this->_base256_rshift($x, self::$base));
- }
- }
-
- if ($this->is_negative) {
- if (MATH_BIGINTEGER_MODE != self::MODE_INTERNAL) {
- $this->is_negative = false;
- }
- $temp = $this->add(new static('-1'));
- $this->value = $temp->value;
- }
- break;
- case 16:
- case -16:
- if ($base > 0 && $x[0] == '-') {
- $this->is_negative = true;
- $x = substr($x, 1);
- }
-
- $x = preg_replace('#^(?:0x)?([A-Fa-f0-9]*).*#s', '$1', $x);
-
- $is_negative = false;
- if ($base < 0 && hexdec($x[0]) >= 8) {
- $this->is_negative = $is_negative = true;
- $x = bin2hex(~pack('H*', $x));
- }
-
- switch (MATH_BIGINTEGER_MODE) {
- case self::MODE_GMP:
- $temp = $this->is_negative ? '-0x' . $x : '0x' . $x;
- $this->value = gmp_init($temp);
- $this->is_negative = false;
- break;
- case self::MODE_BCMATH:
- $x = (strlen($x) & 1) ? '0' . $x : $x;
- $temp = new static(pack('H*', $x), 256);
- $this->value = $this->is_negative ? '-' . $temp->value : $temp->value;
- $this->is_negative = false;
- break;
- default:
- $x = (strlen($x) & 1) ? '0' . $x : $x;
- $temp = new static(pack('H*', $x), 256);
- $this->value = $temp->value;
- }
-
- if ($is_negative) {
- $temp = $this->add(new static('-1'));
- $this->value = $temp->value;
- }
- break;
- case 10:
- case -10:
- // (?<!^)(?:-).*: find any -'s that aren't at the beginning and then any characters that follow that
- // (?<=^|-)0*: find any 0's that are preceded by the start of the string or by a - (ie. octals)
- // [^-0-9].*: find any non-numeric characters and then any characters that follow that
- $x = preg_replace('#(?<!^)(?:-).*|(?<=^|-)0*|[^-0-9].*#s', '', $x);
- if (!strlen($x) || $x == '-') {
- $x = '0';
- }
-
- switch (MATH_BIGINTEGER_MODE) {
- case self::MODE_GMP:
- $this->value = gmp_init($x);
- break;
- case self::MODE_BCMATH:
- // explicitly casting $x to a string is necessary, here, since doing $x[0] on -1 yields different
- // results then doing it on '-1' does (modInverse does $x[0])
- $this->value = $x === '-' ? '0' : (string) $x;
- break;
- default:
- $temp = new static();
-
- $multiplier = new static();
- $multiplier->value = array(self::$max10);
-
- if ($x[0] == '-') {
- $this->is_negative = true;
- $x = substr($x, 1);
- }
-
- $x = str_pad($x, strlen($x) + ((self::$max10Len - 1) * strlen($x)) % self::$max10Len, 0, STR_PAD_LEFT);
- while (strlen($x)) {
- $temp = $temp->multiply($multiplier);
- $temp = $temp->add(new static($this->_int2bytes(substr($x, 0, self::$max10Len)), 256));
- $x = substr($x, self::$max10Len);
- }
-
- $this->value = $temp->value;
- }
- break;
- case 2: // base-2 support originally implemented by Lluis Pamies - thanks!
- case -2:
- if ($base > 0 && $x[0] == '-') {
- $this->is_negative = true;
- $x = substr($x, 1);
- }
-
- $x = preg_replace('#^([01]*).*#s', '$1', $x);
- $x = str_pad($x, strlen($x) + (3 * strlen($x)) % 4, 0, STR_PAD_LEFT);
-
- $str = '0x';
- while (strlen($x)) {
- $part = substr($x, 0, 4);
- $str.= dechex(bindec($part));
- $x = substr($x, 4);
- }
-
- if ($this->is_negative) {
- $str = '-' . $str;
- }
-
- $temp = new static($str, 8 * $base); // ie. either -16 or +16
- $this->value = $temp->value;
- $this->is_negative = $temp->is_negative;
-
- break;
- default:
- // base not supported, so we'll let $this == 0
- }
+ self::$engines = [$main, $modexp];
}
/**
- * Converts a BigInteger to a byte string (eg. base-256).
+ * Returns the engine type
*
- * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're
- * saved as two's compliment.
- *
- * Here's an example:
- * <code>
- * <?php
- * $a = new \phpseclib\Math\BigInteger('65');
- *
- * echo $a->toBytes(); // outputs chr(65)
- * ?>
- * </code>
- *
- * @param bool $twos_compliment
- * @return string
- * @access public
- * @internal Converts a base-2**26 number to base-2**8
+ * @return string[]
*/
- function toBytes($twos_compliment = false)
+ public static function getEngine()
{
- if ($twos_compliment) {
- $comparison = $this->compare(new static());
- if ($comparison == 0) {
- return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : '';
- }
+ self::initialize_static_variables();
- $temp = $comparison < 0 ? $this->add(new static(1)) : $this->copy();
- $bytes = $temp->toBytes();
-
- if (!strlen($bytes)) { // eg. if the number we're trying to convert is -1
- $bytes = chr(0);
- }
-
- if ($this->precision <= 0 && (ord($bytes[0]) & 0x80)) {
- $bytes = chr(0) . $bytes;
- }
-
- return $comparison < 0 ? ~$bytes : $bytes;
- }
-
- switch (MATH_BIGINTEGER_MODE) {
- case self::MODE_GMP:
- if (gmp_cmp($this->value, gmp_init(0)) == 0) {
- return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : '';
- }
-
- if (function_exists('gmp_export')) {
- $temp = gmp_export($this->value);
- } else {
- $temp = gmp_strval(gmp_abs($this->value), 16);
- $temp = (strlen($temp) & 1) ? '0' . $temp : $temp;
- $temp = pack('H*', $temp);
- }
-
- return $this->precision > 0 ?
- substr(str_pad($temp, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) :
- ltrim($temp, chr(0));
- case self::MODE_BCMATH:
- if ($this->value === '0') {
- return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : '';
- }
-
- $value = '';
- $current = $this->value;
-
- if ($current[0] == '-') {
- $current = substr($current, 1);
- }
-
- while (bccomp($current, '0', 0) > 0) {
- $temp = bcmod($current, '16777216');
- $value = chr($temp >> 16) . chr($temp >> 8) . chr($temp) . $value;
- $current = bcdiv($current, '16777216', 0);
- }
-
- return $this->precision > 0 ?
- substr(str_pad($value, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) :
- ltrim($value, chr(0));
- }
-
- if (!count($this->value)) {
- return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : '';
- }
- $result = $this->_int2bytes($this->value[count($this->value) - 1]);
-
- $temp = $this->copy();
-
- for ($i = count($temp->value) - 2; $i >= 0; --$i) {
- $temp->_base256_lshift($result, self::$base);
- $result = $result | str_pad($temp->_int2bytes($temp->value[$i]), strlen($result), chr(0), STR_PAD_LEFT);
- }
-
- return $this->precision > 0 ?
- str_pad(substr($result, -(($this->precision + 7) >> 3)), ($this->precision + 7) >> 3, chr(0), STR_PAD_LEFT) :
- $result;
+ return self::$engines;
}
/**
- * Converts a BigInteger to a hex string (eg. base-16)).
- *
- * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're
- * saved as two's compliment.
- *
- * Here's an example:
- * <code>
- * <?php
- * $a = new \phpseclib\Math\BigInteger('65');
- *
- * echo $a->toHex(); // outputs '41'
- * ?>
- * </code>
- *
- * @param bool $twos_compliment
- * @return string
- * @access public
- * @internal Converts a base-2**26 number to base-2**8
+ * Initialize static variables
*/
- function toHex($twos_compliment = false)
+ private static function initialize_static_variables()
{
- return bin2hex($this->toBytes($twos_compliment));
- }
+ if (!isset(self::$mainEngine)) {
+ $engines = [
+ ['GMP', ['DefaultEngine']],
+ ['PHP64', ['OpenSSL']],
+ ['BCMath', ['OpenSSL']],
+ ['PHP32', ['OpenSSL']],
+ ['PHP64', ['DefaultEngine']],
+ ['PHP32', ['DefaultEngine']]
+ ];
+ // per https://phpseclib.com/docs/speed PHP 8.4.0+ _significantly_ sped up BCMath
+ if (version_compare(PHP_VERSION, '8.4.0') >= 0) {
+ $engines[1][0] = 'BCMath';
+ $engines[2][0] = 'PHP64';
+ }
- /**
- * Converts a BigInteger to a bit string (eg. base-2).
- *
- * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're
- * saved as two's compliment.
- *
- * Here's an example:
- * <code>
- * <?php
- * $a = new \phpseclib\Math\BigInteger('65');
- *
- * echo $a->toBits(); // outputs '1000001'
- * ?>
- * </code>
- *
- * @param bool $twos_compliment
- * @return string
- * @access public
- * @internal Converts a base-2**26 number to base-2**2
- */
- function toBits($twos_compliment = false)
- {
- $hex = $this->toHex($twos_compliment);
- $bits = '';
- for ($i = strlen($hex) - 6, $start = strlen($hex) % 6; $i >= $start; $i-=6) {
- $bits = str_pad(decbin(hexdec(substr($hex, $i, 6))), 24, '0', STR_PAD_LEFT) . $bits;
- }
- if ($start) { // hexdec('') == 0
- $bits = str_pad(decbin(hexdec(substr($hex, 0, $start))), 8 * $start, '0', STR_PAD_LEFT) . $bits;
- }
- $result = $this->precision > 0 ? substr($bits, -$this->precision) : ltrim($bits, '0');
+ foreach ($engines as $engine) {
+ try {
+ self::setEngine($engine[0], $engine[1]);
+ return;
+ } catch (\Exception $e) {
+ }
+ }
- if ($twos_compliment && $this->compare(new static()) > 0 && $this->precision <= 0) {
- return '0' . $result;
+ throw new \UnexpectedValueException('No valid BigInteger found. This is only possible when JIT is enabled on Windows and neither the GMP or BCMath extensions are available so either disable JIT or install GMP / BCMath');
}
-
- return $result;
}
/**
- * Converts a BigInteger to a base-10 number.
- *
- * Here's an example:
- * <code>
- * <?php
- * $a = new \phpseclib\Math\BigInteger('50');
+ * Converts base-2, base-10, base-16, and binary strings (base-256) to BigIntegers.
*
- * echo $a->toString(); // outputs 50
- * ?>
- * </code>
+ * If the second parameter - $base - is negative, then it will be assumed that the number's are encoded using
+ * two's compliment. The sole exception to this is -10, which is treated the same as 10 is.
*
- * @return string
- * @access public
- * @internal Converts a base-2**26 number to base-10**7 (which is pretty much base-10)
+ * @param string|int|Engine $x Base-10 number or base-$base number if $base set.
+ * @param int $base
*/
- function toString()
+ public function __construct($x = 0, $base = 10)
{
- switch (MATH_BIGINTEGER_MODE) {
- case self::MODE_GMP:
- return gmp_strval($this->value);
- case self::MODE_BCMATH:
- if ($this->value === '0') {
- return '0';
- }
-
- return ltrim($this->value, '0');
- }
+ self::initialize_static_variables();
- if (!count($this->value)) {
- return '0';
- }
-
- $temp = $this->copy();
- $temp->bitmask = false;
- $temp->is_negative = false;
-
- $divisor = new static();
- $divisor->value = array(self::$max10);
- $result = '';
- while (count($temp->value)) {
- list($temp, $mod) = $temp->divide($divisor);
- $result = str_pad(isset($mod->value[0]) ? $mod->value[0] : '', self::$max10Len, '0', STR_PAD_LEFT) . $result;
- }
- $result = ltrim($result, '0');
- if (empty($result)) {
- $result = '0';
- }
-
- if ($this->is_negative) {
- $result = '-' . $result;
- }
-
- return $result;
- }
-
- /**
- * Return the size of a BigInteger in bits
- *
- * @return int
- */
- function getLength()
- {
- if (MATH_BIGINTEGER_MODE != self::MODE_INTERNAL) {
- return strlen($this->toBits());
+ if ($x instanceof self::$mainEngine) {
+ $this->value = clone $x;
+ } elseif ($x instanceof Engine) {
+ $this->value = new static("$x");
+ $this->value->setPrecision($x->getPrecision());
+ } else {
+ $this->value = new self::$mainEngine($x, $base);
}
-
- $max = count($this->value) - 1;
- return $max != -1 ?
- $max * self::$base + intval(ceil(log($this->value[$max] + 1, 2))) :
- 0;
}
/**
- * Return the size of a BigInteger in bytes
- *
- * @return int
- */
- function getLengthInBytes()
- {
- return (int) ceil($this->getLength() / 8);
- }
-
- /**
- * Copy an object
- *
- * PHP5 passes objects by reference while PHP4 passes by value. As such, we need a function to guarantee
- * that all objects are passed by value, when appropriate. More information can be found here:
- *
- * {@link http://php.net/language.oop5.basic#51624}
+ * Converts a BigInteger to a base-10 number.
*
- * @access public
- * @see self::__clone()
- * @return \phpseclib\Math\BigInteger
+ * @return string
*/
- function copy()
+ public function toString()
{
- $temp = new static();
- $temp->value = $this->value;
- $temp->is_negative = $this->is_negative;
- $temp->precision = $this->precision;
- $temp->bitmask = $this->bitmask;
- return $temp;
+ return $this->value->toString();
}
/**
* __toString() magic method
- *
- * Will be called, automatically, if you're supporting just PHP5. If you're supporting PHP4, you'll need to call
- * toString().
- *
- * @access public
- * @internal Implemented per a suggestion by Techie-Michael - thanks!
*/
- function __toString()
+ public function __toString()
{
- return $this->toString();
+ return (string)$this->value;
}
/**
- * __clone() magic method
- *
- * Although you can call BigInteger::__toString() directly in PHP5, you cannot call BigInteger::__clone() directly
- * in PHP5. You can in PHP4 since it's not a magic method, but in PHP5, you have to call it by using the PHP5
- * only syntax of $y = clone $x. As such, if you're trying to write an application that works on both PHP4 and
- * PHP5, call BigInteger::copy(), instead.
+ * __debugInfo() magic method
*
- * @access public
- * @see self::copy()
- * @return \phpseclib\Math\BigInteger
+ * Will be called, automatically, when print_r() or var_dump() are called
*/
- function __clone()
+ public function __debugInfo()
{
- return $this->copy();
+ return $this->value->__debugInfo();
}
/**
- * __sleep() magic method
- *
- * Will be called, automatically, when serialize() is called on a BigInteger object.
+ * Converts a BigInteger to a byte string (eg. base-256).
*
- * @see self::__wakeup()
- * @access public
+ * @param bool $twos_compliment
+ * @return string
*/
- function __sleep()
+ public function toBytes($twos_compliment = false)
{
- $this->hex = $this->toHex(true);
- $vars = array('hex');
- if ($this->precision > 0) {
- $vars[] = 'precision';
- }
- return $vars;
+ return $this->value->toBytes($twos_compliment);
}
/**
- * __wakeup() magic method
+ * Converts a BigInteger to a hex string (eg. base-16).
*
- * Will be called, automatically, when unserialize() is called on a BigInteger object.
- *
- * @see self::__sleep()
- * @access public
+ * @param bool $twos_compliment
+ * @return string
*/
- function __wakeup()
+ public function toHex($twos_compliment = false)
{
- $temp = new static($this->hex, -16);
- $this->value = $temp->value;
- $this->is_negative = $temp->is_negative;
- if ($this->precision > 0) {
- // recalculate $this->bitmask
- $this->setPrecision($this->precision);
- }
+ return $this->value->toHex($twos_compliment);
}
/**
- * __debugInfo() magic method
+ * Converts a BigInteger to a bit string (eg. base-2).
*
- * Will be called, automatically, when print_r() or var_dump() are called
+ * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're
+ * saved as two's compliment.
*
- * @access public
+ * @param bool $twos_compliment
+ * @return string
*/
- function __debugInfo()
+ public function toBits($twos_compliment = false)
{
- $opts = array();
- switch (MATH_BIGINTEGER_MODE) {
- case self::MODE_GMP:
- $engine = 'gmp';
- break;
- case self::MODE_BCMATH:
- $engine = 'bcmath';
- break;
- case self::MODE_INTERNAL:
- $engine = 'internal';
- $opts[] = PHP_INT_SIZE == 8 ? '64-bit' : '32-bit';
- }
- if (MATH_BIGINTEGER_MODE != self::MODE_GMP && defined('MATH_BIGINTEGER_OPENSSL_ENABLED')) {
- $opts[] = 'OpenSSL';
- }
- if (!empty($opts)) {
- $engine.= ' (' . implode('.', $opts) . ')';
- }
- return array(
- 'value' => '0x' . $this->toHex(true),
- 'engine' => $engine
- );
+ return $this->value->toBits($twos_compliment);
}
/**
* Adds two BigIntegers.
*
- * Here's an example:
- * <code>
- * <?php
- * $a = new \phpseclib\Math\BigInteger('10');
- * $b = new \phpseclib\Math\BigInteger('20');
- *
- * $c = $a->add($b);
- *
- * echo $c->toString(); // outputs 30
- * ?>
- * </code>
- *
- * @param \phpseclib\Math\BigInteger $y
- * @return \phpseclib\Math\BigInteger
- * @access public
- * @internal Performs base-2**52 addition
- */
- function add($y)
- {
- switch (MATH_BIGINTEGER_MODE) {
- case self::MODE_GMP:
- $temp = new static();
- $temp->value = gmp_add($this->value, $y->value);
-
- return $this->_normalize($temp);
- case self::MODE_BCMATH:
- $temp = new static();
- $temp->value = bcadd($this->value, $y->value, 0);
-
- return $this->_normalize($temp);
- }
-
- $temp = $this->_add($this->value, $this->is_negative, $y->value, $y->is_negative);
-
- $result = new static();
- $result->value = $temp[self::VALUE];
- $result->is_negative = $temp[self::SIGN];
-
- return $this->_normalize($result);
- }
-
- /**
- * Performs addition.
- *
- * @param array $x_value
- * @param bool $x_negative
- * @param array $y_value
- * @param bool $y_negative
- * @return array
- * @access private
+ * @param BigInteger $y
+ * @return BigInteger
*/
- function _add($x_value, $x_negative, $y_value, $y_negative)
+ public function add(BigInteger $y)
{
- $x_size = count($x_value);
- $y_size = count($y_value);
-
- if ($x_size == 0) {
- return array(
- self::VALUE => $y_value,
- self::SIGN => $y_negative
- );
- } elseif ($y_size == 0) {
- return array(
- self::VALUE => $x_value,
- self::SIGN => $x_negative
- );
- }
-
- // subtract, if appropriate
- if ($x_negative != $y_negative) {
- if ($x_value == $y_value) {
- return array(
- self::VALUE => array(),
- self::SIGN => false
- );
- }
-
- $temp = $this->_subtract($x_value, false, $y_value, false);
- $temp[self::SIGN] = $this->_compare($x_value, false, $y_value, false) > 0 ?
- $x_negative : $y_negative;
-
- return $temp;
- }
-
- if ($x_size < $y_size) {
- $size = $x_size;
- $value = $y_value;
- } else {
- $size = $y_size;
- $value = $x_value;
- }
-
- $value[count($value)] = 0; // just in case the carry adds an extra digit
-
- $carry = 0;
- for ($i = 0, $j = 1; $j < $size; $i+=2, $j+=2) {
- $sum = $x_value[$j] * self::$baseFull + $x_value[$i] + $y_value[$j] * self::$baseFull + $y_value[$i] + $carry;
- $carry = $sum >= self::$maxDigit2; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1
- $sum = $carry ? $sum - self::$maxDigit2 : $sum;
-
- $temp = self::$base === 26 ? intval($sum / 0x4000000) : ($sum >> 31);
-
- $value[$i] = (int) ($sum - self::$baseFull * $temp); // eg. a faster alternative to fmod($sum, 0x4000000)
- $value[$j] = $temp;
- }
-
- if ($j == $size) { // ie. if $y_size is odd
- $sum = $x_value[$i] + $y_value[$i] + $carry;
- $carry = $sum >= self::$baseFull;
- $value[$i] = $carry ? $sum - self::$baseFull : $sum;
- ++$i; // ie. let $i = $j since we've just done $value[$i]
- }
-
- if ($carry) {
- for (; $value[$i] == self::$maxDigit; ++$i) {
- $value[$i] = 0;
- }
- ++$value[$i];
- }
-
- return array(
- self::VALUE => $this->_trim($value),
- self::SIGN => $x_negative
- );
+ return new static($this->value->add($y->value));
}
/**
* Subtracts two BigIntegers.
*
- * Here's an example:
- * <code>
- * <?php
- * $a = new \phpseclib\Math\BigInteger('10');
- * $b = new \phpseclib\Math\BigInteger('20');
- *
- * $c = $a->subtract($b);
- *
- * echo $c->toString(); // outputs -10
- * ?>
- * </code>
- *
- * @param \phpseclib\Math\BigInteger $y
- * @return \phpseclib\Math\BigInteger
- * @access public
- * @internal Performs base-2**52 subtraction
+ * @param BigInteger $y
+ * @return BigInteger
*/
- function subtract($y)
+ public function subtract(BigInteger $y)
{
- switch (MATH_BIGINTEGER_MODE) {
- case self::MODE_GMP:
- $temp = new static();
- $temp->value = gmp_sub($this->value, $y->value);
-
- return $this->_normalize($temp);
- case self::MODE_BCMATH:
- $temp = new static();
- $temp->value = bcsub($this->value, $y->value, 0);
-
- return $this->_normalize($temp);
- }
-
- $temp = $this->_subtract($this->value, $this->is_negative, $y->value, $y->is_negative);
-
- $result = new static();
- $result->value = $temp[self::VALUE];
- $result->is_negative = $temp[self::SIGN];
-
- return $this->_normalize($result);
- }
-
- /**
- * Performs subtraction.
- *
- * @param array $x_value
- * @param bool $x_negative
- * @param array $y_value
- * @param bool $y_negative
- * @return array
- * @access private
- */
- function _subtract($x_value, $x_negative, $y_value, $y_negative)
- {
- $x_size = count($x_value);
- $y_size = count($y_value);
-
- if ($x_size == 0) {
- return array(
- self::VALUE => $y_value,
- self::SIGN => !$y_negative
- );
- } elseif ($y_size == 0) {
- return array(
- self::VALUE => $x_value,
- self::SIGN => $x_negative
- );
- }
-
- // add, if appropriate (ie. -$x - +$y or +$x - -$y)
- if ($x_negative != $y_negative) {
- $temp = $this->_add($x_value, false, $y_value, false);
- $temp[self::SIGN] = $x_negative;
-
- return $temp;
- }
-
- $diff = $this->_compare($x_value, $x_negative, $y_value, $y_negative);
-
- if (!$diff) {
- return array(
- self::VALUE => array(),
- self::SIGN => false
- );
- }
-
- // switch $x and $y around, if appropriate.
- if ((!$x_negative && $diff < 0) || ($x_negative && $diff > 0)) {
- $temp = $x_value;
- $x_value = $y_value;
- $y_value = $temp;
-
- $x_negative = !$x_negative;
-
- $x_size = count($x_value);
- $y_size = count($y_value);
- }
-
- // at this point, $x_value should be at least as big as - if not bigger than - $y_value
-
- $carry = 0;
- for ($i = 0, $j = 1; $j < $y_size; $i+=2, $j+=2) {
- $sum = $x_value[$j] * self::$baseFull + $x_value[$i] - $y_value[$j] * self::$baseFull - $y_value[$i] - $carry;
- $carry = $sum < 0; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1
- $sum = $carry ? $sum + self::$maxDigit2 : $sum;
-
- $temp = self::$base === 26 ? intval($sum / 0x4000000) : ($sum >> 31);
-
- $x_value[$i] = (int) ($sum - self::$baseFull * $temp);
- $x_value[$j] = $temp;
- }
-
- if ($j == $y_size) { // ie. if $y_size is odd
- $sum = $x_value[$i] - $y_value[$i] - $carry;
- $carry = $sum < 0;
- $x_value[$i] = $carry ? $sum + self::$baseFull : $sum;
- ++$i;
- }
-
- if ($carry) {
- for (; !$x_value[$i]; ++$i) {
- $x_value[$i] = self::$maxDigit;
- }
- --$x_value[$i];
- }
-
- return array(
- self::VALUE => $this->_trim($x_value),
- self::SIGN => $x_negative
- );
+ return new static($this->value->subtract($y->value));
}
/**
* Multiplies two BigIntegers
*
- * Here's an example:
- * <code>
- * <?php
- * $a = new \phpseclib\Math\BigInteger('10');
- * $b = new \phpseclib\Math\BigInteger('20');
- *
- * $c = $a->multiply($b);
- *
- * echo $c->toString(); // outputs 200
- * ?>
- * </code>
- *
- * @param \phpseclib\Math\BigInteger $x
- * @return \phpseclib\Math\BigInteger
- * @access public
- */
- function multiply($x)
- {
- switch (MATH_BIGINTEGER_MODE) {
- case self::MODE_GMP:
- $temp = new static();
- $temp->value = gmp_mul($this->value, $x->value);
-
- return $this->_normalize($temp);
- case self::MODE_BCMATH:
- $temp = new static();
- $temp->value = bcmul($this->value, $x->value, 0);
-
- return $this->_normalize($temp);
- }
-
- $temp = $this->_multiply($this->value, $this->is_negative, $x->value, $x->is_negative);
-
- $product = new static();
- $product->value = $temp[self::VALUE];
- $product->is_negative = $temp[self::SIGN];
-
- return $this->_normalize($product);
- }
-
- /**
- * Performs multiplication.
- *
- * @param array $x_value
- * @param bool $x_negative
- * @param array $y_value
- * @param bool $y_negative
- * @return array
- * @access private
- */
- function _multiply($x_value, $x_negative, $y_value, $y_negative)
- {
- //if ( $x_value == $y_value ) {
- // return array(
- // self::VALUE => $this->_square($x_value),
- // self::SIGN => $x_sign != $y_value
- // );
- //}
-
- $x_length = count($x_value);
- $y_length = count($y_value);
-
- if (!$x_length || !$y_length) { // a 0 is being multiplied
- return array(
- self::VALUE => array(),
- self::SIGN => false
- );
- }
-
- return array(
- self::VALUE => min($x_length, $y_length) < 2 * self::KARATSUBA_CUTOFF ?
- $this->_trim($this->_regularMultiply($x_value, $y_value)) :
- $this->_trim($this->_karatsuba($x_value, $y_value)),
- self::SIGN => $x_negative != $y_negative
- );
- }
-
- /**
- * Performs long multiplication on two BigIntegers
- *
- * Modeled after 'multiply' in MutableBigInteger.java.
- *
- * @param array $x_value
- * @param array $y_value
- * @return array
- * @access private
+ * @param BigInteger $x
+ * @return BigInteger
*/
- function _regularMultiply($x_value, $y_value)
+ public function multiply(BigInteger $x)
{
- $x_length = count($x_value);
- $y_length = count($y_value);
-
- if (!$x_length || !$y_length) { // a 0 is being multiplied
- return array();
- }
-
- if ($x_length < $y_length) {
- $temp = $x_value;
- $x_value = $y_value;
- $y_value = $temp;
-
- $x_length = count($x_value);
- $y_length = count($y_value);
- }
-
- $product_value = $this->_array_repeat(0, $x_length + $y_length);
-
- // the following for loop could be removed if the for loop following it
- // (the one with nested for loops) initially set $i to 0, but
- // doing so would also make the result in one set of unnecessary adds,
- // since on the outermost loops first pass, $product->value[$k] is going
- // to always be 0
-
- $carry = 0;
-
- for ($j = 0; $j < $x_length; ++$j) { // ie. $i = 0
- $temp = $x_value[$j] * $y_value[0] + $carry; // $product_value[$k] == 0
- $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31);
- $product_value[$j] = (int) ($temp - self::$baseFull * $carry);
- }
-
- $product_value[$j] = $carry;
-
- // the above for loop is what the previous comment was talking about. the
- // following for loop is the "one with nested for loops"
- for ($i = 1; $i < $y_length; ++$i) {
- $carry = 0;
-
- for ($j = 0, $k = $i; $j < $x_length; ++$j, ++$k) {
- $temp = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry;
- $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31);
- $product_value[$k] = (int) ($temp - self::$baseFull * $carry);
- }
-
- $product_value[$k] = $carry;
- }
-
- return $product_value;
- }
-
- /**
- * Performs Karatsuba multiplication on two BigIntegers
- *
- * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and
- * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=120 MPM 5.2.3}.
- *
- * @param array $x_value
- * @param array $y_value
- * @return array
- * @access private
- */
- function _karatsuba($x_value, $y_value)
- {
- $m = min(count($x_value) >> 1, count($y_value) >> 1);
-
- if ($m < self::KARATSUBA_CUTOFF) {
- return $this->_regularMultiply($x_value, $y_value);
- }
-
- $x1 = array_slice($x_value, $m);
- $x0 = array_slice($x_value, 0, $m);
- $y1 = array_slice($y_value, $m);
- $y0 = array_slice($y_value, 0, $m);
-
- $z2 = $this->_karatsuba($x1, $y1);
- $z0 = $this->_karatsuba($x0, $y0);
-
- $z1 = $this->_add($x1, false, $x0, false);
- $temp = $this->_add($y1, false, $y0, false);
- $z1 = $this->_karatsuba($z1[self::VALUE], $temp[self::VALUE]);
- $temp = $this->_add($z2, false, $z0, false);
- $z1 = $this->_subtract($z1, false, $temp[self::VALUE], false);
-
- $z2 = array_merge(array_fill(0, 2 * $m, 0), $z2);
- $z1[self::VALUE] = array_merge(array_fill(0, $m, 0), $z1[self::VALUE]);
-
- $xy = $this->_add($z2, false, $z1[self::VALUE], $z1[self::SIGN]);
- $xy = $this->_add($xy[self::VALUE], $xy[self::SIGN], $z0, false);
-
- return $xy[self::VALUE];
- }
-
- /**
- * Performs squaring
- *
- * @param array $x
- * @return array
- * @access private
- */
- function _square($x = false)
- {
- return count($x) < 2 * self::KARATSUBA_CUTOFF ?
- $this->_trim($this->_baseSquare($x)) :
- $this->_trim($this->_karatsubaSquare($x));
- }
-
- /**
- * Performs traditional squaring on two BigIntegers
- *
- * Squaring can be done faster than multiplying a number by itself can be. See
- * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=7 HAC 14.2.4} /
- * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=141 MPM 5.3} for more information.
- *
- * @param array $value
- * @return array
- * @access private
- */
- function _baseSquare($value)
- {
- if (empty($value)) {
- return array();
- }
- $square_value = $this->_array_repeat(0, 2 * count($value));
-
- for ($i = 0, $max_index = count($value) - 1; $i <= $max_index; ++$i) {
- $i2 = $i << 1;
-
- $temp = $square_value[$i2] + $value[$i] * $value[$i];
- $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31);
- $square_value[$i2] = (int) ($temp - self::$baseFull * $carry);
-
- // note how we start from $i+1 instead of 0 as we do in multiplication.
- for ($j = $i + 1, $k = $i2 + 1; $j <= $max_index; ++$j, ++$k) {
- $temp = $square_value[$k] + 2 * $value[$j] * $value[$i] + $carry;
- $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31);
- $square_value[$k] = (int) ($temp - self::$baseFull * $carry);
- }
-
- // the following line can yield values larger 2**15. at this point, PHP should switch
- // over to floats.
- $square_value[$i + $max_index + 1] = $carry;
- }
-
- return $square_value;
- }
-
- /**
- * Performs Karatsuba "squaring" on two BigIntegers
- *
- * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and
- * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=151 MPM 5.3.4}.
- *
- * @param array $value
- * @return array
- * @access private
- */
- function _karatsubaSquare($value)
- {
- $m = count($value) >> 1;
-
- if ($m < self::KARATSUBA_CUTOFF) {
- return $this->_baseSquare($value);
- }
-
- $x1 = array_slice($value, $m);
- $x0 = array_slice($value, 0, $m);
-
- $z2 = $this->_karatsubaSquare($x1);
- $z0 = $this->_karatsubaSquare($x0);
-
- $z1 = $this->_add($x1, false, $x0, false);
- $z1 = $this->_karatsubaSquare($z1[self::VALUE]);
- $temp = $this->_add($z2, false, $z0, false);
- $z1 = $this->_subtract($z1, false, $temp[self::VALUE], false);
-
- $z2 = array_merge(array_fill(0, 2 * $m, 0), $z2);
- $z1[self::VALUE] = array_merge(array_fill(0, $m, 0), $z1[self::VALUE]);
-
- $xx = $this->_add($z2, false, $z1[self::VALUE], $z1[self::SIGN]);
- $xx = $this->_add($xx[self::VALUE], $xx[self::SIGN], $z0, false);
-
- return $xx[self::VALUE];
+ return new static($this->value->multiply($x->value));
}
/**
@@ -1439,8 +292,8 @@ class BigInteger
* Here's an example:
* <code>
* <?php
- * $a = new \phpseclib\Math\BigInteger('10');
- * $b = new \phpseclib\Math\BigInteger('20');
+ * $a = new \phpseclib3\Math\BigInteger('10');
+ * $b = new \phpseclib3\Math\BigInteger('20');
*
* list($quotient, $remainder) = $a->divide($b);
*
@@ -1450,1272 +303,186 @@ class BigInteger
* ?>
* </code>
*
- * @param \phpseclib\Math\BigInteger $y
- * @return array
- * @access public
- * @internal This function is based off of {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=9 HAC 14.20}.
+ * @param BigInteger $y
+ * @return BigInteger[]
*/
- function divide($y)
+ public function divide(BigInteger $y)
{
- switch (MATH_BIGINTEGER_MODE) {
- case self::MODE_GMP:
- $quotient = new static();
- $remainder = new static();
-
- list($quotient->value, $remainder->value) = gmp_div_qr($this->value, $y->value);
-
- if (gmp_sign($remainder->value) < 0) {
- $remainder->value = gmp_add($remainder->value, gmp_abs($y->value));
- }
-
- return array($this->_normalize($quotient), $this->_normalize($remainder));
- case self::MODE_BCMATH:
- $quotient = new static();
- $remainder = new static();
-
- $quotient->value = bcdiv($this->value, $y->value, 0);
- $remainder->value = bcmod($this->value, $y->value);
-
- if ($remainder->value[0] == '-') {
- $remainder->value = bcadd($remainder->value, $y->value[0] == '-' ? substr($y->value, 1) : $y->value, 0);
- }
-
- return array($this->_normalize($quotient), $this->_normalize($remainder));
- }
-
- if (count($y->value) == 1) {
- list($q, $r) = $this->_divide_digit($this->value, $y->value[0]);
- $quotient = new static();
- $remainder = new static();
- $quotient->value = $q;
- $remainder->value = array($r);
- $quotient->is_negative = $this->is_negative != $y->is_negative;
- return array($this->_normalize($quotient), $this->_normalize($remainder));
- }
-
- static $zero;
- if (!isset($zero)) {
- $zero = new static();
- }
-
- $x = $this->copy();
- $y = $y->copy();
-
- $x_sign = $x->is_negative;
- $y_sign = $y->is_negative;
-
- $x->is_negative = $y->is_negative = false;
-
- $diff = $x->compare($y);
-
- if (!$diff) {
- $temp = new static();
- $temp->value = array(1);
- $temp->is_negative = $x_sign != $y_sign;
- return array($this->_normalize($temp), $this->_normalize(new static()));
- }
-
- if ($diff < 0) {
- // if $x is negative, "add" $y.
- if ($x_sign) {
- $x = $y->subtract($x);
- }
- return array($this->_normalize(new static()), $this->_normalize($x));
- }
-
- // normalize $x and $y as described in HAC 14.23 / 14.24
- $msb = $y->value[count($y->value) - 1];
- for ($shift = 0; !($msb & self::$msb); ++$shift) {
- $msb <<= 1;
- }
- $x->_lshift($shift);
- $y->_lshift($shift);
- $y_value = &$y->value;
-
- $x_max = count($x->value) - 1;
- $y_max = count($y->value) - 1;
-
- $quotient = new static();
- $quotient_value = &$quotient->value;
- $quotient_value = $this->_array_repeat(0, $x_max - $y_max + 1);
-
- static $temp, $lhs, $rhs;
- if (!isset($temp)) {
- $temp = new static();
- $lhs = new static();
- $rhs = new static();
- }
- $temp_value = &$temp->value;
- $rhs_value = &$rhs->value;
-
- // $temp = $y << ($x_max - $y_max-1) in base 2**26
- $temp_value = array_merge($this->_array_repeat(0, $x_max - $y_max), $y_value);
-
- while ($x->compare($temp) >= 0) {
- // calculate the "common residue"
- ++$quotient_value[$x_max - $y_max];
- $x = $x->subtract($temp);
- $x_max = count($x->value) - 1;
- }
-
- for ($i = $x_max; $i >= $y_max + 1; --$i) {
- $x_value = &$x->value;
- $x_window = array(
- isset($x_value[$i]) ? $x_value[$i] : 0,
- isset($x_value[$i - 1]) ? $x_value[$i - 1] : 0,
- isset($x_value[$i - 2]) ? $x_value[$i - 2] : 0
- );
- $y_window = array(
- $y_value[$y_max],
- ($y_max > 0) ? $y_value[$y_max - 1] : 0
- );
-
- $q_index = $i - $y_max - 1;
- if ($x_window[0] == $y_window[0]) {
- $quotient_value[$q_index] = self::$maxDigit;
- } else {
- $quotient_value[$q_index] = $this->_safe_divide(
- $x_window[0] * self::$baseFull + $x_window[1],
- $y_window[0]
- );
- }
-
- $temp_value = array($y_window[1], $y_window[0]);
-
- $lhs->value = array($quotient_value[$q_index]);
- $lhs = $lhs->multiply($temp);
-
- $rhs_value = array($x_window[2], $x_window[1], $x_window[0]);
-
- while ($lhs->compare($rhs) > 0) {
- --$quotient_value[$q_index];
-
- $lhs->value = array($quotient_value[$q_index]);
- $lhs = $lhs->multiply($temp);
- }
-
- $adjust = $this->_array_repeat(0, $q_index);
- $temp_value = array($quotient_value[$q_index]);
- $temp = $temp->multiply($y);
- $temp_value = &$temp->value;
- if (count($temp_value)) {
- $temp_value = array_merge($adjust, $temp_value);
- }
-
- $x = $x->subtract($temp);
-
- if ($x->compare($zero) < 0) {
- $temp_value = array_merge($adjust, $y_value);
- $x = $x->add($temp);
-
- --$quotient_value[$q_index];
- }
-
- $x_max = count($x_value) - 1;
- }
-
- // unnormalize the remainder
- $x->_rshift($shift);
-
- $quotient->is_negative = $x_sign != $y_sign;
-
- // calculate the "common residue", if appropriate
- if ($x_sign) {
- $y->_rshift($shift);
- $x = $y->subtract($x);
- }
-
- return array($this->_normalize($quotient), $this->_normalize($x));
+ list($q, $r) = $this->value->divide($y->value);
+ return [
+ new static($q),
+ new static($r)
+ ];
}
/**
- * Divides a BigInteger by a regular integer
+ * Calculates modular inverses.
*
- * abc / x = a00 / x + b0 / x + c / x
+ * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses.
*
- * @param array $dividend
- * @param array $divisor
- * @return array
- * @access private
+ * @param BigInteger $n
+ * @return BigInteger
*/
- function _divide_digit($dividend, $divisor)
+ public function modInverse(BigInteger $n)
{
- $carry = 0;
- $result = array();
-
- for ($i = count($dividend) - 1; $i >= 0; --$i) {
- $temp = self::$baseFull * $carry + $dividend[$i];
- $result[$i] = $this->_safe_divide($temp, $divisor);
- $carry = (int) ($temp - $divisor * $result[$i]);
- }
-
- return array($result, $carry);
+ return new static($this->value->modInverse($n->value));
}
/**
- * Performs modular exponentiation.
- *
- * Here's an example:
- * <code>
- * <?php
- * $a = new \phpseclib\Math\BigInteger('10');
- * $b = new \phpseclib\Math\BigInteger('20');
- * $c = new \phpseclib\Math\BigInteger('30');
- *
- * $c = $a->modPow($b, $c);
+ * Calculates modular inverses.
*
- * echo $c->toString(); // outputs 10
- * ?>
- * </code>
+ * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses.
*
- * @param \phpseclib\Math\BigInteger $e
- * @param \phpseclib\Math\BigInteger $n
- * @return \phpseclib\Math\BigInteger
- * @access public
- * @internal The most naive approach to modular exponentiation has very unreasonable requirements, and
- * and although the approach involving repeated squaring does vastly better, it, too, is impractical
- * for our purposes. The reason being that division - by far the most complicated and time-consuming
- * of the basic operations (eg. +,-,*,/) - occurs multiple times within it.
- *
- * Modular reductions resolve this issue. Although an individual modular reduction takes more time
- * then an individual division, when performed in succession (with the same modulo), they're a lot faster.
- *
- * The two most commonly used modular reductions are Barrett and Montgomery reduction. Montgomery reduction,
- * although faster, only works when the gcd of the modulo and of the base being used is 1. In RSA, when the
- * base is a power of two, the modulo - a product of two primes - is always going to have a gcd of 1 (because
- * the product of two odd numbers is odd), but what about when RSA isn't used?
- *
- * In contrast, Barrett reduction has no such constraint. As such, some bigint implementations perform a
- * Barrett reduction after every operation in the modpow function. Others perform Barrett reductions when the
- * modulo is even and Montgomery reductions when the modulo is odd. BigInteger.java's modPow method, however,
- * uses a trick involving the Chinese Remainder Theorem to factor the even modulo into two numbers - one odd and
- * the other, a power of two - and recombine them, later. This is the method that this modPow function uses.
- * {@link http://islab.oregonstate.edu/papers/j34monex.pdf Montgomery Reduction with Even Modulus} elaborates.
+ * @param BigInteger $n
+ * @return BigInteger[]
*/
- function modPow($e, $n)
+ public function extendedGCD(BigInteger $n)
{
- $n = $this->bitmask !== false && $this->bitmask->compare($n) < 0 ? $this->bitmask : $n->abs();
-
- if ($e->compare(new static()) < 0) {
- $e = $e->abs();
-
- $temp = $this->modInverse($n);
- if ($temp === false) {
- return false;
- }
-
- return $this->_normalize($temp->modPow($e, $n));
- }
-
- if (MATH_BIGINTEGER_MODE == self::MODE_GMP) {
- $temp = new static();
- $temp->value = gmp_powm($this->value, $e->value, $n->value);
-
- return $this->_normalize($temp);
- }
-
- if ($this->compare(new static()) < 0 || $this->compare($n) > 0) {
- list(, $temp) = $this->divide($n);
- return $temp->modPow($e, $n);
- }
-
- if (defined('MATH_BIGINTEGER_OPENSSL_ENABLED')) {
- $components = array(
- 'modulus' => $n->toBytes(true),
- 'publicExponent' => $e->toBytes(true)
- );
-
- $components = array(
- 'modulus' => pack('Ca*a*', 2, $this->_encodeASN1Length(strlen($components['modulus'])), $components['modulus']),
- 'publicExponent' => pack('Ca*a*', 2, $this->_encodeASN1Length(strlen($components['publicExponent'])), $components['publicExponent'])
- );
-
- $RSAPublicKey = pack(
- 'Ca*a*a*',
- 48,
- $this->_encodeASN1Length(strlen($components['modulus']) + strlen($components['publicExponent'])),
- $components['modulus'],
- $components['publicExponent']
- );
-
- $rsaOID = pack('H*', '300d06092a864886f70d0101010500'); // hex version of MA0GCSqGSIb3DQEBAQUA
- $RSAPublicKey = chr(0) . $RSAPublicKey;
- $RSAPublicKey = chr(3) . $this->_encodeASN1Length(strlen($RSAPublicKey)) . $RSAPublicKey;
-
- $encapsulated = pack(
- 'Ca*a*',
- 48,
- $this->_encodeASN1Length(strlen($rsaOID . $RSAPublicKey)),
- $rsaOID . $RSAPublicKey
- );
-
- $RSAPublicKey = "-----BEGIN PUBLIC KEY-----\r\n" .
- chunk_split(base64_encode($encapsulated)) .
- '-----END PUBLIC KEY-----';
-
- $plaintext = str_pad($this->toBytes(), strlen($n->toBytes(true)) - 1, "\0", STR_PAD_LEFT);
-
- if (openssl_public_encrypt($plaintext, $result, $RSAPublicKey, OPENSSL_NO_PADDING)) {
- return new static($result, 256);
- }
- }
-
- if (MATH_BIGINTEGER_MODE == self::MODE_BCMATH) {
- $temp = new static();
- $temp->value = bcpowmod($this->value, $e->value, $n->value, 0);
-
- return $this->_normalize($temp);
- }
-
- if (empty($e->value)) {
- $temp = new static();
- $temp->value = array(1);
- return $this->_normalize($temp);
- }
-
- if ($e->value == array(1)) {
- list(, $temp) = $this->divide($n);
- return $this->_normalize($temp);
- }
-
- if ($e->value == array(2)) {
- $temp = new static();
- $temp->value = $this->_square($this->value);
- list(, $temp) = $temp->divide($n);
- return $this->_normalize($temp);
- }
-
- return $this->_normalize($this->_slidingWindow($e, $n, self::BARRETT));
-
- // the following code, although not callable, can be run independently of the above code
- // although the above code performed better in my benchmarks the following could might
- // perform better under different circumstances. in lieu of deleting it it's just been
- // made uncallable
-
- // is the modulo odd?
- if ($n->value[0] & 1) {
- return $this->_normalize($this->_slidingWindow($e, $n, self::MONTGOMERY));
- }
- // if it's not, it's even
-
- // find the lowest set bit (eg. the max pow of 2 that divides $n)
- for ($i = 0; $i < count($n->value); ++$i) {
- if ($n->value[$i]) {
- $temp = decbin($n->value[$i]);
- $j = strlen($temp) - strrpos($temp, '1') - 1;
- $j+= 26 * $i;
- break;
- }
- }
- // at this point, 2^$j * $n/(2^$j) == $n
-
- $mod1 = $n->copy();
- $mod1->_rshift($j);
- $mod2 = new static();
- $mod2->value = array(1);
- $mod2->_lshift($j);
-
- $part1 = ($mod1->value != array(1)) ? $this->_slidingWindow($e, $mod1, self::MONTGOMERY) : new static();
- $part2 = $this->_slidingWindow($e, $mod2, self::POWEROF2);
-
- $y1 = $mod2->modInverse($mod1);
- $y2 = $mod1->modInverse($mod2);
-
- $result = $part1->multiply($mod2);
- $result = $result->multiply($y1);
-
- $temp = $part2->multiply($mod1);
- $temp = $temp->multiply($y2);
-
- $result = $result->add($temp);
- list(, $result) = $result->divide($n);
-
- return $this->_normalize($result);
+ $extended = $this->value->extendedGCD($n->value);
+ $gcd = $extended['gcd'];
+ $x = $extended['x'];
+ $y = $extended['y'];
+ return [
+ 'gcd' => new static($gcd),
+ 'x' => new static($x),
+ 'y' => new static($y)
+ ];
}
/**
- * Performs modular exponentiation.
+ * Calculates the greatest common divisor
*
- * Alias for modPow().
+ * Say you have 693 and 609. The GCD is 21.
*
- * @param \phpseclib\Math\BigInteger $e
- * @param \phpseclib\Math\BigInteger $n
- * @return \phpseclib\Math\BigInteger
- * @access public
+ * @param BigInteger $n
+ * @return BigInteger
*/
- function powMod($e, $n)
+ public function gcd(BigInteger $n)
{
- return $this->modPow($e, $n);
+ return new static($this->value->gcd($n->value));
}
/**
- * Sliding Window k-ary Modular Exponentiation
- *
- * Based on {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=27 HAC 14.85} /
- * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=210 MPM 7.7}. In a departure from those algorithims,
- * however, this function performs a modular reduction after every multiplication and squaring operation.
- * As such, this function has the same preconditions that the reductions being used do.
- *
- * @param \phpseclib\Math\BigInteger $e
- * @param \phpseclib\Math\BigInteger $n
- * @param int $mode
- * @return \phpseclib\Math\BigInteger
- * @access private
- */
- function _slidingWindow($e, $n, $mode)
- {
- static $window_ranges = array(7, 25, 81, 241, 673, 1793); // from BigInteger.java's oddModPow function
- //static $window_ranges = array(0, 7, 36, 140, 450, 1303, 3529); // from MPM 7.3.1
-
- $e_value = $e->value;
- $e_length = count($e_value) - 1;
- $e_bits = decbin($e_value[$e_length]);
- for ($i = $e_length - 1; $i >= 0; --$i) {
- $e_bits.= str_pad(decbin($e_value[$i]), self::$base, '0', STR_PAD_LEFT);
- }
-
- $e_length = strlen($e_bits);
-
- // calculate the appropriate window size.
- // $window_size == 3 if $window_ranges is between 25 and 81, for example.
- for ($i = 0, $window_size = 1; $i < count($window_ranges) && $e_length > $window_ranges[$i]; ++$window_size, ++$i) {
- }
-
- $n_value = $n->value;
-
- // precompute $this^0 through $this^$window_size
- $powers = array();
- $powers[1] = $this->_prepareReduce($this->value, $n_value, $mode);
- $powers[2] = $this->_squareReduce($powers[1], $n_value, $mode);
-
- // we do every other number since substr($e_bits, $i, $j+1) (see below) is supposed to end
- // in a 1. ie. it's supposed to be odd.
- $temp = 1 << ($window_size - 1);
- for ($i = 1; $i < $temp; ++$i) {
- $i2 = $i << 1;
- $powers[$i2 + 1] = $this->_multiplyReduce($powers[$i2 - 1], $powers[2], $n_value, $mode);
- }
-
- $result = array(1);
- $result = $this->_prepareReduce($result, $n_value, $mode);
-
- for ($i = 0; $i < $e_length;) {
- if (!$e_bits[$i]) {
- $result = $this->_squareReduce($result, $n_value, $mode);
- ++$i;
- } else {
- for ($j = $window_size - 1; $j > 0; --$j) {
- if (!empty($e_bits[$i + $j])) {
- break;
- }
- }
-
- // eg. the length of substr($e_bits, $i, $j + 1)
- for ($k = 0; $k <= $j; ++$k) {
- $result = $this->_squareReduce($result, $n_value, $mode);
- }
-
- $result = $this->_multiplyReduce($result, $powers[bindec(substr($e_bits, $i, $j + 1))], $n_value, $mode);
-
- $i += $j + 1;
- }
- }
-
- $temp = new static();
- $temp->value = $this->_reduce($result, $n_value, $mode);
-
- return $temp;
- }
-
- /**
- * Modular reduction
- *
- * For most $modes this will return the remainder.
+ * Absolute value.
*
- * @see self::_slidingWindow()
- * @access private
- * @param array $x
- * @param array $n
- * @param int $mode
- * @return array
+ * @return BigInteger
*/
- function _reduce($x, $n, $mode)
+ public function abs()
{
- switch ($mode) {
- case self::MONTGOMERY:
- return $this->_montgomery($x, $n);
- case self::BARRETT:
- return $this->_barrett($x, $n);
- case self::POWEROF2:
- $lhs = new static();
- $lhs->value = $x;
- $rhs = new static();
- $rhs->value = $n;
- return $x->_mod2($n);
- case self::CLASSIC:
- $lhs = new static();
- $lhs->value = $x;
- $rhs = new static();
- $rhs->value = $n;
- list(, $temp) = $lhs->divide($rhs);
- return $temp->value;
- case self::NONE:
- return $x;
- default:
- // an invalid $mode was provided
- }
+ return new static($this->value->abs());
}
/**
- * Modular reduction preperation
+ * Set Precision
*
- * @see self::_slidingWindow()
- * @access private
- * @param array $x
- * @param array $n
- * @param int $mode
- * @return array
- */
- function _prepareReduce($x, $n, $mode)
- {
- if ($mode == self::MONTGOMERY) {
- return $this->_prepMontgomery($x, $n);
- }
- return $this->_reduce($x, $n, $mode);
- }
-
- /**
- * Modular multiply
- *
- * @see self::_slidingWindow()
- * @access private
- * @param array $x
- * @param array $y
- * @param array $n
- * @param int $mode
- * @return array
- */
- function _multiplyReduce($x, $y, $n, $mode)
- {
- if ($mode == self::MONTGOMERY) {
- return $this->_montgomeryMultiply($x, $y, $n);
- }
- $temp = $this->_multiply($x, false, $y, false);
- return $this->_reduce($temp[self::VALUE], $n, $mode);
- }
-
- /**
- * Modular square
+ * Some bitwise operations give different results depending on the precision being used. Examples include left
+ * shift, not, and rotates.
*
- * @see self::_slidingWindow()
- * @access private
- * @param array $x
- * @param array $n
- * @param int $mode
- * @return array
+ * @param int $bits
*/
- function _squareReduce($x, $n, $mode)
+ public function setPrecision($bits)
{
- if ($mode == self::MONTGOMERY) {
- return $this->_montgomeryMultiply($x, $x, $n);
- }
- return $this->_reduce($this->_square($x), $n, $mode);
+ $this->value->setPrecision($bits);
}
/**
- * Modulos for Powers of Two
+ * Get Precision
*
- * Calculates $x%$n, where $n = 2**$e, for some $e. Since this is basically the same as doing $x & ($n-1),
- * we'll just use this function as a wrapper for doing that.
+ * Returns the precision if it exists, false if it doesn't
*
- * @see self::_slidingWindow()
- * @access private
- * @param \phpseclib\Math\BigInteger $n
- * @return \phpseclib\Math\BigInteger
+ * @return int|bool
*/
- function _mod2($n)
+ public function getPrecision()
{
- $temp = new static();
- $temp->value = array(1);
- return $this->bitwise_and($n->subtract($temp));
- }
-
- /**
- * Barrett Modular Reduction
- *
- * See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=14 HAC 14.3.3} /
- * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=165 MPM 6.2.5} for more information. Modified slightly,
- * so as not to require negative numbers (initially, this script didn't support negative numbers).
- *
- * Employs "folding", as described at
- * {@link http://www.cosic.esat.kuleuven.be/publications/thesis-149.pdf#page=66 thesis-149.pdf#page=66}. To quote from
- * it, "the idea [behind folding] is to find a value x' such that x (mod m) = x' (mod m), with x' being smaller than x."
- *
- * Unfortunately, the "Barrett Reduction with Folding" algorithm described in thesis-149.pdf is not, as written, all that
- * usable on account of (1) its not using reasonable radix points as discussed in
- * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=162 MPM 6.2.2} and (2) the fact that, even with reasonable
- * radix points, it only works when there are an even number of digits in the denominator. The reason for (2) is that
- * (x >> 1) + (x >> 1) != x / 2 + x / 2. If x is even, they're the same, but if x is odd, they're not. See the in-line
- * comments for details.
- *
- * @see self::_slidingWindow()
- * @access private
- * @param array $n
- * @param array $m
- * @return array
- */
- function _barrett($n, $m)
- {
- static $cache = array(
- self::VARIABLE => array(),
- self::DATA => array()
- );
-
- $m_length = count($m);
-
- // if ($this->_compare($n, $this->_square($m)) >= 0) {
- if (count($n) > 2 * $m_length) {
- $lhs = new static();
- $rhs = new static();
- $lhs->value = $n;
- $rhs->value = $m;
- list(, $temp) = $lhs->divide($rhs);
- return $temp->value;
- }
-
- // if (m.length >> 1) + 2 <= m.length then m is too small and n can't be reduced
- if ($m_length < 5) {
- return $this->_regularBarrett($n, $m);
- }
-
- // n = 2 * m.length
-
- if (($key = array_search($m, $cache[self::VARIABLE])) === false) {
- $key = count($cache[self::VARIABLE]);
- $cache[self::VARIABLE][] = $m;
-
- $lhs = new static();
- $lhs_value = &$lhs->value;
- $lhs_value = $this->_array_repeat(0, $m_length + ($m_length >> 1));
- $lhs_value[] = 1;
- $rhs = new static();
- $rhs->value = $m;
-
- list($u, $m1) = $lhs->divide($rhs);
- $u = $u->value;
- $m1 = $m1->value;
-
- $cache[self::DATA][] = array(
- 'u' => $u, // m.length >> 1 (technically (m.length >> 1) + 1)
- 'm1'=> $m1 // m.length
- );
- } else {
- extract($cache[self::DATA][$key]);
- }
-
- $cutoff = $m_length + ($m_length >> 1);
- $lsd = array_slice($n, 0, $cutoff); // m.length + (m.length >> 1)
- $msd = array_slice($n, $cutoff); // m.length >> 1
- $lsd = $this->_trim($lsd);
- $temp = $this->_multiply($msd, false, $m1, false);
- $n = $this->_add($lsd, false, $temp[self::VALUE], false); // m.length + (m.length >> 1) + 1
-
- if ($m_length & 1) {
- return $this->_regularBarrett($n[self::VALUE], $m);
- }
-
- // (m.length + (m.length >> 1) + 1) - (m.length - 1) == (m.length >> 1) + 2
- $temp = array_slice($n[self::VALUE], $m_length - 1);
- // if even: ((m.length >> 1) + 2) + (m.length >> 1) == m.length + 2
- // if odd: ((m.length >> 1) + 2) + (m.length >> 1) == (m.length - 1) + 2 == m.length + 1
- $temp = $this->_multiply($temp, false, $u, false);
- // if even: (m.length + 2) - ((m.length >> 1) + 1) = m.length - (m.length >> 1) + 1
- // if odd: (m.length + 1) - ((m.length >> 1) + 1) = m.length - (m.length >> 1)
- $temp = array_slice($temp[self::VALUE], ($m_length >> 1) + 1);
- // if even: (m.length - (m.length >> 1) + 1) + m.length = 2 * m.length - (m.length >> 1) + 1
- // if odd: (m.length - (m.length >> 1)) + m.length = 2 * m.length - (m.length >> 1)
- $temp = $this->_multiply($temp, false, $m, false);
-
- // at this point, if m had an odd number of digits, we'd be subtracting a 2 * m.length - (m.length >> 1) digit
- // number from a m.length + (m.length >> 1) + 1 digit number. ie. there'd be an extra digit and the while loop
- // following this comment would loop a lot (hence our calling _regularBarrett() in that situation).
-
- $result = $this->_subtract($n[self::VALUE], false, $temp[self::VALUE], false);
-
- while ($this->_compare($result[self::VALUE], $result[self::SIGN], $m, false) >= 0) {
- $result = $this->_subtract($result[self::VALUE], $result[self::SIGN], $m, false);
- }
-
- return $result[self::VALUE];
+ return $this->value->getPrecision();
}
/**
- * (Regular) Barrett Modular Reduction
+ * Serialize
*
- * For numbers with more than four digits BigInteger::_barrett() is faster. The difference between that and this
- * is that this function does not fold the denominator into a smaller form.
- *
- * @see self::_slidingWindow()
- * @access private
- * @param array $x
- * @param array $n
- * @return array
- */
- function _regularBarrett($x, $n)
- {
- static $cache = array(
- self::VARIABLE => array(),
- self::DATA => array()
- );
-
- $n_length = count($n);
-
- if (count($x) > 2 * $n_length) {
- $lhs = new static();
- $rhs = new static();
- $lhs->value = $x;
- $rhs->value = $n;
- list(, $temp) = $lhs->divide($rhs);
- return $temp->value;
- }
-
- if (($key = array_search($n, $cache[self::VARIABLE])) === false) {
- $key = count($cache[self::VARIABLE]);
- $cache[self::VARIABLE][] = $n;
- $lhs = new static();
- $lhs_value = &$lhs->value;
- $lhs_value = $this->_array_repeat(0, 2 * $n_length);
- $lhs_value[] = 1;
- $rhs = new static();
- $rhs->value = $n;
- list($temp, ) = $lhs->divide($rhs); // m.length
- $cache[self::DATA][] = $temp->value;
- }
-
- // 2 * m.length - (m.length - 1) = m.length + 1
- $temp = array_slice($x, $n_length - 1);
- // (m.length + 1) + m.length = 2 * m.length + 1
- $temp = $this->_multiply($temp, false, $cache[self::DATA][$key], false);
- // (2 * m.length + 1) - (m.length - 1) = m.length + 2
- $temp = array_slice($temp[self::VALUE], $n_length + 1);
-
- // m.length + 1
- $result = array_slice($x, 0, $n_length + 1);
- // m.length + 1
- $temp = $this->_multiplyLower($temp, false, $n, false, $n_length + 1);
- // $temp == array_slice($temp->_multiply($temp, false, $n, false)->value, 0, $n_length + 1)
-
- if ($this->_compare($result, false, $temp[self::VALUE], $temp[self::SIGN]) < 0) {
- $corrector_value = $this->_array_repeat(0, $n_length + 1);
- $corrector_value[count($corrector_value)] = 1;
- $result = $this->_add($result, false, $corrector_value, false);
- $result = $result[self::VALUE];
- }
-
- // at this point, we're subtracting a number with m.length + 1 digits from another number with m.length + 1 digits
- $result = $this->_subtract($result, false, $temp[self::VALUE], $temp[self::SIGN]);
- while ($this->_compare($result[self::VALUE], $result[self::SIGN], $n, false) > 0) {
- $result = $this->_subtract($result[self::VALUE], $result[self::SIGN], $n, false);
- }
-
- return $result[self::VALUE];
- }
-
- /**
- * Performs long multiplication up to $stop digits
+ * Will be called, automatically, when serialize() is called on a BigInteger object.
*
- * If you're going to be doing array_slice($product->value, 0, $stop), some cycles can be saved.
+ * __sleep() / __wakeup() have been around since PHP 4.0
*
- * @see self::_regularBarrett()
- * @param array $x_value
- * @param bool $x_negative
- * @param array $y_value
- * @param bool $y_negative
- * @param int $stop
- * @return array
- * @access private
- */
- function _multiplyLower($x_value, $x_negative, $y_value, $y_negative, $stop)
- {
- $x_length = count($x_value);
- $y_length = count($y_value);
-
- if (!$x_length || !$y_length) { // a 0 is being multiplied
- return array(
- self::VALUE => array(),
- self::SIGN => false
- );
- }
-
- if ($x_length < $y_length) {
- $temp = $x_value;
- $x_value = $y_value;
- $y_value = $temp;
-
- $x_length = count($x_value);
- $y_length = count($y_value);
- }
-
- $product_value = $this->_array_repeat(0, $x_length + $y_length);
-
- // the following for loop could be removed if the for loop following it
- // (the one with nested for loops) initially set $i to 0, but
- // doing so would also make the result in one set of unnecessary adds,
- // since on the outermost loops first pass, $product->value[$k] is going
- // to always be 0
-
- $carry = 0;
-
- for ($j = 0; $j < $x_length; ++$j) { // ie. $i = 0, $k = $i
- $temp = $x_value[$j] * $y_value[0] + $carry; // $product_value[$k] == 0
- $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31);
- $product_value[$j] = (int) ($temp - self::$baseFull * $carry);
- }
-
- if ($j < $stop) {
- $product_value[$j] = $carry;
- }
-
- // the above for loop is what the previous comment was talking about. the
- // following for loop is the "one with nested for loops"
-
- for ($i = 1; $i < $y_length; ++$i) {
- $carry = 0;
-
- for ($j = 0, $k = $i; $j < $x_length && $k < $stop; ++$j, ++$k) {
- $temp = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry;
- $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31);
- $product_value[$k] = (int) ($temp - self::$baseFull * $carry);
- }
-
- if ($k < $stop) {
- $product_value[$k] = $carry;
- }
- }
-
- return array(
- self::VALUE => $this->_trim($product_value),
- self::SIGN => $x_negative != $y_negative
- );
- }
-
- /**
- * Montgomery Modular Reduction
- *
- * ($x->_prepMontgomery($n))->_montgomery($n) yields $x % $n.
- * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=170 MPM 6.3} provides insights on how this can be
- * improved upon (basically, by using the comba method). gcd($n, 2) must be equal to one for this function
- * to work correctly.
- *
- * @see self::_prepMontgomery()
- * @see self::_slidingWindow()
- * @access private
- * @param array $x
- * @param array $n
- * @return array
- */
- function _montgomery($x, $n)
- {
- static $cache = array(
- self::VARIABLE => array(),
- self::DATA => array()
- );
-
- if (($key = array_search($n, $cache[self::VARIABLE])) === false) {
- $key = count($cache[self::VARIABLE]);
- $cache[self::VARIABLE][] = $x;
- $cache[self::DATA][] = $this->_modInverse67108864($n);
- }
-
- $k = count($n);
-
- $result = array(self::VALUE => $x);
-
- for ($i = 0; $i < $k; ++$i) {
- $temp = $result[self::VALUE][$i] * $cache[self::DATA][$key];
- $temp = $temp - self::$baseFull * (self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31));
- $temp = $this->_regularMultiply(array($temp), $n);
- $temp = array_merge($this->_array_repeat(0, $i), $temp);
- $result = $this->_add($result[self::VALUE], false, $temp, false);
- }
-
- $result[self::VALUE] = array_slice($result[self::VALUE], $k);
-
- if ($this->_compare($result, false, $n, false) >= 0) {
- $result = $this->_subtract($result[self::VALUE], false, $n, false);
- }
-
- return $result[self::VALUE];
- }
-
- /**
- * Montgomery Multiply
+ * \Serializable was introduced in PHP 5.1 and deprecated in PHP 8.1:
+ * https://wiki.php.net/rfc/phase_out_serializable
*
- * Interleaves the montgomery reduction and long multiplication algorithms together as described in
- * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=13 HAC 14.36}
+ * __serialize() / __unserialize() were introduced in PHP 7.4:
+ * https://wiki.php.net/rfc/custom_object_serialization
*
- * @see self::_prepMontgomery()
- * @see self::_montgomery()
- * @access private
- * @param array $x
- * @param array $y
- * @param array $m
* @return array
*/
- function _montgomeryMultiply($x, $y, $m)
+ public function __sleep()
{
- $temp = $this->_multiply($x, false, $y, false);
- return $this->_montgomery($temp[self::VALUE], $m);
-
- // the following code, although not callable, can be run independently of the above code
- // although the above code performed better in my benchmarks the following could might
- // perform better under different circumstances. in lieu of deleting it it's just been
- // made uncallable
-
- static $cache = array(
- self::VARIABLE => array(),
- self::DATA => array()
- );
-
- if (($key = array_search($m, $cache[self::VARIABLE])) === false) {
- $key = count($cache[self::VARIABLE]);
- $cache[self::VARIABLE][] = $m;
- $cache[self::DATA][] = $this->_modInverse67108864($m);
- }
-
- $n = max(count($x), count($y), count($m));
- $x = array_pad($x, $n, 0);
- $y = array_pad($y, $n, 0);
- $m = array_pad($m, $n, 0);
- $a = array(self::VALUE => $this->_array_repeat(0, $n + 1));
- for ($i = 0; $i < $n; ++$i) {
- $temp = $a[self::VALUE][0] + $x[$i] * $y[0];
- $temp = $temp - self::$baseFull * (self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31));
- $temp = $temp * $cache[self::DATA][$key];
- $temp = $temp - self::$baseFull * (self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31));
- $temp = $this->_add($this->_regularMultiply(array($x[$i]), $y), false, $this->_regularMultiply(array($temp), $m), false);
- $a = $this->_add($a[self::VALUE], false, $temp[self::VALUE], false);
- $a[self::VALUE] = array_slice($a[self::VALUE], 1);
- }
- if ($this->_compare($a[self::VALUE], false, $m, false) >= 0) {
- $a = $this->_subtract($a[self::VALUE], false, $m, false);
+ $this->hex = $this->toHex(true);
+ $vars = ['hex'];
+ if ($this->getPrecision() > 0) {
+ $vars[] = 'precision';
}
- return $a[self::VALUE];
- }
-
- /**
- * Prepare a number for use in Montgomery Modular Reductions
- *
- * @see self::_montgomery()
- * @see self::_slidingWindow()
- * @access private
- * @param array $x
- * @param array $n
- * @return array
- */
- function _prepMontgomery($x, $n)
- {
- $lhs = new static();
- $lhs->value = array_merge($this->_array_repeat(0, count($n)), $x);
- $rhs = new static();
- $rhs->value = $n;
-
- list(, $temp) = $lhs->divide($rhs);
- return $temp->value;
- }
-
- /**
- * Modular Inverse of a number mod 2**26 (eg. 67108864)
- *
- * Based off of the bnpInvDigit function implemented and justified in the following URL:
- *
- * {@link http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn.js}
- *
- * The following URL provides more info:
- *
- * {@link http://groups.google.com/group/sci.crypt/msg/7a137205c1be7d85}
- *
- * As for why we do all the bitmasking... strange things can happen when converting from floats to ints. For
- * instance, on some computers, var_dump((int) -4294967297) yields int(-1) and on others, it yields
- * int(-2147483648). To avoid problems stemming from this, we use bitmasks to guarantee that ints aren't
- * auto-converted to floats. The outermost bitmask is present because without it, there's no guarantee that
- * the "residue" returned would be the so-called "common residue". We use fmod, in the last step, because the
- * maximum possible $x is 26 bits and the maximum $result is 16 bits. Thus, we have to be able to handle up to
- * 40 bits, which only 64-bit floating points will support.
- *
- * Thanks to Pedro Gimeno Fortea for input!
- *
- * @see self::_montgomery()
- * @access private
- * @param array $x
- * @return int
- */
- function _modInverse67108864($x) // 2**26 == 67,108,864
- {
- $x = -$x[0];
- $result = $x & 0x3; // x**-1 mod 2**2
- $result = ($result * (2 - $x * $result)) & 0xF; // x**-1 mod 2**4
- $result = ($result * (2 - ($x & 0xFF) * $result)) & 0xFF; // x**-1 mod 2**8
- $result = ($result * ((2 - ($x & 0xFFFF) * $result) & 0xFFFF)) & 0xFFFF; // x**-1 mod 2**16
- $result = fmod($result * (2 - fmod($x * $result, self::$baseFull)), self::$baseFull); // x**-1 mod 2**26
- return $result & self::$maxDigit;
+ return $vars;
}
/**
- * Calculates modular inverses.
- *
- * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses.
- *
- * Here's an example:
- * <code>
- * <?php
- * $a = new \phpseclib\Math\BigInteger(30);
- * $b = new \phpseclib\Math\BigInteger(17);
- *
- * $c = $a->modInverse($b);
- * echo $c->toString(); // outputs 4
- *
- * echo "\r\n";
- *
- * $d = $a->multiply($c);
- * list(, $d) = $d->divide($b);
- * echo $d; // outputs 1 (as per the definition of modular inverse)
- * ?>
- * </code>
+ * Serialize
*
- * @param \phpseclib\Math\BigInteger $n
- * @return \phpseclib\Math\BigInteger|false
- * @access public
- * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=21 HAC 14.64} for more information.
+ * Will be called, automatically, when unserialize() is called on a BigInteger object.
*/
- function modInverse($n)
+ public function __wakeup()
{
- switch (MATH_BIGINTEGER_MODE) {
- case self::MODE_GMP:
- $temp = new static();
- $temp->value = gmp_invert($this->value, $n->value);
-
- return ($temp->value === false) ? false : $this->_normalize($temp);
- }
-
- static $zero, $one;
- if (!isset($zero)) {
- $zero = new static();
- $one = new static(1);
- }
-
- // $x mod -$n == $x mod $n.
- $n = $n->abs();
-
- if ($this->compare($zero) < 0) {
- $temp = $this->abs();
- $temp = $temp->modInverse($n);
- return $this->_normalize($n->subtract($temp));
- }
-
- extract($this->extendedGCD($n));
-
- if (!$gcd->equals($one)) {
- return false;
+ $temp = new static($this->hex, -16);
+ $this->value = $temp->value;
+ if ($this->precision > 0) {
+ // recalculate $this->bitmask
+ $this->setPrecision($this->precision);
}
-
- $x = $x->compare($zero) < 0 ? $x->add($n) : $x;
-
- return $this->compare($zero) < 0 ? $this->_normalize($n->subtract($x)) : $this->_normalize($x);
}
/**
- * Calculates the greatest common divisor and Bezout's identity.
+ * JSON Serialize
*
- * Say you have 693 and 609. The GCD is 21. Bezout's identity states that there exist integers x and y such that
- * 693*x + 609*y == 21. In point of fact, there are actually an infinite number of x and y combinations and which
- * combination is returned is dependent upon which mode is in use. See
- * {@link http://en.wikipedia.org/wiki/B%C3%A9zout%27s_identity Bezout's identity - Wikipedia} for more information.
+ * Will be called, automatically, when json_encode() is called on a BigInteger object.
*
- * Here's an example:
- * <code>
- * <?php
- * $a = new \phpseclib\Math\BigInteger(693);
- * $b = new \phpseclib\Math\BigInteger(609);
- *
- * extract($a->extendedGCD($b));
- *
- * echo $gcd->toString() . "\r\n"; // outputs 21
- * echo $a->toString() * $x->toString() + $b->toString() * $y->toString(); // outputs 21
- * ?>
- * </code>
- *
- * @param \phpseclib\Math\BigInteger $n
- * @return \phpseclib\Math\BigInteger
- * @access public
- * @internal Calculates the GCD using the binary xGCD algorithim described in
- * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=19 HAC 14.61}. As the text above 14.61 notes,
- * the more traditional algorithim requires "relatively costly multiple-precision divisions".
+ * @return array{hex: string, precision?: int]
*/
- function extendedGCD($n)
+ #[\ReturnTypeWillChange]
+ public function jsonSerialize()
{
- switch (MATH_BIGINTEGER_MODE) {
- case self::MODE_GMP:
- extract(gmp_gcdext($this->value, $n->value));
-
- return array(
- 'gcd' => $this->_normalize(new static($g)),
- 'x' => $this->_normalize(new static($s)),
- 'y' => $this->_normalize(new static($t))
- );
- case self::MODE_BCMATH:
- // it might be faster to use the binary xGCD algorithim here, as well, but (1) that algorithim works
- // best when the base is a power of 2 and (2) i don't think it'd make much difference, anyway. as is,
- // the basic extended euclidean algorithim is what we're using.
-
- $u = $this->value;
- $v = $n->value;
-
- $a = '1';
- $b = '0';
- $c = '0';
- $d = '1';
-
- while (bccomp($v, '0', 0) != 0) {
- $q = bcdiv($u, $v, 0);
-
- $temp = $u;
- $u = $v;
- $v = bcsub($temp, bcmul($v, $q, 0), 0);
-
- $temp = $a;
- $a = $c;
- $c = bcsub($temp, bcmul($a, $q, 0), 0);
-
- $temp = $b;
- $b = $d;
- $d = bcsub($temp, bcmul($b, $q, 0), 0);
- }
-
- return array(
- 'gcd' => $this->_normalize(new static($u)),
- 'x' => $this->_normalize(new static($a)),
- 'y' => $this->_normalize(new static($b))
- );
- }
-
- $y = $n->copy();
- $x = $this->copy();
- $g = new static();
- $g->value = array(1);
-
- while (!(($x->value[0] & 1)|| ($y->value[0] & 1))) {
- $x->_rshift(1);
- $y->_rshift(1);
- $g->_lshift(1);
- }
-
- $u = $x->copy();
- $v = $y->copy();
-
- $a = new static();
- $b = new static();
- $c = new static();
- $d = new static();
-
- $a->value = $d->value = $g->value = array(1);
- $b->value = $c->value = array();
-
- while (!empty($u->value)) {
- while (!($u->value[0] & 1)) {
- $u->_rshift(1);
- if ((!empty($a->value) && ($a->value[0] & 1)) || (!empty($b->value) && ($b->value[0] & 1))) {
- $a = $a->add($y);
- $b = $b->subtract($x);
- }
- $a->_rshift(1);
- $b->_rshift(1);
- }
-
- while (!($v->value[0] & 1)) {
- $v->_rshift(1);
- if ((!empty($d->value) && ($d->value[0] & 1)) || (!empty($c->value) && ($c->value[0] & 1))) {
- $c = $c->add($y);
- $d = $d->subtract($x);
- }
- $c->_rshift(1);
- $d->_rshift(1);
- }
-
- if ($u->compare($v) >= 0) {
- $u = $u->subtract($v);
- $a = $a->subtract($c);
- $b = $b->subtract($d);
- } else {
- $v = $v->subtract($u);
- $c = $c->subtract($a);
- $d = $d->subtract($b);
- }
+ $result = ['hex' => $this->toHex(true)];
+ if ($this->precision > 0) {
+ $result['precision'] = $this->getPrecision();
}
-
- return array(
- 'gcd' => $this->_normalize($g->multiply($v)),
- 'x' => $this->_normalize($c),
- 'y' => $this->_normalize($d)
- );
+ return $result;
}
/**
- * Calculates the greatest common divisor
- *
- * Say you have 693 and 609. The GCD is 21.
- *
- * Here's an example:
- * <code>
- * <?php
- * $a = new \phpseclib\Math\BigInteger(693);
- * $b = new \phpseclib\Math\BigInteger(609);
- *
- * $gcd = a->extendedGCD($b);
- *
- * echo $gcd->toString() . "\r\n"; // outputs 21
- * ?>
- * </code>
+ * Performs modular exponentiation.
*
- * @param \phpseclib\Math\BigInteger $n
- * @return \phpseclib\Math\BigInteger
- * @access public
+ * @param BigInteger $e
+ * @param BigInteger $n
+ * @return BigInteger
*/
- function gcd($n)
+ public function powMod(BigInteger $e, BigInteger $n)
{
- extract($this->extendedGCD($n));
- return $gcd;
+ return new static($this->value->powMod($e->value, $n->value));
}
/**
- * Absolute value.
+ * Performs modular exponentiation.
*
- * @return \phpseclib\Math\BigInteger
- * @access public
+ * @param BigInteger $e
+ * @param BigInteger $n
+ * @return BigInteger
*/
- function abs()
+ public function modPow(BigInteger $e, BigInteger $n)
{
- $temp = new static();
-
- switch (MATH_BIGINTEGER_MODE) {
- case self::MODE_GMP:
- $temp->value = gmp_abs($this->value);
- break;
- case self::MODE_BCMATH:
- $temp->value = (bccomp($this->value, '0', 0) < 0) ? substr($this->value, 1) : $this->value;
- break;
- default:
- $temp->value = $this->value;
- }
-
- return $temp;
+ return new static($this->value->modPow($e->value, $n->value));
}
/**
* Compares two numbers.
*
- * Although one might think !$x->compare($y) means $x != $y, it, in fact, means the opposite. The reason for this is
- * demonstrated thusly:
+ * Although one might think !$x->compare($y) means $x != $y, it, in fact, means the opposite. The reason for this
+ * is demonstrated thusly:
*
* $x > $y: $x->compare($y) > 0
* $x < $y: $x->compare($y) < 0
@@ -2723,65 +490,15 @@ class BigInteger
*
* Note how the same comparison operator is used. If you want to test for equality, use $x->equals($y).
*
- * @param \phpseclib\Math\BigInteger $y
- * @return int that is < 0 if $this is less than $y; > 0 if $this is greater than $y, and 0 if they are equal.
- * @access public
- * @see self::equals()
- * @internal Could return $this->subtract($x), but that's not as fast as what we do do.
- */
- function compare($y)
- {
- switch (MATH_BIGINTEGER_MODE) {
- case self::MODE_GMP:
- $r = gmp_cmp($this->value, $y->value);
- if ($r < -1) {
- $r = -1;
- }
- if ($r > 1) {
- $r = 1;
- }
- return $r;
- case self::MODE_BCMATH:
- return bccomp($this->value, $y->value, 0);
- }
-
- return $this->_compare($this->value, $this->is_negative, $y->value, $y->is_negative);
- }
-
- /**
- * Compares two numbers.
+ * {@internal Could return $this->subtract($x), but that's not as fast as what we do do.}
*
- * @param array $x_value
- * @param bool $x_negative
- * @param array $y_value
- * @param bool $y_negative
- * @return int
- * @see self::compare()
- * @access private
+ * @param BigInteger $y
+ * @return int in case < 0 if $this is less than $y; > 0 if $this is greater than $y, and 0 if they are equal.
+ * @see self::equals()
*/
- function _compare($x_value, $x_negative, $y_value, $y_negative)
+ public function compare(BigInteger $y)
{
- if ($x_negative != $y_negative) {
- return (!$x_negative && $y_negative) ? 1 : -1;
- }
-
- $result = $x_negative ? -1 : 1;
-
- if (count($x_value) != count($y_value)) {
- return (count($x_value) > count($y_value)) ? $result : -$result;
- }
- $size = max(count($x_value), count($y_value));
-
- $x_value = array_pad($x_value, $size, 0);
- $y_value = array_pad($y_value, $size, 0);
-
- for ($i = count($x_value) - 1; $i >= 0; --$i) {
- if ($x_value[$i] != $y_value[$i]) {
- return ($x_value[$i] > $y_value[$i]) ? $result : -$result;
- }
- }
-
- return 0;
+ return $this->value->compare($y->value);
}
/**
@@ -2789,201 +506,55 @@ class BigInteger
*
* If you need to see if one number is greater than or less than another number, use BigInteger::compare()
*
- * @param \phpseclib\Math\BigInteger $x
+ * @param BigInteger $x
* @return bool
- * @access public
- * @see self::compare()
*/
- function equals($x)
+ public function equals(BigInteger $x)
{
- switch (MATH_BIGINTEGER_MODE) {
- case self::MODE_GMP:
- return gmp_cmp($this->value, $x->value) == 0;
- default:
- return $this->value === $x->value && $this->is_negative == $x->is_negative;
- }
+ return $this->value->equals($x->value);
}
/**
- * Set Precision
- *
- * Some bitwise operations give different results depending on the precision being used. Examples include left
- * shift, not, and rotates.
+ * Logical Not
*
- * @param int $bits
- * @access public
+ * @return BigInteger
*/
- function setPrecision($bits)
+ public function bitwise_not()
{
- $this->precision = $bits;
- if (MATH_BIGINTEGER_MODE != self::MODE_BCMATH) {
- $this->bitmask = new static(chr((1 << ($bits & 0x7)) - 1) . str_repeat(chr(0xFF), $bits >> 3), 256);
- } else {
- $this->bitmask = new static(bcpow('2', $bits, 0));
- }
-
- $temp = $this->_normalize($this);
- $this->value = $temp->value;
+ return new static($this->value->bitwise_not());
}
/**
* Logical And
*
- * @param \phpseclib\Math\BigInteger $x
- * @access public
- * @internal Implemented per a request by Lluis Pamies i Juarez <lluis _a_ pamies.cat>
- * @return \phpseclib\Math\BigInteger
+ * @param BigInteger $x
+ * @return BigInteger
*/
- function bitwise_and($x)
+ public function bitwise_and(BigInteger $x)
{
- switch (MATH_BIGINTEGER_MODE) {
- case self::MODE_GMP:
- $temp = new static();
- $temp->value = gmp_and($this->value, $x->value);
-
- return $this->_normalize($temp);
- case self::MODE_BCMATH:
- $left = $this->toBytes();
- $right = $x->toBytes();
-
- $length = max(strlen($left), strlen($right));
-
- $left = str_pad($left, $length, chr(0), STR_PAD_LEFT);
- $right = str_pad($right, $length, chr(0), STR_PAD_LEFT);
-
- return $this->_normalize(new static($left & $right, 256));
- }
-
- $result = $this->copy();
-
- $length = min(count($x->value), count($this->value));
-
- $result->value = array_slice($result->value, 0, $length);
-
- for ($i = 0; $i < $length; ++$i) {
- $result->value[$i]&= $x->value[$i];
- }
-
- return $this->_normalize($result);
+ return new static($this->value->bitwise_and($x->value));
}
/**
* Logical Or
*
- * @param \phpseclib\Math\BigInteger $x
- * @access public
- * @internal Implemented per a request by Lluis Pamies i Juarez <lluis _a_ pamies.cat>
- * @return \phpseclib\Math\BigInteger
+ * @param BigInteger $x
+ * @return BigInteger
*/
- function bitwise_or($x)
+ public function bitwise_or(BigInteger $x)
{
- switch (MATH_BIGINTEGER_MODE) {
- case self::MODE_GMP:
- $temp = new static();
- $temp->value = gmp_or($this->value, $x->value);
-
- return $this->_normalize($temp);
- case self::MODE_BCMATH:
- $left = $this->toBytes();
- $right = $x->toBytes();
-
- $length = max(strlen($left), strlen($right));
-
- $left = str_pad($left, $length, chr(0), STR_PAD_LEFT);
- $right = str_pad($right, $length, chr(0), STR_PAD_LEFT);
-
- return $this->_normalize(new static($left | $right, 256));
- }
-
- $length = max(count($this->value), count($x->value));
- $result = $this->copy();
- $result->value = array_pad($result->value, $length, 0);
- $x->value = array_pad($x->value, $length, 0);
-
- for ($i = 0; $i < $length; ++$i) {
- $result->value[$i]|= $x->value[$i];
- }
-
- return $this->_normalize($result);
+ return new static($this->value->bitwise_or($x->value));
}
/**
- * Logical Exclusive-Or
+ * Logical Exclusive Or
*
- * @param \phpseclib\Math\BigInteger $x
- * @access public
- * @internal Implemented per a request by Lluis Pamies i Juarez <lluis _a_ pamies.cat>
- * @return \phpseclib\Math\BigInteger
+ * @param BigInteger $x
+ * @return BigInteger
*/
- function bitwise_xor($x)
+ public function bitwise_xor(BigInteger $x)
{
- switch (MATH_BIGINTEGER_MODE) {
- case self::MODE_GMP:
- $temp = new static();
- $temp->value = gmp_xor(gmp_abs($this->value), gmp_abs($x->value));
- return $this->_normalize($temp);
- case self::MODE_BCMATH:
- $left = $this->toBytes();
- $right = $x->toBytes();
-
- $length = max(strlen($left), strlen($right));
-
- $left = str_pad($left, $length, chr(0), STR_PAD_LEFT);
- $right = str_pad($right, $length, chr(0), STR_PAD_LEFT);
-
- return $this->_normalize(new static($left ^ $right, 256));
- }
-
- $length = max(count($this->value), count($x->value));
- $result = $this->copy();
- $result->is_negative = false;
- $result->value = array_pad($result->value, $length, 0);
- $x->value = array_pad($x->value, $length, 0);
-
- for ($i = 0; $i < $length; ++$i) {
- $result->value[$i]^= $x->value[$i];
- }
-
- return $this->_normalize($result);
- }
-
- /**
- * Logical Not
- *
- * @access public
- * @internal Implemented per a request by Lluis Pamies i Juarez <lluis _a_ pamies.cat>
- * @return \phpseclib\Math\BigInteger
- */
- function bitwise_not()
- {
- // calculuate "not" without regard to $this->precision
- // (will always result in a smaller number. ie. ~1 isn't 1111 1110 - it's 0)
- $temp = $this->toBytes();
- if ($temp == '') {
- return $this->_normalize(new static());
- }
- $pre_msb = decbin(ord($temp[0]));
- $temp = ~$temp;
- $msb = decbin(ord($temp[0]));
- if (strlen($msb) == 8) {
- $msb = substr($msb, strpos($msb, '0'));
- }
- $temp[0] = chr(bindec($msb));
-
- // see if we need to add extra leading 1's
- $current_bits = strlen($pre_msb) + 8 * strlen($temp) - 8;
- $new_bits = $this->precision - $current_bits;
- if ($new_bits <= 0) {
- return $this->_normalize(new static($temp, 256));
- }
-
- // generate as many leading 1's as we need to.
- $leading_ones = chr((1 << ($new_bits & 0x7)) - 1) . str_repeat(chr(0xFF), $new_bits >> 3);
- $this->_base256_lshift($leading_ones, $current_bits);
-
- $temp = str_pad($temp, strlen($leading_ones), chr(0), STR_PAD_LEFT);
-
- return $this->_normalize(new static($leading_ones | $temp, 256));
+ return new static($this->value->bitwise_xor($x->value));
}
/**
@@ -2992,36 +563,11 @@ class BigInteger
* Shifts BigInteger's by $shift bits, effectively dividing by 2**$shift.
*
* @param int $shift
- * @return \phpseclib\Math\BigInteger
- * @access public
- * @internal The only version that yields any speed increases is the internal version.
+ * @return BigInteger
*/
- function bitwise_rightShift($shift)
+ public function bitwise_rightShift($shift)
{
- $temp = new static();
-
- switch (MATH_BIGINTEGER_MODE) {
- case self::MODE_GMP:
- static $two;
-
- if (!isset($two)) {
- $two = gmp_init('2');
- }
-
- $temp->value = gmp_div_q($this->value, gmp_pow($two, $shift));
-
- break;
- case self::MODE_BCMATH:
- $temp->value = bcdiv($this->value, bcpow('2', $shift, 0), 0);
-
- break;
- default: // could just replace _lshift with this, but then all _lshift() calls would need to be rewritten
- // and I don't want to do that...
- $temp->value = $this->value;
- $temp->_rshift($shift);
- }
-
- return $this->_normalize($temp);
+ return new static($this->value->bitwise_rightShift($shift));
}
/**
@@ -3030,36 +576,11 @@ class BigInteger
* Shifts BigInteger's by $shift bits, effectively multiplying by 2**$shift.
*
* @param int $shift
- * @return \phpseclib\Math\BigInteger
- * @access public
- * @internal The only version that yields any speed increases is the internal version.
+ * @return BigInteger
*/
- function bitwise_leftShift($shift)
+ public function bitwise_leftShift($shift)
{
- $temp = new static();
-
- switch (MATH_BIGINTEGER_MODE) {
- case self::MODE_GMP:
- static $two;
-
- if (!isset($two)) {
- $two = gmp_init('2');
- }
-
- $temp->value = gmp_mul($this->value, gmp_pow($two, $shift));
-
- break;
- case self::MODE_BCMATH:
- $temp->value = bcmul($this->value, bcpow('2', $shift, 0), 0);
-
- break;
- default: // could just replace _rshift with this, but then all _lshift() calls would need to be rewritten
- // and I don't want to do that...
- $temp->value = $this->value;
- $temp->_lshift($shift);
- }
-
- return $this->_normalize($temp);
+ return new static($this->value->bitwise_leftShift($shift));
}
/**
@@ -3068,43 +589,11 @@ class BigInteger
* Instead of the top x bits being dropped they're appended to the shifted bit string.
*
* @param int $shift
- * @return \phpseclib\Math\BigInteger
- * @access public
+ * @return BigInteger
*/
- function bitwise_leftRotate($shift)
+ public function bitwise_leftRotate($shift)
{
- $bits = $this->toBytes();
-
- if ($this->precision > 0) {
- $precision = $this->precision;
- if (MATH_BIGINTEGER_MODE == self::MODE_BCMATH) {
- $mask = $this->bitmask->subtract(new static(1));
- $mask = $mask->toBytes();
- } else {
- $mask = $this->bitmask->toBytes();
- }
- } else {
- $temp = ord($bits[0]);
- for ($i = 0; $temp >> $i; ++$i) {
- }
- $precision = 8 * strlen($bits) - 8 + $i;
- $mask = chr((1 << ($precision & 0x7)) - 1) . str_repeat(chr(0xFF), $precision >> 3);
- }
-
- if ($shift < 0) {
- $shift+= $precision;
- }
- $shift%= $precision;
-
- if (!$shift) {
- return $this->copy();
- }
-
- $left = $this->bitwise_leftShift($shift);
- $left = $left->bitwise_and(new static($mask, 256));
- $right = $this->bitwise_rightShift($precision - $shift);
- $result = MATH_BIGINTEGER_MODE != self::MODE_BCMATH ? $left->bitwise_or($right) : $left->add($right);
- return $this->_normalize($result);
+ return new static($this->value->bitwise_leftRotate($shift));
}
/**
@@ -3113,260 +602,117 @@ class BigInteger
* Instead of the bottom x bits being dropped they're prepended to the shifted bit string.
*
* @param int $shift
- * @return \phpseclib\Math\BigInteger
- * @access public
+ * @return BigInteger
*/
- function bitwise_rightRotate($shift)
+ public function bitwise_rightRotate($shift)
{
- return $this->bitwise_leftRotate(-$shift);
+ return new static($this->value->bitwise_rightRotate($shift));
}
/**
- * Generates a random BigInteger
+ * Returns the smallest and largest n-bit number
*
- * Byte length is equal to $length. Uses \phpseclib\Crypt\Random if it's loaded and mt_rand if it's not.
- *
- * @param int $size
- * @return \phpseclib\Math\BigInteger
- * @access private
+ * @param int $bits
+ * @return BigInteger[]
*/
- function _random_number_helper($size)
+ public static function minMaxBits($bits)
{
- if (class_exists('\phpseclib\Crypt\Random')) {
- $random = Random::string($size);
- } else {
- $random = '';
+ self::initialize_static_variables();
- if ($size & 1) {
- $random.= chr(mt_rand(0, 255));
- }
-
- $blocks = $size >> 1;
- for ($i = 0; $i < $blocks; ++$i) {
- // mt_rand(-2147483648, 0x7FFFFFFF) always produces -2147483648 on some systems
- $random.= pack('n', mt_rand(0, 0xFFFF));
- }
- }
+ $class = self::$mainEngine;
+ $minMax = $class::minMaxBits($bits);
+ $min = $minMax['min'];
+ $max = $minMax['max'];
+ return [
+ 'min' => new static($min),
+ 'max' => new static($max)
+ ];
+ }
- return new static($random, 256);
+ /**
+ * Return the size of a BigInteger in bits
+ *
+ * @return int
+ */
+ public function getLength()
+ {
+ return $this->value->getLength();
}
/**
- * Generate a random number
+ * Return the size of a BigInteger in bytes
*
- * Returns a random number between $min and $max where $min and $max
- * can be defined using one of the two methods:
+ * @return int
+ */
+ public function getLengthInBytes()
+ {
+ return $this->value->getLengthInBytes();
+ }
+
+ /**
+ * Generates a random number of a certain size
*
- * $min->random($max)
- * $max->random($min)
+ * Bit length is equal to $size
*
- * @param \phpseclib\Math\BigInteger $arg1
- * @param \phpseclib\Math\BigInteger $arg2
- * @return \phpseclib\Math\BigInteger
- * @access public
- * @internal The API for creating random numbers used to be $a->random($min, $max), where $a was a BigInteger object.
- * That method is still supported for BC purposes.
+ * @param int $size
+ * @return BigInteger
*/
- function random($arg1, $arg2 = false)
+ public static function random($size)
{
- if ($arg1 === false) {
- return false;
- }
+ self::initialize_static_variables();
- if ($arg2 === false) {
- $max = $arg1;
- $min = $this;
- } else {
- $min = $arg1;
- $max = $arg2;
- }
-
- $compare = $max->compare($min);
-
- if (!$compare) {
- return $this->_normalize($min);
- } elseif ($compare < 0) {
- // if $min is bigger then $max, swap $min and $max
- $temp = $max;
- $max = $min;
- $min = $temp;
- }
-
- static $one;
- if (!isset($one)) {
- $one = new static(1);
- }
+ $class = self::$mainEngine;
+ return new static($class::random($size));
+ }
- $max = $max->subtract($min->subtract($one));
- $size = strlen(ltrim($max->toBytes(), chr(0)));
-
- /*
- doing $random % $max doesn't work because some numbers will be more likely to occur than others.
- eg. if $max is 140 and $random's max is 255 then that'd mean both $random = 5 and $random = 145
- would produce 5 whereas the only value of random that could produce 139 would be 139. ie.
- not all numbers would be equally likely. some would be more likely than others.
-
- creating a whole new random number until you find one that is within the range doesn't work
- because, for sufficiently small ranges, the likelihood that you'd get a number within that range
- would be pretty small. eg. with $random's max being 255 and if your $max being 1 the probability
- would be pretty high that $random would be greater than $max.
-
- phpseclib works around this using the technique described here:
-
- http://crypto.stackexchange.com/questions/5708/creating-a-small-number-from-a-cryptographically-secure-random-string
- */
- $random_max = new static(chr(1) . str_repeat("\0", $size), 256);
- $random = $this->_random_number_helper($size);
-
- list($max_multiple) = $random_max->divide($max);
- $max_multiple = $max_multiple->multiply($max);
-
- while ($random->compare($max_multiple) >= 0) {
- $random = $random->subtract($max_multiple);
- $random_max = $random_max->subtract($max_multiple);
- $random = $random->bitwise_leftShift(8);
- $random = $random->add($this->_random_number_helper(1));
- $random_max = $random_max->bitwise_leftShift(8);
- list($max_multiple) = $random_max->divide($max);
- $max_multiple = $max_multiple->multiply($max);
- }
- list(, $random) = $random->divide($max);
+ /**
+ * Generates a random prime number of a certain size
+ *
+ * Bit length is equal to $size
+ *
+ * @param int $size
+ * @return BigInteger
+ */
+ public static function randomPrime($size)
+ {
+ self::initialize_static_variables();
- return $this->_normalize($random->add($min));
+ $class = self::$mainEngine;
+ return new static($class::randomPrime($size));
}
/**
- * Generate a random prime number.
+ * Generate a random prime number between a range
*
* If there's not a prime within the given range, false will be returned.
- * If more than $timeout seconds have elapsed, give up and return false.
- *
- * @param \phpseclib\Math\BigInteger $arg1
- * @param \phpseclib\Math\BigInteger $arg2
- * @param int $timeout
- * @return Math_BigInteger|false
- * @access public
- * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=15 HAC 4.44}.
+ *
+ * @param BigInteger $min
+ * @param BigInteger $max
+ * @return false|BigInteger
*/
- function randomPrime($arg1, $arg2 = false, $timeout = false)
+ public static function randomRangePrime(BigInteger $min, BigInteger $max)
{
- if ($arg1 === false) {
- return false;
- }
-
- if ($arg2 === false) {
- $max = $arg1;
- $min = $this;
- } else {
- $min = $arg1;
- $max = $arg2;
- }
-
- $compare = $max->compare($min);
-
- if (!$compare) {
- return $min->isPrime() ? $min : false;
- } elseif ($compare < 0) {
- // if $min is bigger then $max, swap $min and $max
- $temp = $max;
- $max = $min;
- $min = $temp;
- }
-
- $length = $max->getLength();
- if ($length > 8196) {
- user_error('Generation of random prime numbers larger than 8196 has been disabled');
- }
-
- static $one, $two;
- if (!isset($one)) {
- $one = new static(1);
- $two = new static(2);
- }
-
- $start = time();
-
- $x = $this->random($min, $max);
-
- // gmp_nextprime() requires PHP 5 >= 5.2.0 per <http://php.net/gmp-nextprime>.
- if (MATH_BIGINTEGER_MODE == self::MODE_GMP && extension_loaded('gmp')) {
- $p = new static();
- $p->value = gmp_nextprime($x->value);
-
- if ($p->compare($max) <= 0) {
- return $p;
- }
-
- if (!$min->equals($x)) {
- $x = $x->subtract($one);
- }
-
- return $x->randomPrime($min, $x);
- }
-
- if ($x->equals($two)) {
- return $x;
- }
-
- $x->_make_odd();
- if ($x->compare($max) > 0) {
- // if $x > $max then $max is even and if $min == $max then no prime number exists between the specified range
- if ($min->equals($max)) {
- return false;
- }
- $x = $min->copy();
- $x->_make_odd();
- }
-
- $initial_x = $x->copy();
-
- while (true) {
- if ($timeout !== false && time() - $start > $timeout) {
- return false;
- }
-
- if ($x->isPrime()) {
- return $x;
- }
-
- $x = $x->add($two);
-
- if ($x->compare($max) > 0) {
- $x = $min->copy();
- if ($x->equals($two)) {
- return $x;
- }
- $x->_make_odd();
- }
-
- if ($x->equals($initial_x)) {
- return false;
- }
- }
+ $class = self::$mainEngine;
+ return new static($class::randomRangePrime($min->value, $max->value));
}
/**
- * Make the current number odd
+ * Generate a random number between a range
+ *
+ * Returns a random number between $min and $max where $min and $max
+ * can be defined using one of the two methods:
*
- * If the current number is odd it'll be unchanged. If it's even, one will be added to it.
+ * BigInteger::randomRange($min, $max)
+ * BigInteger::randomRange($max, $min)
*
- * @see self::randomPrime()
- * @access private
+ * @param BigInteger $min
+ * @param BigInteger $max
+ * @return BigInteger
*/
- function _make_odd()
+ public static function randomRange(BigInteger $min, BigInteger $max)
{
- switch (MATH_BIGINTEGER_MODE) {
- case self::MODE_GMP:
- gmp_setbit($this->value, 0);
- break;
- case self::MODE_BCMATH:
- if ($this->value[strlen($this->value) - 1] % 2 == 0) {
- $this->value = bcadd($this->value, '1');
- }
- break;
- default:
- $this->value[0] |= 1;
- }
+ $class = self::$mainEngine;
+ return new static($class::randomRange($min->value, $max->value));
}
/**
@@ -3376,460 +722,173 @@ class BigInteger
* $t parameter is distributability. BigInteger::randomPrime() can be distributed across multiple pageloads
* on a website instead of just one.
*
- * @param \phpseclib\Math\BigInteger $t
+ * @param int|bool $t
* @return bool
- * @access public
- * @internal Uses the
- * {@link http://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test Miller-Rabin primality test}. See
- * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=8 HAC 4.24}.
*/
- function isPrime($t = false)
+ public function isPrime($t = false)
{
- $length = $this->getLength();
- // OpenSSL limits RSA keys to 16384 bits. The length of an RSA key is equal to the length of the modulo, which is
- // produced by multiplying the primes p and q by one another. The largest number two 8196 bit primes can produce is
- // a 16384 bit number so, basically, 8196 bit primes are the largest OpenSSL will generate and if that's the largest
- // that it'll generate it also stands to reason that that's the largest you'll be able to test primality on
- if ($length > 8196) {
- user_error('Primality testing is not supported for numbers larger than 8196 bits');
- }
-
- if (!$t) {
- // see HAC 4.49 "Note (controlling the error probability)"
- // @codingStandardsIgnoreStart
- if ($length >= 163) { $t = 2; } // floor(1300 / 8)
- else if ($length >= 106) { $t = 3; } // floor( 850 / 8)
- else if ($length >= 81 ) { $t = 4; } // floor( 650 / 8)
- else if ($length >= 68 ) { $t = 5; } // floor( 550 / 8)
- else if ($length >= 56 ) { $t = 6; } // floor( 450 / 8)
- else if ($length >= 50 ) { $t = 7; } // floor( 400 / 8)
- else if ($length >= 43 ) { $t = 8; } // floor( 350 / 8)
- else if ($length >= 37 ) { $t = 9; } // floor( 300 / 8)
- else if ($length >= 31 ) { $t = 12; } // floor( 250 / 8)
- else if ($length >= 25 ) { $t = 15; } // floor( 200 / 8)
- else if ($length >= 18 ) { $t = 18; } // floor( 150 / 8)
- else { $t = 27; }
- // @codingStandardsIgnoreEnd
- }
-
- // ie. gmp_testbit($this, 0)
- // ie. isEven() or !isOdd()
- switch (MATH_BIGINTEGER_MODE) {
- case self::MODE_GMP:
- return gmp_prob_prime($this->value, $t) != 0;
- case self::MODE_BCMATH:
- if ($this->value === '2') {
- return true;
- }
- if ($this->value[strlen($this->value) - 1] % 2 == 0) {
- return false;
- }
- break;
- default:
- if ($this->value == array(2)) {
- return true;
- }
- if (~$this->value[0] & 1) {
- return false;
- }
- }
-
- static $primes, $zero, $one, $two;
-
- if (!isset($primes)) {
- $primes = array(
- 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59,
- 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137,
- 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227,
- 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313,
- 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419,
- 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509,
- 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617,
- 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727,
- 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829,
- 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947,
- 953, 967, 971, 977, 983, 991, 997
- );
-
- if (MATH_BIGINTEGER_MODE != self::MODE_INTERNAL) {
- for ($i = 0; $i < count($primes); ++$i) {
- $primes[$i] = new static($primes[$i]);
- }
- }
-
- $zero = new static();
- $one = new static(1);
- $two = new static(2);
- }
-
- if ($this->equals($one)) {
- return false;
- }
-
- // see HAC 4.4.1 "Random search for probable primes"
- if (MATH_BIGINTEGER_MODE != self::MODE_INTERNAL) {
- foreach ($primes as $prime) {
- list(, $r) = $this->divide($prime);
- if ($r->equals($zero)) {
- return $this->equals($prime);
- }
- }
- } else {
- $value = $this->value;
- foreach ($primes as $prime) {
- list(, $r) = $this->_divide_digit($value, $prime);
- if (!$r) {
- return count($value) == 1 && $value[0] == $prime;
- }
- }
- }
-
- $n = $this->copy();
- $n_1 = $n->subtract($one);
- $n_2 = $n->subtract($two);
-
- $r = $n_1->copy();
- $r_value = $r->value;
- // ie. $s = gmp_scan1($n, 0) and $r = gmp_div_q($n, gmp_pow(gmp_init('2'), $s));
- if (MATH_BIGINTEGER_MODE == self::MODE_BCMATH) {
- $s = 0;
- // if $n was 1, $r would be 0 and this would be an infinite loop, hence our $this->equals($one) check earlier
- while ($r->value[strlen($r->value) - 1] % 2 == 0) {
- $r->value = bcdiv($r->value, '2', 0);
- ++$s;
- }
- } else {
- for ($i = 0, $r_length = count($r_value); $i < $r_length; ++$i) {
- $temp = ~$r_value[$i] & 0xFFFFFF;
- for ($j = 1; ($temp >> $j) & 1; ++$j) {
- }
- if ($j != 25) {
- break;
- }
- }
- $s = 26 * $i + $j;
- $r->_rshift($s);
- }
-
- for ($i = 0; $i < $t; ++$i) {
- $a = $this->random($two, $n_2);
- $y = $a->modPow($r, $n);
-
- if (!$y->equals($one) && !$y->equals($n_1)) {
- for ($j = 1; $j < $s && !$y->equals($n_1); ++$j) {
- $y = $y->modPow($two, $n);
- if ($y->equals($one)) {
- return false;
- }
- }
-
- if (!$y->equals($n_1)) {
- return false;
- }
- }
- }
- return true;
+ return $this->value->isPrime($t);
}
/**
- * Logical Left Shift
+ * Calculates the nth root of a biginteger.
*
- * Shifts BigInteger's by $shift bits.
+ * Returns the nth root of a positive biginteger, where n defaults to 2
*
- * @param int $shift
- * @access private
+ * @param int $n optional
+ * @return BigInteger
*/
- function _lshift($shift)
+ public function root($n = 2)
{
- if ($shift == 0) {
- return;
- }
-
- $num_digits = (int) ($shift / self::$base);
- $shift %= self::$base;
- $shift = 1 << $shift;
-
- $carry = 0;
-
- for ($i = 0; $i < count($this->value); ++$i) {
- $temp = $this->value[$i] * $shift + $carry;
- $carry = self::$base === 26 ? intval($temp / 0x4000000) : ($temp >> 31);
- $this->value[$i] = (int) ($temp - $carry * self::$baseFull);
- }
-
- if ($carry) {
- $this->value[count($this->value)] = $carry;
- }
-
- while ($num_digits--) {
- array_unshift($this->value, 0);
- }
+ return new static($this->value->root($n));
}
/**
- * Logical Right Shift
- *
- * Shifts BigInteger's by $shift bits.
+ * Performs exponentiation.
*
- * @param int $shift
- * @access private
+ * @param BigInteger $n
+ * @return BigInteger
*/
- function _rshift($shift)
+ public function pow(BigInteger $n)
{
- if ($shift == 0) {
- return;
- }
-
- $num_digits = (int) ($shift / self::$base);
- $shift %= self::$base;
- $carry_shift = self::$base - $shift;
- $carry_mask = (1 << $shift) - 1;
-
- if ($num_digits) {
- $this->value = array_slice($this->value, $num_digits);
- }
-
- $carry = 0;
-
- for ($i = count($this->value) - 1; $i >= 0; --$i) {
- $temp = $this->value[$i] >> $shift | $carry;
- $carry = ($this->value[$i] & $carry_mask) << $carry_shift;
- $this->value[$i] = $temp;
- }
-
- $this->value = $this->_trim($this->value);
+ return new static($this->value->pow($n->value));
}
/**
- * Normalize
+ * Return the minimum BigInteger between an arbitrary number of BigIntegers.
*
- * Removes leading zeros and truncates (if necessary) to maintain the appropriate precision
- *
- * @param \phpseclib\Math\BigInteger $result
- * @return \phpseclib\Math\BigInteger
- * @see self::_trim()
- * @access private
+ * @param BigInteger ...$nums
+ * @return BigInteger
*/
- function _normalize($result)
+ public static function min(BigInteger ...$nums)
{
- $result->precision = $this->precision;
- $result->bitmask = $this->bitmask;
-
- switch (MATH_BIGINTEGER_MODE) {
- case self::MODE_GMP:
- if ($this->bitmask !== false) {
- $flip = gmp_cmp($result->value, gmp_init(0)) < 0;
- if ($flip) {
- $result->value = gmp_neg($result->value);
- }
- $result->value = gmp_and($result->value, $result->bitmask->value);
- if ($flip) {
- $result->value = gmp_neg($result->value);
- }
- }
-
- return $result;
- case self::MODE_BCMATH:
- if (!empty($result->bitmask->value)) {
- $result->value = bcmod($result->value, $result->bitmask->value);
- }
-
- return $result;
- }
-
- $value = &$result->value;
-
- if (!count($value)) {
- $result->is_negative = false;
- return $result;
- }
-
- $value = $this->_trim($value);
-
- if (!empty($result->bitmask->value)) {
- $length = min(count($value), count($this->bitmask->value));
- $value = array_slice($value, 0, $length);
-
- for ($i = 0; $i < $length; ++$i) {
- $value[$i] = $value[$i] & $this->bitmask->value[$i];
- }
- }
-
- return $result;
+ $class = self::$mainEngine;
+ $nums = array_map(function ($num) {
+ return $num->value;
+ }, $nums);
+ return new static($class::min(...$nums));
}
/**
- * Trim
+ * Return the maximum BigInteger between an arbitrary number of BigIntegers.
*
- * Removes leading zeros
+ * @param BigInteger ...$nums
+ * @return BigInteger
+ */
+ public static function max(BigInteger ...$nums)
+ {
+ $class = self::$mainEngine;
+ $nums = array_map(function ($num) {
+ return $num->value;
+ }, $nums);
+ return new static($class::max(...$nums));
+ }
+
+ /**
+ * Tests BigInteger to see if it is between two integers, inclusive
*
- * @param array $value
- * @return \phpseclib\Math\BigInteger
- * @access private
+ * @param BigInteger $min
+ * @param BigInteger $max
+ * @return bool
*/
- function _trim($value)
+ public function between(BigInteger $min, BigInteger $max)
{
- for ($i = count($value) - 1; $i >= 0; --$i) {
- if ($value[$i]) {
- break;
- }
- unset($value[$i]);
- }
+ return $this->value->between($min->value, $max->value);
+ }
- return $value;
+ /**
+ * Clone
+ */
+ public function __clone()
+ {
+ $this->value = clone $this->value;
}
/**
- * Array Repeat
+ * Is Odd?
*
- * @param array $input
- * @param mixed $multiplier
- * @return array
- * @access private
+ * @return bool
*/
- function _array_repeat($input, $multiplier)
+ public function isOdd()
{
- return ($multiplier) ? array_fill(0, $multiplier, $input) : array();
+ return $this->value->isOdd();
}
/**
- * Logical Left Shift
+ * Tests if a bit is set
*
- * Shifts binary strings $shift bits, essentially multiplying by 2**$shift.
- *
- * @param string $x (by reference)
- * @param int $shift
- * @return string
- * @access private
+ * @param int $x
+ * @return bool
*/
- function _base256_lshift(&$x, $shift)
+ public function testBit($x)
{
- if ($shift == 0) {
- return;
- }
-
- $num_bytes = $shift >> 3; // eg. floor($shift/8)
- $shift &= 7; // eg. $shift % 8
-
- $carry = 0;
- for ($i = strlen($x) - 1; $i >= 0; --$i) {
- $temp = ord($x[$i]) << $shift | $carry;
- $x[$i] = chr($temp);
- $carry = $temp >> 8;
- }
- $carry = ($carry != 0) ? chr($carry) : '';
- $x = $carry . $x . str_repeat(chr(0), $num_bytes);
+ return $this->value->testBit($x);
}
/**
- * Logical Right Shift
- *
- * Shifts binary strings $shift bits, essentially dividing by 2**$shift and returning the remainder.
+ * Is Negative?
*
- * @param string $x (by referenc)
- * @param int $shift
- * @return string
- * @access private
+ * @return bool
*/
- function _base256_rshift(&$x, $shift)
+ public function isNegative()
{
- if ($shift == 0) {
- $x = ltrim($x, chr(0));
- return '';
- }
-
- $num_bytes = $shift >> 3; // eg. floor($shift/8)
- $shift &= 7; // eg. $shift % 8
-
- $remainder = '';
- if ($num_bytes) {
- $start = $num_bytes > strlen($x) ? -strlen($x) : -$num_bytes;
- $remainder = substr($x, $start);
- $x = substr($x, 0, -$num_bytes);
- }
-
- $carry = 0;
- $carry_shift = 8 - $shift;
- for ($i = 0; $i < strlen($x); ++$i) {
- $temp = (ord($x[$i]) >> $shift) | $carry;
- $carry = (ord($x[$i]) << $carry_shift) & 0xFF;
- $x[$i] = chr($temp);
- }
- $x = ltrim($x, chr(0));
-
- $remainder = chr($carry >> $carry_shift) . $remainder;
-
- return ltrim($remainder, chr(0));
+ return $this->value->isNegative();
}
- // one quirk about how the following functions are implemented is that PHP defines N to be an unsigned long
- // at 32-bits, while java's longs are 64-bits.
-
/**
- * Converts 32-bit integers to bytes.
+ * Negate
*
- * @param int $x
- * @return string
- * @access private
+ * Given $k, returns -$k
+ *
+ * @return BigInteger
*/
- function _int2bytes($x)
+ public function negate()
{
- return ltrim(pack('N', $x), chr(0));
+ return new static($this->value->negate());
}
/**
- * Converts bytes to 32-bit integers
+ * Scan for 1 and right shift by that amount
*
- * @param string $x
+ * ie. $s = gmp_scan1($n, 0) and $r = gmp_div_q($n, gmp_pow(gmp_init('2'), $s));
+ *
+ * @param BigInteger $r
* @return int
- * @access private
*/
- function _bytes2int($x)
+ public static function scan1divide(BigInteger $r)
{
- $temp = unpack('Nint', str_pad($x, 4, chr(0), STR_PAD_LEFT));
- return $temp['int'];
+ $class = self::$mainEngine;
+ return $class::scan1divide($r->value);
}
/**
- * DER-encode an integer
+ * Create Recurring Modulo Function
*
- * The ability to DER-encode integers is needed to create RSA public keys for use with OpenSSL
+ * Sometimes it may be desirable to do repeated modulos with the same number outside of
+ * modular exponentiation
*
- * @see self::modPow()
- * @access private
- * @param int $length
- * @return string
+ * @return callable
*/
- function _encodeASN1Length($length)
+ public function createRecurringModuloFunction()
{
- if ($length <= 0x7F) {
- return chr($length);
- }
-
- $temp = ltrim(pack('N', $length), chr(0));
- return pack('Ca*', 0x80 | strlen($temp), $temp);
+ $func = $this->value->createRecurringModuloFunction();
+ return function (BigInteger $x) use ($func) {
+ return new static($func($x->value));
+ };
}
/**
- * Single digit division
+ * Bitwise Split
*
- * Even if int64 is being used the division operator will return a float64 value
- * if the dividend is not evenly divisible by the divisor. Since a float64 doesn't
- * have the precision of int64 this is a problem so, when int64 is being used,
- * we'll guarantee that the dividend is divisible by first subtracting the remainder.
+ * Splits BigInteger's into chunks of $split bits
*
- * @access private
- * @param int $x
- * @param int $y
- * @return int
+ * @param int $split
+ * @return BigInteger[]
*/
- function _safe_divide($x, $y)
+ public function bitwise_split($split)
{
- if (self::$base === 26) {
- return (int) ($x / $y);
- }
-
- // self::$base === 31
- return ($x - ($x % $y)) / $y;
+ return array_map(function ($val) {
+ return new static($val);
+ }, $this->value->bitwise_split($split));
}
}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath.php
new file mode 100644
index 000000000..7b6283002
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath.php
@@ -0,0 +1,702 @@
+<?php
+
+/**
+ * BCMath BigInteger Engine
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Math\BigInteger\Engines;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Exception\BadConfigurationException;
+
+/**
+ * BCMath Engine.
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class BCMath extends Engine
+{
+ /**
+ * Can Bitwise operations be done fast?
+ *
+ * @see parent::bitwise_leftRotate()
+ * @see parent::bitwise_rightRotate()
+ */
+ const FAST_BITWISE = false;
+
+ /**
+ * Engine Directory
+ *
+ * @see parent::setModExpEngine
+ */
+ const ENGINE_DIR = 'BCMath';
+
+ /**
+ * Test to see if bcmod() accepts 2 or 3 parameters
+ */
+ const BCMOD_THREE_PARAMS = PHP_VERSION_ID >= 72000;
+
+ /**
+ * Test for engine validity
+ *
+ * @return bool
+ * @see parent::__construct()
+ */
+ public static function isValidEngine()
+ {
+ return extension_loaded('bcmath');
+ }
+
+ /**
+ * Default constructor
+ *
+ * @param mixed $x integer Base-10 number or base-$base number if $base set.
+ * @param int $base
+ * @see parent::__construct()
+ */
+ public function __construct($x = 0, $base = 10)
+ {
+ if (!isset(static::$isValidEngine[static::class])) {
+ static::$isValidEngine[static::class] = self::isValidEngine();
+ }
+ if (!static::$isValidEngine[static::class]) {
+ throw new BadConfigurationException('BCMath is not setup correctly on this system');
+ }
+
+ $this->value = '0';
+
+ parent::__construct($x, $base);
+ }
+
+ /**
+ * Initialize a BCMath BigInteger Engine instance
+ *
+ * @param int $base
+ * @see parent::__construct()
+ */
+ protected function initialize($base)
+ {
+ switch (abs($base)) {
+ case 256:
+ // round $len to the nearest 4
+ $len = (strlen($this->value) + 3) & ~3;
+
+ $x = str_pad($this->value, $len, chr(0), STR_PAD_LEFT);
+
+ $this->value = '0';
+ for ($i = 0; $i < $len; $i += 4) {
+ $this->value = bcmul($this->value, '4294967296', 0); // 4294967296 == 2**32
+ $this->value = bcadd(
+ $this->value,
+ 0x1000000 * ord($x[$i]) + ((ord($x[$i + 1]) << 16) | (ord(
+ $x[$i + 2]
+ ) << 8) | ord($x[$i + 3])),
+ 0
+ );
+ }
+
+ if ($this->is_negative) {
+ $this->value = '-' . $this->value;
+ }
+ break;
+ case 16:
+ $x = (strlen($this->value) & 1) ? '0' . $this->value : $this->value;
+ $temp = new self(Strings::hex2bin($x), 256);
+ $this->value = $this->is_negative ? '-' . $temp->value : $temp->value;
+ $this->is_negative = false;
+ break;
+ case 10:
+ // explicitly casting $x to a string is necessary, here, since doing $x[0] on -1 yields different
+ // results then doing it on '-1' does (modInverse does $x[0])
+ $this->value = $this->value === '-' ? '0' : (string)$this->value;
+ }
+ }
+
+ /**
+ * Converts a BigInteger to a base-10 number.
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ if ($this->value === '0') {
+ return '0';
+ }
+
+ return ltrim($this->value, '0');
+ }
+
+ /**
+ * Converts a BigInteger to a byte string (eg. base-256).
+ *
+ * @param bool $twos_compliment
+ * @return string
+ */
+ public function toBytes($twos_compliment = false)
+ {
+ if ($twos_compliment) {
+ return $this->toBytesHelper();
+ }
+
+ $value = '';
+ $current = $this->value;
+
+ if ($current[0] == '-') {
+ $current = substr($current, 1);
+ }
+
+ while (bccomp($current, '0', 0) > 0) {
+ $temp = self::BCMOD_THREE_PARAMS ? bcmod($current, '16777216', 0) : bcmod($current, '16777216');
+ $value = chr($temp >> 16) . chr($temp >> 8) . chr($temp) . $value;
+ $current = bcdiv($current, '16777216', 0);
+ }
+
+ return $this->precision > 0 ?
+ substr(str_pad($value, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) :
+ ltrim($value, chr(0));
+ }
+
+ /**
+ * Adds two BigIntegers.
+ *
+ * @param BCMath $y
+ * @return BCMath
+ */
+ public function add(BCMath $y)
+ {
+ $temp = new self();
+ $temp->value = bcadd($this->value, $y->value, 0);
+
+ return $this->normalize($temp);
+ }
+
+ /**
+ * Subtracts two BigIntegers.
+ *
+ * @param BCMath $y
+ * @return BCMath
+ */
+ public function subtract(BCMath $y)
+ {
+ $temp = new self();
+ $temp->value = bcsub($this->value, $y->value, 0);
+
+ return $this->normalize($temp);
+ }
+
+ /**
+ * Multiplies two BigIntegers.
+ *
+ * @param BCMath $x
+ * @return BCMath
+ */
+ public function multiply(BCMath $x)
+ {
+ $temp = new self();
+ $temp->value = bcmul($this->value, $x->value, 0);
+
+ return $this->normalize($temp);
+ }
+
+ /**
+ * Divides two BigIntegers.
+ *
+ * Returns an array whose first element contains the quotient and whose second element contains the
+ * "common residue". If the remainder would be positive, the "common residue" and the remainder are the
+ * same. If the remainder would be negative, the "common residue" is equal to the sum of the remainder
+ * and the divisor (basically, the "common residue" is the first positive modulo).
+ *
+ * @param BCMath $y
+ * @return array{static, static}
+ */
+ public function divide(BCMath $y)
+ {
+ $quotient = new self();
+ $remainder = new self();
+
+ $quotient->value = bcdiv($this->value, $y->value, 0);
+ $remainder->value = self::BCMOD_THREE_PARAMS ? bcmod($this->value, $y->value, 0) : bcmod($this->value, $y->value);
+
+ if ($remainder->value[0] == '-') {
+ $remainder->value = bcadd($remainder->value, $y->value[0] == '-' ? substr($y->value, 1) : $y->value, 0);
+ }
+
+ return [$this->normalize($quotient), $this->normalize($remainder)];
+ }
+
+ /**
+ * Calculates modular inverses.
+ *
+ * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses.
+ *
+ * @param BCMath $n
+ * @return false|BCMath
+ */
+ public function modInverse(BCMath $n)
+ {
+ return $this->modInverseHelper($n);
+ }
+
+ /**
+ * Calculates the greatest common divisor and Bezout's identity.
+ *
+ * Say you have 693 and 609. The GCD is 21. Bezout's identity states that there exist integers x and y such that
+ * 693*x + 609*y == 21. In point of fact, there are actually an infinite number of x and y combinations and which
+ * combination is returned is dependent upon which mode is in use. See
+ * {@link http://en.wikipedia.org/wiki/B%C3%A9zout%27s_identity Bezout's identity - Wikipedia} for more information.
+ *
+ * @param BCMath $n
+ * @return array{gcd: static, x: static, y: static}
+ */
+ public function extendedGCD(BCMath $n)
+ {
+ // it might be faster to use the binary xGCD algorithim here, as well, but (1) that algorithim works
+ // best when the base is a power of 2 and (2) i don't think it'd make much difference, anyway. as is,
+ // the basic extended euclidean algorithim is what we're using.
+
+ $u = $this->value;
+ $v = $n->value;
+
+ $a = '1';
+ $b = '0';
+ $c = '0';
+ $d = '1';
+
+ while (bccomp($v, '0', 0) != 0) {
+ $q = bcdiv($u, $v, 0);
+
+ $temp = $u;
+ $u = $v;
+ $v = bcsub($temp, bcmul($v, $q, 0), 0);
+
+ $temp = $a;
+ $a = $c;
+ $c = bcsub($temp, bcmul($a, $q, 0), 0);
+
+ $temp = $b;
+ $b = $d;
+ $d = bcsub($temp, bcmul($b, $q, 0), 0);
+ }
+
+ return [
+ 'gcd' => $this->normalize(new static($u)),
+ 'x' => $this->normalize(new static($a)),
+ 'y' => $this->normalize(new static($b))
+ ];
+ }
+
+ /**
+ * Calculates the greatest common divisor
+ *
+ * Say you have 693 and 609. The GCD is 21.
+ *
+ * @param BCMath $n
+ * @return BCMath
+ */
+ public function gcd(BCMath $n)
+ {
+ $gcd = $this->extendedGCD($n)['gcd'];
+ return $gcd;
+ }
+
+ /**
+ * Absolute value.
+ *
+ * @return BCMath
+ */
+ public function abs()
+ {
+ $temp = new static();
+ $temp->value = strlen($this->value) && $this->value[0] == '-' ?
+ substr($this->value, 1) :
+ $this->value;
+
+ return $temp;
+ }
+
+ /**
+ * Logical And
+ *
+ * @param BCMath $x
+ * @return BCMath
+ */
+ public function bitwise_and(BCMath $x)
+ {
+ return $this->bitwiseAndHelper($x);
+ }
+
+ /**
+ * Logical Or
+ *
+ * @param BCMath $x
+ * @return BCMath
+ */
+ public function bitwise_or(BCMath $x)
+ {
+ return $this->bitwiseOrHelper($x);
+ }
+
+ /**
+ * Logical Exclusive Or
+ *
+ * @param BCMath $x
+ * @return BCMath
+ */
+ public function bitwise_xor(BCMath $x)
+ {
+ return $this->bitwiseXorHelper($x);
+ }
+
+ /**
+ * Logical Right Shift
+ *
+ * Shifts BigInteger's by $shift bits, effectively dividing by 2**$shift.
+ *
+ * @param int $shift
+ * @return BCMath
+ */
+ public function bitwise_rightShift($shift)
+ {
+ $temp = new static();
+ $temp->value = bcdiv($this->value, bcpow('2', $shift, 0), 0);
+
+ return $this->normalize($temp);
+ }
+
+ /**
+ * Logical Left Shift
+ *
+ * Shifts BigInteger's by $shift bits, effectively multiplying by 2**$shift.
+ *
+ * @param int $shift
+ * @return BCMath
+ */
+ public function bitwise_leftShift($shift)
+ {
+ $temp = new static();
+ $temp->value = bcmul($this->value, bcpow('2', $shift, 0), 0);
+
+ return $this->normalize($temp);
+ }
+
+ /**
+ * Compares two numbers.
+ *
+ * Although one might think !$x->compare($y) means $x != $y, it, in fact, means the opposite. The reason for this
+ * is demonstrated thusly:
+ *
+ * $x > $y: $x->compare($y) > 0
+ * $x < $y: $x->compare($y) < 0
+ * $x == $y: $x->compare($y) == 0
+ *
+ * Note how the same comparison operator is used. If you want to test for equality, use $x->equals($y).
+ *
+ * {@internal Could return $this->subtract($x), but that's not as fast as what we do do.}
+ *
+ * @param BCMath $y
+ * @return int in case < 0 if $this is less than $y; > 0 if $this is greater than $y, and 0 if they are equal.
+ * @see self::equals()
+ */
+ public function compare(BCMath $y)
+ {
+ return bccomp($this->value, $y->value, 0);
+ }
+
+ /**
+ * Tests the equality of two numbers.
+ *
+ * If you need to see if one number is greater than or less than another number, use BigInteger::compare()
+ *
+ * @param BCMath $x
+ * @return bool
+ */
+ public function equals(BCMath $x)
+ {
+ return $this->value == $x->value;
+ }
+
+ /**
+ * Performs modular exponentiation.
+ *
+ * @param BCMath $e
+ * @param BCMath $n
+ * @return BCMath
+ */
+ public function modPow(BCMath $e, BCMath $n)
+ {
+ return $this->powModOuter($e, $n);
+ }
+
+ /**
+ * Performs modular exponentiation.
+ *
+ * Alias for modPow().
+ *
+ * @param BCMath $e
+ * @param BCMath $n
+ * @return BCMath
+ */
+ public function powMod(BCMath $e, BCMath $n)
+ {
+ return $this->powModOuter($e, $n);
+ }
+
+ /**
+ * Performs modular exponentiation.
+ *
+ * @param BCMath $e
+ * @param BCMath $n
+ * @return BCMath
+ */
+ protected function powModInner(BCMath $e, BCMath $n)
+ {
+ try {
+ $class = static::$modexpEngine[static::class];
+ return $class::powModHelper($this, $e, $n, static::class);
+ } catch (\Exception $err) {
+ return BCMath\DefaultEngine::powModHelper($this, $e, $n, static::class);
+ }
+ }
+
+ /**
+ * Normalize
+ *
+ * Removes leading zeros and truncates (if necessary) to maintain the appropriate precision
+ *
+ * @param BCMath $result
+ * @return BCMath
+ */
+ protected function normalize(BCMath $result)
+ {
+ $result->precision = $this->precision;
+ $result->bitmask = $this->bitmask;
+
+ if ($result->bitmask !== false) {
+ $result->value = self::BCMOD_THREE_PARAMS ? bcmod($result->value, $result->bitmask->value, 0) : bcmod($result->value, $result->bitmask->value);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Generate a random prime number between a range
+ *
+ * If there's not a prime within the given range, false will be returned.
+ *
+ * @param BCMath $min
+ * @param BCMath $max
+ * @return false|BCMath
+ */
+ public static function randomRangePrime(BCMath $min, BCMath $max)
+ {
+ return self::randomRangePrimeOuter($min, $max);
+ }
+
+ /**
+ * Generate a random number between a range
+ *
+ * Returns a random number between $min and $max where $min and $max
+ * can be defined using one of the two methods:
+ *
+ * BigInteger::randomRange($min, $max)
+ * BigInteger::randomRange($max, $min)
+ *
+ * @param BCMath $min
+ * @param BCMath $max
+ * @return BCMath
+ */
+ public static function randomRange(BCMath $min, BCMath $max)
+ {
+ return self::randomRangeHelper($min, $max);
+ }
+
+ /**
+ * Make the current number odd
+ *
+ * If the current number is odd it'll be unchanged. If it's even, one will be added to it.
+ *
+ * @see self::randomPrime()
+ */
+ protected function make_odd()
+ {
+ if (!$this->isOdd()) {
+ $this->value = bcadd($this->value, '1', 0);
+ }
+ }
+
+ /**
+ * Test the number against small primes.
+ *
+ * @see self::isPrime()
+ */
+ protected function testSmallPrimes()
+ {
+ if ($this->value === '1') {
+ return false;
+ }
+ if ($this->value === '2') {
+ return true;
+ }
+ if ($this->value[strlen($this->value) - 1] % 2 == 0) {
+ return false;
+ }
+
+ $value = $this->value;
+
+ foreach (self::PRIMES as $prime) {
+ $r = self::BCMOD_THREE_PARAMS ? bcmod($this->value, $prime, 0) : bcmod($this->value, $prime);
+ if ($r == '0') {
+ return $this->value == $prime;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Scan for 1 and right shift by that amount
+ *
+ * ie. $s = gmp_scan1($n, 0) and $r = gmp_div_q($n, gmp_pow(gmp_init('2'), $s));
+ *
+ * @param BCMath $r
+ * @return int
+ * @see self::isPrime()
+ */
+ public static function scan1divide(BCMath $r)
+ {
+ $r_value = &$r->value;
+ $s = 0;
+ // if $n was 1, $r would be 0 and this would be an infinite loop, hence our $this->equals(static::$one[static::class]) check earlier
+ while ($r_value[strlen($r_value) - 1] % 2 == 0) {
+ $r_value = bcdiv($r_value, '2', 0);
+ ++$s;
+ }
+
+ return $s;
+ }
+
+ /**
+ * Performs exponentiation.
+ *
+ * @param BCMath $n
+ * @return BCMath
+ */
+ public function pow(BCMath $n)
+ {
+ $temp = new self();
+ $temp->value = bcpow($this->value, $n->value, 0);
+
+ return $this->normalize($temp);
+ }
+
+ /**
+ * Return the minimum BigInteger between an arbitrary number of BigIntegers.
+ *
+ * @param BCMath ...$nums
+ * @return BCMath
+ */
+ public static function min(BCMath ...$nums)
+ {
+ return self::minHelper($nums);
+ }
+
+ /**
+ * Return the maximum BigInteger between an arbitrary number of BigIntegers.
+ *
+ * @param BCMath ...$nums
+ * @return BCMath
+ */
+ public static function max(BCMath ...$nums)
+ {
+ return self::maxHelper($nums);
+ }
+
+ /**
+ * Tests BigInteger to see if it is between two integers, inclusive
+ *
+ * @param BCMath $min
+ * @param BCMath $max
+ * @return bool
+ */
+ public function between(BCMath $min, BCMath $max)
+ {
+ return $this->compare($min) >= 0 && $this->compare($max) <= 0;
+ }
+
+ /**
+ * Set Bitmask
+ *
+ * @param int $bits
+ * @return Engine
+ * @see self::setPrecision()
+ */
+ protected static function setBitmask($bits)
+ {
+ $temp = parent::setBitmask($bits);
+ return $temp->add(static::$one[static::class]);
+ }
+
+ /**
+ * Is Odd?
+ *
+ * @return bool
+ */
+ public function isOdd()
+ {
+ return $this->value[strlen($this->value) - 1] % 2 == 1;
+ }
+
+ /**
+ * Tests if a bit is set
+ *
+ * @return bool
+ */
+ public function testBit($x)
+ {
+ $divisor = bcpow('2', $x + 1, 0);
+ return bccomp(
+ self::BCMOD_THREE_PARAMS ? bcmod($this->value, $divisor, 0) : bcmod($this->value, $divisor),
+ bcpow('2', $x, 0),
+ 0
+ ) >= 0;
+ }
+
+ /**
+ * Is Negative?
+ *
+ * @return bool
+ */
+ public function isNegative()
+ {
+ return strlen($this->value) && $this->value[0] == '-';
+ }
+
+ /**
+ * Negate
+ *
+ * Given $k, returns -$k
+ *
+ * @return BCMath
+ */
+ public function negate()
+ {
+ $temp = clone $this;
+
+ if (!strlen($temp->value)) {
+ return $temp;
+ }
+
+ $temp->value = $temp->value[0] == '-' ?
+ substr($this->value, 1) :
+ '-' . $this->value;
+
+ return $temp;
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Base.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Base.php
new file mode 100644
index 000000000..88cd93e94
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Base.php
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * Modular Exponentiation Engine
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Math\BigInteger\Engines\BCMath;
+
+use phpseclib3\Math\BigInteger\Engines\BCMath;
+
+/**
+ * Sliding Window Exponentiation Engine
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Base extends BCMath
+{
+ /**
+ * Cache constants
+ *
+ * $cache[self::VARIABLE] tells us whether or not the cached data is still valid.
+ *
+ */
+ const VARIABLE = 0;
+ /**
+ * $cache[self::DATA] contains the cached data.
+ *
+ */
+ const DATA = 1;
+
+ /**
+ * Test for engine validity
+ *
+ * @return bool
+ */
+ public static function isValidEngine()
+ {
+ return static::class != __CLASS__;
+ }
+
+ /**
+ * Performs modular exponentiation.
+ *
+ * @param BCMath $x
+ * @param BCMath $e
+ * @param BCMath $n
+ * @param string $class
+ * @return BCMath
+ */
+ protected static function powModHelper(BCMath $x, BCMath $e, BCMath $n, $class)
+ {
+ if (empty($e->value)) {
+ $temp = new $class();
+ $temp->value = '1';
+ return $x->normalize($temp);
+ }
+
+ return $x->normalize(static::slidingWindow($x, $e, $n, $class));
+ }
+
+ /**
+ * Modular reduction preparation
+ *
+ * @param string $x
+ * @param string $n
+ * @param string $class
+ * @see self::slidingWindow()
+ * @return string
+ */
+ protected static function prepareReduce($x, $n, $class)
+ {
+ return static::reduce($x, $n);
+ }
+
+ /**
+ * Modular multiply
+ *
+ * @param string $x
+ * @param string $y
+ * @param string $n
+ * @param string $class
+ * @see self::slidingWindow()
+ * @return string
+ */
+ protected static function multiplyReduce($x, $y, $n, $class)
+ {
+ return static::reduce(bcmul($x, $y, 0), $n);
+ }
+
+ /**
+ * Modular square
+ *
+ * @param string $x
+ * @param string $n
+ * @param string $class
+ * @see self::slidingWindow()
+ * @return string
+ */
+ protected static function squareReduce($x, $n, $class)
+ {
+ return static::reduce(bcmul($x, $x, 0), $n);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/BuiltIn.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/BuiltIn.php
new file mode 100644
index 000000000..f8bbcfa27
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/BuiltIn.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * Built-In BCMath Modular Exponentiation Engine
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Math\BigInteger\Engines\BCMath;
+
+use phpseclib3\Math\BigInteger\Engines\BCMath;
+
+/**
+ * Built-In BCMath Modular Exponentiation Engine
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class BuiltIn extends BCMath
+{
+ /**
+ * Performs modular exponentiation.
+ *
+ * @param BCMath $x
+ * @param BCMath $e
+ * @param BCMath $n
+ * @return BCMath
+ */
+ protected static function powModHelper(BCMath $x, BCMath $e, BCMath $n)
+ {
+ $temp = new BCMath();
+ $temp->value = bcpowmod($x->value, $e->value, $n->value, 0);
+
+ return $x->normalize($temp);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/DefaultEngine.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/DefaultEngine.php
new file mode 100644
index 000000000..b2d9fa95c
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/DefaultEngine.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * BCMath Default Modular Exponentiation Engine
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Math\BigInteger\Engines\BCMath;
+
+use phpseclib3\Math\BigInteger\Engines\BCMath\Reductions\Barrett;
+
+/**
+ * PHP Default Modular Exponentiation Engine
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class DefaultEngine extends Barrett
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/OpenSSL.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/OpenSSL.php
new file mode 100644
index 000000000..aed949420
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/OpenSSL.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * OpenSSL Modular Exponentiation Engine
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Math\BigInteger\Engines\BCMath;
+
+use phpseclib3\Math\BigInteger\Engines\OpenSSL as Progenitor;
+
+/**
+ * OpenSSL Modular Exponentiation Engine
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class OpenSSL extends Progenitor
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Reductions/Barrett.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Reductions/Barrett.php
new file mode 100644
index 000000000..1bec0a11f
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Reductions/Barrett.php
@@ -0,0 +1,196 @@
+<?php
+
+/**
+ * BCMath Barrett Modular Exponentiation Engine
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Math\BigInteger\Engines\BCMath\Reductions;
+
+use phpseclib3\Math\BigInteger\Engines\BCMath\Base;
+
+/**
+ * PHP Barrett Modular Exponentiation Engine
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Barrett extends Base
+{
+ /**
+ * Cache constants
+ *
+ * $cache[self::VARIABLE] tells us whether or not the cached data is still valid.
+ *
+ */
+ const VARIABLE = 0;
+ /**
+ * $cache[self::DATA] contains the cached data.
+ *
+ */
+ const DATA = 1;
+
+ /**
+ * Barrett Modular Reduction
+ *
+ * See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=14 HAC 14.3.3} /
+ * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=165 MPM 6.2.5} for more information. Modified slightly,
+ * so as not to require negative numbers (initially, this script didn't support negative numbers).
+ *
+ * Employs "folding", as described at
+ * {@link http://www.cosic.esat.kuleuven.be/publications/thesis-149.pdf#page=66 thesis-149.pdf#page=66}. To quote from
+ * it, "the idea [behind folding] is to find a value x' such that x (mod m) = x' (mod m), with x' being smaller than x."
+ *
+ * Unfortunately, the "Barrett Reduction with Folding" algorithm described in thesis-149.pdf is not, as written, all that
+ * usable on account of (1) its not using reasonable radix points as discussed in
+ * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=162 MPM 6.2.2} and (2) the fact that, even with reasonable
+ * radix points, it only works when there are an even number of digits in the denominator. The reason for (2) is that
+ * (x >> 1) + (x >> 1) != x / 2 + x / 2. If x is even, they're the same, but if x is odd, they're not. See the in-line
+ * comments for details.
+ *
+ * @param string $n
+ * @param string $m
+ * @return string
+ */
+ protected static function reduce($n, $m)
+ {
+ static $cache = [
+ self::VARIABLE => [],
+ self::DATA => []
+ ];
+
+ $m_length = strlen($m);
+
+ if (strlen($n) > 2 * $m_length) {
+ return self::BCMOD_THREE_PARAMS ? bcmod($n, $m, 0) : bcmod($n, $m);
+ }
+
+ // if (m.length >> 1) + 2 <= m.length then m is too small and n can't be reduced
+ if ($m_length < 5) {
+ return self::regularBarrett($n, $m);
+ }
+ // n = 2 * m.length
+ $correctionNeeded = false;
+ if ($m_length & 1) {
+ $correctionNeeded = true;
+ $n .= '0';
+ $m .= '0';
+ $m_length++;
+ }
+
+ if (($key = array_search($m, $cache[self::VARIABLE])) === false) {
+ $key = count($cache[self::VARIABLE]);
+ $cache[self::VARIABLE][] = $m;
+
+ $lhs = '1' . str_repeat('0', $m_length + ($m_length >> 1));
+ $u = bcdiv($lhs, $m, 0);
+ $m1 = bcsub($lhs, bcmul($u, $m, 0), 0);
+
+ $cache[self::DATA][] = [
+ 'u' => $u, // m.length >> 1 (technically (m.length >> 1) + 1)
+ 'm1' => $m1 // m.length
+ ];
+ } else {
+ $cacheValues = $cache[self::DATA][$key];
+ $u = $cacheValues['u'];
+ $m1 = $cacheValues['m1'];
+ }
+
+ $cutoff = $m_length + ($m_length >> 1);
+
+ $lsd = substr($n, -$cutoff);
+ $msd = substr($n, 0, -$cutoff);
+
+ $temp = bcmul($msd, $m1, 0); // m.length + (m.length >> 1)
+ $n = bcadd($lsd, $temp, 0); // m.length + (m.length >> 1) + 1 (so basically we're adding two same length numbers)
+ //if ($m_length & 1) {
+ // return self::regularBarrett($n, $m);
+ //}
+
+ // (m.length + (m.length >> 1) + 1) - (m.length - 1) == (m.length >> 1) + 2
+ $temp = substr($n, 0, -$m_length + 1);
+ // if even: ((m.length >> 1) + 2) + (m.length >> 1) == m.length + 2
+ // if odd: ((m.length >> 1) + 2) + (m.length >> 1) == (m.length - 1) + 2 == m.length + 1
+ $temp = bcmul($temp, $u, 0);
+ // if even: (m.length + 2) - ((m.length >> 1) + 1) = m.length - (m.length >> 1) + 1
+ // if odd: (m.length + 1) - ((m.length >> 1) + 1) = m.length - (m.length >> 1)
+ $temp = substr($temp, 0, -($m_length >> 1) - 1);
+ // if even: (m.length - (m.length >> 1) + 1) + m.length = 2 * m.length - (m.length >> 1) + 1
+ // if odd: (m.length - (m.length >> 1)) + m.length = 2 * m.length - (m.length >> 1)
+ $temp = bcmul($temp, $m, 0);
+
+ // at this point, if m had an odd number of digits, we'd be subtracting a 2 * m.length - (m.length >> 1) digit
+ // number from a m.length + (m.length >> 1) + 1 digit number. ie. there'd be an extra digit and the while loop
+ // following this comment would loop a lot (hence our calling _regularBarrett() in that situation).
+
+ $result = bcsub($n, $temp, 0);
+
+ //if (bccomp($result, '0') < 0) {
+ if ($result[0] == '-') {
+ $temp = '1' . str_repeat('0', $m_length + 1);
+ $result = bcadd($result, $temp, 0);
+ }
+
+ while (bccomp($result, $m, 0) >= 0) {
+ $result = bcsub($result, $m, 0);
+ }
+
+ return $correctionNeeded && $result != '0' ? substr($result, 0, -1) : $result;
+ }
+
+ /**
+ * (Regular) Barrett Modular Reduction
+ *
+ * For numbers with more than four digits BigInteger::_barrett() is faster. The difference between that and this
+ * is that this function does not fold the denominator into a smaller form.
+ *
+ * @param string $x
+ * @param string $n
+ * @return string
+ */
+ private static function regularBarrett($x, $n)
+ {
+ static $cache = [
+ self::VARIABLE => [],
+ self::DATA => []
+ ];
+
+ $n_length = strlen($n);
+
+ if (strlen($x) > 2 * $n_length) {
+ return self::BCMOD_THREE_PARAMS ? bcmod($x, $n, 0) : bcmod($x, $n);
+ }
+
+ if (($key = array_search($n, $cache[self::VARIABLE])) === false) {
+ $key = count($cache[self::VARIABLE]);
+ $cache[self::VARIABLE][] = $n;
+ $lhs = '1' . str_repeat('0', 2 * $n_length);
+ $cache[self::DATA][] = bcdiv($lhs, $n, 0);
+ }
+
+ $temp = substr($x, 0, -$n_length + 1);
+ $temp = bcmul($temp, $cache[self::DATA][$key], 0);
+ $temp = substr($temp, 0, -$n_length - 1);
+
+ $r1 = substr($x, -$n_length - 1);
+ $r2 = substr(bcmul($temp, $n, 0), -$n_length - 1);
+ $result = bcsub($r1, $r2);
+
+ //if (bccomp($result, '0') < 0) {
+ if ($result[0] == '-') {
+ $q = '1' . str_repeat('0', $n_length + 1);
+ $result = bcadd($result, $q, 0);
+ }
+
+ while (bccomp($result, $n, 0) >= 0) {
+ $result = bcsub($result, $n, 0);
+ }
+
+ return $result;
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Reductions/EvalBarrett.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Reductions/EvalBarrett.php
new file mode 100644
index 000000000..040d7b5a7
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/BCMath/Reductions/EvalBarrett.php
@@ -0,0 +1,108 @@
+<?php
+
+/**
+ * BCMath Dynamic Barrett Modular Exponentiation Engine
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Math\BigInteger\Engines\BCMath\Reductions;
+
+use phpseclib3\Math\BigInteger\Engines\BCMath;
+use phpseclib3\Math\BigInteger\Engines\BCMath\Base;
+
+/**
+ * PHP Barrett Modular Exponentiation Engine
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class EvalBarrett extends Base
+{
+ /**
+ * Custom Reduction Function
+ *
+ * @see self::generateCustomReduction
+ */
+ private static $custom_reduction;
+
+ /**
+ * Barrett Modular Reduction
+ *
+ * This calls a dynamically generated loop unrolled function that's specific to a given modulo.
+ * Array lookups are avoided as are if statements testing for how many bits the host OS supports, etc.
+ *
+ * @param string $n
+ * @param string $m
+ * @return string
+ */
+ protected static function reduce($n, $m)
+ {
+ $inline = self::$custom_reduction;
+ return $inline($n);
+ }
+
+ /**
+ * Generate Custom Reduction
+ *
+ * @param BCMath $m
+ * @param string $class
+ * @return callable|void
+ */
+ protected static function generateCustomReduction(BCMath $m, $class)
+ {
+ $m_length = strlen($m);
+
+ if ($m_length < 5) {
+ $code = 'return self::BCMOD_THREE_PARAMS ? bcmod($x, $n, 0) : bcmod($x, $n);';
+ eval('$func = function ($n) { ' . $code . '};');
+ self::$custom_reduction = $func;
+ return;
+ }
+
+ $lhs = '1' . str_repeat('0', $m_length + ($m_length >> 1));
+ $u = bcdiv($lhs, $m, 0);
+ $m1 = bcsub($lhs, bcmul($u, $m, 0), 0);
+
+ $cutoff = $m_length + ($m_length >> 1);
+
+ $m = "'$m'";
+ $u = "'$u'";
+ $m1 = "'$m1'";
+
+ $code = '
+ $lsd = substr($n, -' . $cutoff . ');
+ $msd = substr($n, 0, -' . $cutoff . ');
+
+ $temp = bcmul($msd, ' . $m1 . ', 0);
+ $n = bcadd($lsd, $temp, 0);
+
+ $temp = substr($n, 0, ' . (-$m_length + 1) . ');
+ $temp = bcmul($temp, ' . $u . ', 0);
+ $temp = substr($temp, 0, ' . (-($m_length >> 1) - 1) . ');
+ $temp = bcmul($temp, ' . $m . ', 0);
+
+ $result = bcsub($n, $temp, 0);
+
+ if ($result[0] == \'-\') {
+ $temp = \'1' . str_repeat('0', $m_length + 1) . '\';
+ $result = bcadd($result, $temp, 0);
+ }
+
+ while (bccomp($result, ' . $m . ') >= 0) {
+ $result = bcsub($result, ' . $m . ', 0);
+ }
+
+ return $result;';
+
+ eval('$func = function ($n) { ' . $code . '};');
+
+ self::$custom_reduction = $func;
+
+ return $func;
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/Engine.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/Engine.php
new file mode 100644
index 000000000..1892042c5
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/Engine.php
@@ -0,0 +1,1293 @@
+<?php
+
+/**
+ * Base BigInteger Engine
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Math\BigInteger\Engines;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Random;
+use phpseclib3\Exception\BadConfigurationException;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * Base Engine.
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Engine implements \JsonSerializable
+{
+ /* final protected */ const PRIMES = [
+ 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59,
+ 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137,
+ 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227,
+ 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313,
+ 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419,
+ 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509,
+ 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617,
+ 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727,
+ 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829,
+ 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947,
+ 953, 967, 971, 977, 983, 991, 997,
+ ];
+
+ /**
+ * BigInteger(0)
+ *
+ * @var array<class-string<static>, static>
+ */
+ protected static $zero = [];
+
+ /**
+ * BigInteger(1)
+ *
+ * @var array<class-string<static>, static>
+ */
+ protected static $one = [];
+
+ /**
+ * BigInteger(2)
+ *
+ * @var array<class-string<static>, static>
+ */
+ protected static $two = [];
+
+ /**
+ * Modular Exponentiation Engine
+ *
+ * @var array<class-string<static>, class-string<static>>
+ */
+ protected static $modexpEngine;
+
+ /**
+ * Engine Validity Flag
+ *
+ * @var array<class-string<static>, bool>
+ */
+ protected static $isValidEngine;
+
+ /**
+ * Holds the BigInteger's value
+ *
+ * @var \GMP|string|array|int
+ */
+ protected $value;
+
+ /**
+ * Holds the BigInteger's sign
+ *
+ * @var bool
+ */
+ protected $is_negative;
+
+ /**
+ * Precision
+ *
+ * @see static::setPrecision()
+ * @var int
+ */
+ protected $precision = -1;
+
+ /**
+ * Precision Bitmask
+ *
+ * @see static::setPrecision()
+ * @var static|false
+ */
+ protected $bitmask = false;
+
+ /**
+ * Recurring Modulo Function
+ *
+ * @var callable
+ */
+ protected $reduce;
+
+ /**
+ * Mode independent value used for serialization.
+ *
+ * @see self::__sleep()
+ * @see self::__wakeup()
+ * @var string
+ */
+ protected $hex;
+
+ /**
+ * Default constructor
+ *
+ * @param int|numeric-string $x integer Base-10 number or base-$base number if $base set.
+ * @param int $base
+ */
+ public function __construct($x = 0, $base = 10)
+ {
+ if (!array_key_exists(static::class, static::$zero)) {
+ static::$zero[static::class] = null; // Placeholder to prevent infinite loop.
+ static::$zero[static::class] = new static(0);
+ static::$one[static::class] = new static(1);
+ static::$two[static::class] = new static(2);
+ }
+
+ // '0' counts as empty() but when the base is 256 '0' is equal to ord('0') or 48
+ // '0' is the only value like this per http://php.net/empty
+ if (empty($x) && (abs($base) != 256 || $x !== '0')) {
+ return;
+ }
+
+ switch ($base) {
+ case -256:
+ case 256:
+ if ($base == -256 && (ord($x[0]) & 0x80)) {
+ $this->value = ~$x;
+ $this->is_negative = true;
+ } else {
+ $this->value = $x;
+ $this->is_negative = false;
+ }
+
+ $this->initialize($base);
+
+ if ($this->is_negative) {
+ $temp = $this->add(new static('-1'));
+ $this->value = $temp->value;
+ }
+ break;
+ case -16:
+ case 16:
+ if ($base > 0 && $x[0] == '-') {
+ $this->is_negative = true;
+ $x = substr($x, 1);
+ }
+
+ $x = preg_replace('#^(?:0x)?([A-Fa-f0-9]*).*#s', '$1', $x);
+
+ $is_negative = false;
+ if ($base < 0 && hexdec($x[0]) >= 8) {
+ $this->is_negative = $is_negative = true;
+ $x = Strings::bin2hex(~Strings::hex2bin($x));
+ }
+
+ $this->value = $x;
+ $this->initialize($base);
+
+ if ($is_negative) {
+ $temp = $this->add(new static('-1'));
+ $this->value = $temp->value;
+ }
+ break;
+ case -10:
+ case 10:
+ // (?<!^)(?:-).*: find any -'s that aren't at the beginning and then any characters that follow that
+ // (?<=^|-)0*: find any 0's that are preceded by the start of the string or by a - (ie. octals)
+ // [^-0-9].*: find any non-numeric characters and then any characters that follow that
+ $this->value = preg_replace('#(?<!^)(?:-).*|(?<=^|-)0*|[^-0-9].*#s', '', $x);
+ if (!strlen($this->value) || $this->value == '-') {
+ $this->value = '0';
+ }
+ $this->initialize($base);
+ break;
+ case -2:
+ case 2:
+ if ($base > 0 && $x[0] == '-') {
+ $this->is_negative = true;
+ $x = substr($x, 1);
+ }
+
+ $x = preg_replace('#^([01]*).*#s', '$1', $x);
+
+ $temp = new static(Strings::bits2bin($x), 128 * $base); // ie. either -16 or +16
+ $this->value = $temp->value;
+ if ($temp->is_negative) {
+ $this->is_negative = true;
+ }
+
+ break;
+ default:
+ // base not supported, so we'll let $this == 0
+ }
+ }
+
+ /**
+ * Sets engine type.
+ *
+ * Throws an exception if the type is invalid
+ *
+ * @param class-string<Engine> $engine
+ */
+ public static function setModExpEngine($engine)
+ {
+ $fqengine = '\\phpseclib3\\Math\\BigInteger\\Engines\\' . static::ENGINE_DIR . '\\' . $engine;
+ if (!class_exists($fqengine) || !method_exists($fqengine, 'isValidEngine')) {
+ throw new \InvalidArgumentException("$engine is not a valid engine");
+ }
+ if (!$fqengine::isValidEngine()) {
+ throw new BadConfigurationException("$engine is not setup correctly on this system");
+ }
+ static::$modexpEngine[static::class] = $fqengine;
+ }
+
+ /**
+ * Converts a BigInteger to a byte string (eg. base-256).
+ *
+ * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're
+ * saved as two's compliment.
+ * @return string
+ */
+ protected function toBytesHelper()
+ {
+ $comparison = $this->compare(new static());
+ if ($comparison == 0) {
+ return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : '';
+ }
+
+ $temp = $comparison < 0 ? $this->add(new static(1)) : $this;
+ $bytes = $temp->toBytes();
+
+ if (!strlen($bytes)) { // eg. if the number we're trying to convert is -1
+ $bytes = chr(0);
+ }
+
+ if (ord($bytes[0]) & 0x80) {
+ $bytes = chr(0) . $bytes;
+ }
+
+ return $comparison < 0 ? ~$bytes : $bytes;
+ }
+
+ /**
+ * Converts a BigInteger to a hex string (eg. base-16).
+ *
+ * @param bool $twos_compliment
+ * @return string
+ */
+ public function toHex($twos_compliment = false)
+ {
+ return Strings::bin2hex($this->toBytes($twos_compliment));
+ }
+
+ /**
+ * Converts a BigInteger to a bit string (eg. base-2).
+ *
+ * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're
+ * saved as two's compliment.
+ *
+ * @param bool $twos_compliment
+ * @return string
+ */
+ public function toBits($twos_compliment = false)
+ {
+ $hex = $this->toBytes($twos_compliment);
+ $bits = Strings::bin2bits($hex);
+
+ $result = $this->precision > 0 ? substr($bits, -$this->precision) : ltrim($bits, '0');
+
+ if ($twos_compliment && $this->compare(new static()) > 0 && $this->precision <= 0) {
+ return '0' . $result;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Calculates modular inverses.
+ *
+ * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses.
+ *
+ * {@internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=21 HAC 14.64} for more information.}
+ *
+ * @param Engine $n
+ * @return static|false
+ */
+ protected function modInverseHelper(Engine $n)
+ {
+ // $x mod -$n == $x mod $n.
+ $n = $n->abs();
+
+ if ($this->compare(static::$zero[static::class]) < 0) {
+ $temp = $this->abs();
+ $temp = $temp->modInverse($n);
+ return $this->normalize($n->subtract($temp));
+ }
+
+ $extended = $this->extendedGCD($n);
+ $gcd = $extended['gcd'];
+ $x = $extended['x'];
+
+ if (!$gcd->equals(static::$one[static::class])) {
+ return false;
+ }
+
+ $x = $x->compare(static::$zero[static::class]) < 0 ? $x->add($n) : $x;
+
+ return $this->compare(static::$zero[static::class]) < 0 ? $this->normalize($n->subtract($x)) : $this->normalize($x);
+ }
+
+ /**
+ * Serialize
+ *
+ * Will be called, automatically, when serialize() is called on a BigInteger object.
+ *
+ * @return array
+ */
+ public function __sleep()
+ {
+ $this->hex = $this->toHex(true);
+ $vars = ['hex'];
+ if ($this->precision > 0) {
+ $vars[] = 'precision';
+ }
+ return $vars;
+ }
+
+ /**
+ * Serialize
+ *
+ * Will be called, automatically, when unserialize() is called on a BigInteger object.
+ *
+ * @return void
+ */
+ public function __wakeup()
+ {
+ $temp = new static($this->hex, -16);
+ $this->value = $temp->value;
+ $this->is_negative = $temp->is_negative;
+ if ($this->precision > 0) {
+ // recalculate $this->bitmask
+ $this->setPrecision($this->precision);
+ }
+ }
+
+ /**
+ * JSON Serialize
+ *
+ * Will be called, automatically, when json_encode() is called on a BigInteger object.
+ *
+ * @return array{hex: string, precision?: int]
+ */
+ #[\ReturnTypeWillChange]
+ public function jsonSerialize()
+ {
+ $result = ['hex' => $this->toHex(true)];
+ if ($this->precision > 0) {
+ $result['precision'] = $this->precision;
+ }
+ return $result;
+ }
+
+ /**
+ * Converts a BigInteger to a base-10 number.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->toString();
+ }
+
+ /**
+ * __debugInfo() magic method
+ *
+ * Will be called, automatically, when print_r() or var_dump() are called
+ *
+ * @return array
+ */
+ public function __debugInfo()
+ {
+ $result = [
+ 'value' => '0x' . $this->toHex(true),
+ 'engine' => basename(static::class)
+ ];
+ return $this->precision > 0 ? $result + ['precision' => $this->precision] : $result;
+ }
+
+ /**
+ * Set Precision
+ *
+ * Some bitwise operations give different results depending on the precision being used. Examples include left
+ * shift, not, and rotates.
+ *
+ * @param int $bits
+ */
+ public function setPrecision($bits)
+ {
+ if ($bits < 1) {
+ $this->precision = -1;
+ $this->bitmask = false;
+
+ return;
+ }
+ $this->precision = $bits;
+ $this->bitmask = static::setBitmask($bits);
+
+ $temp = $this->normalize($this);
+ $this->value = $temp->value;
+ }
+
+ /**
+ * Get Precision
+ *
+ * Returns the precision if it exists, -1 if it doesn't
+ *
+ * @return int
+ */
+ public function getPrecision()
+ {
+ return $this->precision;
+ }
+
+ /**
+ * Set Bitmask
+ * @return static
+ * @param int $bits
+ * @see self::setPrecision()
+ */
+ protected static function setBitmask($bits)
+ {
+ return new static(chr((1 << ($bits & 0x7)) - 1) . str_repeat(chr(0xFF), $bits >> 3), 256);
+ }
+
+ /**
+ * Logical Not
+ *
+ * @return Engine|string
+ */
+ public function bitwise_not()
+ {
+ // calculuate "not" without regard to $this->precision
+ // (will always result in a smaller number. ie. ~1 isn't 1111 1110 - it's 0)
+ $temp = $this->toBytes();
+ if ($temp == '') {
+ return $this->normalize(static::$zero[static::class]);
+ }
+ $pre_msb = decbin(ord($temp[0]));
+ $temp = ~$temp;
+ $msb = decbin(ord($temp[0]));
+ if (strlen($msb) == 8) {
+ $msb = substr($msb, strpos($msb, '0'));
+ }
+ $temp[0] = chr(bindec($msb));
+
+ // see if we need to add extra leading 1's
+ $current_bits = strlen($pre_msb) + 8 * strlen($temp) - 8;
+ $new_bits = $this->precision - $current_bits;
+ if ($new_bits <= 0) {
+ return $this->normalize(new static($temp, 256));
+ }
+
+ // generate as many leading 1's as we need to.
+ $leading_ones = chr((1 << ($new_bits & 0x7)) - 1) . str_repeat(chr(0xFF), $new_bits >> 3);
+
+ self::base256_lshift($leading_ones, $current_bits);
+
+ $temp = str_pad($temp, strlen($leading_ones), chr(0), STR_PAD_LEFT);
+
+ return $this->normalize(new static($leading_ones | $temp, 256));
+ }
+
+ /**
+ * Logical Left Shift
+ *
+ * Shifts binary strings $shift bits, essentially multiplying by 2**$shift.
+ *
+ * @param string $x
+ * @param int $shift
+ * @return void
+ */
+ protected static function base256_lshift(&$x, $shift)
+ {
+ if ($shift == 0) {
+ return;
+ }
+
+ $num_bytes = $shift >> 3; // eg. floor($shift/8)
+ $shift &= 7; // eg. $shift % 8
+
+ $carry = 0;
+ for ($i = strlen($x) - 1; $i >= 0; --$i) {
+ $temp = ord($x[$i]) << $shift | $carry;
+ $x[$i] = chr($temp);
+ $carry = $temp >> 8;
+ }
+ $carry = ($carry != 0) ? chr($carry) : '';
+ $x = $carry . $x . str_repeat(chr(0), $num_bytes);
+ }
+
+ /**
+ * Logical Left Rotate
+ *
+ * Instead of the top x bits being dropped they're appended to the shifted bit string.
+ *
+ * @param int $shift
+ * @return Engine
+ */
+ public function bitwise_leftRotate($shift)
+ {
+ $bits = $this->toBytes();
+
+ if ($this->precision > 0) {
+ $precision = $this->precision;
+ if (static::FAST_BITWISE) {
+ $mask = $this->bitmask->toBytes();
+ } else {
+ $mask = $this->bitmask->subtract(new static(1));
+ $mask = $mask->toBytes();
+ }
+ } else {
+ $temp = ord($bits[0]);
+ for ($i = 0; $temp >> $i; ++$i) {
+ }
+ $precision = 8 * strlen($bits) - 8 + $i;
+ $mask = chr((1 << ($precision & 0x7)) - 1) . str_repeat(chr(0xFF), $precision >> 3);
+ }
+
+ if ($shift < 0) {
+ $shift += $precision;
+ }
+ $shift %= $precision;
+
+ if (!$shift) {
+ return clone $this;
+ }
+
+ $left = $this->bitwise_leftShift($shift);
+ $left = $left->bitwise_and(new static($mask, 256));
+ $right = $this->bitwise_rightShift($precision - $shift);
+ $result = static::FAST_BITWISE ? $left->bitwise_or($right) : $left->add($right);
+ return $this->normalize($result);
+ }
+
+ /**
+ * Logical Right Rotate
+ *
+ * Instead of the bottom x bits being dropped they're prepended to the shifted bit string.
+ *
+ * @param int $shift
+ * @return Engine
+ */
+ public function bitwise_rightRotate($shift)
+ {
+ return $this->bitwise_leftRotate(-$shift);
+ }
+
+ /**
+ * Returns the smallest and largest n-bit number
+ *
+ * @param int $bits
+ * @return array{min: static, max: static}
+ */
+ public static function minMaxBits($bits)
+ {
+ $bytes = $bits >> 3;
+ $min = str_repeat(chr(0), $bytes);
+ $max = str_repeat(chr(0xFF), $bytes);
+ $msb = $bits & 7;
+ if ($msb) {
+ $min = chr(1 << ($msb - 1)) . $min;
+ $max = chr((1 << $msb) - 1) . $max;
+ } else {
+ $min[0] = chr(0x80);
+ }
+ return [
+ 'min' => new static($min, 256),
+ 'max' => new static($max, 256)
+ ];
+ }
+
+ /**
+ * Return the size of a BigInteger in bits
+ *
+ * @return int
+ */
+ public function getLength()
+ {
+ return strlen($this->toBits());
+ }
+
+ /**
+ * Return the size of a BigInteger in bytes
+ *
+ * @return int
+ */
+ public function getLengthInBytes()
+ {
+ return (int) ceil($this->getLength() / 8);
+ }
+
+ /**
+ * Performs some pre-processing for powMod
+ *
+ * @param Engine $e
+ * @param Engine $n
+ * @return static|false
+ */
+ protected function powModOuter(Engine $e, Engine $n)
+ {
+ $n = $this->bitmask !== false && $this->bitmask->compare($n) < 0 ? $this->bitmask : $n->abs();
+
+ if ($e->compare(new static()) < 0) {
+ $e = $e->abs();
+
+ $temp = $this->modInverse($n);
+ if ($temp === false) {
+ return false;
+ }
+
+ return $this->normalize($temp->powModInner($e, $n));
+ }
+
+ if ($this->compare($n) > 0 || $this->isNegative()) {
+ list(, $temp) = $this->divide($n);
+ return $temp->powModInner($e, $n);
+ }
+
+ return $this->powModInner($e, $n);
+ }
+
+ /**
+ * Sliding Window k-ary Modular Exponentiation
+ *
+ * Based on {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=27 HAC 14.85} /
+ * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=210 MPM 7.7}. In a departure from those algorithims,
+ * however, this function performs a modular reduction after every multiplication and squaring operation.
+ * As such, this function has the same preconditions that the reductions being used do.
+ *
+ * @template T of Engine
+ * @param Engine $x
+ * @param Engine $e
+ * @param Engine $n
+ * @param class-string<T> $class
+ * @return T
+ */
+ protected static function slidingWindow(Engine $x, Engine $e, Engine $n, $class)
+ {
+ static $window_ranges = [7, 25, 81, 241, 673, 1793]; // from BigInteger.java's oddModPow function
+ //static $window_ranges = [0, 7, 36, 140, 450, 1303, 3529]; // from MPM 7.3.1
+
+ $e_bits = $e->toBits();
+ $e_length = strlen($e_bits);
+
+ // calculate the appropriate window size.
+ // $window_size == 3 if $window_ranges is between 25 and 81, for example.
+ for ($i = 0, $window_size = 1; $i < count($window_ranges) && $e_length > $window_ranges[$i]; ++$window_size, ++$i) {
+ }
+
+ $n_value = $n->value;
+
+ if (method_exists(static::class, 'generateCustomReduction')) {
+ static::generateCustomReduction($n, $class);
+ }
+
+ // precompute $this^0 through $this^$window_size
+ $powers = [];
+ $powers[1] = static::prepareReduce($x->value, $n_value, $class);
+ $powers[2] = static::squareReduce($powers[1], $n_value, $class);
+
+ // we do every other number since substr($e_bits, $i, $j+1) (see below) is supposed to end
+ // in a 1. ie. it's supposed to be odd.
+ $temp = 1 << ($window_size - 1);
+ for ($i = 1; $i < $temp; ++$i) {
+ $i2 = $i << 1;
+ $powers[$i2 + 1] = static::multiplyReduce($powers[$i2 - 1], $powers[2], $n_value, $class);
+ }
+
+ $result = new $class(1);
+ $result = static::prepareReduce($result->value, $n_value, $class);
+
+ for ($i = 0; $i < $e_length;) {
+ if (!$e_bits[$i]) {
+ $result = static::squareReduce($result, $n_value, $class);
+ ++$i;
+ } else {
+ for ($j = $window_size - 1; $j > 0; --$j) {
+ if (!empty($e_bits[$i + $j])) {
+ break;
+ }
+ }
+
+ // eg. the length of substr($e_bits, $i, $j + 1)
+ for ($k = 0; $k <= $j; ++$k) {
+ $result = static::squareReduce($result, $n_value, $class);
+ }
+
+ $result = static::multiplyReduce($result, $powers[bindec(substr($e_bits, $i, $j + 1))], $n_value, $class);
+
+ $i += $j + 1;
+ }
+ }
+
+ $temp = new $class();
+ $temp->value = static::reduce($result, $n_value, $class);
+
+ return $temp;
+ }
+
+ /**
+ * Generates a random number of a certain size
+ *
+ * Bit length is equal to $size
+ *
+ * @param int $size
+ * @return Engine
+ */
+ public static function random($size)
+ {
+ $minMax = static::minMaxBits($size);
+ $min = $minMax['min'];
+ $max = $minMax['max'];
+ return static::randomRange($min, $max);
+ }
+
+ /**
+ * Generates a random prime number of a certain size
+ *
+ * Bit length is equal to $size
+ *
+ * @param int $size
+ * @return Engine
+ */
+ public static function randomPrime($size)
+ {
+ $minMax = static::minMaxBits($size);
+ $min = $minMax['min'];
+ $max = $minMax['max'];
+ return static::randomRangePrime($min, $max);
+ }
+
+ /**
+ * Performs some pre-processing for randomRangePrime
+ *
+ * @param Engine $min
+ * @param Engine $max
+ * @return static|false
+ */
+ protected static function randomRangePrimeOuter(Engine $min, Engine $max)
+ {
+ $compare = $max->compare($min);
+
+ if (!$compare) {
+ return $min->isPrime() ? $min : false;
+ } elseif ($compare < 0) {
+ // if $min is bigger then $max, swap $min and $max
+ $temp = $max;
+ $max = $min;
+ $min = $temp;
+ }
+
+ $length = $max->getLength();
+ if ($length > 8196) {
+ throw new \RuntimeException("Generation of random prime numbers larger than 8196 has been disabled ($length)");
+ }
+
+ $x = static::randomRange($min, $max);
+
+ return static::randomRangePrimeInner($x, $min, $max);
+ }
+
+ /**
+ * Generate a random number between a range
+ *
+ * Returns a random number between $min and $max where $min and $max
+ * can be defined using one of the two methods:
+ *
+ * BigInteger::randomRange($min, $max)
+ * BigInteger::randomRange($max, $min)
+ *
+ * @param Engine $min
+ * @param Engine $max
+ * @return Engine
+ */
+ protected static function randomRangeHelper(Engine $min, Engine $max)
+ {
+ $compare = $max->compare($min);
+
+ if (!$compare) {
+ return $min;
+ } elseif ($compare < 0) {
+ // if $min is bigger then $max, swap $min and $max
+ $temp = $max;
+ $max = $min;
+ $min = $temp;
+ }
+
+ if (!isset(static::$one[static::class])) {
+ static::$one[static::class] = new static(1);
+ }
+
+ $max = $max->subtract($min->subtract(static::$one[static::class]));
+
+ $size = strlen(ltrim($max->toBytes(), chr(0)));
+
+ /*
+ doing $random % $max doesn't work because some numbers will be more likely to occur than others.
+ eg. if $max is 140 and $random's max is 255 then that'd mean both $random = 5 and $random = 145
+ would produce 5 whereas the only value of random that could produce 139 would be 139. ie.
+ not all numbers would be equally likely. some would be more likely than others.
+
+ creating a whole new random number until you find one that is within the range doesn't work
+ because, for sufficiently small ranges, the likelihood that you'd get a number within that range
+ would be pretty small. eg. with $random's max being 255 and if your $max being 1 the probability
+ would be pretty high that $random would be greater than $max.
+
+ phpseclib works around this using the technique described here:
+
+ http://crypto.stackexchange.com/questions/5708/creating-a-small-number-from-a-cryptographically-secure-random-string
+ */
+ $random_max = new static(chr(1) . str_repeat("\0", $size), 256);
+ $random = new static(Random::string($size), 256);
+
+ list($max_multiple) = $random_max->divide($max);
+ $max_multiple = $max_multiple->multiply($max);
+
+ while ($random->compare($max_multiple) >= 0) {
+ $random = $random->subtract($max_multiple);
+ $random_max = $random_max->subtract($max_multiple);
+ $random = $random->bitwise_leftShift(8);
+ $random = $random->add(new static(Random::string(1), 256));
+ $random_max = $random_max->bitwise_leftShift(8);
+ list($max_multiple) = $random_max->divide($max);
+ $max_multiple = $max_multiple->multiply($max);
+ }
+ list(, $random) = $random->divide($max);
+
+ return $random->add($min);
+ }
+
+ /**
+ * Performs some post-processing for randomRangePrime
+ *
+ * @param Engine $x
+ * @param Engine $min
+ * @param Engine $max
+ * @return static|false
+ */
+ protected static function randomRangePrimeInner(Engine $x, Engine $min, Engine $max)
+ {
+ if (!isset(static::$two[static::class])) {
+ static::$two[static::class] = new static('2');
+ }
+
+ $x->make_odd();
+ if ($x->compare($max) > 0) {
+ // if $x > $max then $max is even and if $min == $max then no prime number exists between the specified range
+ if ($min->equals($max)) {
+ return false;
+ }
+ $x = clone $min;
+ $x->make_odd();
+ }
+
+ $initial_x = clone $x;
+
+ while (true) {
+ if ($x->isPrime()) {
+ return $x;
+ }
+
+ $x = $x->add(static::$two[static::class]);
+
+ if ($x->compare($max) > 0) {
+ $x = clone $min;
+ if ($x->equals(static::$two[static::class])) {
+ return $x;
+ }
+ $x->make_odd();
+ }
+
+ if ($x->equals($initial_x)) {
+ return false;
+ }
+ }
+ }
+
+ /**
+ * Sets the $t parameter for primality testing
+ *
+ * @return int
+ */
+ protected function setupIsPrime()
+ {
+ $length = $this->getLengthInBytes();
+
+ // see HAC 4.49 "Note (controlling the error probability)"
+ // @codingStandardsIgnoreStart
+ if ($length >= 163) { $t = 2; } // floor(1300 / 8)
+ else if ($length >= 106) { $t = 3; } // floor( 850 / 8)
+ else if ($length >= 81 ) { $t = 4; } // floor( 650 / 8)
+ else if ($length >= 68 ) { $t = 5; } // floor( 550 / 8)
+ else if ($length >= 56 ) { $t = 6; } // floor( 450 / 8)
+ else if ($length >= 50 ) { $t = 7; } // floor( 400 / 8)
+ else if ($length >= 43 ) { $t = 8; } // floor( 350 / 8)
+ else if ($length >= 37 ) { $t = 9; } // floor( 300 / 8)
+ else if ($length >= 31 ) { $t = 12; } // floor( 250 / 8)
+ else if ($length >= 25 ) { $t = 15; } // floor( 200 / 8)
+ else if ($length >= 18 ) { $t = 18; } // floor( 150 / 8)
+ else { $t = 27; }
+ // @codingStandardsIgnoreEnd
+
+ return $t;
+ }
+
+ /**
+ * Tests Primality
+ *
+ * Uses the {@link http://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test Miller-Rabin primality test}.
+ * See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=8 HAC 4.24} for more info.
+ *
+ * @param int $t
+ * @return bool
+ */
+ protected function testPrimality($t)
+ {
+ if (!$this->testSmallPrimes()) {
+ return false;
+ }
+
+ $n = clone $this;
+ $n_1 = $n->subtract(static::$one[static::class]);
+ $n_2 = $n->subtract(static::$two[static::class]);
+
+ $r = clone $n_1;
+ $s = static::scan1divide($r);
+
+ for ($i = 0; $i < $t; ++$i) {
+ $a = static::randomRange(static::$two[static::class], $n_2);
+ $y = $a->modPow($r, $n);
+
+ if (!$y->equals(static::$one[static::class]) && !$y->equals($n_1)) {
+ for ($j = 1; $j < $s && !$y->equals($n_1); ++$j) {
+ $y = $y->modPow(static::$two[static::class], $n);
+ if ($y->equals(static::$one[static::class])) {
+ return false;
+ }
+ }
+
+ if (!$y->equals($n_1)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks a numer to see if it's prime
+ *
+ * Assuming the $t parameter is not set, this function has an error rate of 2**-80. The main motivation for the
+ * $t parameter is distributability. BigInteger::randomPrime() can be distributed across multiple pageloads
+ * on a website instead of just one.
+ *
+ * @param int|bool $t
+ * @return bool
+ */
+ public function isPrime($t = false)
+ {
+ // OpenSSL limits RSA keys to 16384 bits. The length of an RSA key is equal to the length of the modulo, which is
+ // produced by multiplying the primes p and q by one another. The largest number two 8196 bit primes can produce is
+ // a 16384 bit number so, basically, 8196 bit primes are the largest OpenSSL will generate and if that's the largest
+ // that it'll generate it also stands to reason that that's the largest you'll be able to test primality on
+ $length = $this->getLength();
+ if ($length > 8196) {
+ throw new \RuntimeException("Primality testing is not supported for numbers larger than 8196 bits ($length)");
+ }
+
+ if (!$t) {
+ $t = $this->setupIsPrime();
+ }
+ return $this->testPrimality($t);
+ }
+
+ /**
+ * Performs a few preliminary checks on root
+ *
+ * @param int $n
+ * @return Engine
+ */
+ protected function rootHelper($n)
+ {
+ if ($n < 1) {
+ return clone static::$zero[static::class];
+ } // we want positive exponents
+ if ($this->compare(static::$one[static::class]) < 0) {
+ return clone static::$zero[static::class];
+ } // we want positive numbers
+ if ($this->compare(static::$two[static::class]) < 0) {
+ return clone static::$one[static::class];
+ } // n-th root of 1 or 2 is 1
+
+ return $this->rootInner($n);
+ }
+
+ /**
+ * Calculates the nth root of a biginteger.
+ *
+ * Returns the nth root of a positive biginteger, where n defaults to 2
+ *
+ * {@internal This function is based off of {@link http://mathforum.org/library/drmath/view/52605.html this page} and {@link http://stackoverflow.com/questions/11242920/calculating-nth-root-with-bcmath-in-php this stackoverflow question}.}
+ *
+ * @param int $n
+ * @return Engine
+ */
+ protected function rootInner($n)
+ {
+ $n = new static($n);
+
+ // g is our guess number
+ $g = static::$two[static::class];
+ // while (g^n < num) g=g*2
+ while ($g->pow($n)->compare($this) < 0) {
+ $g = $g->multiply(static::$two[static::class]);
+ }
+ // if (g^n==num) num is a power of 2, we're lucky, end of job
+ // == 0 bccomp(bcpow($g, $n), $n->value)==0
+ if ($g->pow($n)->equals($this) > 0) {
+ $root = $g;
+ return $this->normalize($root);
+ }
+
+ // if we're here num wasn't a power of 2 :(
+ $og = $g; // og means original guess and here is our upper bound
+ $g = $g->divide(static::$two[static::class])[0]; // g is set to be our lower bound
+ $step = $og->subtract($g)->divide(static::$two[static::class])[0]; // step is the half of upper bound - lower bound
+ $g = $g->add($step); // we start at lower bound + step , basically in the middle of our interval
+
+ // while step>1
+
+ while ($step->compare(static::$one[static::class]) == 1) {
+ $guess = $g->pow($n);
+ $step = $step->divide(static::$two[static::class])[0];
+ $comp = $guess->compare($this); // compare our guess with real number
+ switch ($comp) {
+ case -1: // if guess is lower we add the new step
+ $g = $g->add($step);
+ break;
+ case 1: // if guess is higher we sub the new step
+ $g = $g->subtract($step);
+ break;
+ case 0: // if guess is exactly the num we're done, we return the value
+ $root = $g;
+ break 2;
+ }
+ }
+
+ if ($comp == 1) {
+ $g = $g->subtract($step);
+ }
+
+ // whatever happened, g is the closest guess we can make so return it
+ $root = $g;
+
+ return $this->normalize($root);
+ }
+
+ /**
+ * Calculates the nth root of a biginteger.
+ *
+ * @param int $n
+ * @return Engine
+ */
+ public function root($n = 2)
+ {
+ return $this->rootHelper($n);
+ }
+
+ /**
+ * Return the minimum BigInteger between an arbitrary number of BigIntegers.
+ *
+ * @param array $nums
+ * @return Engine
+ */
+ protected static function minHelper(array $nums)
+ {
+ if (count($nums) == 1) {
+ return $nums[0];
+ }
+ $min = $nums[0];
+ for ($i = 1; $i < count($nums); $i++) {
+ $min = $min->compare($nums[$i]) > 0 ? $nums[$i] : $min;
+ }
+ return $min;
+ }
+
+ /**
+ * Return the minimum BigInteger between an arbitrary number of BigIntegers.
+ *
+ * @param array $nums
+ * @return Engine
+ */
+ protected static function maxHelper(array $nums)
+ {
+ if (count($nums) == 1) {
+ return $nums[0];
+ }
+ $max = $nums[0];
+ for ($i = 1; $i < count($nums); $i++) {
+ $max = $max->compare($nums[$i]) < 0 ? $nums[$i] : $max;
+ }
+ return $max;
+ }
+
+ /**
+ * Create Recurring Modulo Function
+ *
+ * Sometimes it may be desirable to do repeated modulos with the same number outside of
+ * modular exponentiation
+ *
+ * @return callable
+ */
+ public function createRecurringModuloFunction()
+ {
+ $class = static::class;
+
+ $fqengine = !method_exists(static::$modexpEngine[static::class], 'reduce') ?
+ '\\phpseclib3\\Math\\BigInteger\\Engines\\' . static::ENGINE_DIR . '\\DefaultEngine' :
+ static::$modexpEngine[static::class];
+ if (method_exists($fqengine, 'generateCustomReduction')) {
+ $func = $fqengine::generateCustomReduction($this, static::class);
+ return eval('return function(' . static::class . ' $x) use ($func, $class) {
+ $r = new $class();
+ $r->value = $func($x->value);
+ return $r;
+ };');
+ }
+ $n = $this->value;
+ return eval('return function(' . static::class . ' $x) use ($n, $fqengine, $class) {
+ $r = new $class();
+ $r->value = $fqengine::reduce($x->value, $n, $class);
+ return $r;
+ };');
+ }
+
+ /**
+ * Calculates the greatest common divisor and Bezout's identity.
+ *
+ * @param Engine $n
+ * @return array{gcd: Engine, x: Engine, y: Engine}
+ */
+ protected function extendedGCDHelper(Engine $n)
+ {
+ $u = clone $this;
+ $v = clone $n;
+
+ $one = new static(1);
+ $zero = new static();
+
+ $a = clone $one;
+ $b = clone $zero;
+ $c = clone $zero;
+ $d = clone $one;
+
+ while (!$v->equals($zero)) {
+ list($q) = $u->divide($v);
+
+ $temp = $u;
+ $u = $v;
+ $v = $temp->subtract($v->multiply($q));
+
+ $temp = $a;
+ $a = $c;
+ $c = $temp->subtract($a->multiply($q));
+
+ $temp = $b;
+ $b = $d;
+ $d = $temp->subtract($b->multiply($q));
+ }
+
+ return [
+ 'gcd' => $u,
+ 'x' => $a,
+ 'y' => $b
+ ];
+ }
+
+ /**
+ * Bitwise Split
+ *
+ * Splits BigInteger's into chunks of $split bits
+ *
+ * @param int $split
+ * @return Engine[]
+ */
+ public function bitwise_split($split)
+ {
+ if ($split < 1) {
+ throw new \RuntimeException('Offset must be greater than 1');
+ }
+
+ $mask = static::$one[static::class]->bitwise_leftShift($split)->subtract(static::$one[static::class]);
+
+ $num = clone $this;
+
+ $vals = [];
+ while (!$num->equals(static::$zero[static::class])) {
+ $vals[] = $num->bitwise_and($mask);
+ $num = $num->bitwise_rightShift($split);
+ }
+
+ return array_reverse($vals);
+ }
+
+ /**
+ * Logical And
+ *
+ * @param Engine $x
+ * @return Engine
+ */
+ protected function bitwiseAndHelper(Engine $x)
+ {
+ $left = $this->toBytes(true);
+ $right = $x->toBytes(true);
+
+ $length = max(strlen($left), strlen($right));
+
+ $left = str_pad($left, $length, chr(0), STR_PAD_LEFT);
+ $right = str_pad($right, $length, chr(0), STR_PAD_LEFT);
+
+ return $this->normalize(new static($left & $right, -256));
+ }
+
+ /**
+ * Logical Or
+ *
+ * @param Engine $x
+ * @return Engine
+ */
+ protected function bitwiseOrHelper(Engine $x)
+ {
+ $left = $this->toBytes(true);
+ $right = $x->toBytes(true);
+
+ $length = max(strlen($left), strlen($right));
+
+ $left = str_pad($left, $length, chr(0), STR_PAD_LEFT);
+ $right = str_pad($right, $length, chr(0), STR_PAD_LEFT);
+
+ return $this->normalize(new static($left | $right, -256));
+ }
+
+ /**
+ * Logical Exclusive Or
+ *
+ * @param Engine $x
+ * @return Engine
+ */
+ protected function bitwiseXorHelper(Engine $x)
+ {
+ $left = $this->toBytes(true);
+ $right = $x->toBytes(true);
+
+ $length = max(strlen($left), strlen($right));
+
+
+ $left = str_pad($left, $length, chr(0), STR_PAD_LEFT);
+ $right = str_pad($right, $length, chr(0), STR_PAD_LEFT);
+ return $this->normalize(new static($left ^ $right, -256));
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/GMP.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/GMP.php
new file mode 100644
index 000000000..0db43ae63
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/GMP.php
@@ -0,0 +1,697 @@
+<?php
+
+/**
+ * GMP BigInteger Engine
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Math\BigInteger\Engines;
+
+use phpseclib3\Exception\BadConfigurationException;
+
+/**
+ * GMP Engine.
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class GMP extends Engine
+{
+ /**
+ * Can Bitwise operations be done fast?
+ *
+ * @see parent::bitwise_leftRotate()
+ * @see parent::bitwise_rightRotate()
+ */
+ const FAST_BITWISE = true;
+
+ /**
+ * Engine Directory
+ *
+ * @see parent::setModExpEngine
+ */
+ const ENGINE_DIR = 'GMP';
+
+ /**
+ * Test for engine validity
+ *
+ * @return bool
+ * @see parent::__construct()
+ */
+ public static function isValidEngine()
+ {
+ return extension_loaded('gmp');
+ }
+
+ /**
+ * Default constructor
+ *
+ * @param mixed $x integer Base-10 number or base-$base number if $base set.
+ * @param int $base
+ * @see parent::__construct()
+ */
+ public function __construct($x = 0, $base = 10)
+ {
+ if (!isset(static::$isValidEngine[static::class])) {
+ static::$isValidEngine[static::class] = self::isValidEngine();
+ }
+ if (!static::$isValidEngine[static::class]) {
+ throw new BadConfigurationException('GMP is not setup correctly on this system');
+ }
+
+ if ($x instanceof \GMP) {
+ $this->value = $x;
+ return;
+ }
+
+ $this->value = gmp_init(0);
+
+ parent::__construct($x, $base);
+ }
+
+ /**
+ * Initialize a GMP BigInteger Engine instance
+ *
+ * @param int $base
+ * @see parent::__construct()
+ */
+ protected function initialize($base)
+ {
+ switch (abs($base)) {
+ case 256:
+ $this->value = gmp_import($this->value);
+ if ($this->is_negative) {
+ $this->value = -$this->value;
+ }
+ break;
+ case 16:
+ $temp = $this->is_negative ? '-0x' . $this->value : '0x' . $this->value;
+ $this->value = gmp_init($temp);
+ break;
+ case 10:
+ $this->value = gmp_init(isset($this->value) ? $this->value : '0');
+ }
+ }
+
+ /**
+ * Converts a BigInteger to a base-10 number.
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return (string)$this->value;
+ }
+
+ /**
+ * Converts a BigInteger to a bit string (eg. base-2).
+ *
+ * Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're
+ * saved as two's compliment.
+ *
+ * @param bool $twos_compliment
+ * @return string
+ */
+ public function toBits($twos_compliment = false)
+ {
+ $hex = $this->toHex($twos_compliment);
+
+ $bits = gmp_strval(gmp_init($hex, 16), 2);
+
+ if ($this->precision > 0) {
+ $bits = substr($bits, -$this->precision);
+ }
+
+ if ($twos_compliment && $this->compare(new static()) > 0 && $this->precision <= 0) {
+ return '0' . $bits;
+ }
+
+ return $bits;
+ }
+
+ /**
+ * Converts a BigInteger to a byte string (eg. base-256).
+ *
+ * @param bool $twos_compliment
+ * @return string
+ */
+ public function toBytes($twos_compliment = false)
+ {
+ if ($twos_compliment) {
+ return $this->toBytesHelper();
+ }
+
+ if (gmp_cmp($this->value, gmp_init(0)) == 0) {
+ return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : '';
+ }
+
+ $temp = gmp_export($this->value);
+
+ return $this->precision > 0 ?
+ substr(str_pad($temp, $this->precision >> 3, chr(0), STR_PAD_LEFT), -($this->precision >> 3)) :
+ ltrim($temp, chr(0));
+ }
+
+ /**
+ * Adds two BigIntegers.
+ *
+ * @param GMP $y
+ * @return GMP
+ */
+ public function add(GMP $y)
+ {
+ $temp = new self();
+ $temp->value = $this->value + $y->value;
+
+ return $this->normalize($temp);
+ }
+
+ /**
+ * Subtracts two BigIntegers.
+ *
+ * @param GMP $y
+ * @return GMP
+ */
+ public function subtract(GMP $y)
+ {
+ $temp = new self();
+ $temp->value = $this->value - $y->value;
+
+ return $this->normalize($temp);
+ }
+
+ /**
+ * Multiplies two BigIntegers.
+ *
+ * @param GMP $x
+ * @return GMP
+ */
+ public function multiply(GMP $x)
+ {
+ $temp = new self();
+ $temp->value = $this->value * $x->value;
+
+ return $this->normalize($temp);
+ }
+
+ /**
+ * Divides two BigIntegers.
+ *
+ * Returns an array whose first element contains the quotient and whose second element contains the
+ * "common residue". If the remainder would be positive, the "common residue" and the remainder are the
+ * same. If the remainder would be negative, the "common residue" is equal to the sum of the remainder
+ * and the divisor (basically, the "common residue" is the first positive modulo).
+ *
+ * @param GMP $y
+ * @return array{GMP, GMP}
+ */
+ public function divide(GMP $y)
+ {
+ $quotient = new self();
+ $remainder = new self();
+
+ list($quotient->value, $remainder->value) = gmp_div_qr($this->value, $y->value);
+
+ if (gmp_sign($remainder->value) < 0) {
+ $remainder->value = $remainder->value + gmp_abs($y->value);
+ }
+
+ return [$this->normalize($quotient), $this->normalize($remainder)];
+ }
+
+ /**
+ * Compares two numbers.
+ *
+ * Although one might think !$x->compare($y) means $x != $y, it, in fact, means the opposite. The reason for this
+ * is demonstrated thusly:
+ *
+ * $x > $y: $x->compare($y) > 0
+ * $x < $y: $x->compare($y) < 0
+ * $x == $y: $x->compare($y) == 0
+ *
+ * Note how the same comparison operator is used. If you want to test for equality, use $x->equals($y).
+ *
+ * {@internal Could return $this->subtract($x), but that's not as fast as what we do do.}
+ *
+ * @param GMP $y
+ * @return int in case < 0 if $this is less than $y; > 0 if $this is greater than $y, and 0 if they are equal.
+ * @see self::equals()
+ */
+ public function compare(GMP $y)
+ {
+ $r = gmp_cmp($this->value, $y->value);
+ if ($r < -1) {
+ $r = -1;
+ }
+ if ($r > 1) {
+ $r = 1;
+ }
+ return $r;
+ }
+
+ /**
+ * Tests the equality of two numbers.
+ *
+ * If you need to see if one number is greater than or less than another number, use BigInteger::compare()
+ *
+ * @param GMP $x
+ * @return bool
+ */
+ public function equals(GMP $x)
+ {
+ return $this->value == $x->value;
+ }
+
+ /**
+ * Calculates modular inverses.
+ *
+ * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses.
+ *
+ * @param GMP $n
+ * @return false|GMP
+ */
+ public function modInverse(GMP $n)
+ {
+ $temp = new self();
+ $temp->value = gmp_invert($this->value, $n->value);
+
+ return $temp->value === false ? false : $this->normalize($temp);
+ }
+
+ /**
+ * Calculates the greatest common divisor and Bezout's identity.
+ *
+ * Say you have 693 and 609. The GCD is 21. Bezout's identity states that there exist integers x and y such that
+ * 693*x + 609*y == 21. In point of fact, there are actually an infinite number of x and y combinations and which
+ * combination is returned is dependent upon which mode is in use. See
+ * {@link http://en.wikipedia.org/wiki/B%C3%A9zout%27s_identity Bezout's identity - Wikipedia} for more information.
+ *
+ * @param GMP $n
+ * @return GMP[]
+ */
+ public function extendedGCD(GMP $n)
+ {
+ $extended = gmp_gcdext($this->value, $n->value);
+ $g = $extended['g'];
+ $s = $extended['s'];
+ $t = $extended['t'];
+
+ return [
+ 'gcd' => $this->normalize(new self($g)),
+ 'x' => $this->normalize(new self($s)),
+ 'y' => $this->normalize(new self($t))
+ ];
+ }
+
+ /**
+ * Calculates the greatest common divisor
+ *
+ * Say you have 693 and 609. The GCD is 21.
+ *
+ * @param GMP $n
+ * @return GMP
+ */
+ public function gcd(GMP $n)
+ {
+ $r = gmp_gcd($this->value, $n->value);
+ return $this->normalize(new self($r));
+ }
+
+ /**
+ * Absolute value.
+ *
+ * @return GMP
+ */
+ public function abs()
+ {
+ $temp = new self();
+ $temp->value = gmp_abs($this->value);
+
+ return $temp;
+ }
+
+ /**
+ * Logical And
+ *
+ * @param GMP $x
+ * @return GMP
+ */
+ public function bitwise_and(GMP $x)
+ {
+ $temp = new self();
+ $temp->value = $this->value & $x->value;
+
+ return $this->normalize($temp);
+ }
+
+ /**
+ * Logical Or
+ *
+ * @param GMP $x
+ * @return GMP
+ */
+ public function bitwise_or(GMP $x)
+ {
+ $temp = new self();
+ $temp->value = $this->value | $x->value;
+
+ return $this->normalize($temp);
+ }
+
+ /**
+ * Logical Exclusive Or
+ *
+ * @param GMP $x
+ * @return GMP
+ */
+ public function bitwise_xor(GMP $x)
+ {
+ $temp = new self();
+ $temp->value = $this->value ^ $x->value;
+
+ return $this->normalize($temp);
+ }
+
+ /**
+ * Logical Right Shift
+ *
+ * Shifts BigInteger's by $shift bits, effectively dividing by 2**$shift.
+ *
+ * @param int $shift
+ * @return GMP
+ */
+ public function bitwise_rightShift($shift)
+ {
+ // 0xFFFFFFFF >> 2 == -1 (on 32-bit systems)
+ // gmp_init('0xFFFFFFFF') >> 2 == gmp_init('0x3FFFFFFF')
+
+ $temp = new self();
+ $temp->value = $this->value >> $shift;
+
+ return $this->normalize($temp);
+ }
+
+ /**
+ * Logical Left Shift
+ *
+ * Shifts BigInteger's by $shift bits, effectively multiplying by 2**$shift.
+ *
+ * @param int $shift
+ * @return GMP
+ */
+ public function bitwise_leftShift($shift)
+ {
+ $temp = new self();
+ $temp->value = $this->value << $shift;
+
+ return $this->normalize($temp);
+ }
+
+ /**
+ * Performs modular exponentiation.
+ *
+ * @param GMP $e
+ * @param GMP $n
+ * @return GMP
+ */
+ public function modPow(GMP $e, GMP $n)
+ {
+ return $this->powModOuter($e, $n);
+ }
+
+ /**
+ * Performs modular exponentiation.
+ *
+ * Alias for modPow().
+ *
+ * @param GMP $e
+ * @param GMP $n
+ * @return GMP
+ */
+ public function powMod(GMP $e, GMP $n)
+ {
+ return $this->powModOuter($e, $n);
+ }
+
+ /**
+ * Performs modular exponentiation.
+ *
+ * @param GMP $e
+ * @param GMP $n
+ * @return GMP
+ */
+ protected function powModInner(GMP $e, GMP $n)
+ {
+ $class = static::$modexpEngine[static::class];
+ return $class::powModHelper($this, $e, $n);
+ }
+
+ /**
+ * Normalize
+ *
+ * Removes leading zeros and truncates (if necessary) to maintain the appropriate precision
+ *
+ * @param GMP $result
+ * @return GMP
+ */
+ protected function normalize(GMP $result)
+ {
+ $result->precision = $this->precision;
+ $result->bitmask = $this->bitmask;
+
+ if ($result->bitmask !== false) {
+ $flip = $result->value < 0;
+ if ($flip) {
+ $result->value = -$result->value;
+ }
+ $result->value = $result->value & $result->bitmask->value;
+ if ($flip) {
+ $result->value = -$result->value;
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Performs some post-processing for randomRangePrime
+ *
+ * @param Engine $x
+ * @param Engine $min
+ * @param Engine $max
+ * @return GMP
+ */
+ protected static function randomRangePrimeInner(Engine $x, Engine $min, Engine $max)
+ {
+ $p = gmp_nextprime($x->value);
+
+ if ($p <= $max->value) {
+ return new self($p);
+ }
+
+ if ($min->value != $x->value) {
+ $x = new self($x->value - 1);
+ }
+
+ return self::randomRangePrime($min, $x);
+ }
+
+ /**
+ * Generate a random prime number between a range
+ *
+ * If there's not a prime within the given range, false will be returned.
+ *
+ * @param GMP $min
+ * @param GMP $max
+ * @return false|GMP
+ */
+ public static function randomRangePrime(GMP $min, GMP $max)
+ {
+ return self::randomRangePrimeOuter($min, $max);
+ }
+
+ /**
+ * Generate a random number between a range
+ *
+ * Returns a random number between $min and $max where $min and $max
+ * can be defined using one of the two methods:
+ *
+ * BigInteger::randomRange($min, $max)
+ * BigInteger::randomRange($max, $min)
+ *
+ * @param GMP $min
+ * @param GMP $max
+ * @return GMP
+ */
+ public static function randomRange(GMP $min, GMP $max)
+ {
+ return self::randomRangeHelper($min, $max);
+ }
+
+ /**
+ * Make the current number odd
+ *
+ * If the current number is odd it'll be unchanged. If it's even, one will be added to it.
+ *
+ * @see self::randomPrime()
+ */
+ protected function make_odd()
+ {
+ gmp_setbit($this->value, 0);
+ }
+
+ /**
+ * Tests Primality
+ *
+ * @param int $t
+ * @return bool
+ */
+ protected function testPrimality($t)
+ {
+ return gmp_prob_prime($this->value, $t) != 0;
+ }
+
+ /**
+ * Calculates the nth root of a biginteger.
+ *
+ * Returns the nth root of a positive biginteger, where n defaults to 2
+ *
+ * @param int $n
+ * @return GMP
+ */
+ protected function rootInner($n)
+ {
+ $root = new self();
+ $root->value = gmp_root($this->value, $n);
+ return $this->normalize($root);
+ }
+
+ /**
+ * Performs exponentiation.
+ *
+ * @param GMP $n
+ * @return GMP
+ */
+ public function pow(GMP $n)
+ {
+ $temp = new self();
+ $temp->value = $this->value ** $n->value;
+
+ return $this->normalize($temp);
+ }
+
+ /**
+ * Return the minimum BigInteger between an arbitrary number of BigIntegers.
+ *
+ * @param GMP ...$nums
+ * @return GMP
+ */
+ public static function min(GMP ...$nums)
+ {
+ return self::minHelper($nums);
+ }
+
+ /**
+ * Return the maximum BigInteger between an arbitrary number of BigIntegers.
+ *
+ * @param GMP ...$nums
+ * @return GMP
+ */
+ public static function max(GMP ...$nums)
+ {
+ return self::maxHelper($nums);
+ }
+
+ /**
+ * Tests BigInteger to see if it is between two integers, inclusive
+ *
+ * @param GMP $min
+ * @param GMP $max
+ * @return bool
+ */
+ public function between(GMP $min, GMP $max)
+ {
+ return $this->compare($min) >= 0 && $this->compare($max) <= 0;
+ }
+
+ /**
+ * Create Recurring Modulo Function
+ *
+ * Sometimes it may be desirable to do repeated modulos with the same number outside of
+ * modular exponentiation
+ *
+ * @return callable
+ */
+ public function createRecurringModuloFunction()
+ {
+ $temp = $this->value;
+ return function (GMP $x) use ($temp) {
+ return new GMP($x->value % $temp);
+ };
+ }
+
+ /**
+ * Scan for 1 and right shift by that amount
+ *
+ * ie. $s = gmp_scan1($n, 0) and $r = gmp_div_q($n, gmp_pow(gmp_init('2'), $s));
+ *
+ * @param GMP $r
+ * @return int
+ */
+ public static function scan1divide(GMP $r)
+ {
+ $s = gmp_scan1($r->value, 0);
+ $r->value >>= $s;
+ return $s;
+ }
+
+ /**
+ * Is Odd?
+ *
+ * @return bool
+ */
+ public function isOdd()
+ {
+ return gmp_testbit($this->value, 0);
+ }
+
+ /**
+ * Tests if a bit is set
+ *
+ * @return bool
+ */
+ public function testBit($x)
+ {
+ return gmp_testbit($this->value, $x);
+ }
+
+ /**
+ * Is Negative?
+ *
+ * @return bool
+ */
+ public function isNegative()
+ {
+ return gmp_sign($this->value) == -1;
+ }
+
+ /**
+ * Negate
+ *
+ * Given $k, returns -$k
+ *
+ * @return GMP
+ */
+ public function negate()
+ {
+ $temp = clone $this;
+ $temp->value = -$this->value;
+
+ return $temp;
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/GMP/DefaultEngine.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/GMP/DefaultEngine.php
new file mode 100644
index 000000000..bc219fbee
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/GMP/DefaultEngine.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * GMP Modular Exponentiation Engine
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Math\BigInteger\Engines\GMP;
+
+use phpseclib3\Math\BigInteger\Engines\GMP;
+
+/**
+ * GMP Modular Exponentiation Engine
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class DefaultEngine extends GMP
+{
+ /**
+ * Performs modular exponentiation.
+ *
+ * @param GMP $x
+ * @param GMP $e
+ * @param GMP $n
+ * @return GMP
+ */
+ protected static function powModHelper(GMP $x, GMP $e, GMP $n)
+ {
+ $temp = new GMP();
+ $temp->value = gmp_powm($x->value, $e->value, $n->value);
+
+ return $x->normalize($temp);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/OpenSSL.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/OpenSSL.php
new file mode 100644
index 000000000..e33a9f196
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/OpenSSL.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * OpenSSL Modular Exponentiation Engine
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Math\BigInteger\Engines;
+
+use phpseclib3\Crypt\RSA\Formats\Keys\PKCS8;
+use phpseclib3\Math\BigInteger;
+
+/**
+ * OpenSSL Modular Exponentiation Engine
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class OpenSSL
+{
+ /**
+ * Test for engine validity
+ *
+ * @return bool
+ */
+ public static function isValidEngine()
+ {
+ return extension_loaded('openssl') && static::class != __CLASS__;
+ }
+
+ /**
+ * Performs modular exponentiation.
+ *
+ * @param Engine $x
+ * @param Engine $e
+ * @param Engine $n
+ * @return Engine
+ */
+ public static function powModHelper(Engine $x, Engine $e, Engine $n)
+ {
+ if ($n->getLengthInBytes() < 31 || $n->getLengthInBytes() > 16384) {
+ throw new \OutOfRangeException('Only modulo between 31 and 16384 bits are accepted');
+ }
+
+ $key = PKCS8::savePublicKey(
+ new BigInteger($n),
+ new BigInteger($e)
+ );
+
+ $plaintext = str_pad($x->toBytes(), $n->getLengthInBytes(), "\0", STR_PAD_LEFT);
+
+ // this is easily prone to failure. if the modulo is a multiple of 2 or 3 or whatever it
+ // won't work and you'll get a "failure: error:0906D06C:PEM routines:PEM_read_bio:no start line"
+ // error. i suppose, for even numbers, we could do what PHP\Montgomery.php does, but then what
+ // about odd numbers divisible by 3, by 5, etc?
+ if (!openssl_public_encrypt($plaintext, $result, $key, OPENSSL_NO_PADDING)) {
+ throw new \UnexpectedValueException(openssl_error_string());
+ }
+
+ $class = get_class($x);
+ return new $class($result, 256);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP.php
new file mode 100644
index 000000000..de556a3b2
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP.php
@@ -0,0 +1,1360 @@
+<?php
+
+/**
+ * Pure-PHP BigInteger Engine
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Math\BigInteger\Engines;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Exception\BadConfigurationException;
+
+/**
+ * Pure-PHP Engine.
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PHP extends Engine
+{
+ /**#@+
+ * Array constants
+ *
+ * Rather than create a thousands and thousands of new BigInteger objects in repeated function calls to add() and
+ * multiply() or whatever, we'll just work directly on arrays, taking them in as parameters and returning them.
+ *
+ */
+ /**
+ * $result[self::VALUE] contains the value.
+ */
+ const VALUE = 0;
+ /**
+ * $result[self::SIGN] contains the sign.
+ */
+ const SIGN = 1;
+ /**#@-*/
+
+ /**
+ * Karatsuba Cutoff
+ *
+ * At what point do we switch between Karatsuba multiplication and schoolbook long multiplication?
+ *
+ */
+ const KARATSUBA_CUTOFF = 25;
+
+ /**
+ * Can Bitwise operations be done fast?
+ *
+ * @see parent::bitwise_leftRotate()
+ * @see parent::bitwise_rightRotate()
+ */
+ const FAST_BITWISE = true;
+
+ /**
+ * Engine Directory
+ *
+ * @see parent::setModExpEngine
+ */
+ const ENGINE_DIR = 'PHP';
+
+ /**
+ * Default constructor
+ *
+ * @param mixed $x integer Base-10 number or base-$base number if $base set.
+ * @param int $base
+ * @return PHP
+ * @see parent::__construct()
+ */
+ public function __construct($x = 0, $base = 10)
+ {
+ if (!isset(static::$isValidEngine[static::class])) {
+ static::$isValidEngine[static::class] = static::isValidEngine();
+ }
+ if (!static::$isValidEngine[static::class]) {
+ throw new BadConfigurationException(static::class . ' is not setup correctly on this system');
+ }
+
+ $this->value = [];
+ parent::__construct($x, $base);
+ }
+
+ /**
+ * Initialize a PHP BigInteger Engine instance
+ *
+ * @param int $base
+ * @see parent::__construct()
+ */
+ protected function initialize($base)
+ {
+ switch (abs($base)) {
+ case 16:
+ $x = (strlen($this->value) & 1) ? '0' . $this->value : $this->value;
+ $temp = new static(Strings::hex2bin($x), 256);
+ $this->value = $temp->value;
+ break;
+ case 10:
+ $temp = new static();
+
+ $multiplier = new static();
+ $multiplier->value = [static::MAX10];
+
+ $x = $this->value;
+
+ if ($x[0] == '-') {
+ $this->is_negative = true;
+ $x = substr($x, 1);
+ }
+
+ $x = str_pad(
+ $x,
+ strlen($x) + ((static::MAX10LEN - 1) * strlen($x)) % static::MAX10LEN,
+ 0,
+ STR_PAD_LEFT
+ );
+ while (strlen($x)) {
+ $temp = $temp->multiply($multiplier);
+ $temp = $temp->add(new static($this->int2bytes(substr($x, 0, static::MAX10LEN)), 256));
+ $x = substr($x, static::MAX10LEN);
+ }
+
+ $this->value = $temp->value;
+ }
+ }
+
+ /**
+ * Pads strings so that unpack may be used on them
+ *
+ * @param string $str
+ * @return string
+ */
+ protected function pad($str)
+ {
+ $length = strlen($str);
+
+ $pad = 4 - (strlen($str) % 4);
+
+ return str_pad($str, $length + $pad, "\0", STR_PAD_LEFT);
+ }
+
+ /**
+ * Converts a BigInteger to a base-10 number.
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ if (!count($this->value)) {
+ return '0';
+ }
+
+ $temp = clone $this;
+ $temp->bitmask = false;
+ $temp->is_negative = false;
+
+ $divisor = new static();
+ $divisor->value = [static::MAX10];
+ $result = '';
+ while (count($temp->value)) {
+ list($temp, $mod) = $temp->divide($divisor);
+ $result = str_pad(
+ isset($mod->value[0]) ? $mod->value[0] : '',
+ static::MAX10LEN,
+ '0',
+ STR_PAD_LEFT
+ ) . $result;
+ }
+ $result = ltrim($result, '0');
+ if (empty($result)) {
+ $result = '0';
+ }
+
+ if ($this->is_negative) {
+ $result = '-' . $result;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Converts a BigInteger to a byte string (eg. base-256).
+ *
+ * @param bool $twos_compliment
+ * @return string
+ */
+ public function toBytes($twos_compliment = false)
+ {
+ if ($twos_compliment) {
+ return $this->toBytesHelper();
+ }
+
+ if (!count($this->value)) {
+ return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : '';
+ }
+
+ $result = $this->bitwise_small_split(8);
+ $result = implode('', array_map('chr', $result));
+
+ return $this->precision > 0 ?
+ str_pad(
+ substr($result, -(($this->precision + 7) >> 3)),
+ ($this->precision + 7) >> 3,
+ chr(0),
+ STR_PAD_LEFT
+ ) :
+ $result;
+ }
+
+ /**
+ * Performs addition.
+ *
+ * @param array $x_value
+ * @param bool $x_negative
+ * @param array $y_value
+ * @param bool $y_negative
+ * @return array
+ */
+ protected static function addHelper(array $x_value, $x_negative, array $y_value, $y_negative)
+ {
+ $x_size = count($x_value);
+ $y_size = count($y_value);
+
+ if ($x_size == 0) {
+ return [
+ self::VALUE => $y_value,
+ self::SIGN => $y_negative
+ ];
+ } elseif ($y_size == 0) {
+ return [
+ self::VALUE => $x_value,
+ self::SIGN => $x_negative
+ ];
+ }
+
+ // subtract, if appropriate
+ if ($x_negative != $y_negative) {
+ if ($x_value == $y_value) {
+ return [
+ self::VALUE => [],
+ self::SIGN => false
+ ];
+ }
+
+ $temp = self::subtractHelper($x_value, false, $y_value, false);
+ $temp[self::SIGN] = self::compareHelper($x_value, false, $y_value, false) > 0 ?
+ $x_negative : $y_negative;
+
+ return $temp;
+ }
+
+ if ($x_size < $y_size) {
+ $size = $x_size;
+ $value = $y_value;
+ } else {
+ $size = $y_size;
+ $value = $x_value;
+ }
+
+ $value[count($value)] = 0; // just in case the carry adds an extra digit
+
+ $carry = 0;
+ for ($i = 0, $j = 1; $j < $size; $i += 2, $j += 2) {
+ //$sum = $x_value[$j] * static::BASE_FULL + $x_value[$i] + $y_value[$j] * static::BASE_FULL + $y_value[$i] + $carry;
+ $sum = ($x_value[$j] + $y_value[$j]) * static::BASE_FULL + $x_value[$i] + $y_value[$i] + $carry;
+ $carry = $sum >= static::MAX_DIGIT2; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1
+ $sum = $carry ? $sum - static::MAX_DIGIT2 : $sum;
+
+ $temp = static::BASE === 26 ? intval($sum / 0x4000000) : ($sum >> 31);
+
+ $value[$i] = (int)($sum - static::BASE_FULL * $temp); // eg. a faster alternative to fmod($sum, 0x4000000)
+ $value[$j] = $temp;
+ }
+
+ if ($j == $size) { // ie. if $y_size is odd
+ $sum = $x_value[$i] + $y_value[$i] + $carry;
+ $carry = $sum >= static::BASE_FULL;
+ $value[$i] = $carry ? $sum - static::BASE_FULL : $sum;
+ ++$i; // ie. let $i = $j since we've just done $value[$i]
+ }
+
+ if ($carry) {
+ for (; $value[$i] == static::MAX_DIGIT; ++$i) {
+ $value[$i] = 0;
+ }
+ ++$value[$i];
+ }
+
+ return [
+ self::VALUE => self::trim($value),
+ self::SIGN => $x_negative
+ ];
+ }
+
+ /**
+ * Performs subtraction.
+ *
+ * @param array $x_value
+ * @param bool $x_negative
+ * @param array $y_value
+ * @param bool $y_negative
+ * @return array
+ */
+ public static function subtractHelper(array $x_value, $x_negative, array $y_value, $y_negative)
+ {
+ $x_size = count($x_value);
+ $y_size = count($y_value);
+
+ if ($x_size == 0) {
+ return [
+ self::VALUE => $y_value,
+ self::SIGN => !$y_negative
+ ];
+ } elseif ($y_size == 0) {
+ return [
+ self::VALUE => $x_value,
+ self::SIGN => $x_negative
+ ];
+ }
+
+ // add, if appropriate (ie. -$x - +$y or +$x - -$y)
+ if ($x_negative != $y_negative) {
+ $temp = self::addHelper($x_value, false, $y_value, false);
+ $temp[self::SIGN] = $x_negative;
+
+ return $temp;
+ }
+
+ $diff = self::compareHelper($x_value, $x_negative, $y_value, $y_negative);
+
+ if (!$diff) {
+ return [
+ self::VALUE => [],
+ self::SIGN => false
+ ];
+ }
+
+ // switch $x and $y around, if appropriate.
+ if ((!$x_negative && $diff < 0) || ($x_negative && $diff > 0)) {
+ $temp = $x_value;
+ $x_value = $y_value;
+ $y_value = $temp;
+
+ $x_negative = !$x_negative;
+
+ $x_size = count($x_value);
+ $y_size = count($y_value);
+ }
+
+ // at this point, $x_value should be at least as big as - if not bigger than - $y_value
+
+ $carry = 0;
+ for ($i = 0, $j = 1; $j < $y_size; $i += 2, $j += 2) {
+ $sum = ($x_value[$j] - $y_value[$j]) * static::BASE_FULL + $x_value[$i] - $y_value[$i] - $carry;
+
+ $carry = $sum < 0; // eg. floor($sum / 2**52); only possible values (in any base) are 0 and 1
+ $sum = $carry ? $sum + static::MAX_DIGIT2 : $sum;
+
+ $temp = static::BASE === 26 ? intval($sum / 0x4000000) : ($sum >> 31);
+
+ $x_value[$i] = (int)($sum - static::BASE_FULL * $temp);
+ $x_value[$j] = $temp;
+ }
+
+ if ($j == $y_size) { // ie. if $y_size is odd
+ $sum = $x_value[$i] - $y_value[$i] - $carry;
+ $carry = $sum < 0;
+ $x_value[$i] = $carry ? $sum + static::BASE_FULL : $sum;
+ ++$i;
+ }
+
+ if ($carry) {
+ for (; !$x_value[$i]; ++$i) {
+ $x_value[$i] = static::MAX_DIGIT;
+ }
+ --$x_value[$i];
+ }
+
+ return [
+ self::VALUE => self::trim($x_value),
+ self::SIGN => $x_negative
+ ];
+ }
+
+ /**
+ * Performs multiplication.
+ *
+ * @param array $x_value
+ * @param bool $x_negative
+ * @param array $y_value
+ * @param bool $y_negative
+ * @return array
+ */
+ protected static function multiplyHelper(array $x_value, $x_negative, array $y_value, $y_negative)
+ {
+ //if ( $x_value == $y_value ) {
+ // return [
+ // self::VALUE => self::square($x_value),
+ // self::SIGN => $x_sign != $y_value
+ // ];
+ //}
+
+ $x_length = count($x_value);
+ $y_length = count($y_value);
+
+ if (!$x_length || !$y_length) { // a 0 is being multiplied
+ return [
+ self::VALUE => [],
+ self::SIGN => false
+ ];
+ }
+
+ return [
+ self::VALUE => min($x_length, $y_length) < 2 * self::KARATSUBA_CUTOFF ?
+ self::trim(self::regularMultiply($x_value, $y_value)) :
+ self::trim(self::karatsuba($x_value, $y_value)),
+ self::SIGN => $x_negative != $y_negative
+ ];
+ }
+
+ /**
+ * Performs Karatsuba multiplication on two BigIntegers
+ *
+ * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and
+ * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=120 MPM 5.2.3}.
+ *
+ * @param array $x_value
+ * @param array $y_value
+ * @return array
+ */
+ private static function karatsuba(array $x_value, array $y_value)
+ {
+ $m = min(count($x_value) >> 1, count($y_value) >> 1);
+
+ if ($m < self::KARATSUBA_CUTOFF) {
+ return self::regularMultiply($x_value, $y_value);
+ }
+
+ $x1 = array_slice($x_value, $m);
+ $x0 = array_slice($x_value, 0, $m);
+ $y1 = array_slice($y_value, $m);
+ $y0 = array_slice($y_value, 0, $m);
+
+ $z2 = self::karatsuba($x1, $y1);
+ $z0 = self::karatsuba($x0, $y0);
+
+ $z1 = self::addHelper($x1, false, $x0, false);
+ $temp = self::addHelper($y1, false, $y0, false);
+ $z1 = self::karatsuba($z1[self::VALUE], $temp[self::VALUE]);
+ $temp = self::addHelper($z2, false, $z0, false);
+ $z1 = self::subtractHelper($z1, false, $temp[self::VALUE], false);
+
+ $z2 = array_merge(array_fill(0, 2 * $m, 0), $z2);
+ $z1[self::VALUE] = array_merge(array_fill(0, $m, 0), $z1[self::VALUE]);
+
+ $xy = self::addHelper($z2, false, $z1[self::VALUE], $z1[self::SIGN]);
+ $xy = self::addHelper($xy[self::VALUE], $xy[self::SIGN], $z0, false);
+
+ return $xy[self::VALUE];
+ }
+
+ /**
+ * Performs long multiplication on two BigIntegers
+ *
+ * Modeled after 'multiply' in MutableBigInteger.java.
+ *
+ * @param array $x_value
+ * @param array $y_value
+ * @return array
+ */
+ protected static function regularMultiply(array $x_value, array $y_value)
+ {
+ $x_length = count($x_value);
+ $y_length = count($y_value);
+
+ if (!$x_length || !$y_length) { // a 0 is being multiplied
+ return [];
+ }
+
+ $product_value = self::array_repeat(0, $x_length + $y_length);
+
+ // the following for loop could be removed if the for loop following it
+ // (the one with nested for loops) initially set $i to 0, but
+ // doing so would also make the result in one set of unnecessary adds,
+ // since on the outermost loops first pass, $product->value[$k] is going
+ // to always be 0
+
+ $carry = 0;
+ for ($j = 0; $j < $x_length; ++$j) { // ie. $i = 0
+ $temp = $x_value[$j] * $y_value[0] + $carry; // $product_value[$k] == 0
+ $carry = static::BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31);
+ $product_value[$j] = (int)($temp - static::BASE_FULL * $carry);
+ }
+
+ $product_value[$j] = $carry;
+
+ // the above for loop is what the previous comment was talking about. the
+ // following for loop is the "one with nested for loops"
+ for ($i = 1; $i < $y_length; ++$i) {
+ $carry = 0;
+
+ for ($j = 0, $k = $i; $j < $x_length; ++$j, ++$k) {
+ $temp = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry;
+ $carry = static::BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31);
+ $product_value[$k] = (int)($temp - static::BASE_FULL * $carry);
+ }
+
+ $product_value[$k] = $carry;
+ }
+
+ return $product_value;
+ }
+
+ /**
+ * Divides two BigIntegers.
+ *
+ * Returns an array whose first element contains the quotient and whose second element contains the
+ * "common residue". If the remainder would be positive, the "common residue" and the remainder are the
+ * same. If the remainder would be negative, the "common residue" is equal to the sum of the remainder
+ * and the divisor (basically, the "common residue" is the first positive modulo).
+ *
+ * @return array{static, static}
+ * @internal This function is based off of
+ * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=9 HAC 14.20}.
+ */
+ protected function divideHelper(PHP $y)
+ {
+ if (count($y->value) == 1) {
+ list($q, $r) = $this->divide_digit($this->value, $y->value[0]);
+ $quotient = new static();
+ $remainder = new static();
+ $quotient->value = $q;
+ if ($this->is_negative) {
+ $r = $y->value[0] - $r;
+ }
+ $remainder->value = [$r];
+ $quotient->is_negative = $this->is_negative != $y->is_negative;
+ return [$this->normalize($quotient), $this->normalize($remainder)];
+ }
+
+ $x = clone $this;
+ $y = clone $y;
+
+ $x_sign = $x->is_negative;
+ $y_sign = $y->is_negative;
+
+ $x->is_negative = $y->is_negative = false;
+
+ $diff = $x->compare($y);
+
+ if (!$diff) {
+ $temp = new static();
+ $temp->value = [1];
+ $temp->is_negative = $x_sign != $y_sign;
+ return [$this->normalize($temp), $this->normalize(static::$zero[static::class])];
+ }
+
+ if ($diff < 0) {
+ // if $x is negative, "add" $y.
+ if ($x_sign) {
+ $x = $y->subtract($x);
+ }
+ return [$this->normalize(static::$zero[static::class]), $this->normalize($x)];
+ }
+
+ // normalize $x and $y as described in HAC 14.23 / 14.24
+ $msb = $y->value[count($y->value) - 1];
+ for ($shift = 0; !($msb & static::MSB); ++$shift) {
+ $msb <<= 1;
+ }
+ $x->lshift($shift);
+ $y->lshift($shift);
+ $y_value = &$y->value;
+
+ $x_max = count($x->value) - 1;
+ $y_max = count($y->value) - 1;
+
+ $quotient = new static();
+ $quotient_value = &$quotient->value;
+ $quotient_value = self::array_repeat(0, $x_max - $y_max + 1);
+
+ static $temp, $lhs, $rhs;
+ if (!isset($temp)) {
+ $temp = new static();
+ $lhs = new static();
+ $rhs = new static();
+ }
+ if (static::class != get_class($temp)) {
+ $temp = new static();
+ $lhs = new static();
+ $rhs = new static();
+ }
+ $temp_value = &$temp->value;
+ $rhs_value = &$rhs->value;
+
+ // $temp = $y << ($x_max - $y_max-1) in base 2**26
+ $temp_value = array_merge(self::array_repeat(0, $x_max - $y_max), $y_value);
+
+ while ($x->compare($temp) >= 0) {
+ // calculate the "common residue"
+ ++$quotient_value[$x_max - $y_max];
+ $x = $x->subtract($temp);
+ $x_max = count($x->value) - 1;
+ }
+
+ for ($i = $x_max; $i >= $y_max + 1; --$i) {
+ $x_value = &$x->value;
+ $x_window = [
+ isset($x_value[$i]) ? $x_value[$i] : 0,
+ isset($x_value[$i - 1]) ? $x_value[$i - 1] : 0,
+ isset($x_value[$i - 2]) ? $x_value[$i - 2] : 0
+ ];
+ $y_window = [
+ $y_value[$y_max],
+ ($y_max > 0) ? $y_value[$y_max - 1] : 0
+ ];
+
+ $q_index = $i - $y_max - 1;
+ if ($x_window[0] == $y_window[0]) {
+ $quotient_value[$q_index] = static::MAX_DIGIT;
+ } else {
+ $quotient_value[$q_index] = self::safe_divide(
+ $x_window[0] * static::BASE_FULL + $x_window[1],
+ $y_window[0]
+ );
+ }
+
+ $temp_value = [$y_window[1], $y_window[0]];
+
+ $lhs->value = [$quotient_value[$q_index]];
+ $lhs = $lhs->multiply($temp);
+
+ $rhs_value = [$x_window[2], $x_window[1], $x_window[0]];
+
+ while ($lhs->compare($rhs) > 0) {
+ --$quotient_value[$q_index];
+
+ $lhs->value = [$quotient_value[$q_index]];
+ $lhs = $lhs->multiply($temp);
+ }
+
+ $adjust = self::array_repeat(0, $q_index);
+ $temp_value = [$quotient_value[$q_index]];
+ $temp = $temp->multiply($y);
+ $temp_value = &$temp->value;
+ if (count($temp_value)) {
+ $temp_value = array_merge($adjust, $temp_value);
+ }
+
+ $x = $x->subtract($temp);
+
+ if ($x->compare(static::$zero[static::class]) < 0) {
+ $temp_value = array_merge($adjust, $y_value);
+ $x = $x->add($temp);
+
+ --$quotient_value[$q_index];
+ }
+
+ $x_max = count($x_value) - 1;
+ }
+
+ // unnormalize the remainder
+ $x->rshift($shift);
+
+ $quotient->is_negative = $x_sign != $y_sign;
+
+ // calculate the "common residue", if appropriate
+ if ($x_sign) {
+ $y->rshift($shift);
+ $x = $y->subtract($x);
+ }
+
+ return [$this->normalize($quotient), $this->normalize($x)];
+ }
+
+ /**
+ * Divides a BigInteger by a regular integer
+ *
+ * abc / x = a00 / x + b0 / x + c / x
+ *
+ * @param array $dividend
+ * @param int $divisor
+ * @return array
+ */
+ private static function divide_digit(array $dividend, $divisor)
+ {
+ $carry = 0;
+ $result = [];
+
+ for ($i = count($dividend) - 1; $i >= 0; --$i) {
+ $temp = static::BASE_FULL * $carry + $dividend[$i];
+ $result[$i] = self::safe_divide($temp, $divisor);
+ $carry = (int)($temp - $divisor * $result[$i]);
+ }
+
+ return [$result, $carry];
+ }
+
+ /**
+ * Single digit division
+ *
+ * Even if int64 is being used the division operator will return a float64 value
+ * if the dividend is not evenly divisible by the divisor. Since a float64 doesn't
+ * have the precision of int64 this is a problem so, when int64 is being used,
+ * we'll guarantee that the dividend is divisible by first subtracting the remainder.
+ *
+ * @param int $x
+ * @param int $y
+ * @return int
+ */
+ private static function safe_divide($x, $y)
+ {
+ if (static::BASE === 26) {
+ return (int)($x / $y);
+ }
+
+ // static::BASE === 31
+ /** @var int */
+ return ($x - ($x % $y)) / $y;
+ }
+
+ /**
+ * Convert an array / boolean to a PHP BigInteger object
+ *
+ * @param array $arr
+ * @return static
+ */
+ protected function convertToObj(array $arr)
+ {
+ $result = new static();
+ $result->value = $arr[self::VALUE];
+ $result->is_negative = $arr[self::SIGN];
+
+ return $this->normalize($result);
+ }
+
+ /**
+ * Normalize
+ *
+ * Removes leading zeros and truncates (if necessary) to maintain the appropriate precision
+ *
+ * @param PHP $result
+ * @return static
+ */
+ protected function normalize(PHP $result)
+ {
+ $result->precision = $this->precision;
+ $result->bitmask = $this->bitmask;
+
+ $value = &$result->value;
+
+ if (!count($value)) {
+ $result->is_negative = false;
+ return $result;
+ }
+
+ $value = static::trim($value);
+
+ if (!empty($result->bitmask->value)) {
+ $length = min(count($value), count($result->bitmask->value));
+ $value = array_slice($value, 0, $length);
+
+ for ($i = 0; $i < $length; ++$i) {
+ $value[$i] = $value[$i] & $result->bitmask->value[$i];
+ }
+
+ $value = static::trim($value);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Compares two numbers.
+ *
+ * @param array $x_value
+ * @param bool $x_negative
+ * @param array $y_value
+ * @param bool $y_negative
+ * @return int
+ * @see static::compare()
+ */
+ protected static function compareHelper(array $x_value, $x_negative, array $y_value, $y_negative)
+ {
+ if ($x_negative != $y_negative) {
+ return (!$x_negative && $y_negative) ? 1 : -1;
+ }
+
+ $result = $x_negative ? -1 : 1;
+
+ if (count($x_value) != count($y_value)) {
+ return (count($x_value) > count($y_value)) ? $result : -$result;
+ }
+ $size = max(count($x_value), count($y_value));
+
+ $x_value = array_pad($x_value, $size, 0);
+ $y_value = array_pad($y_value, $size, 0);
+
+ for ($i = count($x_value) - 1; $i >= 0; --$i) {
+ if ($x_value[$i] != $y_value[$i]) {
+ return ($x_value[$i] > $y_value[$i]) ? $result : -$result;
+ }
+ }
+
+ return 0;
+ }
+
+ /**
+ * Absolute value.
+ *
+ * @return PHP
+ */
+ public function abs()
+ {
+ $temp = new static();
+ $temp->value = $this->value;
+
+ return $temp;
+ }
+
+ /**
+ * Trim
+ *
+ * Removes leading zeros
+ *
+ * @param list<static> $value
+ * @return list<static>
+ */
+ protected static function trim(array $value)
+ {
+ for ($i = count($value) - 1; $i >= 0; --$i) {
+ if ($value[$i]) {
+ break;
+ }
+ unset($value[$i]);
+ }
+
+ return $value;
+ }
+
+ /**
+ * Logical Right Shift
+ *
+ * Shifts BigInteger's by $shift bits, effectively dividing by 2**$shift.
+ *
+ * @param int $shift
+ * @return PHP
+ */
+ public function bitwise_rightShift($shift)
+ {
+ $temp = new static();
+
+ // could just replace lshift with this, but then all lshift() calls would need to be rewritten
+ // and I don't want to do that...
+ $temp->value = $this->value;
+ $temp->rshift($shift);
+
+ return $this->normalize($temp);
+ }
+
+ /**
+ * Logical Left Shift
+ *
+ * Shifts BigInteger's by $shift bits, effectively multiplying by 2**$shift.
+ *
+ * @param int $shift
+ * @return PHP
+ */
+ public function bitwise_leftShift($shift)
+ {
+ $temp = new static();
+ // could just replace _rshift with this, but then all _lshift() calls would need to be rewritten
+ // and I don't want to do that...
+ $temp->value = $this->value;
+ $temp->lshift($shift);
+
+ return $this->normalize($temp);
+ }
+
+ /**
+ * Converts 32-bit integers to bytes.
+ *
+ * @param int $x
+ * @return string
+ */
+ private static function int2bytes($x)
+ {
+ return ltrim(pack('N', $x), chr(0));
+ }
+
+ /**
+ * Array Repeat
+ *
+ * @param int $input
+ * @param int $multiplier
+ * @return array
+ */
+ protected static function array_repeat($input, $multiplier)
+ {
+ return $multiplier ? array_fill(0, $multiplier, $input) : [];
+ }
+
+ /**
+ * Logical Left Shift
+ *
+ * Shifts BigInteger's by $shift bits.
+ *
+ * @param int $shift
+ */
+ protected function lshift($shift)
+ {
+ if ($shift == 0) {
+ return;
+ }
+
+ $num_digits = (int)($shift / static::BASE);
+ $shift %= static::BASE;
+ $shift = 1 << $shift;
+
+ $carry = 0;
+
+ for ($i = 0; $i < count($this->value); ++$i) {
+ $temp = $this->value[$i] * $shift + $carry;
+ $carry = static::BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31);
+ $this->value[$i] = (int)($temp - $carry * static::BASE_FULL);
+ }
+
+ if ($carry) {
+ $this->value[count($this->value)] = $carry;
+ }
+
+ while ($num_digits--) {
+ array_unshift($this->value, 0);
+ }
+ }
+
+ /**
+ * Logical Right Shift
+ *
+ * Shifts BigInteger's by $shift bits.
+ *
+ * @param int $shift
+ */
+ protected function rshift($shift)
+ {
+ if ($shift == 0) {
+ return;
+ }
+
+ $num_digits = (int)($shift / static::BASE);
+ $shift %= static::BASE;
+ $carry_shift = static::BASE - $shift;
+ $carry_mask = (1 << $shift) - 1;
+
+ if ($num_digits) {
+ $this->value = array_slice($this->value, $num_digits);
+ }
+
+ $carry = 0;
+
+ for ($i = count($this->value) - 1; $i >= 0; --$i) {
+ $temp = $this->value[$i] >> $shift | $carry;
+ $carry = ($this->value[$i] & $carry_mask) << $carry_shift;
+ $this->value[$i] = $temp;
+ }
+
+ $this->value = static::trim($this->value);
+ }
+
+ /**
+ * Performs modular exponentiation.
+ *
+ * @param PHP $e
+ * @param PHP $n
+ * @return PHP
+ */
+ protected function powModInner(PHP $e, PHP $n)
+ {
+ try {
+ $class = static::$modexpEngine[static::class];
+ return $class::powModHelper($this, $e, $n, static::class);
+ } catch (\Exception $err) {
+ return PHP\DefaultEngine::powModHelper($this, $e, $n, static::class);
+ }
+ }
+
+ /**
+ * Performs squaring
+ *
+ * @param list<static> $x
+ * @return list<static>
+ */
+ protected static function square(array $x)
+ {
+ return count($x) < 2 * self::KARATSUBA_CUTOFF ?
+ self::trim(self::baseSquare($x)) :
+ self::trim(self::karatsubaSquare($x));
+ }
+
+ /**
+ * Performs traditional squaring on two BigIntegers
+ *
+ * Squaring can be done faster than multiplying a number by itself can be. See
+ * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=7 HAC 14.2.4} /
+ * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=141 MPM 5.3} for more information.
+ *
+ * @param array $value
+ * @return array
+ */
+ protected static function baseSquare(array $value)
+ {
+ if (empty($value)) {
+ return [];
+ }
+ $square_value = self::array_repeat(0, 2 * count($value));
+
+ for ($i = 0, $max_index = count($value) - 1; $i <= $max_index; ++$i) {
+ $i2 = $i << 1;
+
+ $temp = $square_value[$i2] + $value[$i] * $value[$i];
+ $carry = static::BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31);
+ $square_value[$i2] = (int)($temp - static::BASE_FULL * $carry);
+
+ // note how we start from $i+1 instead of 0 as we do in multiplication.
+ for ($j = $i + 1, $k = $i2 + 1; $j <= $max_index; ++$j, ++$k) {
+ $temp = $square_value[$k] + 2 * $value[$j] * $value[$i] + $carry;
+ $carry = static::BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31);
+ $square_value[$k] = (int)($temp - static::BASE_FULL * $carry);
+ }
+
+ // the following line can yield values larger 2**15. at this point, PHP should switch
+ // over to floats.
+ $square_value[$i + $max_index + 1] = $carry;
+ }
+
+ return $square_value;
+ }
+
+ /**
+ * Performs Karatsuba "squaring" on two BigIntegers
+ *
+ * See {@link http://en.wikipedia.org/wiki/Karatsuba_algorithm Karatsuba algorithm} and
+ * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=151 MPM 5.3.4}.
+ *
+ * @param array $value
+ * @return array
+ */
+ protected static function karatsubaSquare(array $value)
+ {
+ $m = count($value) >> 1;
+
+ if ($m < self::KARATSUBA_CUTOFF) {
+ return self::baseSquare($value);
+ }
+
+ $x1 = array_slice($value, $m);
+ $x0 = array_slice($value, 0, $m);
+
+ $z2 = self::karatsubaSquare($x1);
+ $z0 = self::karatsubaSquare($x0);
+
+ $z1 = self::addHelper($x1, false, $x0, false);
+ $z1 = self::karatsubaSquare($z1[self::VALUE]);
+ $temp = self::addHelper($z2, false, $z0, false);
+ $z1 = self::subtractHelper($z1, false, $temp[self::VALUE], false);
+
+ $z2 = array_merge(array_fill(0, 2 * $m, 0), $z2);
+ $z1[self::VALUE] = array_merge(array_fill(0, $m, 0), $z1[self::VALUE]);
+
+ $xx = self::addHelper($z2, false, $z1[self::VALUE], $z1[self::SIGN]);
+ $xx = self::addHelper($xx[self::VALUE], $xx[self::SIGN], $z0, false);
+
+ return $xx[self::VALUE];
+ }
+
+ /**
+ * Make the current number odd
+ *
+ * If the current number is odd it'll be unchanged. If it's even, one will be added to it.
+ *
+ * @see self::randomPrime()
+ */
+ protected function make_odd()
+ {
+ $this->value[0] |= 1;
+ }
+
+ /**
+ * Test the number against small primes.
+ *
+ * @see self::isPrime()
+ */
+ protected function testSmallPrimes()
+ {
+ if ($this->value == [1]) {
+ return false;
+ }
+ if ($this->value == [2]) {
+ return true;
+ }
+ if (~$this->value[0] & 1) {
+ return false;
+ }
+
+ $value = $this->value;
+ foreach (static::PRIMES as $prime) {
+ list(, $r) = self::divide_digit($value, $prime);
+ if (!$r) {
+ return count($value) == 1 && $value[0] == $prime;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Scan for 1 and right shift by that amount
+ *
+ * ie. $s = gmp_scan1($n, 0) and $r = gmp_div_q($n, gmp_pow(gmp_init('2'), $s));
+ *
+ * @param PHP $r
+ * @return int
+ * @see self::isPrime()
+ */
+ public static function scan1divide(PHP $r)
+ {
+ $r_value = &$r->value;
+ for ($i = 0, $r_length = count($r_value); $i < $r_length; ++$i) {
+ $temp = ~$r_value[$i] & static::MAX_DIGIT;
+ for ($j = 1; ($temp >> $j) & 1; ++$j) {
+ }
+ if ($j <= static::BASE) {
+ break;
+ }
+ }
+ $s = static::BASE * $i + $j;
+ $r->rshift($s);
+ return $s;
+ }
+
+ /**
+ * Performs exponentiation.
+ *
+ * @param PHP $n
+ * @return PHP
+ */
+ protected function powHelper(PHP $n)
+ {
+ if ($n->compare(static::$zero[static::class]) == 0) {
+ return new static(1);
+ } // n^0 = 1
+
+ $temp = clone $this;
+ while (!$n->equals(static::$one[static::class])) {
+ $temp = $temp->multiply($this);
+ $n = $n->subtract(static::$one[static::class]);
+ }
+
+ return $temp;
+ }
+
+ /**
+ * Is Odd?
+ *
+ * @return bool
+ */
+ public function isOdd()
+ {
+ return (bool)($this->value[0] & 1);
+ }
+
+ /**
+ * Tests if a bit is set
+ *
+ * @return bool
+ */
+ public function testBit($x)
+ {
+ $digit = (int) floor($x / static::BASE);
+ $bit = $x % static::BASE;
+
+ if (!isset($this->value[$digit])) {
+ return false;
+ }
+
+ return (bool)($this->value[$digit] & (1 << $bit));
+ }
+
+ /**
+ * Is Negative?
+ *
+ * @return bool
+ */
+ public function isNegative()
+ {
+ return $this->is_negative;
+ }
+
+ /**
+ * Negate
+ *
+ * Given $k, returns -$k
+ *
+ * @return static
+ */
+ public function negate()
+ {
+ $temp = clone $this;
+ $temp->is_negative = !$temp->is_negative;
+
+ return $temp;
+ }
+
+ /**
+ * Bitwise Split
+ *
+ * Splits BigInteger's into chunks of $split bits
+ *
+ * @param int $split
+ * @return list<static>
+ */
+ public function bitwise_split($split)
+ {
+ if ($split < 1) {
+ throw new \RuntimeException('Offset must be greater than 1');
+ }
+
+ $width = (int)($split / static::BASE);
+ if (!$width) {
+ $arr = $this->bitwise_small_split($split);
+ return array_map(function ($digit) {
+ $temp = new static();
+ $temp->value = $digit != 0 ? [$digit] : [];
+ return $temp;
+ }, $arr);
+ }
+
+ $vals = [];
+ $val = $this->value;
+
+ $i = $overflow = 0;
+ $len = count($val);
+ while ($i < $len) {
+ $digit = [];
+ if (!$overflow) {
+ $digit = array_slice($val, $i, $width);
+ $i += $width;
+ $overflow = $split % static::BASE;
+ if ($overflow) {
+ $mask = (1 << $overflow) - 1;
+ $temp = isset($val[$i]) ? $val[$i] : 0;
+ $digit[] = $temp & $mask;
+ }
+ } else {
+ $remaining = static::BASE - $overflow;
+ $tempsplit = $split - $remaining;
+ $tempwidth = (int)($tempsplit / static::BASE + 1);
+ $digit = array_slice($val, $i, $tempwidth);
+ $i += $tempwidth;
+ $tempoverflow = $tempsplit % static::BASE;
+ if ($tempoverflow) {
+ $tempmask = (1 << $tempoverflow) - 1;
+ $temp = isset($val[$i]) ? $val[$i] : 0;
+ $digit[] = $temp & $tempmask;
+ }
+ $newbits = 0;
+ for ($j = count($digit) - 1; $j >= 0; $j--) {
+ $temp = $digit[$j] & $mask;
+ $digit[$j] = ($digit[$j] >> $overflow) | ($newbits << $remaining);
+ $newbits = $temp;
+ }
+ $overflow = $tempoverflow;
+ $mask = $tempmask;
+ }
+ $temp = new static();
+ $temp->value = static::trim($digit);
+ $vals[] = $temp;
+ }
+
+ return array_reverse($vals);
+ }
+
+ /**
+ * Bitwise Split where $split < static::BASE
+ *
+ * @param int $split
+ * @return list<int>
+ */
+ private function bitwise_small_split($split)
+ {
+ $vals = [];
+ $val = $this->value;
+
+ $mask = (1 << $split) - 1;
+
+ $i = $overflow = 0;
+ $len = count($val);
+ $val[] = 0;
+ $remaining = static::BASE;
+ while ($i != $len) {
+ $digit = $val[$i] & $mask;
+ $val[$i] >>= $split;
+ if (!$overflow) {
+ $remaining -= $split;
+ $overflow = $split <= $remaining ? 0 : $split - $remaining;
+
+ if (!$remaining) {
+ $i++;
+ $remaining = static::BASE;
+ $overflow = 0;
+ }
+ } elseif (++$i != $len) {
+ $tempmask = (1 << $overflow) - 1;
+ $digit |= ($val[$i] & $tempmask) << $remaining;
+ $val[$i] >>= $overflow;
+ $remaining = static::BASE - $overflow;
+ $overflow = $split <= $remaining ? 0 : $split - $remaining;
+ }
+
+ $vals[] = $digit;
+ }
+
+ while ($vals[count($vals) - 1] == 0) {
+ unset($vals[count($vals) - 1]);
+ }
+
+ return array_reverse($vals);
+ }
+
+ /**
+ * @return bool
+ */
+ protected static function testJITOnWindows()
+ {
+ // see https://github.com/php/php-src/issues/11917
+ if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' && function_exists('opcache_get_status') && PHP_VERSION_ID < 80213 && !defined('PHPSECLIB_ALLOW_JIT')) {
+ $status = opcache_get_status();
+ if ($status && isset($status['jit']) && $status['jit']['enabled'] && $status['jit']['on']) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Return the size of a BigInteger in bits
+ *
+ * @return int
+ */
+ public function getLength()
+ {
+ $max = count($this->value) - 1;
+ return $max != -1 ?
+ $max * static::BASE + intval(ceil(log($this->value[$max] + 1, 2))) :
+ 0;
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Base.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Base.php
new file mode 100644
index 000000000..40f64bd17
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Base.php
@@ -0,0 +1,143 @@
+<?php
+
+/**
+ * PHP Modular Exponentiation Engine
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Math\BigInteger\Engines\PHP;
+
+use phpseclib3\Math\BigInteger\Engines\PHP;
+
+/**
+ * PHP Modular Exponentiation Engine
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Base extends PHP
+{
+ /**
+ * Cache constants
+ *
+ * $cache[self::VARIABLE] tells us whether or not the cached data is still valid.
+ *
+ */
+ const VARIABLE = 0;
+ /**
+ * $cache[self::DATA] contains the cached data.
+ *
+ */
+ const DATA = 1;
+
+ /**
+ * Test for engine validity
+ *
+ * @return bool
+ */
+ public static function isValidEngine()
+ {
+ return static::class != __CLASS__;
+ }
+
+ /**
+ * Performs modular exponentiation.
+ *
+ * The most naive approach to modular exponentiation has very unreasonable requirements, and
+ * and although the approach involving repeated squaring does vastly better, it, too, is impractical
+ * for our purposes. The reason being that division - by far the most complicated and time-consuming
+ * of the basic operations (eg. +,-,*,/) - occurs multiple times within it.
+ *
+ * Modular reductions resolve this issue. Although an individual modular reduction takes more time
+ * then an individual division, when performed in succession (with the same modulo), they're a lot faster.
+ *
+ * The two most commonly used modular reductions are Barrett and Montgomery reduction. Montgomery reduction,
+ * although faster, only works when the gcd of the modulo and of the base being used is 1. In RSA, when the
+ * base is a power of two, the modulo - a product of two primes - is always going to have a gcd of 1 (because
+ * the product of two odd numbers is odd), but what about when RSA isn't used?
+ *
+ * In contrast, Barrett reduction has no such constraint. As such, some bigint implementations perform a
+ * Barrett reduction after every operation in the modpow function. Others perform Barrett reductions when the
+ * modulo is even and Montgomery reductions when the modulo is odd. BigInteger.java's modPow method, however,
+ * uses a trick involving the Chinese Remainder Theorem to factor the even modulo into two numbers - one odd and
+ * the other, a power of two - and recombine them, later. This is the method that this modPow function uses.
+ * {@link http://islab.oregonstate.edu/papers/j34monex.pdf Montgomery Reduction with Even Modulus} elaborates.
+ *
+ * @param PHP $x
+ * @param PHP $e
+ * @param PHP $n
+ * @param string $class
+ * @return PHP
+ */
+ protected static function powModHelper(PHP $x, PHP $e, PHP $n, $class)
+ {
+ if (empty($e->value)) {
+ $temp = new $class();
+ $temp->value = [1];
+ return $x->normalize($temp);
+ }
+
+ if ($e->value == [1]) {
+ list(, $temp) = $x->divide($n);
+ return $x->normalize($temp);
+ }
+
+ if ($e->value == [2]) {
+ $temp = new $class();
+ $temp->value = $class::square($x->value);
+ list(, $temp) = $temp->divide($n);
+ return $x->normalize($temp);
+ }
+
+ return $x->normalize(static::slidingWindow($x, $e, $n, $class));
+ }
+
+ /**
+ * Modular reduction preparation
+ *
+ * @param array $x
+ * @param array $n
+ * @param string $class
+ * @see self::slidingWindow()
+ * @return array
+ */
+ protected static function prepareReduce(array $x, array $n, $class)
+ {
+ return static::reduce($x, $n, $class);
+ }
+
+ /**
+ * Modular multiply
+ *
+ * @param array $x
+ * @param array $y
+ * @param array $n
+ * @param string $class
+ * @see self::slidingWindow()
+ * @return array
+ */
+ protected static function multiplyReduce(array $x, array $y, array $n, $class)
+ {
+ $temp = $class::multiplyHelper($x, false, $y, false);
+ return static::reduce($temp[self::VALUE], $n, $class);
+ }
+
+ /**
+ * Modular square
+ *
+ * @param array $x
+ * @param array $n
+ * @param string $class
+ * @see self::slidingWindow()
+ * @return array
+ */
+ protected static function squareReduce(array $x, array $n, $class)
+ {
+ return static::reduce($class::square($x), $n, $class);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/DefaultEngine.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/DefaultEngine.php
new file mode 100644
index 000000000..6d33532e1
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/DefaultEngine.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * PHP Default Modular Exponentiation Engine
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Math\BigInteger\Engines\PHP;
+
+use phpseclib3\Math\BigInteger\Engines\PHP\Reductions\EvalBarrett;
+
+/**
+ * PHP Default Modular Exponentiation Engine
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class DefaultEngine extends EvalBarrett
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Montgomery.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Montgomery.php
new file mode 100644
index 000000000..09f825f95
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Montgomery.php
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * PHP Montgomery Modular Exponentiation Engine
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Math\BigInteger\Engines\PHP;
+
+use phpseclib3\Math\BigInteger\Engines\Engine;
+use phpseclib3\Math\BigInteger\Engines\PHP;
+use phpseclib3\Math\BigInteger\Engines\PHP\Reductions\PowerOfTwo;
+
+/**
+ * PHP Montgomery Modular Exponentiation Engine
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Montgomery extends Base
+{
+ /**
+ * Test for engine validity
+ *
+ * @return bool
+ */
+ public static function isValidEngine()
+ {
+ return static::class != __CLASS__;
+ }
+
+ /**
+ * Performs modular exponentiation.
+ *
+ * @template T of Engine
+ * @param Engine $x
+ * @param Engine $e
+ * @param Engine $n
+ * @param class-string<T> $class
+ * @return T
+ */
+ protected static function slidingWindow(Engine $x, Engine $e, Engine $n, $class)
+ {
+ // is the modulo odd?
+ if ($n->value[0] & 1) {
+ return parent::slidingWindow($x, $e, $n, $class);
+ }
+ // if it's not, it's even
+
+ // find the lowest set bit (eg. the max pow of 2 that divides $n)
+ for ($i = 0; $i < count($n->value); ++$i) {
+ if ($n->value[$i]) {
+ $temp = decbin($n->value[$i]);
+ $j = strlen($temp) - strrpos($temp, '1') - 1;
+ $j += $class::BASE * $i;
+ break;
+ }
+ }
+ // at this point, 2^$j * $n/(2^$j) == $n
+
+ $mod1 = clone $n;
+ $mod1->rshift($j);
+ $mod2 = new $class();
+ $mod2->value = [1];
+ $mod2->lshift($j);
+
+ $part1 = $mod1->value != [1] ? parent::slidingWindow($x, $e, $mod1, $class) : new $class();
+ $part2 = PowerOfTwo::slidingWindow($x, $e, $mod2, $class);
+
+ $y1 = $mod2->modInverse($mod1);
+ $y2 = $mod1->modInverse($mod2);
+
+ $result = $part1->multiply($mod2);
+ $result = $result->multiply($y1);
+
+ $temp = $part2->multiply($mod1);
+ $temp = $temp->multiply($y2);
+
+ $result = $result->add($temp);
+ list(, $result) = $result->divide($n);
+
+ return $result;
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/OpenSSL.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/OpenSSL.php
new file mode 100644
index 000000000..eddd25e2e
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/OpenSSL.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * OpenSSL Modular Exponentiation Engine
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Math\BigInteger\Engines\PHP;
+
+use phpseclib3\Math\BigInteger\Engines\OpenSSL as Progenitor;
+
+/**
+ * OpenSSL Modular Exponentiation Engine
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class OpenSSL extends Progenitor
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Barrett.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Barrett.php
new file mode 100644
index 000000000..e624f3cad
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Barrett.php
@@ -0,0 +1,296 @@
+<?php
+
+/**
+ * PHP Barrett Modular Exponentiation Engine
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Math\BigInteger\Engines\PHP\Reductions;
+
+use phpseclib3\Math\BigInteger\Engines\PHP;
+use phpseclib3\Math\BigInteger\Engines\PHP\Base;
+
+/**
+ * PHP Barrett Modular Exponentiation Engine
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Barrett extends Base
+{
+ /**
+ * Barrett Modular Reduction
+ *
+ * See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=14 HAC 14.3.3} /
+ * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=165 MPM 6.2.5} for more information. Modified slightly,
+ * so as not to require negative numbers (initially, this script didn't support negative numbers).
+ *
+ * Employs "folding", as described at
+ * {@link http://www.cosic.esat.kuleuven.be/publications/thesis-149.pdf#page=66 thesis-149.pdf#page=66}. To quote from
+ * it, "the idea [behind folding] is to find a value x' such that x (mod m) = x' (mod m), with x' being smaller than x."
+ *
+ * Unfortunately, the "Barrett Reduction with Folding" algorithm described in thesis-149.pdf is not, as written, all that
+ * usable on account of (1) its not using reasonable radix points as discussed in
+ * {@link http://math.libtomcrypt.com/files/tommath.pdf#page=162 MPM 6.2.2} and (2) the fact that, even with reasonable
+ * radix points, it only works when there are an even number of digits in the denominator. The reason for (2) is that
+ * (x >> 1) + (x >> 1) != x / 2 + x / 2. If x is even, they're the same, but if x is odd, they're not. See the in-line
+ * comments for details.
+ *
+ * @param array $n
+ * @param array $m
+ * @param class-string<PHP> $class
+ * @return array
+ */
+ protected static function reduce(array $n, array $m, $class)
+ {
+ static $cache = [
+ self::VARIABLE => [],
+ self::DATA => []
+ ];
+
+ $m_length = count($m);
+
+ // if (self::compareHelper($n, $static::square($m)) >= 0) {
+ if (count($n) > 2 * $m_length) {
+ $lhs = new $class();
+ $rhs = new $class();
+ $lhs->value = $n;
+ $rhs->value = $m;
+ list(, $temp) = $lhs->divide($rhs);
+ return $temp->value;
+ }
+
+ // if (m.length >> 1) + 2 <= m.length then m is too small and n can't be reduced
+ if ($m_length < 5) {
+ return self::regularBarrett($n, $m, $class);
+ }
+ // n = 2 * m.length
+ $correctionNeeded = false;
+ if ($m_length & 1) {
+ $correctionNeeded = true;
+ array_unshift($n, 0);
+ array_unshift($m, 0);
+ $m_length++;
+ }
+
+ if (($key = array_search($m, $cache[self::VARIABLE])) === false) {
+ $key = count($cache[self::VARIABLE]);
+ $cache[self::VARIABLE][] = $m;
+
+ $lhs = new $class();
+ $lhs_value = &$lhs->value;
+ $lhs_value = self::array_repeat(0, $m_length + ($m_length >> 1));
+ $lhs_value[] = 1;
+ $rhs = new $class();
+ $rhs->value = $m;
+
+ list($u, $m1) = $lhs->divide($rhs);
+ $u = $u->value;
+ $m1 = $m1->value;
+
+ $cache[self::DATA][] = [
+ 'u' => $u, // m.length >> 1 (technically (m.length >> 1) + 1)
+ 'm1' => $m1 // m.length
+ ];
+ } else {
+ $cacheValues = $cache[self::DATA][$key];
+ $u = $cacheValues['u'];
+ $m1 = $cacheValues['m1'];
+ }
+
+ $cutoff = $m_length + ($m_length >> 1);
+ $lsd = array_slice($n, 0, $cutoff); // m.length + (m.length >> 1)
+ $msd = array_slice($n, $cutoff); // m.length >> 1
+
+ $lsd = self::trim($lsd);
+ $temp = $class::multiplyHelper($msd, false, $m1, false); // m.length + (m.length >> 1)
+ $n = $class::addHelper($lsd, false, $temp[self::VALUE], false); // m.length + (m.length >> 1) + 1 (so basically we're adding two same length numbers)
+ //if ($m_length & 1) {
+ // return self::regularBarrett($n[self::VALUE], $m, $class);
+ //}
+
+ // (m.length + (m.length >> 1) + 1) - (m.length - 1) == (m.length >> 1) + 2
+ $temp = array_slice($n[self::VALUE], $m_length - 1);
+ // if even: ((m.length >> 1) + 2) + (m.length >> 1) == m.length + 2
+ // if odd: ((m.length >> 1) + 2) + (m.length >> 1) == (m.length - 1) + 2 == m.length + 1
+ // note that these are upper bounds. let's say m.length is 2. then you'd be multiplying a
+ // 3 digit number by a 1 digit number. if you're doing 999 * 9 (in base 10) the result will
+ // be a 4 digit number. but if you're multiplying 111 * 1 then the result will be a 3 digit
+ // number.
+ $temp = $class::multiplyHelper($temp, false, $u, false);
+ // if even: (m.length + 2) - ((m.length >> 1) + 1) = m.length - (m.length >> 1) + 1
+ // if odd: (m.length + 1) - ((m.length >> 1) + 1) = m.length - (m.length >> 1)
+ $temp = array_slice($temp[self::VALUE], ($m_length >> 1) + 1);
+ // if even: (m.length - (m.length >> 1) + 1) + m.length = 2 * m.length - (m.length >> 1) + 1
+ // if odd: (m.length - (m.length >> 1)) + m.length = 2 * m.length - (m.length >> 1)
+ $temp = $class::multiplyHelper($temp, false, $m, false);
+ // at this point, if m had an odd number of digits, we'd (probably) be subtracting a 2 * m.length - (m.length >> 1)
+ // digit number from a m.length + (m.length >> 1) + 1 digit number. ie. there'd be an extra digit and the while loop
+ // following this comment would loop a lot (hence our calling _regularBarrett() in that situation).
+ $result = $class::subtractHelper($n[self::VALUE], false, $temp[self::VALUE], false);
+
+ while (self::compareHelper($result[self::VALUE], $result[self::SIGN], $m, false) >= 0) {
+ $result = $class::subtractHelper($result[self::VALUE], $result[self::SIGN], $m, false);
+ }
+
+ if ($correctionNeeded) {
+ array_shift($result[self::VALUE]);
+ }
+
+ return $result[self::VALUE];
+ }
+
+ /**
+ * (Regular) Barrett Modular Reduction
+ *
+ * For numbers with more than four digits BigInteger::_barrett() is faster. The difference between that and this
+ * is that this function does not fold the denominator into a smaller form.
+ *
+ * @param array $x
+ * @param array $n
+ * @param string $class
+ * @return array
+ */
+ private static function regularBarrett(array $x, array $n, $class)
+ {
+ static $cache = [
+ self::VARIABLE => [],
+ self::DATA => []
+ ];
+
+ $n_length = count($n);
+
+ if (count($x) > 2 * $n_length) {
+ $lhs = new $class();
+ $rhs = new $class();
+ $lhs->value = $x;
+ $rhs->value = $n;
+ list(, $temp) = $lhs->divide($rhs);
+ return $temp->value;
+ }
+
+ if (($key = array_search($n, $cache[self::VARIABLE])) === false) {
+ $key = count($cache[self::VARIABLE]);
+ $cache[self::VARIABLE][] = $n;
+ $lhs = new $class();
+ $lhs_value = &$lhs->value;
+ $lhs_value = self::array_repeat(0, 2 * $n_length);
+ $lhs_value[] = 1;
+ $rhs = new $class();
+ $rhs->value = $n;
+ list($temp, ) = $lhs->divide($rhs); // m.length
+ $cache[self::DATA][] = $temp->value;
+ }
+
+ // 2 * m.length - (m.length - 1) = m.length + 1
+ $temp = array_slice($x, $n_length - 1);
+ // (m.length + 1) + m.length = 2 * m.length + 1
+ $temp = $class::multiplyHelper($temp, false, $cache[self::DATA][$key], false);
+ // (2 * m.length + 1) - (m.length - 1) = m.length + 2
+ $temp = array_slice($temp[self::VALUE], $n_length + 1);
+
+ // m.length + 1
+ $result = array_slice($x, 0, $n_length + 1);
+ // m.length + 1
+ $temp = self::multiplyLower($temp, false, $n, false, $n_length + 1, $class);
+ // $temp == array_slice($class::regularMultiply($temp, false, $n, false)->value, 0, $n_length + 1)
+
+ if (self::compareHelper($result, false, $temp[self::VALUE], $temp[self::SIGN]) < 0) {
+ $corrector_value = self::array_repeat(0, $n_length + 1);
+ $corrector_value[count($corrector_value)] = 1;
+ $result = $class::addHelper($result, false, $corrector_value, false);
+ $result = $result[self::VALUE];
+ }
+
+ // at this point, we're subtracting a number with m.length + 1 digits from another number with m.length + 1 digits
+ $result = $class::subtractHelper($result, false, $temp[self::VALUE], $temp[self::SIGN]);
+ while (self::compareHelper($result[self::VALUE], $result[self::SIGN], $n, false) > 0) {
+ $result = $class::subtractHelper($result[self::VALUE], $result[self::SIGN], $n, false);
+ }
+
+ return $result[self::VALUE];
+ }
+
+ /**
+ * Performs long multiplication up to $stop digits
+ *
+ * If you're going to be doing array_slice($product->value, 0, $stop), some cycles can be saved.
+ *
+ * @see self::regularBarrett()
+ * @param array $x_value
+ * @param bool $x_negative
+ * @param array $y_value
+ * @param bool $y_negative
+ * @param int $stop
+ * @param string $class
+ * @return array
+ */
+ private static function multiplyLower(array $x_value, $x_negative, array $y_value, $y_negative, $stop, $class)
+ {
+ $x_length = count($x_value);
+ $y_length = count($y_value);
+
+ if (!$x_length || !$y_length) { // a 0 is being multiplied
+ return [
+ self::VALUE => [],
+ self::SIGN => false
+ ];
+ }
+
+ if ($x_length < $y_length) {
+ $temp = $x_value;
+ $x_value = $y_value;
+ $y_value = $temp;
+
+ $x_length = count($x_value);
+ $y_length = count($y_value);
+ }
+
+ $product_value = self::array_repeat(0, $x_length + $y_length);
+
+ // the following for loop could be removed if the for loop following it
+ // (the one with nested for loops) initially set $i to 0, but
+ // doing so would also make the result in one set of unnecessary adds,
+ // since on the outermost loops first pass, $product->value[$k] is going
+ // to always be 0
+
+ $carry = 0;
+
+ for ($j = 0; $j < $x_length; ++$j) { // ie. $i = 0, $k = $i
+ $temp = $x_value[$j] * $y_value[0] + $carry; // $product_value[$k] == 0
+ $carry = $class::BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31);
+ $product_value[$j] = (int) ($temp - $class::BASE_FULL * $carry);
+ }
+
+ if ($j < $stop) {
+ $product_value[$j] = $carry;
+ }
+
+ // the above for loop is what the previous comment was talking about. the
+ // following for loop is the "one with nested for loops"
+
+ for ($i = 1; $i < $y_length; ++$i) {
+ $carry = 0;
+
+ for ($j = 0, $k = $i; $j < $x_length && $k < $stop; ++$j, ++$k) {
+ $temp = $product_value[$k] + $x_value[$j] * $y_value[$i] + $carry;
+ $carry = $class::BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31);
+ $product_value[$k] = (int) ($temp - $class::BASE_FULL * $carry);
+ }
+
+ if ($k < $stop) {
+ $product_value[$k] = $carry;
+ }
+ }
+
+ return [
+ self::VALUE => self::trim($product_value),
+ self::SIGN => $x_negative != $y_negative
+ ];
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Classic.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Classic.php
new file mode 100644
index 000000000..54f3b863b
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Classic.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * PHP Classic Modular Exponentiation Engine
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Math\BigInteger\Engines\PHP\Reductions;
+
+use phpseclib3\Math\BigInteger\Engines\PHP\Base;
+
+/**
+ * PHP Classic Modular Exponentiation Engine
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Classic extends Base
+{
+ /**
+ * Regular Division
+ *
+ * @param array $x
+ * @param array $n
+ * @param string $class
+ * @return array
+ */
+ protected static function reduce(array $x, array $n, $class)
+ {
+ $lhs = new $class();
+ $lhs->value = $x;
+ $rhs = new $class();
+ $rhs->value = $n;
+ list(, $temp) = $lhs->divide($rhs);
+ return $temp->value;
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/EvalBarrett.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/EvalBarrett.php
new file mode 100644
index 000000000..01df0b611
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/EvalBarrett.php
@@ -0,0 +1,500 @@
+<?php
+
+/**
+ * PHP Dynamic Barrett Modular Exponentiation Engine
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Math\BigInteger\Engines\PHP\Reductions;
+
+use phpseclib3\Math\BigInteger\Engines\PHP;
+use phpseclib3\Math\BigInteger\Engines\PHP\Base;
+
+/**
+ * PHP Dynamic Barrett Modular Exponentiation Engine
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class EvalBarrett extends Base
+{
+ /**
+ * Custom Reduction Function
+ *
+ * @see self::generateCustomReduction
+ */
+ private static $custom_reduction;
+
+ /**
+ * Barrett Modular Reduction
+ *
+ * This calls a dynamically generated loop unrolled function that's specific to a given modulo.
+ * Array lookups are avoided as are if statements testing for how many bits the host OS supports, etc.
+ *
+ * @param array $n
+ * @param array $m
+ * @param string $class
+ * @return array
+ */
+ protected static function reduce(array $n, array $m, $class)
+ {
+ $inline = self::$custom_reduction;
+ return $inline($n);
+ }
+
+ /**
+ * Generate Custom Reduction
+ *
+ * @param PHP $m
+ * @param string $class
+ * @return callable
+ */
+ protected static function generateCustomReduction(PHP $m, $class)
+ {
+ $m_length = count($m->value);
+
+ if ($m_length < 5) {
+ $code = '
+ $lhs = new ' . $class . '();
+ $lhs->value = $x;
+ $rhs = new ' . $class . '();
+ $rhs->value = [' .
+ implode(',', array_map(self::class . '::float2string', $m->value)) . '];
+ list(, $temp) = $lhs->divide($rhs);
+ return $temp->value;
+ ';
+ eval('$func = function ($x) { ' . $code . '};');
+ self::$custom_reduction = $func;
+ //self::$custom_reduction = \Closure::bind($func, $m, $class);
+ return $func;
+ }
+
+ $correctionNeeded = false;
+ if ($m_length & 1) {
+ $correctionNeeded = true;
+ $m = clone $m;
+ array_unshift($m->value, 0);
+ $m_length++;
+ }
+
+ $lhs = new $class();
+ $lhs_value = &$lhs->value;
+
+ $lhs_value = self::array_repeat(0, $m_length + ($m_length >> 1));
+ $lhs_value[] = 1;
+ $rhs = new $class();
+
+ list($u, $m1) = $lhs->divide($m);
+
+ if ($class::BASE != 26) {
+ $u = $u->value;
+ } else {
+ $lhs_value = self::array_repeat(0, 2 * $m_length);
+ $lhs_value[] = 1;
+ $rhs = new $class();
+
+ list($u) = $lhs->divide($m);
+ $u = $u->value;
+ }
+
+ $m = $m->value;
+ $m1 = $m1->value;
+
+ $cutoff = count($m) + (count($m) >> 1);
+
+ $code = $correctionNeeded ?
+ 'array_unshift($n, 0);' :
+ '';
+
+ $code .= '
+ if (count($n) > ' . (2 * count($m)) . ') {
+ $lhs = new ' . $class . '();
+ $rhs = new ' . $class . '();
+ $lhs->value = $n;
+ $rhs->value = [' .
+ implode(',', array_map(self::class . '::float2string', $m)) . '];
+ list(, $temp) = $lhs->divide($rhs);
+ return $temp->value;
+ }
+
+ $lsd = array_slice($n, 0, ' . $cutoff . ');
+ $msd = array_slice($n, ' . $cutoff . ');';
+
+ $code .= self::generateInlineTrim('msd');
+ $code .= self::generateInlineMultiply('msd', $m1, 'temp', $class);
+ $code .= self::generateInlineAdd('lsd', 'temp', 'n', $class);
+
+ $code .= '$temp = array_slice($n, ' . (count($m) - 1) . ');';
+ $code .= self::generateInlineMultiply('temp', $u, 'temp2', $class);
+ $code .= self::generateInlineTrim('temp2');
+
+ $code .= $class::BASE == 26 ?
+ '$temp = array_slice($temp2, ' . (count($m) + 1) . ');' :
+ '$temp = array_slice($temp2, ' . ((count($m) >> 1) + 1) . ');';
+ $code .= self::generateInlineMultiply('temp', $m, 'temp2', $class);
+ $code .= self::generateInlineTrim('temp2');
+
+ /*
+ if ($class::BASE == 26) {
+ $code.= '$n = array_slice($n, 0, ' . (count($m) + 1) . ');
+ $temp2 = array_slice($temp2, 0, ' . (count($m) + 1) . ');';
+ }
+ */
+
+ $code .= self::generateInlineSubtract2('n', 'temp2', 'temp', $class);
+
+ $subcode = self::generateInlineSubtract1('temp', $m, 'temp2', $class);
+ $subcode .= '$temp = $temp2;';
+
+ $code .= self::generateInlineCompare($m, 'temp', $subcode);
+
+ if ($correctionNeeded) {
+ $code .= 'array_shift($temp);';
+ }
+
+ $code .= 'return $temp;';
+
+ eval('$func = function ($n) { ' . $code . '};');
+
+ self::$custom_reduction = $func;
+
+ return $func;
+
+ //self::$custom_reduction = \Closure::bind($func, $m, $class);
+ }
+
+ /**
+ * Inline Trim
+ *
+ * Removes leading zeros
+ *
+ * @param string $name
+ * @return string
+ */
+ private static function generateInlineTrim($name)
+ {
+ return '
+ for ($i = count($' . $name . ') - 1; $i >= 0; --$i) {
+ if ($' . $name . '[$i]) {
+ break;
+ }
+ unset($' . $name . '[$i]);
+ }';
+ }
+
+ /**
+ * Inline Multiply (unknown, known)
+ *
+ * @param string $input
+ * @param array $arr
+ * @param string $output
+ * @param string $class
+ * @return string
+ */
+ private static function generateInlineMultiply($input, array $arr, $output, $class)
+ {
+ if (!count($arr)) {
+ return 'return [];';
+ }
+
+ $regular = '
+ $length = count($' . $input . ');
+ if (!$length) {
+ $' . $output . ' = [];
+ }else{
+ $' . $output . ' = array_fill(0, $length + ' . count($arr) . ', 0);
+ $carry = 0;';
+
+ for ($i = 0; $i < count($arr); $i++) {
+ $regular .= '
+ $subtemp = $' . $input . '[0] * ' . $arr[$i];
+ $regular .= $i ? ' + $carry;' : ';';
+
+ $regular .= '$carry = ';
+ $regular .= $class::BASE === 26 ?
+ 'intval($subtemp / 0x4000000);' :
+ '$subtemp >> 31;';
+ $regular .=
+ '$' . $output . '[' . $i . '] = ';
+ if ($class::BASE === 26) {
+ $regular .= '(int) (';
+ }
+ $regular .= '$subtemp - ' . $class::BASE_FULL . ' * $carry';
+ $regular .= $class::BASE === 26 ? ');' : ';';
+ }
+
+ $regular .= '$' . $output . '[' . count($arr) . '] = $carry;';
+
+ $regular .= '
+ for ($i = 1; $i < $length; ++$i) {';
+
+ for ($j = 0; $j < count($arr); $j++) {
+ $regular .= $j ? '$k++;' : '$k = $i;';
+ $regular .= '
+ $subtemp = $' . $output . '[$k] + $' . $input . '[$i] * ' . $arr[$j];
+ $regular .= $j ? ' + $carry;' : ';';
+
+ $regular .= '$carry = ';
+ $regular .= $class::BASE === 26 ?
+ 'intval($subtemp / 0x4000000);' :
+ '$subtemp >> 31;';
+ $regular .=
+ '$' . $output . '[$k] = ';
+ if ($class::BASE === 26) {
+ $regular .= '(int) (';
+ }
+ $regular .= '$subtemp - ' . $class::BASE_FULL . ' * $carry';
+ $regular .= $class::BASE === 26 ? ');' : ';';
+ }
+
+ $regular .= '$' . $output . '[++$k] = $carry; $carry = 0;';
+
+ $regular .= '}}';
+
+ //if (count($arr) < 2 * self::KARATSUBA_CUTOFF) {
+ //}
+
+ return $regular;
+ }
+
+ /**
+ * Inline Addition
+ *
+ * @param string $x
+ * @param string $y
+ * @param string $result
+ * @param string $class
+ * @return string
+ */
+ private static function generateInlineAdd($x, $y, $result, $class)
+ {
+ $code = '
+ $length = max(count($' . $x . '), count($' . $y . '));
+ $' . $result . ' = array_pad($' . $x . ', $length + 1, 0);
+ $_' . $y . ' = array_pad($' . $y . ', $length, 0);
+ $carry = 0;
+ for ($i = 0, $j = 1; $j < $length; $i+=2, $j+=2) {
+ $sum = ($' . $result . '[$j] + $_' . $y . '[$j]) * ' . $class::BASE_FULL . '
+ + $' . $result . '[$i] + $_' . $y . '[$i] +
+ $carry;
+ $carry = $sum >= ' . self::float2string($class::MAX_DIGIT2) . ';
+ $sum = $carry ? $sum - ' . self::float2string($class::MAX_DIGIT2) . ' : $sum;';
+
+ $code .= $class::BASE === 26 ?
+ '$upper = intval($sum / 0x4000000); $' . $result . '[$i] = (int) ($sum - ' . $class::BASE_FULL . ' * $upper);' :
+ '$upper = $sum >> 31; $' . $result . '[$i] = $sum - ' . $class::BASE_FULL . ' * $upper;';
+ $code .= '
+ $' . $result . '[$j] = $upper;
+ }
+ if ($j == $length) {
+ $sum = $' . $result . '[$i] + $_' . $y . '[$i] + $carry;
+ $carry = $sum >= ' . self::float2string($class::BASE_FULL) . ';
+ $' . $result . '[$i] = $carry ? $sum - ' . self::float2string($class::BASE_FULL) . ' : $sum;
+ ++$i;
+ }
+ if ($carry) {
+ for (; $' . $result . '[$i] == ' . $class::MAX_DIGIT . '; ++$i) {
+ $' . $result . '[$i] = 0;
+ }
+ ++$' . $result . '[$i];
+ }';
+ $code .= self::generateInlineTrim($result);
+
+ return $code;
+ }
+
+ /**
+ * Inline Subtraction 2
+ *
+ * For when $known is more digits than $unknown. This is the harder use case to optimize for.
+ *
+ * @param string $known
+ * @param string $unknown
+ * @param string $result
+ * @param string $class
+ * @return string
+ */
+ private static function generateInlineSubtract2($known, $unknown, $result, $class)
+ {
+ $code = '
+ $' . $result . ' = $' . $known . ';
+ $carry = 0;
+ $size = count($' . $unknown . ');
+ for ($i = 0, $j = 1; $j < $size; $i+= 2, $j+= 2) {
+ $sum = ($' . $known . '[$j] - $' . $unknown . '[$j]) * ' . $class::BASE_FULL . ' + $' . $known . '[$i]
+ - $' . $unknown . '[$i]
+ - $carry;
+ $carry = $sum < 0;
+ if ($carry) {
+ $sum+= ' . self::float2string($class::MAX_DIGIT2) . ';
+ }
+ $subtemp = ';
+ $code .= $class::BASE === 26 ?
+ 'intval($sum / 0x4000000);' :
+ '$sum >> 31;';
+ $code .= '$' . $result . '[$i] = ';
+ if ($class::BASE === 26) {
+ $code .= '(int) (';
+ }
+ $code .= '$sum - ' . $class::BASE_FULL . ' * $subtemp';
+ if ($class::BASE === 26) {
+ $code .= ')';
+ }
+ $code .= ';
+ $' . $result . '[$j] = $subtemp;
+ }
+ if ($j == $size) {
+ $sum = $' . $known . '[$i] - $' . $unknown . '[$i] - $carry;
+ $carry = $sum < 0;
+ $' . $result . '[$i] = $carry ? $sum + ' . $class::BASE_FULL . ' : $sum;
+ ++$i;
+ }
+
+ if ($carry) {
+ for (; !$' . $result . '[$i]; ++$i) {
+ $' . $result . '[$i] = ' . $class::MAX_DIGIT . ';
+ }
+ --$' . $result . '[$i];
+ }';
+
+ $code .= self::generateInlineTrim($result);
+
+ return $code;
+ }
+
+ /**
+ * Inline Subtraction 1
+ *
+ * For when $unknown is more digits than $known. This is the easier use case to optimize for.
+ *
+ * @param string $unknown
+ * @param array $known
+ * @param string $result
+ * @param string $class
+ * @return string
+ */
+ private static function generateInlineSubtract1($unknown, array $known, $result, $class)
+ {
+ $code = '$' . $result . ' = $' . $unknown . ';';
+ for ($i = 0, $j = 1; $j < count($known); $i += 2, $j += 2) {
+ $code .= '$sum = $' . $unknown . '[' . $j . '] * ' . $class::BASE_FULL . ' + $' . $unknown . '[' . $i . '] - ';
+ $code .= self::float2string($known[$j] * $class::BASE_FULL + $known[$i]);
+ if ($i != 0) {
+ $code .= ' - $carry';
+ }
+
+ $code .= ';
+ if ($carry = $sum < 0) {
+ $sum+= ' . self::float2string($class::MAX_DIGIT2) . ';
+ }
+ $subtemp = ';
+ $code .= $class::BASE === 26 ?
+ 'intval($sum / 0x4000000);' :
+ '$sum >> 31;';
+ $code .= '
+ $' . $result . '[' . $i . '] = ';
+ if ($class::BASE === 26) {
+ $code .= ' (int) (';
+ }
+ $code .= '$sum - ' . $class::BASE_FULL . ' * $subtemp';
+ if ($class::BASE === 26) {
+ $code .= ')';
+ }
+ $code .= ';
+ $' . $result . '[' . $j . '] = $subtemp;';
+ }
+
+ $code .= '$i = ' . $i . ';';
+
+ if ($j == count($known)) {
+ $code .= '
+ $sum = $' . $unknown . '[' . $i . '] - ' . $known[$i] . ' - $carry;
+ $carry = $sum < 0;
+ $' . $result . '[' . $i . '] = $carry ? $sum + ' . $class::BASE_FULL . ' : $sum;
+ ++$i;';
+ }
+
+ $code .= '
+ if ($carry) {
+ for (; !$' . $result . '[$i]; ++$i) {
+ $' . $result . '[$i] = ' . $class::MAX_DIGIT . ';
+ }
+ --$' . $result . '[$i];
+ }';
+ $code .= self::generateInlineTrim($result);
+
+ return $code;
+ }
+
+ /**
+ * Inline Comparison
+ *
+ * If $unknown >= $known then loop
+ *
+ * @param array $known
+ * @param string $unknown
+ * @param string $subcode
+ * @return string
+ */
+ private static function generateInlineCompare(array $known, $unknown, $subcode)
+ {
+ $uniqid = uniqid();
+ $code = 'loop_' . $uniqid . ':
+ $clength = count($' . $unknown . ');
+ switch (true) {
+ case $clength < ' . count($known) . ':
+ goto end_' . $uniqid . ';
+ case $clength > ' . count($known) . ':';
+ for ($i = count($known) - 1; $i >= 0; $i--) {
+ $code .= '
+ case $' . $unknown . '[' . $i . '] > ' . $known[$i] . ':
+ goto subcode_' . $uniqid . ';
+ case $' . $unknown . '[' . $i . '] < ' . $known[$i] . ':
+ goto end_' . $uniqid . ';';
+ }
+ $code .= '
+ default:
+ // do subcode
+ }
+
+ subcode_' . $uniqid . ':' . $subcode . '
+ goto loop_' . $uniqid . ';
+
+ end_' . $uniqid . ':';
+
+ return $code;
+ }
+
+ /**
+ * Convert a float to a string
+ *
+ * If you do echo floatval(pow(2, 52)) you'll get 4.6116860184274E+18. It /can/ be displayed without a loss of
+ * precision but displayed in this way there will be precision loss, hence the need for this method.
+ *
+ * @param int|float $num
+ * @return string
+ */
+ private static function float2string($num)
+ {
+ if (!is_float($num)) {
+ return (string) $num;
+ }
+
+ if ($num < 0) {
+ return '-' . self::float2string(abs($num));
+ }
+
+ $temp = '';
+ while ($num) {
+ $temp = fmod($num, 10) . $temp;
+ $num = floor($num / 10);
+ }
+
+ return $temp;
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Montgomery.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Montgomery.php
new file mode 100644
index 000000000..a34035e7a
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/Montgomery.php
@@ -0,0 +1,126 @@
+<?php
+
+/**
+ * PHP Montgomery Modular Exponentiation Engine
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Math\BigInteger\Engines\PHP\Reductions;
+
+use phpseclib3\Math\BigInteger\Engines\PHP\Montgomery as Progenitor;
+
+/**
+ * PHP Montgomery Modular Exponentiation Engine
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Montgomery extends Progenitor
+{
+ /**
+ * Prepare a number for use in Montgomery Modular Reductions
+ *
+ * @param array $x
+ * @param array $n
+ * @param string $class
+ * @return array
+ */
+ protected static function prepareReduce(array $x, array $n, $class)
+ {
+ $lhs = new $class();
+ $lhs->value = array_merge(self::array_repeat(0, count($n)), $x);
+ $rhs = new $class();
+ $rhs->value = $n;
+
+ list(, $temp) = $lhs->divide($rhs);
+ return $temp->value;
+ }
+
+ /**
+ * Montgomery Multiply
+ *
+ * Interleaves the montgomery reduction and long multiplication algorithms together as described in
+ * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=13 HAC 14.36}
+ *
+ * @param array $x
+ * @param array $n
+ * @param string $class
+ * @return array
+ */
+ protected static function reduce(array $x, array $n, $class)
+ {
+ static $cache = [
+ self::VARIABLE => [],
+ self::DATA => []
+ ];
+
+ if (($key = array_search($n, $cache[self::VARIABLE])) === false) {
+ $key = count($cache[self::VARIABLE]);
+ $cache[self::VARIABLE][] = $x;
+ $cache[self::DATA][] = self::modInverse67108864($n, $class);
+ }
+
+ $k = count($n);
+
+ $result = [self::VALUE => $x];
+
+ for ($i = 0; $i < $k; ++$i) {
+ $temp = $result[self::VALUE][$i] * $cache[self::DATA][$key];
+ $temp = $temp - $class::BASE_FULL * ($class::BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31));
+ $temp = $class::regularMultiply([$temp], $n);
+ $temp = array_merge(self::array_repeat(0, $i), $temp);
+ $result = $class::addHelper($result[self::VALUE], false, $temp, false);
+ }
+
+ $result[self::VALUE] = array_slice($result[self::VALUE], $k);
+
+ if (self::compareHelper($result, false, $n, false) >= 0) {
+ $result = $class::subtractHelper($result[self::VALUE], false, $n, false);
+ }
+
+ return $result[self::VALUE];
+ }
+
+ /**
+ * Modular Inverse of a number mod 2**26 (eg. 67108864)
+ *
+ * Based off of the bnpInvDigit function implemented and justified in the following URL:
+ *
+ * {@link http://www-cs-students.stanford.edu/~tjw/jsbn/jsbn.js}
+ *
+ * The following URL provides more info:
+ *
+ * {@link http://groups.google.com/group/sci.crypt/msg/7a137205c1be7d85}
+ *
+ * As for why we do all the bitmasking... strange things can happen when converting from floats to ints. For
+ * instance, on some computers, var_dump((int) -4294967297) yields int(-1) and on others, it yields
+ * int(-2147483648). To avoid problems stemming from this, we use bitmasks to guarantee that ints aren't
+ * auto-converted to floats. The outermost bitmask is present because without it, there's no guarantee that
+ * the "residue" returned would be the so-called "common residue". We use fmod, in the last step, because the
+ * maximum possible $x is 26 bits and the maximum $result is 16 bits. Thus, we have to be able to handle up to
+ * 40 bits, which only 64-bit floating points will support.
+ *
+ * Thanks to Pedro Gimeno Fortea for input!
+ *
+ * @param array $x
+ * @param string $class
+ * @return int
+ */
+ protected static function modInverse67108864(array $x, $class) // 2**26 == 67,108,864
+ {
+ $x = -$x[0];
+ $result = $x & 0x3; // x**-1 mod 2**2
+ $result = ($result * (2 - $x * $result)) & 0xF; // x**-1 mod 2**4
+ $result = ($result * (2 - ($x & 0xFF) * $result)) & 0xFF; // x**-1 mod 2**8
+ $result = ($result * ((2 - ($x & 0xFFFF) * $result) & 0xFFFF)) & 0xFFFF; // x**-1 mod 2**16
+ $result = $class::BASE == 26 ?
+ fmod($result * (2 - fmod($x * $result, $class::BASE_FULL)), $class::BASE_FULL) : // x**-1 mod 2**26
+ ($result * (2 - ($x * $result) % $class::BASE_FULL)) % $class::BASE_FULL;
+ return $result & $class::MAX_DIGIT;
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/MontgomeryMult.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/MontgomeryMult.php
new file mode 100644
index 000000000..4fed3c3fa
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/MontgomeryMult.php
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * PHP Montgomery Modular Exponentiation Engine with interleaved multiplication
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Math\BigInteger\Engines\PHP\Reductions;
+
+use phpseclib3\Math\BigInteger\Engines\PHP;
+
+/**
+ * PHP Montgomery Modular Exponentiation Engine with interleaved multiplication
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class MontgomeryMult extends Montgomery
+{
+ /**
+ * Montgomery Multiply
+ *
+ * Interleaves the montgomery reduction and long multiplication algorithms together as described in
+ * {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=13 HAC 14.36}
+ *
+ * @see self::_prepMontgomery()
+ * @see self::_montgomery()
+ * @param array $x
+ * @param array $y
+ * @param array $m
+ * @param class-string<PHP> $class
+ * @return array
+ */
+ public static function multiplyReduce(array $x, array $y, array $m, $class)
+ {
+ // the following code, although not callable, can be run independently of the above code
+ // although the above code performed better in my benchmarks the following could might
+ // perform better under different circumstances. in lieu of deleting it it's just been
+ // made uncallable
+
+ static $cache = [
+ self::VARIABLE => [],
+ self::DATA => []
+ ];
+
+ if (($key = array_search($m, $cache[self::VARIABLE])) === false) {
+ $key = count($cache[self::VARIABLE]);
+ $cache[self::VARIABLE][] = $m;
+ $cache[self::DATA][] = self::modInverse67108864($m, $class);
+ }
+
+ $n = max(count($x), count($y), count($m));
+ $x = array_pad($x, $n, 0);
+ $y = array_pad($y, $n, 0);
+ $m = array_pad($m, $n, 0);
+ $a = [self::VALUE => self::array_repeat(0, $n + 1)];
+ for ($i = 0; $i < $n; ++$i) {
+ $temp = $a[self::VALUE][0] + $x[$i] * $y[0];
+ $temp = $temp - $class::BASE_FULL * ($class::BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31));
+ $temp = $temp * $cache[self::DATA][$key];
+ $temp = $temp - $class::BASE_FULL * ($class::BASE === 26 ? intval($temp / 0x4000000) : ($temp >> 31));
+ $temp = $class::addHelper($class::regularMultiply([$x[$i]], $y), false, $class::regularMultiply([$temp], $m), false);
+ $a = $class::addHelper($a[self::VALUE], false, $temp[self::VALUE], false);
+ $a[self::VALUE] = array_slice($a[self::VALUE], 1);
+ }
+ if (self::compareHelper($a[self::VALUE], false, $m, false) >= 0) {
+ $a = $class::subtractHelper($a[self::VALUE], false, $m, false);
+ }
+ return $a[self::VALUE];
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/PowerOfTwo.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/PowerOfTwo.php
new file mode 100644
index 000000000..9da133a14
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP/Reductions/PowerOfTwo.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * PHP Power of Two Modular Exponentiation Engine
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Math\BigInteger\Engines\PHP\Reductions;
+
+use phpseclib3\Math\BigInteger\Engines\PHP\Base;
+
+/**
+ * PHP Power Of Two Modular Exponentiation Engine
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class PowerOfTwo extends Base
+{
+ /**
+ * Prepare a number for use in Montgomery Modular Reductions
+ *
+ * @param array $x
+ * @param array $n
+ * @param string $class
+ * @return array
+ */
+ protected static function prepareReduce(array $x, array $n, $class)
+ {
+ return self::reduce($x, $n, $class);
+ }
+
+ /**
+ * Power Of Two Reduction
+ *
+ * @param array $x
+ * @param array $n
+ * @param string $class
+ * @return array
+ */
+ protected static function reduce(array $x, array $n, $class)
+ {
+ $lhs = new $class();
+ $lhs->value = $x;
+ $rhs = new $class();
+ $rhs->value = $n;
+
+ $temp = new $class();
+ $temp->value = [1];
+
+ $result = $lhs->bitwise_and($rhs->subtract($temp));
+ return $result->value;
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP32.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP32.php
new file mode 100644
index 000000000..3a775e7db
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP32.php
@@ -0,0 +1,371 @@
+<?php
+
+/**
+ * Pure-PHP 32-bit BigInteger Engine
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Math\BigInteger\Engines;
+
+/**
+ * Pure-PHP 32-bit Engine.
+ *
+ * Uses 64-bit floats if int size is 4 bits
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class PHP32 extends PHP
+{
+ // Constants used by PHP.php
+ const BASE = 26;
+ const BASE_FULL = 0x4000000;
+ const MAX_DIGIT = 0x3FFFFFF;
+ const MSB = 0x2000000;
+
+ /**
+ * MAX10 in greatest MAX10LEN satisfying
+ * MAX10 = 10**MAX10LEN <= 2**BASE.
+ */
+ const MAX10 = 10000000;
+
+ /**
+ * MAX10LEN in greatest MAX10LEN satisfying
+ * MAX10 = 10**MAX10LEN <= 2**BASE.
+ */
+ const MAX10LEN = 7;
+ const MAX_DIGIT2 = 4503599627370496;
+
+ /**
+ * Initialize a PHP32 BigInteger Engine instance
+ *
+ * @param int $base
+ * @see parent::initialize()
+ */
+ protected function initialize($base)
+ {
+ if ($base != 256 && $base != -256) {
+ return parent::initialize($base);
+ }
+
+ $val = $this->value;
+ $this->value = [];
+ $vals = &$this->value;
+ $i = strlen($val);
+ if (!$i) {
+ return;
+ }
+
+ while (true) {
+ $i -= 4;
+ if ($i < 0) {
+ if ($i == -4) {
+ break;
+ }
+ $val = substr($val, 0, 4 + $i);
+ $val = str_pad($val, 4, "\0", STR_PAD_LEFT);
+ if ($val == "\0\0\0\0") {
+ break;
+ }
+ $i = 0;
+ }
+ list(, $digit) = unpack('N', substr($val, $i, 4));
+ if ($digit < 0) {
+ $digit += 0xFFFFFFFF + 1;
+ }
+ $step = count($vals) & 3;
+ if ($step) {
+ $digit = (int) floor($digit / pow(2, 2 * $step));
+ }
+ if ($step != 3) {
+ $digit = (int) fmod($digit, static::BASE_FULL);
+ $i++;
+ }
+ $vals[] = $digit;
+ }
+ while (end($vals) === 0) {
+ array_pop($vals);
+ }
+ reset($vals);
+ }
+
+ /**
+ * Test for engine validity
+ *
+ * @see parent::__construct()
+ * @return bool
+ */
+ public static function isValidEngine()
+ {
+ return PHP_INT_SIZE >= 4 && !self::testJITOnWindows();
+ }
+
+ /**
+ * Adds two BigIntegers.
+ *
+ * @param PHP32 $y
+ * @return PHP32
+ */
+ public function add(PHP32 $y)
+ {
+ $temp = self::addHelper($this->value, $this->is_negative, $y->value, $y->is_negative);
+
+ return $this->convertToObj($temp);
+ }
+
+ /**
+ * Subtracts two BigIntegers.
+ *
+ * @param PHP32 $y
+ * @return PHP32
+ */
+ public function subtract(PHP32 $y)
+ {
+ $temp = self::subtractHelper($this->value, $this->is_negative, $y->value, $y->is_negative);
+
+ return $this->convertToObj($temp);
+ }
+
+ /**
+ * Multiplies two BigIntegers.
+ *
+ * @param PHP32 $y
+ * @return PHP32
+ */
+ public function multiply(PHP32 $y)
+ {
+ $temp = self::multiplyHelper($this->value, $this->is_negative, $y->value, $y->is_negative);
+
+ return $this->convertToObj($temp);
+ }
+
+ /**
+ * Divides two BigIntegers.
+ *
+ * Returns an array whose first element contains the quotient and whose second element contains the
+ * "common residue". If the remainder would be positive, the "common residue" and the remainder are the
+ * same. If the remainder would be negative, the "common residue" is equal to the sum of the remainder
+ * and the divisor (basically, the "common residue" is the first positive modulo).
+ *
+ * @param PHP32 $y
+ * @return array{PHP32, PHP32}
+ */
+ public function divide(PHP32 $y)
+ {
+ return $this->divideHelper($y);
+ }
+
+ /**
+ * Calculates modular inverses.
+ *
+ * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses.
+ * @param PHP32 $n
+ * @return false|PHP32
+ */
+ public function modInverse(PHP32 $n)
+ {
+ return $this->modInverseHelper($n);
+ }
+
+ /**
+ * Calculates modular inverses.
+ *
+ * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses.
+ * @param PHP32 $n
+ * @return PHP32[]
+ */
+ public function extendedGCD(PHP32 $n)
+ {
+ return $this->extendedGCDHelper($n);
+ }
+
+ /**
+ * Calculates the greatest common divisor
+ *
+ * Say you have 693 and 609. The GCD is 21.
+ *
+ * @param PHP32 $n
+ * @return PHP32
+ */
+ public function gcd(PHP32 $n)
+ {
+ return $this->extendedGCD($n)['gcd'];
+ }
+
+ /**
+ * Logical And
+ *
+ * @param PHP32 $x
+ * @return PHP32
+ */
+ public function bitwise_and(PHP32 $x)
+ {
+ return $this->bitwiseAndHelper($x);
+ }
+
+ /**
+ * Logical Or
+ *
+ * @param PHP32 $x
+ * @return PHP32
+ */
+ public function bitwise_or(PHP32 $x)
+ {
+ return $this->bitwiseOrHelper($x);
+ }
+
+ /**
+ * Logical Exclusive Or
+ *
+ * @param PHP32 $x
+ * @return PHP32
+ */
+ public function bitwise_xor(PHP32 $x)
+ {
+ return $this->bitwiseXorHelper($x);
+ }
+
+ /**
+ * Compares two numbers.
+ *
+ * Although one might think !$x->compare($y) means $x != $y, it, in fact, means the opposite. The reason for this is
+ * demonstrated thusly:
+ *
+ * $x > $y: $x->compare($y) > 0
+ * $x < $y: $x->compare($y) < 0
+ * $x == $y: $x->compare($y) == 0
+ *
+ * Note how the same comparison operator is used. If you want to test for equality, use $x->equals($y).
+ *
+ * {@internal Could return $this->subtract($x), but that's not as fast as what we do do.}
+ *
+ * @param PHP32 $y
+ * @return int in case < 0 if $this is less than $y; > 0 if $this is greater than $y, and 0 if they are equal.
+ * @see self::equals()
+ */
+ public function compare(PHP32 $y)
+ {
+ return $this->compareHelper($this->value, $this->is_negative, $y->value, $y->is_negative);
+ }
+
+ /**
+ * Tests the equality of two numbers.
+ *
+ * If you need to see if one number is greater than or less than another number, use BigInteger::compare()
+ *
+ * @param PHP32 $x
+ * @return bool
+ */
+ public function equals(PHP32 $x)
+ {
+ return $this->value === $x->value && $this->is_negative == $x->is_negative;
+ }
+
+ /**
+ * Performs modular exponentiation.
+ *
+ * @param PHP32 $e
+ * @param PHP32 $n
+ * @return PHP32
+ */
+ public function modPow(PHP32 $e, PHP32 $n)
+ {
+ return $this->powModOuter($e, $n);
+ }
+
+ /**
+ * Performs modular exponentiation.
+ *
+ * Alias for modPow().
+ *
+ * @param PHP32 $e
+ * @param PHP32 $n
+ * @return PHP32
+ */
+ public function powMod(PHP32 $e, PHP32 $n)
+ {
+ return $this->powModOuter($e, $n);
+ }
+
+ /**
+ * Generate a random prime number between a range
+ *
+ * If there's not a prime within the given range, false will be returned.
+ *
+ * @param PHP32 $min
+ * @param PHP32 $max
+ * @return false|PHP32
+ */
+ public static function randomRangePrime(PHP32 $min, PHP32 $max)
+ {
+ return self::randomRangePrimeOuter($min, $max);
+ }
+
+ /**
+ * Generate a random number between a range
+ *
+ * Returns a random number between $min and $max where $min and $max
+ * can be defined using one of the two methods:
+ *
+ * BigInteger::randomRange($min, $max)
+ * BigInteger::randomRange($max, $min)
+ *
+ * @param PHP32 $min
+ * @param PHP32 $max
+ * @return PHP32
+ */
+ public static function randomRange(PHP32 $min, PHP32 $max)
+ {
+ return self::randomRangeHelper($min, $max);
+ }
+
+ /**
+ * Performs exponentiation.
+ *
+ * @param PHP32 $n
+ * @return PHP32
+ */
+ public function pow(PHP32 $n)
+ {
+ return $this->powHelper($n);
+ }
+
+ /**
+ * Return the minimum BigInteger between an arbitrary number of BigIntegers.
+ *
+ * @param PHP32 ...$nums
+ * @return PHP32
+ */
+ public static function min(PHP32 ...$nums)
+ {
+ return self::minHelper($nums);
+ }
+
+ /**
+ * Return the maximum BigInteger between an arbitrary number of BigIntegers.
+ *
+ * @param PHP32 ...$nums
+ * @return PHP32
+ */
+ public static function max(PHP32 ...$nums)
+ {
+ return self::maxHelper($nums);
+ }
+
+ /**
+ * Tests BigInteger to see if it is between two integers, inclusive
+ *
+ * @param PHP32 $min
+ * @param PHP32 $max
+ * @return bool
+ */
+ public function between(PHP32 $min, PHP32 $max)
+ {
+ return $this->compare($min) >= 0 && $this->compare($max) <= 0;
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP64.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP64.php
new file mode 100644
index 000000000..70a2e173b
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BigInteger/Engines/PHP64.php
@@ -0,0 +1,372 @@
+<?php
+
+/**
+ * Pure-PHP 64-bit BigInteger Engine
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Math\BigInteger\Engines;
+
+/**
+ * Pure-PHP 64-bit Engine.
+ *
+ * Uses 64-bit integers if int size is 8 bits
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class PHP64 extends PHP
+{
+ // Constants used by PHP.php
+ const BASE = 31;
+ const BASE_FULL = 0x80000000;
+ const MAX_DIGIT = 0x7FFFFFFF;
+ const MSB = 0x40000000;
+
+ /**
+ * MAX10 in greatest MAX10LEN satisfying
+ * MAX10 = 10**MAX10LEN <= 2**BASE.
+ */
+ const MAX10 = 1000000000;
+
+ /**
+ * MAX10LEN in greatest MAX10LEN satisfying
+ * MAX10 = 10**MAX10LEN <= 2**BASE.
+ */
+ const MAX10LEN = 9;
+ const MAX_DIGIT2 = 4611686018427387904;
+
+ /**
+ * Initialize a PHP64 BigInteger Engine instance
+ *
+ * @param int $base
+ * @see parent::initialize()
+ */
+ protected function initialize($base)
+ {
+ if ($base != 256 && $base != -256) {
+ return parent::initialize($base);
+ }
+
+ $val = $this->value;
+ $this->value = [];
+ $vals = &$this->value;
+ $i = strlen($val);
+ if (!$i) {
+ return;
+ }
+
+ while (true) {
+ $i -= 4;
+ if ($i < 0) {
+ if ($i == -4) {
+ break;
+ }
+ $val = substr($val, 0, 4 + $i);
+ $val = str_pad($val, 4, "\0", STR_PAD_LEFT);
+ if ($val == "\0\0\0\0") {
+ break;
+ }
+ $i = 0;
+ }
+ list(, $digit) = unpack('N', substr($val, $i, 4));
+ $step = count($vals) & 7;
+ if (!$step) {
+ $digit &= static::MAX_DIGIT;
+ $i++;
+ } else {
+ $shift = 8 - $step;
+ $digit >>= $shift;
+ $shift = 32 - $shift;
+ $digit &= (1 << $shift) - 1;
+ $temp = $i > 0 ? ord($val[$i - 1]) : 0;
+ $digit |= ($temp << $shift) & 0x7F000000;
+ }
+ $vals[] = $digit;
+ }
+ while (end($vals) === 0) {
+ array_pop($vals);
+ }
+ reset($vals);
+ }
+
+ /**
+ * Test for engine validity
+ *
+ * @see parent::__construct()
+ * @return bool
+ */
+ public static function isValidEngine()
+ {
+ return PHP_INT_SIZE >= 8 && !self::testJITOnWindows();
+ }
+
+ /**
+ * Adds two BigIntegers.
+ *
+ * @param PHP64 $y
+ * @return PHP64
+ */
+ public function add(PHP64 $y)
+ {
+ $temp = self::addHelper($this->value, $this->is_negative, $y->value, $y->is_negative);
+
+ return $this->convertToObj($temp);
+ }
+
+ /**
+ * Subtracts two BigIntegers.
+ *
+ * @param PHP64 $y
+ * @return PHP64
+ */
+ public function subtract(PHP64 $y)
+ {
+ $temp = self::subtractHelper($this->value, $this->is_negative, $y->value, $y->is_negative);
+
+ return $this->convertToObj($temp);
+ }
+
+ /**
+ * Multiplies two BigIntegers.
+ *
+ * @param PHP64 $y
+ * @return PHP64
+ */
+ public function multiply(PHP64 $y)
+ {
+ $temp = self::multiplyHelper($this->value, $this->is_negative, $y->value, $y->is_negative);
+
+ return $this->convertToObj($temp);
+ }
+
+ /**
+ * Divides two BigIntegers.
+ *
+ * Returns an array whose first element contains the quotient and whose second element contains the
+ * "common residue". If the remainder would be positive, the "common residue" and the remainder are the
+ * same. If the remainder would be negative, the "common residue" is equal to the sum of the remainder
+ * and the divisor (basically, the "common residue" is the first positive modulo).
+ *
+ * @param PHP64 $y
+ * @return array{PHP64, PHP64}
+ */
+ public function divide(PHP64 $y)
+ {
+ return $this->divideHelper($y);
+ }
+
+ /**
+ * Calculates modular inverses.
+ *
+ * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses.
+ * @param PHP64 $n
+ * @return false|PHP64
+ */
+ public function modInverse(PHP64 $n)
+ {
+ return $this->modInverseHelper($n);
+ }
+
+ /**
+ * Calculates modular inverses.
+ *
+ * Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses.
+ * @param PHP64 $n
+ * @return PHP64[]
+ */
+ public function extendedGCD(PHP64 $n)
+ {
+ return $this->extendedGCDHelper($n);
+ }
+
+ /**
+ * Calculates the greatest common divisor
+ *
+ * Say you have 693 and 609. The GCD is 21.
+ *
+ * @param PHP64 $n
+ * @return PHP64
+ */
+ public function gcd(PHP64 $n)
+ {
+ return $this->extendedGCD($n)['gcd'];
+ }
+
+ /**
+ * Logical And
+ *
+ * @param PHP64 $x
+ * @return PHP64
+ */
+ public function bitwise_and(PHP64 $x)
+ {
+ return $this->bitwiseAndHelper($x);
+ }
+
+ /**
+ * Logical Or
+ *
+ * @param PHP64 $x
+ * @return PHP64
+ */
+ public function bitwise_or(PHP64 $x)
+ {
+ return $this->bitwiseOrHelper($x);
+ }
+
+ /**
+ * Logical Exclusive Or
+ *
+ * @param PHP64 $x
+ * @return PHP64
+ */
+ public function bitwise_xor(PHP64 $x)
+ {
+ return $this->bitwiseXorHelper($x);
+ }
+
+ /**
+ * Compares two numbers.
+ *
+ * Although one might think !$x->compare($y) means $x != $y, it, in fact, means the opposite. The reason for this is
+ * demonstrated thusly:
+ *
+ * $x > $y: $x->compare($y) > 0
+ * $x < $y: $x->compare($y) < 0
+ * $x == $y: $x->compare($y) == 0
+ *
+ * Note how the same comparison operator is used. If you want to test for equality, use $x->equals($y).
+ *
+ * {@internal Could return $this->subtract($x), but that's not as fast as what we do do.}
+ *
+ * @param PHP64 $y
+ * @return int in case < 0 if $this is less than $y; > 0 if $this is greater than $y, and 0 if they are equal.
+ * @see self::equals()
+ */
+ public function compare(PHP64 $y)
+ {
+ return parent::compareHelper($this->value, $this->is_negative, $y->value, $y->is_negative);
+ }
+
+ /**
+ * Tests the equality of two numbers.
+ *
+ * If you need to see if one number is greater than or less than another number, use BigInteger::compare()
+ *
+ * @param PHP64 $x
+ * @return bool
+ */
+ public function equals(PHP64 $x)
+ {
+ return $this->value === $x->value && $this->is_negative == $x->is_negative;
+ }
+
+ /**
+ * Performs modular exponentiation.
+ *
+ * @param PHP64 $e
+ * @param PHP64 $n
+ * @return PHP64
+ */
+ public function modPow(PHP64 $e, PHP64 $n)
+ {
+ return $this->powModOuter($e, $n);
+ }
+
+ /**
+ * Performs modular exponentiation.
+ *
+ * Alias for modPow().
+ *
+ * @param PHP64 $e
+ * @param PHP64 $n
+ * @return PHP64|false
+ */
+ public function powMod(PHP64 $e, PHP64 $n)
+ {
+ return $this->powModOuter($e, $n);
+ }
+
+ /**
+ * Generate a random prime number between a range
+ *
+ * If there's not a prime within the given range, false will be returned.
+ *
+ * @param PHP64 $min
+ * @param PHP64 $max
+ * @return false|PHP64
+ */
+ public static function randomRangePrime(PHP64 $min, PHP64 $max)
+ {
+ return self::randomRangePrimeOuter($min, $max);
+ }
+
+ /**
+ * Generate a random number between a range
+ *
+ * Returns a random number between $min and $max where $min and $max
+ * can be defined using one of the two methods:
+ *
+ * BigInteger::randomRange($min, $max)
+ * BigInteger::randomRange($max, $min)
+ *
+ * @param PHP64 $min
+ * @param PHP64 $max
+ * @return PHP64
+ */
+ public static function randomRange(PHP64 $min, PHP64 $max)
+ {
+ return self::randomRangeHelper($min, $max);
+ }
+
+ /**
+ * Performs exponentiation.
+ *
+ * @param PHP64 $n
+ * @return PHP64
+ */
+ public function pow(PHP64 $n)
+ {
+ return $this->powHelper($n);
+ }
+
+ /**
+ * Return the minimum BigInteger between an arbitrary number of BigIntegers.
+ *
+ * @param PHP64 ...$nums
+ * @return PHP64
+ */
+ public static function min(PHP64 ...$nums)
+ {
+ return self::minHelper($nums);
+ }
+
+ /**
+ * Return the maximum BigInteger between an arbitrary number of BigIntegers.
+ *
+ * @param PHP64 ...$nums
+ * @return PHP64
+ */
+ public static function max(PHP64 ...$nums)
+ {
+ return self::maxHelper($nums);
+ }
+
+ /**
+ * Tests BigInteger to see if it is between two integers, inclusive
+ *
+ * @param PHP64 $min
+ * @param PHP64 $max
+ * @return bool
+ */
+ public function between(PHP64 $min, PHP64 $max)
+ {
+ return $this->compare($min) >= 0 && $this->compare($max) <= 0;
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BinaryField.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BinaryField.php
new file mode 100644
index 000000000..5da8c937e
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BinaryField.php
@@ -0,0 +1,203 @@
+<?php
+
+/**
+ * Binary Finite Fields
+ *
+ * Utilizes the factory design pattern
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ */
+
+namespace phpseclib3\Math;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Math\BinaryField\Integer;
+use phpseclib3\Math\Common\FiniteField;
+
+/**
+ * Binary Finite Fields
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class BinaryField extends FiniteField
+{
+ /**
+ * Instance Counter
+ *
+ * @var int
+ */
+ private static $instanceCounter = 0;
+
+ /**
+ * Keeps track of current instance
+ *
+ * @var int
+ */
+ protected $instanceID;
+
+ /** @var BigInteger */
+ private $randomMax;
+
+ /**
+ * Default constructor
+ */
+ public function __construct(...$indices)
+ {
+ $m = array_shift($indices);
+ if ($m > 571) {
+ /* sect571r1 and sect571k1 are the largest binary curves that https://www.secg.org/sec2-v2.pdf defines
+ altho theoretically there may be legit reasons to use binary finite fields with larger degrees
+ imposing a limit on the maximum size is both reasonable and precedented. in particular,
+ http://tools.ietf.org/html/rfc4253#section-6.1 (The Secure Shell (SSH) Transport Layer Protocol) says
+ "implementations SHOULD check that the packet length is reasonable in order for the implementation to
+ avoid denial of service and/or buffer overflow attacks" */
+ throw new \OutOfBoundsException('Degrees larger than 571 are not supported');
+ }
+ $val = str_repeat('0', $m) . '1';
+ foreach ($indices as $index) {
+ $val[$index] = '1';
+ }
+ $modulo = static::base2ToBase256(strrev($val));
+
+ $mStart = 2 * $m - 2;
+ $t = ceil($m / 8);
+ $finalMask = chr((1 << ($m % 8)) - 1);
+ if ($finalMask == "\0") {
+ $finalMask = "\xFF";
+ }
+ $bitLen = $mStart + 1;
+ $pad = ceil($bitLen / 8);
+ $h = $bitLen & 7;
+ $h = $h ? 8 - $h : 0;
+
+ $r = rtrim(substr($val, 0, -1), '0');
+ $u = [static::base2ToBase256(strrev($r))];
+ for ($i = 1; $i < 8; $i++) {
+ $u[] = static::base2ToBase256(strrev(str_repeat('0', $i) . $r));
+ }
+
+ // implements algorithm 2.40 (in section 2.3.5) in "Guide to Elliptic Curve Cryptography"
+ // with W = 8
+ $reduce = function ($c) use ($u, $mStart, $m, $t, $finalMask, $pad, $h) {
+ $c = str_pad($c, $pad, "\0", STR_PAD_LEFT);
+ for ($i = $mStart; $i >= $m;) {
+ $g = $h >> 3;
+ $mask = $h & 7;
+ $mask = $mask ? 1 << (7 - $mask) : 0x80;
+ for (; $mask > 0; $mask >>= 1, $i--, $h++) {
+ if (ord($c[$g]) & $mask) {
+ $temp = $i - $m;
+ $j = $temp >> 3;
+ $k = $temp & 7;
+ $t1 = $j ? substr($c, 0, -$j) : $c;
+ $length = strlen($t1);
+ if ($length) {
+ $t2 = str_pad($u[$k], $length, "\0", STR_PAD_LEFT);
+ $temp = $t1 ^ $t2;
+ $c = $j ? substr_replace($c, $temp, 0, $length) : $temp;
+ }
+ }
+ }
+ }
+ $c = substr($c, -$t);
+ if (strlen($c) == $t) {
+ $c[0] = $c[0] & $finalMask;
+ }
+ return ltrim($c, "\0");
+ };
+
+ $this->instanceID = self::$instanceCounter++;
+ Integer::setModulo($this->instanceID, $modulo);
+ Integer::setRecurringModuloFunction($this->instanceID, $reduce);
+
+ $this->randomMax = new BigInteger($modulo, 2);
+ }
+
+ /**
+ * Returns an instance of a dynamically generated PrimeFieldInteger class
+ *
+ * @param string $num
+ * @return Integer
+ */
+ public function newInteger($num)
+ {
+ return new Integer($this->instanceID, $num instanceof BigInteger ? $num->toBytes() : $num);
+ }
+
+ /**
+ * Returns an integer on the finite field between one and the prime modulo
+ *
+ * @return Integer
+ */
+ public function randomInteger()
+ {
+ static $one;
+ if (!isset($one)) {
+ $one = new BigInteger(1);
+ }
+
+ return new Integer($this->instanceID, BigInteger::randomRange($one, $this->randomMax)->toBytes());
+ }
+
+ /**
+ * Returns the length of the modulo in bytes
+ *
+ * @return int
+ */
+ public function getLengthInBytes()
+ {
+ return strlen(Integer::getModulo($this->instanceID));
+ }
+
+ /**
+ * Returns the length of the modulo in bits
+ *
+ * @return int
+ */
+ public function getLength()
+ {
+ return strlen(Integer::getModulo($this->instanceID)) << 3;
+ }
+
+ /**
+ * Converts a base-2 string to a base-256 string
+ *
+ * @param string $x
+ * @param int|null $size
+ * @return string
+ */
+ public static function base2ToBase256($x, $size = null)
+ {
+ $str = Strings::bits2bin($x);
+
+ $pad = strlen($x) >> 3;
+ if (strlen($x) & 3) {
+ $pad++;
+ }
+ $str = str_pad($str, $pad, "\0", STR_PAD_LEFT);
+ if (isset($size)) {
+ $str = str_pad($str, $size, "\0", STR_PAD_LEFT);
+ }
+
+ return $str;
+ }
+
+ /**
+ * Converts a base-256 string to a base-2 string
+ *
+ * @param string $x
+ * @return string
+ */
+ public static function base256ToBase2($x)
+ {
+ if (function_exists('gmp_import')) {
+ return gmp_strval(gmp_import($x), 2);
+ }
+
+ return Strings::bin2bits($x);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/BinaryField/Integer.php b/vendor/phpseclib/phpseclib/phpseclib/Math/BinaryField/Integer.php
new file mode 100644
index 000000000..8e880589c
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/BinaryField/Integer.php
@@ -0,0 +1,516 @@
+<?php
+
+/**
+ * Binary Finite Fields
+ *
+ * In a binary finite field numbers are actually polynomial equations. If you
+ * represent the number as a sequence of bits you get a sequence of 1's or 0's.
+ * These 1's or 0's represent the coefficients of the x**n, where n is the
+ * location of the given bit. When you add numbers over a binary finite field
+ * the result should have a coefficient of 1 or 0 as well. Hence addition
+ * and subtraction become the same operation as XOR.
+ * eg. 1 + 1 + 1 == 3 % 2 == 1 or 0 - 1 == -1 % 2 == 1
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ */
+
+namespace phpseclib3\Math\BinaryField;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Math\BigInteger;
+use phpseclib3\Math\BinaryField;
+use phpseclib3\Math\Common\FiniteField\Integer as Base;
+
+/**
+ * Binary Finite Fields
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class Integer extends Base
+{
+ /**
+ * Holds the BinaryField's value
+ *
+ * @var string
+ */
+ protected $value;
+
+ /**
+ * Keeps track of current instance
+ *
+ * @var int
+ */
+ protected $instanceID;
+
+ /**
+ * Holds the PrimeField's modulo
+ *
+ * @var array<int, string>
+ */
+ protected static $modulo;
+
+ /**
+ * Holds a pre-generated function to perform modulo reductions
+ *
+ * @var callable[]
+ */
+ protected static $reduce;
+
+ /**
+ * Default constructor
+ */
+ public function __construct($instanceID, $num = '')
+ {
+ $this->instanceID = $instanceID;
+ if (!strlen($num)) {
+ $this->value = '';
+ } else {
+ $reduce = static::$reduce[$instanceID];
+ $this->value = $reduce($num);
+ }
+ }
+
+ /**
+ * Set the modulo for a given instance
+ * @param int $instanceID
+ * @param string $modulo
+ */
+ public static function setModulo($instanceID, $modulo)
+ {
+ static::$modulo[$instanceID] = $modulo;
+ }
+
+ /**
+ * Set the modulo for a given instance
+ */
+ public static function setRecurringModuloFunction($instanceID, callable $function)
+ {
+ static::$reduce[$instanceID] = $function;
+ }
+
+ /**
+ * Tests a parameter to see if it's of the right instance
+ *
+ * Throws an exception if the incorrect class is being utilized
+ */
+ private static function checkInstance(self $x, self $y)
+ {
+ if ($x->instanceID != $y->instanceID) {
+ throw new \UnexpectedValueException('The instances of the two BinaryField\Integer objects do not match');
+ }
+ }
+
+ /**
+ * Tests the equality of two numbers.
+ *
+ * @return bool
+ */
+ public function equals(self $x)
+ {
+ static::checkInstance($this, $x);
+
+ return $this->value == $x->value;
+ }
+
+ /**
+ * Compares two numbers.
+ *
+ * @return int
+ */
+ public function compare(self $x)
+ {
+ static::checkInstance($this, $x);
+
+ $a = $this->value;
+ $b = $x->value;
+
+ $length = max(strlen($a), strlen($b));
+
+ $a = str_pad($a, $length, "\0", STR_PAD_LEFT);
+ $b = str_pad($b, $length, "\0", STR_PAD_LEFT);
+
+ return strcmp($a, $b);
+ }
+
+ /**
+ * Returns the degree of the polynomial
+ *
+ * @param string $x
+ * @return int
+ */
+ private static function deg($x)
+ {
+ $x = ltrim($x, "\0");
+ $xbit = decbin(ord($x[0]));
+ $xlen = $xbit == '0' ? 0 : strlen($xbit);
+ $len = strlen($x);
+ if (!$len) {
+ return -1;
+ }
+ return 8 * strlen($x) - 9 + $xlen;
+ }
+
+ /**
+ * Perform polynomial division
+ *
+ * @return string[]
+ * @link https://en.wikipedia.org/wiki/Polynomial_greatest_common_divisor#Euclidean_division
+ */
+ private static function polynomialDivide($x, $y)
+ {
+ // in wikipedia's description of the algorithm, lc() is the leading coefficient. over a binary field that's
+ // always going to be 1.
+
+ $q = chr(0);
+ $d = static::deg($y);
+ $r = $x;
+ while (($degr = static::deg($r)) >= $d) {
+ $s = '1' . str_repeat('0', $degr - $d);
+ $s = BinaryField::base2ToBase256($s);
+ $length = max(strlen($s), strlen($q));
+ $q = !isset($q) ? $s :
+ str_pad($q, $length, "\0", STR_PAD_LEFT) ^
+ str_pad($s, $length, "\0", STR_PAD_LEFT);
+ $s = static::polynomialMultiply($s, $y);
+ $length = max(strlen($r), strlen($s));
+ $r = str_pad($r, $length, "\0", STR_PAD_LEFT) ^
+ str_pad($s, $length, "\0", STR_PAD_LEFT);
+ }
+
+ return [ltrim($q, "\0"), ltrim($r, "\0")];
+ }
+
+ /**
+ * Perform polynomial multiplation in the traditional way
+ *
+ * @return string
+ * @link https://en.wikipedia.org/wiki/Finite_field_arithmetic#Multiplication
+ */
+ private static function regularPolynomialMultiply($x, $y)
+ {
+ $precomputed = [ltrim($x, "\0")];
+ $x = strrev(BinaryField::base256ToBase2($x));
+ $y = strrev(BinaryField::base256ToBase2($y));
+ if (strlen($x) == strlen($y)) {
+ $length = strlen($x);
+ } else {
+ $length = max(strlen($x), strlen($y));
+ $x = str_pad($x, $length, '0');
+ $y = str_pad($y, $length, '0');
+ }
+ $result = str_repeat('0', 2 * $length - 1);
+ $result = BinaryField::base2ToBase256($result);
+ $size = strlen($result);
+ $x = strrev($x);
+
+ // precompute left shift 1 through 7
+ for ($i = 1; $i < 8; $i++) {
+ $precomputed[$i] = BinaryField::base2ToBase256($x . str_repeat('0', $i));
+ }
+ for ($i = 0; $i < strlen($y); $i++) {
+ if ($y[$i] == '1') {
+ $temp = $precomputed[$i & 7] . str_repeat("\0", $i >> 3);
+ $result ^= str_pad($temp, $size, "\0", STR_PAD_LEFT);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Perform polynomial multiplation
+ *
+ * Uses karatsuba multiplication to reduce x-bit multiplications to a series of 32-bit multiplications
+ *
+ * @return string
+ * @link https://en.wikipedia.org/wiki/Karatsuba_algorithm
+ */
+ private static function polynomialMultiply($x, $y)
+ {
+ if (strlen($x) == strlen($y)) {
+ $length = strlen($x);
+ } else {
+ $length = max(strlen($x), strlen($y));
+ $x = str_pad($x, $length, "\0", STR_PAD_LEFT);
+ $y = str_pad($y, $length, "\0", STR_PAD_LEFT);
+ }
+
+ switch (true) {
+ case PHP_INT_SIZE == 8 && $length <= 4:
+ return $length != 4 ?
+ self::subMultiply(str_pad($x, 4, "\0", STR_PAD_LEFT), str_pad($y, 4, "\0", STR_PAD_LEFT)) :
+ self::subMultiply($x, $y);
+ case PHP_INT_SIZE == 4 || $length > 32:
+ return self::regularPolynomialMultiply($x, $y);
+ }
+
+ $m = $length >> 1;
+
+ $x1 = substr($x, 0, -$m);
+ $x0 = substr($x, -$m);
+ $y1 = substr($y, 0, -$m);
+ $y0 = substr($y, -$m);
+
+ $z2 = self::polynomialMultiply($x1, $y1);
+ $z0 = self::polynomialMultiply($x0, $y0);
+ $z1 = self::polynomialMultiply(
+ self::subAdd2($x1, $x0),
+ self::subAdd2($y1, $y0)
+ );
+
+ $z1 = self::subAdd3($z1, $z2, $z0);
+
+ $xy = self::subAdd3(
+ $z2 . str_repeat("\0", 2 * $m),
+ $z1 . str_repeat("\0", $m),
+ $z0
+ );
+
+ return ltrim($xy, "\0");
+ }
+
+ /**
+ * Perform polynomial multiplication on 2x 32-bit numbers, returning
+ * a 64-bit number
+ *
+ * @param string $x
+ * @param string $y
+ * @return string
+ * @link https://www.bearssl.org/constanttime.html#ghash-for-gcm
+ */
+ private static function subMultiply($x, $y)
+ {
+ $x = unpack('N', $x)[1];
+ $y = unpack('N', $y)[1];
+
+ $x0 = $x & 0x11111111;
+ $x1 = $x & 0x22222222;
+ $x2 = $x & 0x44444444;
+ $x3 = $x & 0x88888888;
+
+ $y0 = $y & 0x11111111;
+ $y1 = $y & 0x22222222;
+ $y2 = $y & 0x44444444;
+ $y3 = $y & 0x88888888;
+
+ $z0 = ($x0 * $y0) ^ ($x1 * $y3) ^ ($x2 * $y2) ^ ($x3 * $y1);
+ $z1 = ($x0 * $y1) ^ ($x1 * $y0) ^ ($x2 * $y3) ^ ($x3 * $y2);
+ $z2 = ($x0 * $y2) ^ ($x1 * $y1) ^ ($x2 * $y0) ^ ($x3 * $y3);
+ $z3 = ($x0 * $y3) ^ ($x1 * $y2) ^ ($x2 * $y1) ^ ($x3 * $y0);
+
+ $z0 &= 0x1111111111111111;
+ $z1 &= 0x2222222222222222;
+ $z2 &= 0x4444444444444444;
+ $z3 &= -8608480567731124088; // 0x8888888888888888 gets interpreted as a float
+
+ $z = $z0 | $z1 | $z2 | $z3;
+
+ return pack('J', $z);
+ }
+
+ /**
+ * Adds two numbers
+ *
+ * @param string $x
+ * @param string $y
+ * @return string
+ */
+ private static function subAdd2($x, $y)
+ {
+ $length = max(strlen($x), strlen($y));
+ $x = str_pad($x, $length, "\0", STR_PAD_LEFT);
+ $y = str_pad($y, $length, "\0", STR_PAD_LEFT);
+ return $x ^ $y;
+ }
+
+ /**
+ * Adds three numbers
+ *
+ * @param string $x
+ * @param string $y
+ * @return string
+ */
+ private static function subAdd3($x, $y, $z)
+ {
+ $length = max(strlen($x), strlen($y), strlen($z));
+ $x = str_pad($x, $length, "\0", STR_PAD_LEFT);
+ $y = str_pad($y, $length, "\0", STR_PAD_LEFT);
+ $z = str_pad($z, $length, "\0", STR_PAD_LEFT);
+ return $x ^ $y ^ $z;
+ }
+
+ /**
+ * Adds two BinaryFieldIntegers.
+ *
+ * @return static
+ */
+ public function add(self $y)
+ {
+ static::checkInstance($this, $y);
+
+ $length = strlen(static::$modulo[$this->instanceID]);
+
+ $x = str_pad($this->value, $length, "\0", STR_PAD_LEFT);
+ $y = str_pad($y->value, $length, "\0", STR_PAD_LEFT);
+
+ return new static($this->instanceID, $x ^ $y);
+ }
+
+ /**
+ * Subtracts two BinaryFieldIntegers.
+ *
+ * @return static
+ */
+ public function subtract(self $x)
+ {
+ return $this->add($x);
+ }
+
+ /**
+ * Multiplies two BinaryFieldIntegers.
+ *
+ * @return static
+ */
+ public function multiply(self $y)
+ {
+ static::checkInstance($this, $y);
+
+ return new static($this->instanceID, static::polynomialMultiply($this->value, $y->value));
+ }
+
+ /**
+ * Returns the modular inverse of a BinaryFieldInteger
+ *
+ * @return static
+ */
+ public function modInverse()
+ {
+ $remainder0 = static::$modulo[$this->instanceID];
+ $remainder1 = $this->value;
+
+ if ($remainder1 == '') {
+ return new static($this->instanceID);
+ }
+
+ $aux0 = "\0";
+ $aux1 = "\1";
+ while ($remainder1 != "\1") {
+ list($q, $r) = static::polynomialDivide($remainder0, $remainder1);
+ $remainder0 = $remainder1;
+ $remainder1 = $r;
+ // the auxiliary in row n is given by the sum of the auxiliary in
+ // row n-2 and the product of the quotient and the auxiliary in row
+ // n-1
+ $temp = static::polynomialMultiply($aux1, $q);
+ $aux = str_pad($aux0, strlen($temp), "\0", STR_PAD_LEFT) ^
+ str_pad($temp, strlen($aux0), "\0", STR_PAD_LEFT);
+ $aux0 = $aux1;
+ $aux1 = $aux;
+ }
+
+ $temp = new static($this->instanceID);
+ $temp->value = ltrim($aux1, "\0");
+ return $temp;
+ }
+
+ /**
+ * Divides two PrimeFieldIntegers.
+ *
+ * @return static
+ */
+ public function divide(self $x)
+ {
+ static::checkInstance($this, $x);
+
+ $x = $x->modInverse();
+ return $this->multiply($x);
+ }
+
+ /**
+ * Negate
+ *
+ * A negative number can be written as 0-12. With modulos, 0 is the same thing as the modulo
+ * so 0-12 is the same thing as modulo-12
+ *
+ * @return object
+ */
+ public function negate()
+ {
+ $x = str_pad($this->value, strlen(static::$modulo[$this->instanceID]), "\0", STR_PAD_LEFT);
+
+ return new static($this->instanceID, $x ^ static::$modulo[$this->instanceID]);
+ }
+
+ /**
+ * Returns the modulo
+ *
+ * @return string
+ */
+ public static function getModulo($instanceID)
+ {
+ return static::$modulo[$instanceID];
+ }
+
+ /**
+ * Converts an Integer to a byte string (eg. base-256).
+ *
+ * @return string
+ */
+ public function toBytes()
+ {
+ return str_pad($this->value, strlen(static::$modulo[$this->instanceID]), "\0", STR_PAD_LEFT);
+ }
+
+ /**
+ * Converts an Integer to a hex string (eg. base-16).
+ *
+ * @return string
+ */
+ public function toHex()
+ {
+ return Strings::bin2hex($this->toBytes());
+ }
+
+ /**
+ * Converts an Integer to a bit string (eg. base-2).
+ *
+ * @return string
+ */
+ public function toBits()
+ {
+ //return str_pad(BinaryField::base256ToBase2($this->value), strlen(static::$modulo[$this->instanceID]), '0', STR_PAD_LEFT);
+ return BinaryField::base256ToBase2($this->value);
+ }
+
+ /**
+ * Converts an Integer to a BigInteger
+ *
+ * @return string
+ */
+ public function toBigInteger()
+ {
+ return new BigInteger($this->value, 256);
+ }
+
+ /**
+ * __toString() magic method
+ *
+ */
+ public function __toString()
+ {
+ return (string) $this->toBigInteger();
+ }
+
+ /**
+ * __debugInfo() magic method
+ *
+ */
+ public function __debugInfo()
+ {
+ return ['value' => $this->toHex()];
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/Common/FiniteField.php b/vendor/phpseclib/phpseclib/phpseclib/Math/Common/FiniteField.php
new file mode 100644
index 000000000..2ea5f4858
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/Common/FiniteField.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * Finite Fields Base Class
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ */
+
+namespace phpseclib3\Math\Common;
+
+/**
+ * Finite Fields
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class FiniteField
+{
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/Common/FiniteField/Integer.php b/vendor/phpseclib/phpseclib/phpseclib/Math/Common/FiniteField/Integer.php
new file mode 100644
index 000000000..3c959e94f
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/Common/FiniteField/Integer.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * Finite Field Integer Base Class
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ */
+
+namespace phpseclib3\Math\Common\FiniteField;
+
+/**
+ * Finite Field Integer
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+abstract class Integer implements \JsonSerializable
+{
+ /**
+ * JSON Serialize
+ *
+ * Will be called, automatically, when json_encode() is called on a BigInteger object.
+ *
+ * PHP Serialize isn't supported because unserializing would require the factory be
+ * serialized as well and that just sounds like too much
+ *
+ * @return array{hex: string}
+ */
+ #[\ReturnTypeWillChange]
+ public function jsonSerialize()
+ {
+ return ['hex' => $this->toHex(true)];
+ }
+
+ /**
+ * Converts an Integer to a hex string (eg. base-16).
+ *
+ * @return string
+ */
+ abstract public function toHex();
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/PrimeField.php b/vendor/phpseclib/phpseclib/phpseclib/Math/PrimeField.php
new file mode 100644
index 000000000..1a0667f09
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/PrimeField.php
@@ -0,0 +1,118 @@
+<?php
+
+/**
+ * Prime Finite Fields
+ *
+ * Utilizes the factory design pattern
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://pear.php.net/package/Math_BigInteger
+ */
+
+namespace phpseclib3\Math;
+
+use phpseclib3\Math\Common\FiniteField;
+use phpseclib3\Math\PrimeField\Integer;
+
+/**
+ * Prime Finite Fields
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class PrimeField extends FiniteField
+{
+ /**
+ * Instance Counter
+ *
+ * @var int
+ */
+ private static $instanceCounter = 0;
+
+ /**
+ * Keeps track of current instance
+ *
+ * @var int
+ */
+ protected $instanceID;
+
+ /**
+ * Default constructor
+ */
+ public function __construct(BigInteger $modulo)
+ {
+ if (!$modulo->isPrime()) {
+ throw new \UnexpectedValueException('PrimeField requires a prime number be passed to the constructor');
+ }
+
+ $this->instanceID = self::$instanceCounter++;
+ Integer::setModulo($this->instanceID, $modulo);
+ Integer::setRecurringModuloFunction($this->instanceID, $modulo->createRecurringModuloFunction());
+ }
+
+ /**
+ * Use a custom defined modular reduction function
+ *
+ * @return void
+ */
+ public function setReduction(\Closure $func)
+ {
+ $this->reduce = $func->bindTo($this, $this);
+ }
+
+ /**
+ * Returns an instance of a dynamically generated PrimeFieldInteger class
+ *
+ * @return Integer
+ */
+ public function newInteger(BigInteger $num)
+ {
+ return new Integer($this->instanceID, $num);
+ }
+
+ /**
+ * Returns an integer on the finite field between one and the prime modulo
+ *
+ * @return Integer
+ */
+ public function randomInteger()
+ {
+ static $one;
+ if (!isset($one)) {
+ $one = new BigInteger(1);
+ }
+
+ return new Integer($this->instanceID, BigInteger::randomRange($one, Integer::getModulo($this->instanceID)));
+ }
+
+ /**
+ * Returns the length of the modulo in bytes
+ *
+ * @return int
+ */
+ public function getLengthInBytes()
+ {
+ return Integer::getModulo($this->instanceID)->getLengthInBytes();
+ }
+
+ /**
+ * Returns the length of the modulo in bits
+ *
+ * @return int
+ */
+ public function getLength()
+ {
+ return Integer::getModulo($this->instanceID)->getLength();
+ }
+
+ /**
+ * Destructor
+ */
+ public function __destruct()
+ {
+ Integer::cleanupCache($this->instanceID);
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Math/PrimeField/Integer.php b/vendor/phpseclib/phpseclib/phpseclib/Math/PrimeField/Integer.php
new file mode 100644
index 000000000..1ebb2f5d7
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/Math/PrimeField/Integer.php
@@ -0,0 +1,442 @@
+<?php
+
+/**
+ * Prime Finite Fields
+ *
+ * PHP version 5 and 7
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2017 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ */
+
+namespace phpseclib3\Math\PrimeField;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Math\BigInteger;
+use phpseclib3\Math\Common\FiniteField\Integer as Base;
+
+/**
+ * Prime Finite Fields
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+class Integer extends Base
+{
+ /**
+ * Holds the PrimeField's value
+ *
+ * @var BigInteger
+ */
+ protected $value;
+
+ /**
+ * Keeps track of current instance
+ *
+ * @var int
+ */
+ protected $instanceID;
+
+ /**
+ * Holds the PrimeField's modulo
+ *
+ * @var array<int, BigInteger>
+ */
+ protected static $modulo;
+
+ /**
+ * Holds a pre-generated function to perform modulo reductions
+ *
+ * @var array<int, callable(BigInteger):BigInteger>
+ */
+ protected static $reduce;
+
+ /**
+ * Zero
+ *
+ * @var BigInteger[]
+ */
+ protected static $zero;
+
+ /**
+ * One
+ *
+ * @var BigInteger[]
+ */
+ protected static $one;
+
+ /**
+ * Two
+ *
+ * @var BigInteger[]
+ */
+ protected static $two;
+
+ /**
+ * Default constructor
+ *
+ * @param int $instanceID
+ * @param BigInteger $num
+ */
+ public function __construct($instanceID, $num = null)
+ {
+ $this->instanceID = $instanceID;
+ if (!isset($num)) {
+ $this->value = clone static::$zero[$instanceID];
+ } else {
+ $reduce = static::$reduce[$instanceID];
+ $this->value = $reduce($num);
+ }
+ }
+
+ /**
+ * Set the modulo for a given instance
+ *
+ * @param int $instanceID
+ * @return void
+ */
+ public static function setModulo($instanceID, BigInteger $modulo)
+ {
+ static::$modulo[$instanceID] = $modulo;
+ }
+
+ /**
+ * Set the modulo for a given instance
+ *
+ * @param int $instanceID
+ * @return void
+ */
+ public static function setRecurringModuloFunction($instanceID, callable $function)
+ {
+ static::$reduce[$instanceID] = $function;
+ if (!isset(static::$zero[$instanceID])) {
+ static::$zero[$instanceID] = new BigInteger();
+ }
+ }
+
+ /**
+ * Delete the modulo for a given instance
+ */
+ public static function cleanupCache($instanceID)
+ {
+ unset(static::$modulo[$instanceID]);
+ unset(static::$reduce[$instanceID]);
+ unset(static::$zero[$instanceID]);
+ unset(static::$one[$instanceID]);
+ unset(static::$two[$instanceID]);
+ }
+
+ /**
+ * Returns the modulo
+ *
+ * @param int $instanceID
+ * @return BigInteger
+ */
+ public static function getModulo($instanceID)
+ {
+ return static::$modulo[$instanceID];
+ }
+
+ /**
+ * Tests a parameter to see if it's of the right instance
+ *
+ * Throws an exception if the incorrect class is being utilized
+ *
+ * @return void
+ */
+ public static function checkInstance(self $x, self $y)
+ {
+ if ($x->instanceID != $y->instanceID) {
+ throw new \UnexpectedValueException('The instances of the two PrimeField\Integer objects do not match');
+ }
+ }
+
+ /**
+ * Tests the equality of two numbers.
+ *
+ * @return bool
+ */
+ public function equals(self $x)
+ {
+ static::checkInstance($this, $x);
+
+ return $this->value->equals($x->value);
+ }
+
+ /**
+ * Compares two numbers.
+ *
+ * @return int
+ */
+ public function compare(self $x)
+ {
+ static::checkInstance($this, $x);
+
+ return $this->value->compare($x->value);
+ }
+
+ /**
+ * Adds two PrimeFieldIntegers.
+ *
+ * @return static
+ */
+ public function add(self $x)
+ {
+ static::checkInstance($this, $x);
+
+ $temp = new static($this->instanceID);
+ $temp->value = $this->value->add($x->value);
+ if ($temp->value->compare(static::$modulo[$this->instanceID]) >= 0) {
+ $temp->value = $temp->value->subtract(static::$modulo[$this->instanceID]);
+ }
+
+ return $temp;
+ }
+
+ /**
+ * Subtracts two PrimeFieldIntegers.
+ *
+ * @return static
+ */
+ public function subtract(self $x)
+ {
+ static::checkInstance($this, $x);
+
+ $temp = new static($this->instanceID);
+ $temp->value = $this->value->subtract($x->value);
+ if ($temp->value->isNegative()) {
+ $temp->value = $temp->value->add(static::$modulo[$this->instanceID]);
+ }
+
+ return $temp;
+ }
+
+ /**
+ * Multiplies two PrimeFieldIntegers.
+ *
+ * @return static
+ */
+ public function multiply(self $x)
+ {
+ static::checkInstance($this, $x);
+
+ return new static($this->instanceID, $this->value->multiply($x->value));
+ }
+
+ /**
+ * Divides two PrimeFieldIntegers.
+ *
+ * @return static
+ */
+ public function divide(self $x)
+ {
+ static::checkInstance($this, $x);
+
+ $denominator = $x->value->modInverse(static::$modulo[$this->instanceID]);
+ return new static($this->instanceID, $this->value->multiply($denominator));
+ }
+
+ /**
+ * Performs power operation on a PrimeFieldInteger.
+ *
+ * @return static
+ */
+ public function pow(BigInteger $x)
+ {
+ $temp = new static($this->instanceID);
+ $temp->value = $this->value->powMod($x, static::$modulo[$this->instanceID]);
+
+ return $temp;
+ }
+
+ /**
+ * Calculates the square root
+ *
+ * @link https://en.wikipedia.org/wiki/Tonelli%E2%80%93Shanks_algorithm
+ * @return static|false
+ */
+ public function squareRoot()
+ {
+ if (!isset(static::$one[$this->instanceID])) {
+ static::$one[$this->instanceID] = new BigInteger(1);
+ static::$two[$this->instanceID] = new BigInteger(2);
+ }
+ $one = &static::$one[$this->instanceID];
+ $two = &static::$two[$this->instanceID];
+ $modulo = &static::$modulo[$this->instanceID];
+ $reduce = &static::$reduce[$this->instanceID];
+
+ $p_1 = $modulo->subtract($one);
+ $q = clone $p_1;
+ $s = BigInteger::scan1divide($q);
+ list($pow) = $p_1->divide($two);
+ for ($z = $one; !$z->equals($modulo); $z = $z->add($one)) {
+ $temp = $z->powMod($pow, $modulo);
+ if ($temp->equals($p_1)) {
+ break;
+ }
+ }
+
+ $m = new BigInteger($s);
+ $c = $z->powMod($q, $modulo);
+ $t = $this->value->powMod($q, $modulo);
+ list($temp) = $q->add($one)->divide($two);
+ $r = $this->value->powMod($temp, $modulo);
+
+ while (!$t->equals($one)) {
+ for ($i = clone $one; $i->compare($m) < 0; $i = $i->add($one)) {
+ if ($t->powMod($two->pow($i), $modulo)->equals($one)) {
+ break;
+ }
+ }
+
+ if ($i->compare($m) == 0) {
+ return false;
+ }
+ $b = $c->powMod($two->pow($m->subtract($i)->subtract($one)), $modulo);
+ $m = $i;
+ $c = $reduce($b->multiply($b));
+ $t = $reduce($t->multiply($c));
+ $r = $reduce($r->multiply($b));
+ }
+
+ return new static($this->instanceID, $r);
+ }
+
+ /**
+ * Is Odd?
+ *
+ * @return bool
+ */
+ public function isOdd()
+ {
+ return $this->value->isOdd();
+ }
+
+ /**
+ * Negate
+ *
+ * A negative number can be written as 0-12. With modulos, 0 is the same thing as the modulo
+ * so 0-12 is the same thing as modulo-12
+ *
+ * @return static
+ */
+ public function negate()
+ {
+ return new static($this->instanceID, static::$modulo[$this->instanceID]->subtract($this->value));
+ }
+
+ /**
+ * Converts an Integer to a byte string (eg. base-256).
+ *
+ * @return string
+ */
+ public function toBytes()
+ {
+ if (isset(static::$modulo[$this->instanceID])) {
+ $length = static::$modulo[$this->instanceID]->getLengthInBytes();
+ return str_pad($this->value->toBytes(), $length, "\0", STR_PAD_LEFT);
+ }
+ return $this->value->toBytes();
+ }
+
+ /**
+ * Converts an Integer to a hex string (eg. base-16).
+ *
+ * @return string
+ */
+ public function toHex()
+ {
+ return Strings::bin2hex($this->toBytes());
+ }
+
+ /**
+ * Converts an Integer to a bit string (eg. base-2).
+ *
+ * @return string
+ */
+ public function toBits()
+ {
+ // return $this->value->toBits();
+ static $length;
+ if (!isset($length)) {
+ $length = static::$modulo[$this->instanceID]->getLength();
+ }
+
+ return str_pad($this->value->toBits(), $length, '0', STR_PAD_LEFT);
+ }
+
+ /**
+ * Returns the w-ary non-adjacent form (wNAF)
+ *
+ * @param int $w optional
+ * @return array<int, int>
+ */
+ public function getNAF($w = 1)
+ {
+ $w++;
+
+ $zero = &static::$zero[$this->instanceID];
+
+ $mask = new BigInteger((1 << $w) - 1);
+ $sub = new BigInteger(1 << $w);
+ //$sub = new BigInteger(1 << ($w - 1));
+ $d = $this->toBigInteger();
+ $d_i = [];
+
+ $i = 0;
+ while ($d->compare($zero) > 0) {
+ if ($d->isOdd()) {
+ // start mods
+
+ $bigInteger = $d->testBit($w - 1) ?
+ $d->bitwise_and($mask)->subtract($sub) :
+ //$sub->subtract($d->bitwise_and($mask)) :
+ $d->bitwise_and($mask);
+ // end mods
+ $d = $d->subtract($bigInteger);
+ $d_i[$i] = (int) $bigInteger->toString();
+ } else {
+ $d_i[$i] = 0;
+ }
+ $shift = !$d->equals($zero) && $d->bitwise_and($mask)->equals($zero) ? $w : 1; // $w or $w + 1?
+ $d = $d->bitwise_rightShift($shift);
+ while (--$shift > 0) {
+ $d_i[++$i] = 0;
+ }
+ $i++;
+ }
+
+ return $d_i;
+ }
+
+ /**
+ * Converts an Integer to a BigInteger
+ *
+ * @return BigInteger
+ */
+ public function toBigInteger()
+ {
+ return clone $this->value;
+ }
+
+ /**
+ * __toString() magic method
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return (string) $this->value;
+ }
+
+ /**
+ * __debugInfo() magic method
+ *
+ * @return array
+ */
+ public function __debugInfo()
+ {
+ return ['value' => $this->toHex()];
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Net/SCP.php b/vendor/phpseclib/phpseclib/phpseclib/Net/SCP.php
deleted file mode 100644
index ee6e1c9d9..000000000
--- a/vendor/phpseclib/phpseclib/phpseclib/Net/SCP.php
+++ /dev/null
@@ -1,349 +0,0 @@
-<?php
-
-/**
- * Pure-PHP implementation of SCP.
- *
- * PHP version 5
- *
- * The API for this library is modeled after the API from PHP's {@link http://php.net/book.ftp FTP extension}.
- *
- * Here's a short example of how to use this library:
- * <code>
- * <?php
- * include 'vendor/autoload.php';
- *
- * $ssh = new \phpseclib\Net\SSH2('www.domain.tld');
- * if (!$ssh->login('username', 'password')) {
- * exit('bad login');
- * }
- * $scp = new \phpseclib\Net\SCP($ssh);
- *
- * $scp->put('abcd', str_repeat('x', 1024*1024));
- * ?>
- * </code>
- *
- * @category Net
- * @package SCP
- * @author Jim Wigginton <terrafrost@php.net>
- * @copyright 2010 Jim Wigginton
- * @license http://www.opensource.org/licenses/mit-license.html MIT License
- * @link http://phpseclib.sourceforge.net
- */
-
-namespace phpseclib\Net;
-
-/**
- * Pure-PHP implementations of SCP.
- *
- * @package SCP
- * @author Jim Wigginton <terrafrost@php.net>
- * @access public
- */
-class SCP
-{
- /**#@+
- * @access public
- * @see \phpseclib\Net\SCP::put()
- */
- /**
- * Reads data from a local file.
- */
- const SOURCE_LOCAL_FILE = 1;
- /**
- * Reads data from a string.
- */
- const SOURCE_STRING = 2;
- /**#@-*/
-
- /**#@+
- * @access private
- * @see \phpseclib\Net\SCP::_send()
- * @see \phpseclib\Net\SCP::_receive()
- */
- /**
- * SSH1 is being used.
- */
- const MODE_SSH1 = 1;
- /**
- * SSH2 is being used.
- */
- const MODE_SSH2 = 2;
- /**#@-*/
-
- /**
- * SSH Object
- *
- * @var object
- * @access private
- */
- var $ssh;
-
- /**
- * Packet Size
- *
- * @var int
- * @access private
- */
- var $packet_size;
-
- /**
- * Mode
- *
- * @var int
- * @access private
- */
- var $mode;
-
- /**
- * Default Constructor.
- *
- * Connects to an SSH server
- *
- * @param \phpseclib\Net\SSH1|\phpseclib\Net\SSH2 $ssh
- * @return \phpseclib\Net\SCP
- * @access public
- */
- function __construct($ssh)
- {
- if ($ssh instanceof SSH2) {
- $this->mode = self::MODE_SSH2;
- } elseif ($ssh instanceof SSH1) {
- $this->packet_size = 50000;
- $this->mode = self::MODE_SSH1;
- } else {
- return;
- }
-
- $this->ssh = $ssh;
- }
-
- /**
- * Uploads a file to the SCP server.
- *
- * By default, \phpseclib\Net\SCP::put() does not read from the local filesystem. $data is dumped directly into $remote_file.
- * So, for example, if you set $data to 'filename.ext' and then do \phpseclib\Net\SCP::get(), you will get a file, twelve bytes
- * long, containing 'filename.ext' as its contents.
- *
- * Setting $mode to self::SOURCE_LOCAL_FILE will change the above behavior. With self::SOURCE_LOCAL_FILE, $remote_file will
- * contain as many bytes as filename.ext does on your local filesystem. If your filename.ext is 1MB then that is how
- * large $remote_file will be, as well.
- *
- * Currently, only binary mode is supported. As such, if the line endings need to be adjusted, you will need to take
- * care of that, yourself.
- *
- * @param string $remote_file
- * @param string $data
- * @param int $mode
- * @param callable $callback
- * @return bool
- * @access public
- */
- function put($remote_file, $data, $mode = self::SOURCE_STRING, $callback = null)
- {
- if (!isset($this->ssh)) {
- return false;
- }
-
- if (empty($remote_file)) {
- user_error('remote_file cannot be blank', E_USER_NOTICE);
- return false;
- }
-
- if (!$this->ssh->exec('scp -t ' . escapeshellarg($remote_file), false)) { // -t = to
- return false;
- }
-
- $temp = $this->_receive();
- if ($temp !== chr(0)) {
- return false;
- }
-
- if ($this->mode == self::MODE_SSH2) {
- $this->packet_size = $this->ssh->packet_size_client_to_server[SSH2::CHANNEL_EXEC] - 4;
- }
-
- $remote_file = basename($remote_file);
-
- if ($mode == self::SOURCE_STRING) {
- $size = strlen($data);
- } else {
- if (!is_file($data)) {
- user_error("$data is not a valid file", E_USER_NOTICE);
- return false;
- }
-
- $fp = @fopen($data, 'rb');
- if (!$fp) {
- return false;
- }
- $size = filesize($data);
- }
-
- $this->_send('C0644 ' . $size . ' ' . $remote_file . "\n");
-
- $temp = $this->_receive();
- if ($temp !== chr(0)) {
- return false;
- }
-
- $sent = 0;
- while ($sent < $size) {
- $temp = $mode & self::SOURCE_STRING ? substr($data, $sent, $this->packet_size) : fread($fp, $this->packet_size);
- $this->_send($temp);
- $sent+= strlen($temp);
-
- if (is_callable($callback)) {
- call_user_func($callback, $sent);
- }
- }
- $this->_close();
-
- if ($mode != self::SOURCE_STRING) {
- fclose($fp);
- }
-
- return true;
- }
-
- /**
- * Downloads a file from the SCP server.
- *
- * Returns a string containing the contents of $remote_file if $local_file is left undefined or a boolean false if
- * the operation was unsuccessful. If $local_file is defined, returns true or false depending on the success of the
- * operation
- *
- * @param string $remote_file
- * @param string $local_file
- * @return mixed
- * @access public
- */
- function get($remote_file, $local_file = false)
- {
- if (!isset($this->ssh)) {
- return false;
- }
-
- if (!$this->ssh->exec('scp -f ' . escapeshellarg($remote_file), false)) { // -f = from
- return false;
- }
-
- $this->_send("\0");
-
- if (!preg_match('#(?<perms>[^ ]+) (?<size>\d+) (?<name>.+)#', rtrim($this->_receive()), $info)) {
- return false;
- }
-
- $this->_send("\0");
-
- $size = 0;
-
- if ($local_file !== false) {
- $fp = @fopen($local_file, 'wb');
- if (!$fp) {
- return false;
- }
- }
-
- $content = '';
- while ($size < $info['size']) {
- $data = $this->_receive();
-
- // Terminate the loop in case the server repeatedly sends an empty response
- if ($data === false) {
- user_error('No data received from server', E_USER_NOTICE);
- return false;
- }
-
- // SCP usually seems to split stuff out into 16k chunks
- $size+= strlen($data);
-
- if ($local_file === false) {
- $content.= $data;
- } else {
- fputs($fp, $data);
- }
- }
-
- $this->_close();
-
- if ($local_file !== false) {
- fclose($fp);
- return true;
- }
-
- return $content;
- }
-
- /**
- * Sends a packet to an SSH server
- *
- * @param string $data
- * @access private
- */
- function _send($data)
- {
- switch ($this->mode) {
- case self::MODE_SSH2:
- $this->ssh->_send_channel_packet(SSH2::CHANNEL_EXEC, $data);
- break;
- case self::MODE_SSH1:
- $data = pack('CNa*', NET_SSH1_CMSG_STDIN_DATA, strlen($data), $data);
- $this->ssh->_send_binary_packet($data);
- }
- }
-
- /**
- * Receives a packet from an SSH server
- *
- * @return string
- * @access private
- */
- function _receive()
- {
- switch ($this->mode) {
- case self::MODE_SSH2:
- return $this->ssh->_get_channel_packet(SSH2::CHANNEL_EXEC, true);
- case self::MODE_SSH1:
- if (!$this->ssh->bitmap) {
- return false;
- }
- while (true) {
- $response = $this->ssh->_get_binary_packet();
- switch ($response[SSH1::RESPONSE_TYPE]) {
- case NET_SSH1_SMSG_STDOUT_DATA:
- if (strlen($response[SSH1::RESPONSE_DATA]) < 4) {
- return false;
- }
- extract(unpack('Nlength', $response[SSH1::RESPONSE_DATA]));
- return $this->ssh->_string_shift($response[SSH1::RESPONSE_DATA], $length);
- case NET_SSH1_SMSG_STDERR_DATA:
- break;
- case NET_SSH1_SMSG_EXITSTATUS:
- $this->ssh->_send_binary_packet(chr(NET_SSH1_CMSG_EXIT_CONFIRMATION));
- fclose($this->ssh->fsock);
- $this->ssh->bitmap = 0;
- return false;
- default:
- user_error('Unknown packet received', E_USER_NOTICE);
- return false;
- }
- }
- }
- }
-
- /**
- * Closes the connection to an SSH server
- *
- * @access private
- */
- function _close()
- {
- switch ($this->mode) {
- case self::MODE_SSH2:
- $this->ssh->_close_channel(SSH2::CHANNEL_EXEC, true);
- break;
- case self::MODE_SSH1:
- $this->ssh->disconnect();
- }
- }
-}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Net/SFTP.php b/vendor/phpseclib/phpseclib/phpseclib/Net/SFTP.php
index 1c6ef7f9a..7e25544cf 100644
--- a/vendor/phpseclib/phpseclib/phpseclib/Net/SFTP.php
+++ b/vendor/phpseclib/phpseclib/phpseclib/Net/SFTP.php
@@ -14,7 +14,7 @@
* <?php
* include 'vendor/autoload.php';
*
- * $sftp = new \phpseclib\Net\SFTP('www.domain.tld');
+ * $sftp = new \phpseclib3\Net\SFTP('www.domain.tld');
* if (!$sftp->login('username', 'password')) {
* exit('Login Failed');
* }
@@ -25,63 +25,66 @@
* ?>
* </code>
*
- * @category Net
- * @package SFTP
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2009 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
-namespace phpseclib\Net;
+namespace phpseclib3\Net;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Exception\FileNotFoundException;
/**
* Pure-PHP implementations of SFTP.
*
- * @package SFTP
* @author Jim Wigginton <terrafrost@php.net>
- * @access public
*/
class SFTP extends SSH2
{
/**
* SFTP channel constant
*
- * \phpseclib\Net\SSH2::exec() uses 0 and \phpseclib\Net\SSH2::read() / \phpseclib\Net\SSH2::write() use 1.
+ * \phpseclib3\Net\SSH2::exec() uses 0 and \phpseclib3\Net\SSH2::read() / \phpseclib3\Net\SSH2::write() use 1.
*
- * @see \phpseclib\Net\SSH2::_send_channel_packet()
- * @see \phpseclib\Net\SSH2::_get_channel_packet()
- * @access private
+ * @see \phpseclib3\Net\SSH2::send_channel_packet()
+ * @see \phpseclib3\Net\SSH2::get_channel_packet()
*/
const CHANNEL = 0x100;
- /**#@+
- * @access public
- * @see \phpseclib\Net\SFTP::put()
- */
/**
* Reads data from a local file.
+ *
+ * @see \phpseclib3\Net\SFTP::put()
*/
const SOURCE_LOCAL_FILE = 1;
/**
* Reads data from a string.
+ *
+ * @see \phpseclib3\Net\SFTP::put()
*/
// this value isn't really used anymore but i'm keeping it reserved for historical reasons
const SOURCE_STRING = 2;
/**
* Reads data from callback:
* function callback($length) returns string to proceed, null for EOF
+ *
+ * @see \phpseclib3\Net\SFTP::put()
*/
const SOURCE_CALLBACK = 16;
/**
* Resumes an upload
+ *
+ * @see \phpseclib3\Net\SFTP::put()
*/
const RESUME = 4;
/**
* Append a local file to an already existing remote file
+ *
+ * @see \phpseclib3\Net\SFTP::put()
*/
const RESUME_START = 8;
- /**#@-*/
/**
* Packet Types
@@ -90,7 +93,7 @@ class SFTP extends SSH2
* @var array
* @access private
*/
- var $packet_types = array();
+ private static $packet_types = [];
/**
* Status Codes
@@ -99,7 +102,19 @@ class SFTP extends SSH2
* @var array
* @access private
*/
- var $status_codes = array();
+ private static $status_codes = [];
+
+ /** @var array<int, string> */
+ private static $attributes;
+
+ /** @var array<int, string> */
+ private static $open_flags;
+
+ /** @var array<int, string> */
+ private static $open_flags5;
+
+ /** @var array<int, string> */
+ private static $file_types;
/**
* The Request ID
@@ -109,9 +124,8 @@ class SFTP extends SSH2
*
* @var boolean
* @see self::_send_sftp_packet()
- * @access private
*/
- var $use_request_id = false;
+ private $use_request_id = false;
/**
* The Packet Type
@@ -121,82 +135,106 @@ class SFTP extends SSH2
*
* @var int
* @see self::_get_sftp_packet()
- * @access private
*/
- var $packet_type = -1;
+ private $packet_type = -1;
/**
* Packet Buffer
*
* @var string
* @see self::_get_sftp_packet()
- * @access private
*/
- var $packet_buffer = '';
+ private $packet_buffer = '';
/**
* Extensions supported by the server
*
* @var array
* @see self::_initChannel()
- * @access private
*/
- var $extensions = array();
+ private $extensions = [];
/**
* Server SFTP version
*
* @var int
* @see self::_initChannel()
- * @access private
*/
- var $version;
+ private $version;
/**
* Default Server SFTP version
*
* @var int
* @see self::_initChannel()
- * @access private
*/
- var $defaultVersion;
+ private $defaultVersion;
/**
* Preferred SFTP version
*
* @var int
* @see self::_initChannel()
- * @access private
*/
- var $preferredVersion = 3;
+ private $preferredVersion = 3;
/**
* Current working directory
*
- * @var string
+ * @var string|bool
* @see self::realpath()
* @see self::chdir()
- * @access private
*/
- var $pwd = false;
+ private $pwd = false;
/**
* Packet Type Log
*
* @see self::getLog()
* @var array
- * @access private
*/
- var $packet_type_log = array();
+ private $packet_type_log = [];
/**
* Packet Log
*
* @see self::getLog()
* @var array
- * @access private
*/
- var $packet_log = array();
+ private $packet_log = [];
+
+ /**
+ * Real-time log file pointer
+ *
+ * @see self::_append_log()
+ * @var resource|closed-resource
+ */
+ private $realtime_log_file;
+
+ /**
+ * Real-time log file size
+ *
+ * @see self::_append_log()
+ * @var int
+ */
+ private $realtime_log_size;
+
+ /**
+ * Real-time log file wrap boolean
+ *
+ * @see self::_append_log()
+ * @var bool
+ */
+ private $realtime_log_wrap;
+
+ /**
+ * Current log size
+ *
+ * Should never exceed self::LOG_MAX_SIZE
+ *
+ * @var int
+ */
+ private $log_size;
/**
* Error information
@@ -204,9 +242,8 @@ class SFTP extends SSH2
* @see self::getSFTPErrors()
* @see self::getLastSFTPError()
* @var array
- * @access private
*/
- var $sftp_errors = array();
+ private $sftp_errors = [];
/**
* Stat Cache
@@ -218,19 +255,17 @@ class SFTP extends SSH2
* @see self::_remove_from_stat_cache()
* @see self::_query_stat_cache()
* @var array
- * @access private
*/
- var $stat_cache = array();
+ private $stat_cache = [];
/**
* Max SFTP Packet Size
*
* @see self::__construct()
* @see self::get()
- * @var array
- * @access private
+ * @var int
*/
- var $max_sftp_packet;
+ private $max_sftp_packet;
/**
* Stat Cache Flag
@@ -238,9 +273,8 @@ class SFTP extends SSH2
* @see self::disableStatCache()
* @see self::enableStatCache()
* @var bool
- * @access private
*/
- var $use_stat_cache = true;
+ private $use_stat_cache = true;
/**
* Sort Options
@@ -248,9 +282,8 @@ class SFTP extends SSH2
* @see self::_comparator()
* @see self::setListOrder()
* @var array
- * @access private
*/
- var $sortOptions = array();
+ protected $sortOptions = [];
/**
* Canonicalization Flag
@@ -262,18 +295,16 @@ class SFTP extends SSH2
* @see self::disablePathCanonicalization()
* @see self::realpath()
* @var bool
- * @access private
*/
- var $canonicalize_paths = true;
+ private $canonicalize_paths = true;
/**
* Request Buffers
*
* @see self::_get_sftp_packet()
* @var array
- * @access private
*/
- var $requestBuffer = array();
+ private $requestBuffer = [];
/**
* Preserve timestamps on file downloads / uploads
@@ -281,9 +312,8 @@ class SFTP extends SSH2
* @see self::get()
* @see self::put()
* @var bool
- * @access private
*/
- var $preserveTime = false;
+ private $preserveTime = false;
/**
* Arbitrary Length Packets Flag
@@ -296,9 +326,8 @@ class SFTP extends SSH2
* @see self::enableArbitraryLengthPackets()
* @see self::_get_sftp_packet()
* @var bool
- * @access private
*/
- var $allow_arbitrary_length_packets = false;
+ private $allow_arbitrary_length_packets = false;
/**
* Was the last packet due to the channels being closed or not?
@@ -306,212 +335,184 @@ class SFTP extends SSH2
* @see self::get()
* @see self::get_sftp_packet()
* @var bool
- * @access private
*/
- var $channel_close = false;
+ private $channel_close = false;
/**
* Has the SFTP channel been partially negotiated?
*
* @var bool
- * @access private
- */
- var $partial_init = false;
-
- /**
- * http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1
- * the order, in this case, matters quite a lot - see \phpseclib3\Net\SFTP::_parseAttributes() to understand why
- *
- * @var array
- * @access private
- */
- var $attributes = array();
-
- /**
- * @var array
- * @access private
- */
- var $open_flags = array();
-
- /**
- * SFTPv5+ changed the flags up:
- * https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-8.1.1.3
- *
- * @var array
- * @access private
*/
- var $open_flags5 = array();
-
- /**
- * http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2
- * see \phpseclib\Net\SFTP::_parseLongname() for an explanation
- *
- * @var array
- */
- var $file_types = array();
+ private $partial_init = false;
/**
* Default Constructor.
*
* Connects to an SFTP server
*
- * @param string $host
+ * $host can either be a string, representing the host, or a stream resource.
+ *
+ * @param mixed $host
* @param int $port
* @param int $timeout
- * @return \phpseclib\Net\SFTP
- * @access public
*/
- function __construct($host, $port = 22, $timeout = 10)
+ public function __construct($host, $port = 22, $timeout = 10)
{
parent::__construct($host, $port, $timeout);
$this->max_sftp_packet = 1 << 15;
- $this->packet_types = array(
- 1 => 'NET_SFTP_INIT',
- 2 => 'NET_SFTP_VERSION',
- 3 => 'NET_SFTP_OPEN',
- 4 => 'NET_SFTP_CLOSE',
- 5 => 'NET_SFTP_READ',
- 6 => 'NET_SFTP_WRITE',
- 7 => 'NET_SFTP_LSTAT',
- 9 => 'NET_SFTP_SETSTAT',
- 10 => 'NET_SFTP_FSETSTAT',
- 11 => 'NET_SFTP_OPENDIR',
- 12 => 'NET_SFTP_READDIR',
- 13 => 'NET_SFTP_REMOVE',
- 14 => 'NET_SFTP_MKDIR',
- 15 => 'NET_SFTP_RMDIR',
- 16 => 'NET_SFTP_REALPATH',
- 17 => 'NET_SFTP_STAT',
- 18 => 'NET_SFTP_RENAME',
- 19 => 'NET_SFTP_READLINK',
- 20 => 'NET_SFTP_SYMLINK',
- 21 => 'NET_SFTP_LINK',
-
- 101=> 'NET_SFTP_STATUS',
- 102=> 'NET_SFTP_HANDLE',
- 103=> 'NET_SFTP_DATA',
- 104=> 'NET_SFTP_NAME',
- 105=> 'NET_SFTP_ATTRS',
-
- 200=> 'NET_SFTP_EXTENDED'
- );
- $this->status_codes = array(
- 0 => 'NET_SFTP_STATUS_OK',
- 1 => 'NET_SFTP_STATUS_EOF',
- 2 => 'NET_SFTP_STATUS_NO_SUCH_FILE',
- 3 => 'NET_SFTP_STATUS_PERMISSION_DENIED',
- 4 => 'NET_SFTP_STATUS_FAILURE',
- 5 => 'NET_SFTP_STATUS_BAD_MESSAGE',
- 6 => 'NET_SFTP_STATUS_NO_CONNECTION',
- 7 => 'NET_SFTP_STATUS_CONNECTION_LOST',
- 8 => 'NET_SFTP_STATUS_OP_UNSUPPORTED',
- 9 => 'NET_SFTP_STATUS_INVALID_HANDLE',
- 10 => 'NET_SFTP_STATUS_NO_SUCH_PATH',
- 11 => 'NET_SFTP_STATUS_FILE_ALREADY_EXISTS',
- 12 => 'NET_SFTP_STATUS_WRITE_PROTECT',
- 13 => 'NET_SFTP_STATUS_NO_MEDIA',
- 14 => 'NET_SFTP_STATUS_NO_SPACE_ON_FILESYSTEM',
- 15 => 'NET_SFTP_STATUS_QUOTA_EXCEEDED',
- 16 => 'NET_SFTP_STATUS_UNKNOWN_PRINCIPAL',
- 17 => 'NET_SFTP_STATUS_LOCK_CONFLICT',
- 18 => 'NET_SFTP_STATUS_DIR_NOT_EMPTY',
- 19 => 'NET_SFTP_STATUS_NOT_A_DIRECTORY',
- 20 => 'NET_SFTP_STATUS_INVALID_FILENAME',
- 21 => 'NET_SFTP_STATUS_LINK_LOOP',
- 22 => 'NET_SFTP_STATUS_CANNOT_DELETE',
- 23 => 'NET_SFTP_STATUS_INVALID_PARAMETER',
- 24 => 'NET_SFTP_STATUS_FILE_IS_A_DIRECTORY',
- 25 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_CONFLICT',
- 26 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_REFUSED',
- 27 => 'NET_SFTP_STATUS_DELETE_PENDING',
- 28 => 'NET_SFTP_STATUS_FILE_CORRUPT',
- 29 => 'NET_SFTP_STATUS_OWNER_INVALID',
- 30 => 'NET_SFTP_STATUS_GROUP_INVALID',
- 31 => 'NET_SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK'
- );
- // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1
- // the order, in this case, matters quite a lot - see \phpseclib\Net\SFTP::_parseAttributes() to understand why
- $this->attributes = array(
- 0x00000001 => 'NET_SFTP_ATTR_SIZE',
- 0x00000002 => 'NET_SFTP_ATTR_UIDGID', // defined in SFTPv3, removed in SFTPv4+
- 0x00000080 => 'NET_SFTP_ATTR_OWNERGROUP', // defined in SFTPv4+
- 0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS',
- 0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME',
- 0x00000010 => 'NET_SFTP_ATTR_CREATETIME', // SFTPv4+
- 0x00000020 => 'NET_SFTP_ATTR_MODIFYTIME',
- 0x00000040 => 'NET_SFTP_ATTR_ACL',
- 0x00000100 => 'NET_SFTP_ATTR_SUBSECOND_TIMES',
- 0x00000200 => 'NET_SFTP_ATTR_BITS', // SFTPv5+
- 0x00000400 => 'NET_SFTP_ATTR_ALLOCATION_SIZE', // SFTPv6+
- 0x00000800 => 'NET_SFTP_ATTR_TEXT_HINT',
- 0x00001000 => 'NET_SFTP_ATTR_MIME_TYPE',
- 0x00002000 => 'NET_SFTP_ATTR_LINK_COUNT',
- 0x00004000 => 'NET_SFTP_ATTR_UNTRANSLATED_NAME',
- 0x00008000 => 'NET_SFTP_ATTR_CTIME',
- // 0x80000000 will yield a floating point on 32-bit systems and converting floating points to integers
- // yields inconsistent behavior depending on how php is compiled. so we left shift -1 (which, in
- // two's compliment, consists of all 1 bits) by 31. on 64-bit systems this'll yield 0xFFFFFFFF80000000.
- // that's not a problem, however, and 'anded' and a 32-bit number, as all the leading 1 bits are ignored.
- (PHP_INT_SIZE == 4 ? (-1 << 31) : 0x80000000) => 'NET_SFTP_ATTR_EXTENDED'
- );
- $this->open_flags = array(
- 0x00000001 => 'NET_SFTP_OPEN_READ',
- 0x00000002 => 'NET_SFTP_OPEN_WRITE',
- 0x00000004 => 'NET_SFTP_OPEN_APPEND',
- 0x00000008 => 'NET_SFTP_OPEN_CREATE',
- 0x00000010 => 'NET_SFTP_OPEN_TRUNCATE',
- 0x00000020 => 'NET_SFTP_OPEN_EXCL',
- 0x00000040 => 'NET_SFTP_OPEN_TEXT' // defined in SFTPv4
- );
- // SFTPv5+ changed the flags up:
- // https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-8.1.1.3
- $this->open_flags5 = array(
- // when SSH_FXF_ACCESS_DISPOSITION is a 3 bit field that controls how the file is opened
- 0x00000000 => 'NET_SFTP_OPEN_CREATE_NEW',
- 0x00000001 => 'NET_SFTP_OPEN_CREATE_TRUNCATE',
- 0x00000002 => 'NET_SFTP_OPEN_OPEN_EXISTING',
- 0x00000003 => 'NET_SFTP_OPEN_OPEN_OR_CREATE',
- 0x00000004 => 'NET_SFTP_OPEN_TRUNCATE_EXISTING',
- // the rest of the flags are not supported
- 0x00000008 => 'NET_SFTP_OPEN_APPEND_DATA', // "the offset field of SS_FXP_WRITE requests is ignored"
- 0x00000010 => 'NET_SFTP_OPEN_APPEND_DATA_ATOMIC',
- 0x00000020 => 'NET_SFTP_OPEN_TEXT_MODE',
- 0x00000040 => 'NET_SFTP_OPEN_BLOCK_READ',
- 0x00000080 => 'NET_SFTP_OPEN_BLOCK_WRITE',
- 0x00000100 => 'NET_SFTP_OPEN_BLOCK_DELETE',
- 0x00000200 => 'NET_SFTP_OPEN_BLOCK_ADVISORY',
- 0x00000400 => 'NET_SFTP_OPEN_NOFOLLOW',
- 0x00000800 => 'NET_SFTP_OPEN_DELETE_ON_CLOSE',
- 0x00001000 => 'NET_SFTP_OPEN_ACCESS_AUDIT_ALARM_INFO',
- 0x00002000 => 'NET_SFTP_OPEN_ACCESS_BACKUP',
- 0x00004000 => 'NET_SFTP_OPEN_BACKUP_STREAM',
- 0x00008000 => 'NET_SFTP_OPEN_OVERRIDE_OWNER',
- );
- // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2
- // see \phpseclib\Net\SFTP::_parseLongname() for an explanation
- $this->file_types = array(
- 1 => 'NET_SFTP_TYPE_REGULAR',
- 2 => 'NET_SFTP_TYPE_DIRECTORY',
- 3 => 'NET_SFTP_TYPE_SYMLINK',
- 4 => 'NET_SFTP_TYPE_SPECIAL',
- 5 => 'NET_SFTP_TYPE_UNKNOWN',
- // the followin types were first defined for use in SFTPv5+
- // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
- 6 => 'NET_SFTP_TYPE_SOCKET',
- 7 => 'NET_SFTP_TYPE_CHAR_DEVICE',
- 8 => 'NET_SFTP_TYPE_BLOCK_DEVICE',
- 9 => 'NET_SFTP_TYPE_FIFO'
- );
- $this->_define_array(
- $this->packet_types,
- $this->status_codes,
- $this->attributes,
- $this->open_flags,
- $this->open_flags5,
- $this->file_types
- );
+ if (empty(self::$packet_types)) {
+ self::$packet_types = [
+ 1 => 'NET_SFTP_INIT',
+ 2 => 'NET_SFTP_VERSION',
+ 3 => 'NET_SFTP_OPEN',
+ 4 => 'NET_SFTP_CLOSE',
+ 5 => 'NET_SFTP_READ',
+ 6 => 'NET_SFTP_WRITE',
+ 7 => 'NET_SFTP_LSTAT',
+ 9 => 'NET_SFTP_SETSTAT',
+ 10 => 'NET_SFTP_FSETSTAT',
+ 11 => 'NET_SFTP_OPENDIR',
+ 12 => 'NET_SFTP_READDIR',
+ 13 => 'NET_SFTP_REMOVE',
+ 14 => 'NET_SFTP_MKDIR',
+ 15 => 'NET_SFTP_RMDIR',
+ 16 => 'NET_SFTP_REALPATH',
+ 17 => 'NET_SFTP_STAT',
+ 18 => 'NET_SFTP_RENAME',
+ 19 => 'NET_SFTP_READLINK',
+ 20 => 'NET_SFTP_SYMLINK',
+ 21 => 'NET_SFTP_LINK',
+
+ 101 => 'NET_SFTP_STATUS',
+ 102 => 'NET_SFTP_HANDLE',
+ 103 => 'NET_SFTP_DATA',
+ 104 => 'NET_SFTP_NAME',
+ 105 => 'NET_SFTP_ATTRS',
+
+ 200 => 'NET_SFTP_EXTENDED',
+ 201 => 'NET_SFTP_EXTENDED_REPLY'
+ ];
+ self::$status_codes = [
+ 0 => 'NET_SFTP_STATUS_OK',
+ 1 => 'NET_SFTP_STATUS_EOF',
+ 2 => 'NET_SFTP_STATUS_NO_SUCH_FILE',
+ 3 => 'NET_SFTP_STATUS_PERMISSION_DENIED',
+ 4 => 'NET_SFTP_STATUS_FAILURE',
+ 5 => 'NET_SFTP_STATUS_BAD_MESSAGE',
+ 6 => 'NET_SFTP_STATUS_NO_CONNECTION',
+ 7 => 'NET_SFTP_STATUS_CONNECTION_LOST',
+ 8 => 'NET_SFTP_STATUS_OP_UNSUPPORTED',
+ 9 => 'NET_SFTP_STATUS_INVALID_HANDLE',
+ 10 => 'NET_SFTP_STATUS_NO_SUCH_PATH',
+ 11 => 'NET_SFTP_STATUS_FILE_ALREADY_EXISTS',
+ 12 => 'NET_SFTP_STATUS_WRITE_PROTECT',
+ 13 => 'NET_SFTP_STATUS_NO_MEDIA',
+ 14 => 'NET_SFTP_STATUS_NO_SPACE_ON_FILESYSTEM',
+ 15 => 'NET_SFTP_STATUS_QUOTA_EXCEEDED',
+ 16 => 'NET_SFTP_STATUS_UNKNOWN_PRINCIPAL',
+ 17 => 'NET_SFTP_STATUS_LOCK_CONFLICT',
+ 18 => 'NET_SFTP_STATUS_DIR_NOT_EMPTY',
+ 19 => 'NET_SFTP_STATUS_NOT_A_DIRECTORY',
+ 20 => 'NET_SFTP_STATUS_INVALID_FILENAME',
+ 21 => 'NET_SFTP_STATUS_LINK_LOOP',
+ 22 => 'NET_SFTP_STATUS_CANNOT_DELETE',
+ 23 => 'NET_SFTP_STATUS_INVALID_PARAMETER',
+ 24 => 'NET_SFTP_STATUS_FILE_IS_A_DIRECTORY',
+ 25 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_CONFLICT',
+ 26 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_REFUSED',
+ 27 => 'NET_SFTP_STATUS_DELETE_PENDING',
+ 28 => 'NET_SFTP_STATUS_FILE_CORRUPT',
+ 29 => 'NET_SFTP_STATUS_OWNER_INVALID',
+ 30 => 'NET_SFTP_STATUS_GROUP_INVALID',
+ 31 => 'NET_SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK'
+ ];
+ // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1
+ // the order, in this case, matters quite a lot - see \phpseclib3\Net\SFTP::_parseAttributes() to understand why
+ self::$attributes = [
+ 0x00000001 => 'NET_SFTP_ATTR_SIZE',
+ 0x00000002 => 'NET_SFTP_ATTR_UIDGID', // defined in SFTPv3, removed in SFTPv4+
+ 0x00000080 => 'NET_SFTP_ATTR_OWNERGROUP', // defined in SFTPv4+
+ 0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS',
+ 0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME',
+ 0x00000010 => 'NET_SFTP_ATTR_CREATETIME', // SFTPv4+
+ 0x00000020 => 'NET_SFTP_ATTR_MODIFYTIME',
+ 0x00000040 => 'NET_SFTP_ATTR_ACL',
+ 0x00000100 => 'NET_SFTP_ATTR_SUBSECOND_TIMES',
+ 0x00000200 => 'NET_SFTP_ATTR_BITS', // SFTPv5+
+ 0x00000400 => 'NET_SFTP_ATTR_ALLOCATION_SIZE', // SFTPv6+
+ 0x00000800 => 'NET_SFTP_ATTR_TEXT_HINT',
+ 0x00001000 => 'NET_SFTP_ATTR_MIME_TYPE',
+ 0x00002000 => 'NET_SFTP_ATTR_LINK_COUNT',
+ 0x00004000 => 'NET_SFTP_ATTR_UNTRANSLATED_NAME',
+ 0x00008000 => 'NET_SFTP_ATTR_CTIME',
+ // 0x80000000 will yield a floating point on 32-bit systems and converting floating points to integers
+ // yields inconsistent behavior depending on how php is compiled. so we left shift -1 (which, in
+ // two's compliment, consists of all 1 bits) by 31. on 64-bit systems this'll yield 0xFFFFFFFF80000000.
+ // that's not a problem, however, and 'anded' and a 32-bit number, as all the leading 1 bits are ignored.
+ (PHP_INT_SIZE == 4 ? (-1 << 31) : 0x80000000) => 'NET_SFTP_ATTR_EXTENDED'
+ ];
+ // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3
+ // the flag definitions change somewhat in SFTPv5+. if SFTPv5+ support is added to this library, maybe name
+ // the array for that $this->open5_flags and similarly alter the constant names.
+ self::$open_flags = [
+ 0x00000001 => 'NET_SFTP_OPEN_READ',
+ 0x00000002 => 'NET_SFTP_OPEN_WRITE',
+ 0x00000004 => 'NET_SFTP_OPEN_APPEND',
+ 0x00000008 => 'NET_SFTP_OPEN_CREATE',
+ 0x00000010 => 'NET_SFTP_OPEN_TRUNCATE',
+ 0x00000020 => 'NET_SFTP_OPEN_EXCL',
+ 0x00000040 => 'NET_SFTP_OPEN_TEXT' // defined in SFTPv4
+ ];
+ // SFTPv5+ changed the flags up:
+ // https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-8.1.1.3
+ self::$open_flags5 = [
+ // when SSH_FXF_ACCESS_DISPOSITION is a 3 bit field that controls how the file is opened
+ 0x00000000 => 'NET_SFTP_OPEN_CREATE_NEW',
+ 0x00000001 => 'NET_SFTP_OPEN_CREATE_TRUNCATE',
+ 0x00000002 => 'NET_SFTP_OPEN_OPEN_EXISTING',
+ 0x00000003 => 'NET_SFTP_OPEN_OPEN_OR_CREATE',
+ 0x00000004 => 'NET_SFTP_OPEN_TRUNCATE_EXISTING',
+ // the rest of the flags are not supported
+ 0x00000008 => 'NET_SFTP_OPEN_APPEND_DATA', // "the offset field of SS_FXP_WRITE requests is ignored"
+ 0x00000010 => 'NET_SFTP_OPEN_APPEND_DATA_ATOMIC',
+ 0x00000020 => 'NET_SFTP_OPEN_TEXT_MODE',
+ 0x00000040 => 'NET_SFTP_OPEN_BLOCK_READ',
+ 0x00000080 => 'NET_SFTP_OPEN_BLOCK_WRITE',
+ 0x00000100 => 'NET_SFTP_OPEN_BLOCK_DELETE',
+ 0x00000200 => 'NET_SFTP_OPEN_BLOCK_ADVISORY',
+ 0x00000400 => 'NET_SFTP_OPEN_NOFOLLOW',
+ 0x00000800 => 'NET_SFTP_OPEN_DELETE_ON_CLOSE',
+ 0x00001000 => 'NET_SFTP_OPEN_ACCESS_AUDIT_ALARM_INFO',
+ 0x00002000 => 'NET_SFTP_OPEN_ACCESS_BACKUP',
+ 0x00004000 => 'NET_SFTP_OPEN_BACKUP_STREAM',
+ 0x00008000 => 'NET_SFTP_OPEN_OVERRIDE_OWNER',
+ ];
+ // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2
+ // see \phpseclib3\Net\SFTP::_parseLongname() for an explanation
+ self::$file_types = [
+ 1 => 'NET_SFTP_TYPE_REGULAR',
+ 2 => 'NET_SFTP_TYPE_DIRECTORY',
+ 3 => 'NET_SFTP_TYPE_SYMLINK',
+ 4 => 'NET_SFTP_TYPE_SPECIAL',
+ 5 => 'NET_SFTP_TYPE_UNKNOWN',
+ // the following types were first defined for use in SFTPv5+
+ // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
+ 6 => 'NET_SFTP_TYPE_SOCKET',
+ 7 => 'NET_SFTP_TYPE_CHAR_DEVICE',
+ 8 => 'NET_SFTP_TYPE_BLOCK_DEVICE',
+ 9 => 'NET_SFTP_TYPE_FIFO'
+ ];
+ self::define_array(
+ self::$packet_types,
+ self::$status_codes,
+ self::$attributes,
+ self::$open_flags,
+ self::$open_flags5,
+ self::$file_types
+ );
+ }
if (!defined('NET_SFTP_QUEUE_SIZE')) {
define('NET_SFTP_QUEUE_SIZE', 32);
@@ -525,16 +526,15 @@ class SFTP extends SSH2
* Check a few things before SFTP functions are called
*
* @return bool
- * @access public
*/
- function _precheck()
+ private function precheck()
{
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
return false;
}
if ($this->pwd === false) {
- return $this->_init_sftp_connection();
+ return $this->init_sftp_connection();
}
return true;
@@ -543,53 +543,29 @@ class SFTP extends SSH2
/**
* Partially initialize an SFTP connection
*
+ * @throws \UnexpectedValueException on receipt of unexpected packets
* @return bool
- * @access public
*/
- function _partial_init_sftp_connection()
+ private function partial_init_sftp_connection()
{
- $this->window_size_server_to_client[self::CHANNEL] = $this->window_size;
-
- $packet = pack(
- 'CNa*N3',
- NET_SSH2_MSG_CHANNEL_OPEN,
- strlen('session'),
- 'session',
- self::CHANNEL,
- $this->window_size,
- 0x4000
- );
-
- if (!$this->_send_binary_packet($packet)) {
- return false;
- }
-
- $this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_OPEN;
-
- $response = $this->_get_channel_packet(self::CHANNEL, true);
- if ($response === false) {
- return false;
- } elseif ($response === true && $this->isTimeout()) {
+ $response = $this->open_channel(self::CHANNEL, true);
+ if ($response === true && $this->isTimeout()) {
return false;
}
- $packet = pack(
- 'CNNa*CNa*',
+ $packet = Strings::packSSH2(
+ 'CNsbs',
NET_SSH2_MSG_CHANNEL_REQUEST,
$this->server_channels[self::CHANNEL],
- strlen('subsystem'),
'subsystem',
- 1,
- strlen('sftp'),
+ true,
'sftp'
);
- if (!$this->_send_binary_packet($packet)) {
- return false;
- }
+ $this->send_binary_packet($packet);
$this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
- $response = $this->_get_channel_packet(self::CHANNEL, true);
+ $response = $this->get_channel_packet(self::CHANNEL, true);
if ($response === false) {
// from PuTTY's psftp.exe
$command = "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n" .
@@ -597,23 +573,19 @@ class SFTP extends SSH2
"exec sftp-server";
// we don't do $this->exec($command, false) because exec() operates on a different channel and plus the SSH_MSG_CHANNEL_OPEN that exec() does
// is redundant
- $packet = pack(
- 'CNNa*CNa*',
+ $packet = Strings::packSSH2(
+ 'CNsCs',
NET_SSH2_MSG_CHANNEL_REQUEST,
$this->server_channels[self::CHANNEL],
- strlen('exec'),
'exec',
1,
- strlen($command),
$command
);
- if (!$this->_send_binary_packet($packet)) {
- return false;
- }
+ $this->send_binary_packet($packet);
$this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
- $response = $this->_get_channel_packet(self::CHANNEL, true);
+ $response = $this->get_channel_packet(self::CHANNEL, true);
if ($response === false) {
return false;
}
@@ -622,35 +594,19 @@ class SFTP extends SSH2
}
$this->channel_status[self::CHANNEL] = NET_SSH2_MSG_CHANNEL_DATA;
+ $this->send_sftp_packet(NET_SFTP_INIT, "\0\0\0\3");
- if (!$this->_send_sftp_packet(NET_SFTP_INIT, "\0\0\0\3")) {
- return false;
- }
-
- $response = $this->_get_sftp_packet();
+ $response = $this->get_sftp_packet();
if ($this->packet_type != NET_SFTP_VERSION) {
- user_error('Expected SSH_FXP_VERSION');
- return false;
+ throw new \UnexpectedValueException('Expected NET_SFTP_VERSION. '
+ . 'Got packet type: ' . $this->packet_type);
}
$this->use_request_id = true;
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nversion', $this->_string_shift($response, 4)));
- $this->defaultVersion = $version;
+ list($this->defaultVersion) = Strings::unpackSSH2('N', $response);
while (!empty($response)) {
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
- $key = $this->_string_shift($response, $length);
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
- $value = $this->_string_shift($response, $length);
+ list($key, $value) = Strings::unpackSSH2('ss', $response);
$this->extensions[$key] = $value;
}
@@ -663,11 +619,10 @@ class SFTP extends SSH2
* (Re)initializes the SFTP channel
*
* @return bool
- * @access private
*/
- function _init_sftp_connection()
+ private function init_sftp_connection()
{
- if (!$this->partial_init && !$this->_partial_init_sftp_connection()) {
+ if (!$this->partial_init && !$this->partial_init_sftp_connection()) {
return false;
}
@@ -691,15 +646,15 @@ class SFTP extends SSH2
So what do you do if you have a client whose initial SSH_FXP_INIT packet says it implements v3 and
a server whose initial SSH_FXP_VERSION reply says it implements v4 and only v4? If it only implements
v4, the "versions" extension is likely not going to have been sent so version re-negotiation as discussed
- in draft-ietf-secsh-filexfer-13 would be quite impossible. As such, what \phpseclib\Net\SFTP would do is close the
+ in draft-ietf-secsh-filexfer-13 would be quite impossible. As such, what \phpseclib3\Net\SFTP would do is close the
channel and reopen it with a new and updated SSH_FXP_INIT packet.
*/
$this->version = $this->defaultVersion;
if (isset($this->extensions['versions']) && (!$this->preferredVersion || $this->preferredVersion != $this->version)) {
$versions = explode(',', $this->extensions['versions']);
- $supported = array(6, 5, 4);
+ $supported = [6, 5, 4];
if ($this->preferredVersion) {
- $supported = array_diff($supported, array($this->preferredVersion));
+ $supported = array_diff($supported, [$this->preferredVersion]);
array_unshift($supported, $this->preferredVersion);
}
foreach ($supported as $ver) {
@@ -708,25 +663,19 @@ class SFTP extends SSH2
break;
}
$this->version = (int) $ver;
- $packet = pack('Na*Na*', strlen('version-select'), 'version-select', strlen($ver), $ver);
- if (!$this->_send_sftp_packet(NET_SFTP_EXTENDED, $packet)) {
- return false;
- }
- $response = $this->_get_sftp_packet();
+ $packet = Strings::packSSH2('ss', 'version-select', "$ver");
+ $this->send_sftp_packet(NET_SFTP_EXTENDED, $packet);
+ $response = $this->get_sftp_packet();
if ($this->packet_type != NET_SFTP_STATUS) {
- user_error('Expected SSH_FXP_STATUS');
- return false;
- }
-
- if (strlen($response) < 4) {
- return false;
+ throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
+ . 'Got packet type: ' . $this->packet_type);
}
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
+ list($status) = Strings::unpackSSH2('N', $response);
if ($status != NET_SFTP_STATUS_OK) {
- $this->_logError($response, $status);
- return false;
+ $this->logError($response, $status);
+ throw new \UnexpectedValueException('Expected NET_SFTP_STATUS_OK. '
+ . ' Got ' . $status);
}
-
break;
}
}
@@ -745,24 +694,23 @@ class SFTP extends SSH2
unset($this->extensions['newline@vandyke.com']);
}
*/
-
if ($this->version < 2 || $this->version > 6) {
return false;
}
$this->pwd = true;
- $this->pwd = $this->_realpath('.');
- if ($this->pwd === false) {
+ try {
+ $this->pwd = $this->realpath('.');
+ } catch (\UnexpectedValueException $e) {
if (!$this->canonicalize_paths) {
- user_error('Unable to canonicalize current working directory');
- return false;
+ throw $e;
}
$this->canonicalize_paths = false;
- $this->_reset_sftp();
- return $this->_init_sftp_connection();
+ $this->reset_sftp();
+ return $this->init_sftp_connection();
}
- $this->_update_stat_cache($this->pwd, array());
+ $this->update_stat_cache($this->pwd, []);
return true;
}
@@ -770,9 +718,8 @@ class SFTP extends SSH2
/**
* Disable the stat cache
*
- * @access public
*/
- function disableStatCache()
+ public function disableStatCache()
{
$this->use_stat_cache = false;
}
@@ -780,9 +727,8 @@ class SFTP extends SSH2
/**
* Enable the stat cache
*
- * @access public
*/
- function enableStatCache()
+ public function enableStatCache()
{
$this->use_stat_cache = true;
}
@@ -790,19 +736,17 @@ class SFTP extends SSH2
/**
* Clear the stat cache
*
- * @access public
*/
- function clearStatCache()
+ public function clearStatCache()
{
- $this->stat_cache = array();
+ $this->stat_cache = [];
}
/**
* Enable path canonicalization
*
- * @access public
*/
- function enablePathCanonicalization()
+ public function enablePathCanonicalization()
{
$this->canonicalize_paths = true;
}
@@ -812,9 +756,8 @@ class SFTP extends SSH2
*
* If this is enabled then $sftp->pwd() will not return the canonicalized absolute path
*
- * @access public
*/
- function disablePathCanonicalization()
+ public function disablePathCanonicalization()
{
$this->canonicalize_paths = false;
}
@@ -822,9 +765,8 @@ class SFTP extends SSH2
/**
* Enable arbitrary length packets
*
- * @access public
*/
- function enableArbitraryLengthPackets()
+ public function enableArbitraryLengthPackets()
{
$this->allow_arbitrary_length_packets = true;
}
@@ -832,9 +774,8 @@ class SFTP extends SSH2
/**
* Disable arbitrary length packets
*
- * @access public
*/
- function disableArbitraryLengthPackets()
+ public function disableArbitraryLengthPackets()
{
$this->allow_arbitrary_length_packets = false;
}
@@ -842,12 +783,11 @@ class SFTP extends SSH2
/**
* Returns the current directory name
*
- * @return mixed
- * @access public
+ * @return string|bool
*/
- function pwd()
+ public function pwd()
{
- if (!$this->_precheck()) {
+ if (!$this->precheck()) {
return false;
}
@@ -859,47 +799,24 @@ class SFTP extends SSH2
*
* @param string $response
* @param int $status
- * @access public
*/
- function _logError($response, $status = -1)
+ private function logError($response, $status = -1)
{
if ($status == -1) {
- if (strlen($response) < 4) {
- return;
- }
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
+ list($status) = Strings::unpackSSH2('N', $response);
}
- $error = $this->status_codes[$status];
+ $error = self::$status_codes[$status];
if ($this->version > 2) {
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
- $this->sftp_errors[] = $error . ': ' . $this->_string_shift($response, $length);
+ list($message) = Strings::unpackSSH2('s', $response);
+ $this->sftp_errors[] = "$error: $message";
} else {
$this->sftp_errors[] = $error;
}
}
/**
- * Returns canonicalized absolute pathname
- *
- * realpath() expands all symbolic links and resolves references to '/./', '/../' and extra '/' characters in the input
- * path and returns the canonicalized absolute pathname.
- *
- * @param string $path
- * @return mixed
- * @access public
- */
- function realpath($path)
- {
- if (!$this->_precheck()) {
- return false;
- }
-
- return $this->_realpath($path);
- }
-
- /**
* Canonicalize the Server-Side Path Name
*
* SFTP doesn't provide a mechanism by which the current working directory can be changed, so we'll emulate it. Returns
@@ -910,11 +827,17 @@ class SFTP extends SSH2
* @see self::chdir()
* @see self::disablePathCanonicalization()
* @param string $path
+ * @throws \UnexpectedValueException on receipt of unexpected packets
* @return mixed
- * @access private
*/
- function _realpath($path)
+ public function realpath($path)
{
+ if ($this->precheck() === false) {
+ return false;
+ }
+
+ $path = (string) $path;
+
if (!$this->canonicalize_paths) {
if ($this->pwd === true) {
return '.';
@@ -922,9 +845,8 @@ class SFTP extends SSH2
if (!strlen($path) || $path[0] != '/') {
$path = $this->pwd . '/' . $path;
}
-
$parts = explode('/', $path);
- $afterPWD = $beforePWD = array();
+ $afterPWD = $beforePWD = [];
foreach ($parts as $part) {
switch ($part) {
//case '': // some SFTP servers /require/ double /'s. see https://github.com/phpseclib/phpseclib/pull/1137
@@ -941,34 +863,28 @@ class SFTP extends SSH2
$afterPWD[] = $part;
}
}
-
$beforePWD = count($beforePWD) ? implode('/', $beforePWD) : '.';
return $beforePWD . '/' . implode('/', $afterPWD);
}
if ($this->pwd === true) {
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9
- if (!$this->_send_sftp_packet(NET_SFTP_REALPATH, pack('Na*', strlen($path), $path))) {
- return false;
- }
+ $this->send_sftp_packet(NET_SFTP_REALPATH, Strings::packSSH2('s', $path));
- $response = $this->_get_sftp_packet();
+ $response = $this->get_sftp_packet();
switch ($this->packet_type) {
case NET_SFTP_NAME:
// although SSH_FXP_NAME is implemented differently in SFTPv3 than it is in SFTPv4+, the following
// should work on all SFTP versions since the only part of the SSH_FXP_NAME packet the following looks
// at is the first part and that part is defined the same in SFTP versions 3 through 6.
- $this->_string_shift($response, 4); // skip over the count - it should be 1, anyway
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
- return $this->_string_shift($response, $length);
+ list(, $filename) = Strings::unpackSSH2('Ns', $response);
+ return $filename;
case NET_SFTP_STATUS:
- $this->_logError($response);
+ $this->logError($response);
return false;
default:
- return false;
+ throw new \UnexpectedValueException('Expected NET_SFTP_NAME or NET_SFTP_STATUS. '
+ . 'Got packet type: ' . $this->packet_type);
}
}
@@ -977,7 +893,7 @@ class SFTP extends SSH2
}
$path = explode('/', $path);
- $new = array();
+ $new = [];
foreach ($path as $dir) {
if (!strlen($dir)) {
continue;
@@ -985,6 +901,7 @@ class SFTP extends SSH2
switch ($dir) {
case '..':
array_pop($new);
+ // fall-through
case '.':
break;
default:
@@ -999,27 +916,29 @@ class SFTP extends SSH2
* Changes the current directory
*
* @param string $dir
+ * @throws \UnexpectedValueException on receipt of unexpected packets
* @return bool
- * @access public
*/
- function chdir($dir)
+ public function chdir($dir)
{
- if (!$this->_precheck()) {
+ if (!$this->precheck()) {
return false;
}
+ $dir = (string) $dir;
+
// assume current dir if $dir is empty
if ($dir === '') {
$dir = './';
// suffix a slash if needed
} elseif ($dir[strlen($dir) - 1] != '/') {
- $dir.= '/';
+ $dir .= '/';
}
- $dir = $this->_realpath($dir);
+ $dir = $this->realpath($dir);
// confirm that $dir is, in fact, a valid directory
- if ($this->use_stat_cache && is_array($this->_query_stat_cache($dir))) {
+ if ($this->use_stat_cache && is_array($this->query_stat_cache($dir))) {
$this->pwd = $dir;
return true;
}
@@ -1029,29 +948,27 @@ class SFTP extends SSH2
// the file's uid / gid match the currently logged in user's uid / gid but how there's no easy
// way to get those with SFTP
- if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {
- return false;
- }
+ $this->send_sftp_packet(NET_SFTP_OPENDIR, Strings::packSSH2('s', $dir));
- // see \phpseclib\Net\SFTP::nlist() for a more thorough explanation of the following
- $response = $this->_get_sftp_packet();
+ // see \phpseclib3\Net\SFTP::nlist() for a more thorough explanation of the following
+ $response = $this->get_sftp_packet();
switch ($this->packet_type) {
case NET_SFTP_HANDLE:
$handle = substr($response, 4);
break;
case NET_SFTP_STATUS:
- $this->_logError($response);
+ $this->logError($response);
return false;
default:
- user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
- return false;
+ throw new \UnexpectedValueException('Expected NET_SFTP_HANDLE or NET_SFTP_STATUS' .
+ 'Got packet type: ' . $this->packet_type);
}
- if (!$this->_close_handle($handle)) {
+ if (!$this->close_handle($handle)) {
return false;
}
- $this->_update_stat_cache($dir, array());
+ $this->update_stat_cache($dir, []);
$this->pwd = $dir;
return true;
@@ -1062,12 +979,11 @@ class SFTP extends SSH2
*
* @param string $dir
* @param bool $recursive
- * @return mixed
- * @access public
+ * @return array|false
*/
- function nlist($dir = '.', $recursive = false)
+ public function nlist($dir = '.', $recursive = false)
{
- return $this->_nlist_helper($dir, $recursive, '');
+ return $this->nlist_helper($dir, $recursive, '');
}
/**
@@ -1076,12 +992,11 @@ class SFTP extends SSH2
* @param string $dir
* @param bool $recursive
* @param string $relativeDir
- * @return mixed
- * @access private
+ * @return array|false
*/
- function _nlist_helper($dir, $recursive, $relativeDir)
+ private function nlist_helper($dir, $recursive, $relativeDir)
{
- $files = $this->_list($dir, false);
+ $files = $this->readlist($dir, false);
// If we get an int back, then that is an "unexpected" status.
// We do not have a file list, so return false.
@@ -1093,17 +1008,15 @@ class SFTP extends SSH2
return $files;
}
- $result = array();
+ $result = [];
foreach ($files as $value) {
if ($value == '.' || $value == '..') {
- if ($relativeDir == '') {
- $result[] = $value;
- }
+ $result[] = $relativeDir . $value;
continue;
}
- if (is_array($this->_query_stat_cache($this->_realpath($dir . '/' . $value)))) {
- $temp = $this->_nlist_helper($dir . '/' . $value, true, $relativeDir . $value . '/');
- $temp = is_array($temp) ? $temp : array();
+ if (is_array($this->query_stat_cache($this->realpath($dir . '/' . $value)))) {
+ $temp = $this->nlist_helper($dir . '/' . $value, true, $relativeDir . $value . '/');
+ $temp = is_array($temp) ? $temp : [];
$result = array_merge($result, $temp);
} else {
$result[] = $relativeDir . $value;
@@ -1118,12 +1031,11 @@ class SFTP extends SSH2
*
* @param string $dir
* @param bool $recursive
- * @return mixed
- * @access public
+ * @return array|false
*/
- function rawlist($dir = '.', $recursive = false)
+ public function rawlist($dir = '.', $recursive = false)
{
- $files = $this->_list($dir, true);
+ $files = $this->readlist($dir, true);
// If we get an int back, then that is an "unexpected" status.
// We do not have a file list, so return false.
@@ -1145,7 +1057,7 @@ class SFTP extends SSH2
$is_directory = false;
if ($key != '.' && $key != '..') {
if ($this->use_stat_cache) {
- $is_directory = is_array($this->_query_stat_cache($this->_realpath($dir . '/' . $key)));
+ $is_directory = is_array($this->query_stat_cache($this->realpath($dir . '/' . $key)));
} else {
$stat = $this->lstat($dir . '/' . $key);
$is_directory = $stat && $stat['type'] === NET_SFTP_TYPE_DIRECTORY;
@@ -1169,26 +1081,24 @@ class SFTP extends SSH2
*
* @param string $dir
* @param bool $raw
- * @return mixed
- * @access private
+ * @return array|false
+ * @throws \UnexpectedValueException on receipt of unexpected packets
*/
- function _list($dir, $raw = true)
+ private function readlist($dir, $raw = true)
{
- if (!$this->_precheck()) {
+ if (!$this->precheck()) {
return false;
}
- $dir = $this->_realpath($dir . '/');
+ $dir = $this->realpath($dir . '/');
if ($dir === false) {
return false;
}
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.2
- if (!$this->_send_sftp_packet(NET_SFTP_OPENDIR, pack('Na*', strlen($dir), $dir))) {
- return false;
- }
+ $this->send_sftp_packet(NET_SFTP_OPENDIR, Strings::packSSH2('s', $dir));
- $response = $this->_get_sftp_packet();
+ $response = $this->get_sftp_packet();
switch ($this->packet_type) {
case NET_SFTP_HANDLE:
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.2
@@ -1198,95 +1108,76 @@ class SFTP extends SSH2
break;
case NET_SFTP_STATUS:
// presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
- $this->_logError($response, $status);
+ list($status) = Strings::unpackSSH2('N', $response);
+ $this->logError($response, $status);
return $status;
default:
- user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
- return false;
+ throw new \UnexpectedValueException('Expected NET_SFTP_HANDLE or NET_SFTP_STATUS. '
+ . 'Got packet type: ' . $this->packet_type);
}
- $this->_update_stat_cache($dir, array());
+ $this->update_stat_cache($dir, []);
- $contents = array();
+ $contents = [];
while (true) {
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.2
// why multiple SSH_FXP_READDIR packets would be sent when the response to a single one can span arbitrarily many
// SSH_MSG_CHANNEL_DATA messages is not known to me.
- if (!$this->_send_sftp_packet(NET_SFTP_READDIR, pack('Na*', strlen($handle), $handle))) {
- return false;
- }
+ $this->send_sftp_packet(NET_SFTP_READDIR, Strings::packSSH2('s', $handle));
- $response = $this->_get_sftp_packet();
+ $response = $this->get_sftp_packet();
switch ($this->packet_type) {
case NET_SFTP_NAME:
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Ncount', $this->_string_shift($response, 4)));
+ list($count) = Strings::unpackSSH2('N', $response);
for ($i = 0; $i < $count; $i++) {
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
- $shortname = $this->_string_shift($response, $length);
+ list($shortname) = Strings::unpackSSH2('s', $response);
// SFTPv4 "removed the long filename from the names structure-- it can now be
// built from information available in the attrs structure."
if ($this->version < 4) {
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
- $longname = $this->_string_shift($response, $length);
+ list($longname) = Strings::unpackSSH2('s', $response);
}
- $attributes = $this->_parseAttributes($response);
+ $attributes = $this->parseAttributes($response);
if (!isset($attributes['type']) && $this->version < 4) {
- $fileType = $this->_parseLongname($longname);
+ $fileType = $this->parseLongname($longname);
if ($fileType) {
$attributes['type'] = $fileType;
}
}
- $contents[$shortname] = $attributes + array('filename' => $shortname);
+ $contents[$shortname] = $attributes + ['filename' => $shortname];
if (isset($attributes['type']) && $attributes['type'] == NET_SFTP_TYPE_DIRECTORY && ($shortname != '.' && $shortname != '..')) {
- $this->_update_stat_cache($dir . '/' . $shortname, array());
+ $this->update_stat_cache($dir . '/' . $shortname, []);
} else {
if ($shortname == '..') {
- $temp = $this->_realpath($dir . '/..') . '/.';
+ $temp = $this->realpath($dir . '/..') . '/.';
} else {
$temp = $dir . '/' . $shortname;
}
- $this->_update_stat_cache($temp, (object) array('lstat' => $attributes));
+ $this->update_stat_cache($temp, (object) ['lstat' => $attributes]);
}
// SFTPv6 has an optional boolean end-of-list field, but we'll ignore that, since the
// final SSH_FXP_STATUS packet should tell us that, already.
}
break;
case NET_SFTP_STATUS:
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
+ list($status) = Strings::unpackSSH2('N', $response);
if ($status != NET_SFTP_STATUS_EOF) {
- $this->_logError($response, $status);
+ $this->logError($response, $status);
return $status;
}
break 2;
default:
- user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
- return false;
+ throw new \UnexpectedValueException('Expected NET_SFTP_NAME or NET_SFTP_STATUS. '
+ . 'Got packet type: ' . $this->packet_type);
}
}
- if (!$this->_close_handle($handle)) {
+ if (!$this->close_handle($handle)) {
return false;
}
if (count($this->sortOptions)) {
- uasort($contents, array(&$this, '_comparator'));
+ uasort($contents, [&$this, 'comparator']);
}
return $raw ? $contents : array_map('strval', array_keys($contents));
@@ -1300,9 +1191,8 @@ class SFTP extends SSH2
* @param array $a
* @param array $b
* @return int
- * @access private
*/
- function _comparator($a, $b)
+ private function comparator(array $a, array $b)
{
switch (true) {
case $a['filename'] === '.' || $b['filename'] === '.':
@@ -1343,10 +1233,10 @@ class SFTP extends SSH2
return $order === SORT_DESC ? -$result : $result;
}
break;
- case 'permissions':
case 'mode':
- $a[$sort]&= 07777;
- $b[$sort]&= 07777;
+ $a[$sort] &= 07777;
+ $b[$sort] &= 07777;
+ // fall-through
default:
if ($a[$sort] === $b[$sort]) {
break;
@@ -1374,50 +1264,30 @@ class SFTP extends SSH2
* $sftp->setListOrder();
* Don't do any sort of sorting
*
- * @access public
+ * @param string ...$args
*/
- function setListOrder()
+ public function setListOrder(...$args)
{
- $this->sortOptions = array();
- $args = func_get_args();
+ $this->sortOptions = [];
if (empty($args)) {
return;
}
$len = count($args) & 0x7FFFFFFE;
- for ($i = 0; $i < $len; $i+=2) {
+ for ($i = 0; $i < $len; $i += 2) {
$this->sortOptions[$args[$i]] = $args[$i + 1];
}
if (!count($this->sortOptions)) {
- $this->sortOptions = array('bogus' => true);
+ $this->sortOptions = ['bogus' => true];
}
}
/**
- * Returns the file size, in bytes, or false, on failure
- *
- * Files larger than 4GB will show up as being exactly 4GB.
- *
- * @param string $filename
- * @return mixed
- * @access public
- */
- function size($filename)
- {
- $result = $this->stat($filename);
- if ($result === false) {
- return false;
- }
- return isset($result['size']) ? $result['size'] : -1;
- }
-
- /**
* Save files / directories to cache
*
* @param string $path
* @param mixed $value
- * @access private
*/
- function _update_stat_cache($path, $value)
+ private function update_stat_cache($path, $value)
{
if ($this->use_stat_cache === false) {
return;
@@ -1433,10 +1303,10 @@ class SFTP extends SSH2
// 1. a file was deleted and changed to a directory behind phpseclib's back
// 2. it's a symlink. when lstat is done it's unclear what it's a symlink to
if (is_object($temp)) {
- $temp = array();
+ $temp = [];
}
if (!isset($temp[$dir])) {
- $temp[$dir] = array();
+ $temp[$dir] = [];
}
if ($i === $max) {
if (is_object($temp[$dir]) && is_object($value)) {
@@ -1459,9 +1329,8 @@ class SFTP extends SSH2
*
* @param string $path
* @return bool
- * @access private
*/
- function _remove_from_stat_cache($path)
+ private function remove_from_stat_cache($path)
{
$dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
@@ -1489,9 +1358,8 @@ class SFTP extends SSH2
*
* @param string $path
* @return mixed
- * @access private
*/
- function _query_stat_cache($path)
+ private function query_stat_cache($path)
{
$dirs = explode('/', preg_replace('#^/|/(?=/)|/$#', '', $path));
@@ -1514,22 +1382,21 @@ class SFTP extends SSH2
* Returns an array on success and false otherwise.
*
* @param string $filename
- * @return mixed
- * @access public
+ * @return array|false
*/
- function stat($filename)
+ public function stat($filename)
{
- if (!$this->_precheck()) {
+ if (!$this->precheck()) {
return false;
}
- $filename = $this->_realpath($filename);
+ $filename = $this->realpath($filename);
if ($filename === false) {
return false;
}
if ($this->use_stat_cache) {
- $result = $this->_query_stat_cache($filename);
+ $result = $this->query_stat_cache($filename);
if (is_array($result) && isset($result['.']) && isset($result['.']->stat)) {
return $result['.']->stat;
}
@@ -1538,16 +1405,16 @@ class SFTP extends SSH2
}
}
- $stat = $this->_stat($filename, NET_SFTP_STAT);
+ $stat = $this->stat_helper($filename, NET_SFTP_STAT);
if ($stat === false) {
- $this->_remove_from_stat_cache($filename);
+ $this->remove_from_stat_cache($filename);
return false;
}
if (isset($stat['type'])) {
if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
- $filename.= '/.';
+ $filename .= '/.';
}
- $this->_update_stat_cache($filename, (object) array('stat' => $stat));
+ $this->update_stat_cache($filename, (object) ['stat' => $stat]);
return $stat;
}
@@ -1558,9 +1425,9 @@ class SFTP extends SSH2
$this->pwd = $pwd;
if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
- $filename.= '/.';
+ $filename .= '/.';
}
- $this->_update_stat_cache($filename, (object) array('stat' => $stat));
+ $this->update_stat_cache($filename, (object) ['stat' => $stat]);
return $stat;
}
@@ -1571,22 +1438,21 @@ class SFTP extends SSH2
* Returns an array on success and false otherwise.
*
* @param string $filename
- * @return mixed
- * @access public
+ * @return array|false
*/
- function lstat($filename)
+ public function lstat($filename)
{
- if (!$this->_precheck()) {
+ if (!$this->precheck()) {
return false;
}
- $filename = $this->_realpath($filename);
+ $filename = $this->realpath($filename);
if ($filename === false) {
return false;
}
if ($this->use_stat_cache) {
- $result = $this->_query_stat_cache($filename);
+ $result = $this->query_stat_cache($filename);
if (is_array($result) && isset($result['.']) && isset($result['.']->lstat)) {
return $result['.']->lstat;
}
@@ -1595,24 +1461,24 @@ class SFTP extends SSH2
}
}
- $lstat = $this->_stat($filename, NET_SFTP_LSTAT);
+ $lstat = $this->stat_helper($filename, NET_SFTP_LSTAT);
if ($lstat === false) {
- $this->_remove_from_stat_cache($filename);
+ $this->remove_from_stat_cache($filename);
return false;
}
if (isset($lstat['type'])) {
if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) {
- $filename.= '/.';
+ $filename .= '/.';
}
- $this->_update_stat_cache($filename, (object) array('lstat' => $lstat));
+ $this->update_stat_cache($filename, (object) ['lstat' => $lstat]);
return $lstat;
}
- $stat = $this->_stat($filename, NET_SFTP_STAT);
+ $stat = $this->stat_helper($filename, NET_SFTP_STAT);
if ($lstat != $stat) {
- $lstat = array_merge($lstat, array('type' => NET_SFTP_TYPE_SYMLINK));
- $this->_update_stat_cache($filename, (object) array('lstat' => $lstat));
+ $lstat = array_merge($lstat, ['type' => NET_SFTP_TYPE_SYMLINK]);
+ $this->update_stat_cache($filename, (object) ['lstat' => $lstat]);
return $stat;
}
@@ -1623,9 +1489,9 @@ class SFTP extends SSH2
$this->pwd = $pwd;
if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) {
- $filename.= '/.';
+ $filename .= '/.';
}
- $this->_update_stat_cache($filename, (object) array('lstat' => $lstat));
+ $this->update_stat_cache($filename, (object) ['lstat' => $lstat]);
return $lstat;
}
@@ -1633,33 +1499,31 @@ class SFTP extends SSH2
/**
* Returns general information about a file or symbolic link
*
- * Determines information without calling \phpseclib\Net\SFTP::realpath().
+ * Determines information without calling \phpseclib3\Net\SFTP::realpath().
* The second parameter can be either NET_SFTP_STAT or NET_SFTP_LSTAT.
*
* @param string $filename
* @param int $type
- * @return mixed
- * @access private
+ * @throws \UnexpectedValueException on receipt of unexpected packets
+ * @return array|false
*/
- function _stat($filename, $type)
+ private function stat_helper($filename, $type)
{
// SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
- $packet = pack('Na*', strlen($filename), $filename);
- if (!$this->_send_sftp_packet($type, $packet)) {
- return false;
- }
+ $packet = Strings::packSSH2('s', $filename);
+ $this->send_sftp_packet($type, $packet);
- $response = $this->_get_sftp_packet();
+ $response = $this->get_sftp_packet();
switch ($this->packet_type) {
case NET_SFTP_ATTRS:
- return $this->_parseAttributes($response);
+ return $this->parseAttributes($response);
case NET_SFTP_STATUS:
- $this->_logError($response);
+ $this->logError($response);
return false;
}
- user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS');
- return false;
+ throw new \UnexpectedValueException('Expected NET_SFTP_ATTRS or NET_SFTP_STATUS. '
+ . 'Got packet type: ' . $this->packet_type);
}
/**
@@ -1668,13 +1532,12 @@ class SFTP extends SSH2
* @param string $filename
* @param int $new_size
* @return bool
- * @access public
*/
- function truncate($filename, $new_size)
+ public function truncate($filename, $new_size)
{
- $attr = pack('N3', NET_SFTP_ATTR_SIZE, $new_size / 4294967296, $new_size); // 4294967296 == 0x100000000 == 1<<32
+ $attr = Strings::packSSH2('NQ', NET_SFTP_ATTR_SIZE, $new_size);
- return $this->_setstat($filename, $attr, false);
+ return $this->setstat($filename, $attr, false);
}
/**
@@ -1685,16 +1548,16 @@ class SFTP extends SSH2
* @param string $filename
* @param int $time
* @param int $atime
+ * @throws \UnexpectedValueException on receipt of unexpected packets
* @return bool
- * @access public
*/
- function touch($filename, $time = null, $atime = null)
+ public function touch($filename, $time = null, $atime = null)
{
- if (!$this->_precheck()) {
+ if (!$this->precheck()) {
return false;
}
- $filename = $this->_realpath($filename);
+ $filename = $this->realpath($filename);
if ($filename === false) {
return false;
}
@@ -1706,42 +1569,31 @@ class SFTP extends SSH2
$atime = $time;
}
- if ($this->version < 4) {
- $attr = pack('N3', NET_SFTP_ATTR_ACCESSTIME, $atime, $time);
- } else {
- $attr = pack(
- 'N5',
- NET_SFTP_ATTR_ACCESSTIME | NET_SFTP_ATTR_MODIFYTIME,
- $atime / 4294967296,
- $atime,
- $time / 4294967296,
- $time
- );
- }
+ $attr = $this->version < 4 ?
+ pack('N3', NET_SFTP_ATTR_ACCESSTIME, $atime, $time) :
+ Strings::packSSH2('NQ2', NET_SFTP_ATTR_ACCESSTIME | NET_SFTP_ATTR_MODIFYTIME, $atime, $time);
- $packet = pack('Na*', strlen($filename), $filename);
- $packet.= $this->version >= 5 ?
+ $packet = Strings::packSSH2('s', $filename);
+ $packet .= $this->version >= 5 ?
pack('N2', 0, NET_SFTP_OPEN_OPEN_EXISTING) :
pack('N', NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE | NET_SFTP_OPEN_EXCL);
- $packet.= $attr;
+ $packet .= $attr;
- if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
- return false;
- }
+ $this->send_sftp_packet(NET_SFTP_OPEN, $packet);
- $response = $this->_get_sftp_packet();
+ $response = $this->get_sftp_packet();
switch ($this->packet_type) {
case NET_SFTP_HANDLE:
- return $this->_close_handle(substr($response, 4));
+ return $this->close_handle(substr($response, 4));
case NET_SFTP_STATUS:
- $this->_logError($response);
+ $this->logError($response);
break;
default:
- user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
- return false;
+ throw new \UnexpectedValueException('Expected NET_SFTP_HANDLE or NET_SFTP_STATUS. '
+ . 'Got packet type: ' . $this->packet_type);
}
- return $this->_setstat($filename, $attr, false);
+ return $this->setstat($filename, $attr, false);
}
/**
@@ -1758,9 +1610,8 @@ class SFTP extends SSH2
* @param int|string $uid
* @param bool $recursive
* @return bool
- * @access public
*/
- function chown($filename, $uid, $recursive = false)
+ public function chown($filename, $uid, $recursive = false)
{
/*
quoting <https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.5>,
@@ -1787,9 +1638,9 @@ class SFTP extends SSH2
// "If either the owner or group field is zero length, the field should be
// considered absent, and no change should be made to that specific field
// during a modification operation"
- pack('NNa*Na*', NET_SFTP_ATTR_OWNERGROUP, strlen($uid), $uid, 0, '');
+ Strings::packSSH2('Nss', NET_SFTP_ATTR_OWNERGROUP, $uid, '');
- return $this->_setstat($filename, $attr, $recursive);
+ return $this->setstat($filename, $attr, $recursive);
}
/**
@@ -1806,15 +1657,14 @@ class SFTP extends SSH2
* @param int|string $gid
* @param bool $recursive
* @return bool
- * @access public
*/
- function chgrp($filename, $gid, $recursive = false)
+ public function chgrp($filename, $gid, $recursive = false)
{
$attr = $this->version < 4 ?
pack('N3', NET_SFTP_ATTR_UIDGID, -1, $gid) :
- pack('NNa*Na*', NET_SFTP_ATTR_OWNERGROUP, 0, '', strlen($gid), $gid);
+ Strings::packSSH2('Nss', NET_SFTP_ATTR_OWNERGROUP, '', $gid);
- return $this->_setstat($filename, $attr, $recursive);
+ return $this->setstat($filename, $attr, $recursive);
}
/**
@@ -1826,10 +1676,10 @@ class SFTP extends SSH2
* @param int $mode
* @param string $filename
* @param bool $recursive
+ * @throws \UnexpectedValueException on receipt of unexpected packets
* @return mixed
- * @access public
*/
- function chmod($mode, $filename, $recursive = false)
+ public function chmod($mode, $filename, $recursive = false)
{
if (is_string($mode) && is_int($filename)) {
$temp = $mode;
@@ -1838,7 +1688,7 @@ class SFTP extends SSH2
}
$attr = pack('N2', NET_SFTP_ATTR_PERMISSIONS, $mode & 07777);
- if (!$this->_setstat($filename, $attr, $recursive)) {
+ if (!$this->setstat($filename, $attr, $recursive)) {
return false;
}
if ($recursive) {
@@ -1850,22 +1700,20 @@ class SFTP extends SSH2
// tell us if the file actually exists.
// incidentally, SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
$packet = pack('Na*', strlen($filename), $filename);
- if (!$this->_send_sftp_packet(NET_SFTP_STAT, $packet)) {
- return false;
- }
+ $this->send_sftp_packet(NET_SFTP_STAT, $packet);
- $response = $this->_get_sftp_packet();
+ $response = $this->get_sftp_packet();
switch ($this->packet_type) {
case NET_SFTP_ATTRS:
- $attrs = $this->_parseAttributes($response);
- return $attrs['permissions'];
+ $attrs = $this->parseAttributes($response);
+ return $attrs['mode'];
case NET_SFTP_STATUS:
- $this->_logError($response);
+ $this->logError($response);
return false;
}
- user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS');
- return false;
+ throw new \UnexpectedValueException('Expected NET_SFTP_ATTRS or NET_SFTP_STATUS. '
+ . 'Got packet type: ' . $this->packet_type);
}
/**
@@ -1874,35 +1722,34 @@ class SFTP extends SSH2
* @param string $filename
* @param string $attr
* @param bool $recursive
+ * @throws \UnexpectedValueException on receipt of unexpected packets
* @return bool
- * @access private
*/
- function _setstat($filename, $attr, $recursive)
+ private function setstat($filename, $attr, $recursive)
{
- if (!$this->_precheck()) {
+ if (!$this->precheck()) {
return false;
}
- $filename = $this->_realpath($filename);
+ $filename = $this->realpath($filename);
if ($filename === false) {
return false;
}
- $this->_remove_from_stat_cache($filename);
+ $this->remove_from_stat_cache($filename);
if ($recursive) {
$i = 0;
- $result = $this->_setstat_recursive($filename, $attr, $i);
- $this->_read_put_responses($i);
+ $result = $this->setstat_recursive($filename, $attr, $i);
+ $this->read_put_responses($i);
return $result;
}
- $packet = $this->version >= 4 ?
- pack('Na*a*Ca*', strlen($filename), $filename, substr($attr, 0, 4), NET_SFTP_TYPE_UNKNOWN, substr($attr, 4)) :
- pack('Na*a*', strlen($filename), $filename, $attr);
- if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, $packet)) {
- return false;
- }
+ $packet = Strings::packSSH2('s', $filename);
+ $packet .= $this->version >= 4 ?
+ pack('a*Ca*', substr($attr, 0, 4), NET_SFTP_TYPE_UNKNOWN, substr($attr, 4)) :
+ $attr;
+ $this->send_sftp_packet(NET_SFTP_SETSTAT, $packet);
/*
"Because some systems must use separate system calls to set various attributes, it is possible that a failure
@@ -1911,18 +1758,15 @@ class SFTP extends SSH2
-- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.6
*/
- $response = $this->_get_sftp_packet();
+ $response = $this->get_sftp_packet();
if ($this->packet_type != NET_SFTP_STATUS) {
- user_error('Expected SSH_FXP_STATUS');
- return false;
+ throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
+ . 'Got packet type: ' . $this->packet_type);
}
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
+ list($status) = Strings::unpackSSH2('N', $response);
if ($status != NET_SFTP_STATUS_OK) {
- $this->_logError($response, $status);
+ $this->logError($response, $status);
return false;
}
@@ -1938,18 +1782,17 @@ class SFTP extends SSH2
* @param string $attr
* @param int $i
* @return bool
- * @access private
*/
- function _setstat_recursive($path, $attr, &$i)
+ private function setstat_recursive($path, $attr, &$i)
{
- if (!$this->_read_put_responses($i)) {
+ if (!$this->read_put_responses($i)) {
return false;
}
$i = 0;
- $entries = $this->_list($path, true);
+ $entries = $this->readlist($path, true);
if ($entries === false || is_int($entries)) {
- return $this->_setstat($path, $attr, false);
+ return $this->setstat($path, $attr, false);
}
// normally $entries would have at least . and .. but it might not if the directories
@@ -1966,21 +1809,20 @@ class SFTP extends SSH2
$temp = $path . '/' . $filename;
if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
- if (!$this->_setstat_recursive($temp, $attr, $i)) {
+ if (!$this->setstat_recursive($temp, $attr, $i)) {
return false;
}
} else {
- $packet = $this->version >= 4 ?
- pack('Na*Ca*', strlen($temp), $temp, NET_SFTP_TYPE_UNKNOWN, $attr) :
- pack('Na*a*', strlen($temp), $temp, $attr);
- if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, $packet)) {
- return false;
- }
+ $packet = Strings::packSSH2('s', $temp);
+ $packet .= $this->version >= 4 ?
+ pack('Ca*', NET_SFTP_TYPE_UNKNOWN, $attr) :
+ $attr;
+ $this->send_sftp_packet(NET_SFTP_SETSTAT, $packet);
$i++;
if ($i >= NET_SFTP_QUEUE_SIZE) {
- if (!$this->_read_put_responses($i)) {
+ if (!$this->read_put_responses($i)) {
return false;
}
$i = 0;
@@ -1988,17 +1830,16 @@ class SFTP extends SSH2
}
}
- $packet = $this->version >= 4 ?
- pack('Na*Ca*', strlen($temp), $temp, NET_SFTP_TYPE_UNKNOWN, $attr) :
- pack('Na*a*', strlen($temp), $temp, $attr);
- if (!$this->_send_sftp_packet(NET_SFTP_SETSTAT, $packet)) {
- return false;
- }
+ $packet = Strings::packSSH2('s', $path);
+ $packet .= $this->version >= 4 ?
+ pack('Ca*', NET_SFTP_TYPE_UNKNOWN, $attr) :
+ $attr;
+ $this->send_sftp_packet(NET_SFTP_SETSTAT, $packet);
$i++;
if ($i >= NET_SFTP_QUEUE_SIZE) {
- if (!$this->_read_put_responses($i)) {
+ if (!$this->read_put_responses($i)) {
return false;
}
$i = 0;
@@ -2011,47 +1852,40 @@ class SFTP extends SSH2
* Return the target of a symbolic link
*
* @param string $link
+ * @throws \UnexpectedValueException on receipt of unexpected packets
* @return mixed
- * @access public
*/
- function readlink($link)
+ public function readlink($link)
{
- if (!$this->_precheck()) {
+ if (!$this->precheck()) {
return false;
}
- $link = $this->_realpath($link);
+ $link = $this->realpath($link);
- if (!$this->_send_sftp_packet(NET_SFTP_READLINK, pack('Na*', strlen($link), $link))) {
- return false;
- }
+ $this->send_sftp_packet(NET_SFTP_READLINK, Strings::packSSH2('s', $link));
- $response = $this->_get_sftp_packet();
+ $response = $this->get_sftp_packet();
switch ($this->packet_type) {
case NET_SFTP_NAME:
break;
case NET_SFTP_STATUS:
- $this->_logError($response);
+ $this->logError($response);
return false;
default:
- user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
- return false;
+ throw new \UnexpectedValueException('Expected NET_SFTP_NAME or NET_SFTP_STATUS. '
+ . 'Got packet type: ' . $this->packet_type);
}
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Ncount', $this->_string_shift($response, 4)));
+ list($count) = Strings::unpackSSH2('N', $response);
// the file isn't a symlink
if (!$count) {
return false;
}
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
- return $this->_string_shift($response, $length);
+ list($filename) = Strings::unpackSSH2('s', $response);
+
+ return $filename;
}
/**
@@ -2061,17 +1895,17 @@ class SFTP extends SSH2
*
* @param string $target
* @param string $link
+ * @throws \UnexpectedValueException on receipt of unexpected packets
* @return bool
- * @access public
*/
- function symlink($target, $link)
+ public function symlink($target, $link)
{
- if (!$this->_precheck()) {
+ if (!$this->precheck()) {
return false;
}
- //$target = $this->_realpath($target);
- $link = $this->_realpath($link);
+ //$target = $this->realpath($target);
+ $link = $this->realpath($link);
/* quoting https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-09#section-12.1 :
@@ -2082,7 +1916,7 @@ class SFTP extends SSH2
*/
if ($this->version == 6) {
$type = NET_SFTP_LINK;
- $packet = pack('Na*Na*C', strlen($link), $link, strlen($target), $target, 1);
+ $packet = Strings::packSSH2('ssC', $link, $target, 1);
} else {
$type = NET_SFTP_SYMLINK;
/* quoting http://bxr.su/OpenBSD/usr.bin/ssh/PROTOCOL#347 :
@@ -2100,25 +1934,20 @@ class SFTP extends SSH2
string targetpath
string linkpath */
$packet = substr($this->server_identifier, 0, 15) == 'SSH-2.0-OpenSSH' ?
- pack('Na*Na*', strlen($target), $target, strlen($link), $link) :
- pack('Na*Na*', strlen($link), $link, strlen($target), $target);
- }
- if (!$this->_send_sftp_packet($type, $packet)) {
- return false;
+ Strings::packSSH2('ss', $target, $link) :
+ Strings::packSSH2('ss', $link, $target);
}
+ $this->send_sftp_packet($type, $packet);
- $response = $this->_get_sftp_packet();
+ $response = $this->get_sftp_packet();
if ($this->packet_type != NET_SFTP_STATUS) {
- user_error('Expected SSH_FXP_STATUS');
- return false;
+ throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
+ . 'Got packet type: ' . $this->packet_type);
}
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
+ list($status) = Strings::unpackSSH2('N', $response);
if ($status != NET_SFTP_STATUS_OK) {
- $this->_logError($response, $status);
+ $this->logError($response, $status);
return false;
}
@@ -2132,15 +1961,14 @@ class SFTP extends SSH2
* @param int $mode
* @param bool $recursive
* @return bool
- * @access public
*/
- function mkdir($dir, $mode = -1, $recursive = false)
+ public function mkdir($dir, $mode = -1, $recursive = false)
{
- if (!$this->_precheck()) {
+ if (!$this->precheck()) {
return false;
}
- $dir = $this->_realpath($dir);
+ $dir = $this->realpath($dir);
if ($recursive) {
$dirs = explode('/', preg_replace('#/(?=/)|/$#', '', $dir));
@@ -2151,12 +1979,12 @@ class SFTP extends SSH2
for ($i = 0; $i < count($dirs); $i++) {
$temp = array_slice($dirs, 0, $i + 1);
$temp = implode('/', $temp);
- $result = $this->_mkdir_helper($temp, $mode);
+ $result = $this->mkdir_helper($temp, $mode);
}
return $result;
}
- return $this->_mkdir_helper($dir, $mode);
+ return $this->mkdir_helper($dir, $mode);
}
/**
@@ -2165,27 +1993,21 @@ class SFTP extends SSH2
* @param string $dir
* @param int $mode
* @return bool
- * @access private
*/
- function _mkdir_helper($dir, $mode)
+ private function mkdir_helper($dir, $mode)
{
// send SSH_FXP_MKDIR without any attributes (that's what the \0\0\0\0 is doing)
- if (!$this->_send_sftp_packet(NET_SFTP_MKDIR, pack('Na*a*', strlen($dir), $dir, "\0\0\0\0"))) {
- return false;
- }
+ $this->send_sftp_packet(NET_SFTP_MKDIR, Strings::packSSH2('s', $dir) . "\0\0\0\0");
- $response = $this->_get_sftp_packet();
+ $response = $this->get_sftp_packet();
if ($this->packet_type != NET_SFTP_STATUS) {
- user_error('Expected SSH_FXP_STATUS');
- return false;
+ throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
+ . 'Got packet type: ' . $this->packet_type);
}
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
+ list($status) = Strings::unpackSSH2('N', $response);
if ($status != NET_SFTP_STATUS_OK) {
- $this->_logError($response, $status);
+ $this->logError($response, $status);
return false;
}
@@ -2200,45 +2022,40 @@ class SFTP extends SSH2
* Removes a directory.
*
* @param string $dir
+ * @throws \UnexpectedValueException on receipt of unexpected packets
* @return bool
- * @access public
*/
- function rmdir($dir)
+ public function rmdir($dir)
{
- if (!$this->_precheck()) {
+ if (!$this->precheck()) {
return false;
}
- $dir = $this->_realpath($dir);
+ $dir = $this->realpath($dir);
if ($dir === false) {
return false;
}
- if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($dir), $dir))) {
- return false;
- }
+ $this->send_sftp_packet(NET_SFTP_RMDIR, Strings::packSSH2('s', $dir));
- $response = $this->_get_sftp_packet();
+ $response = $this->get_sftp_packet();
if ($this->packet_type != NET_SFTP_STATUS) {
- user_error('Expected SSH_FXP_STATUS');
- return false;
+ throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
+ . 'Got packet type: ' . $this->packet_type);
}
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
+ list($status) = Strings::unpackSSH2('N', $response);
if ($status != NET_SFTP_STATUS_OK) {
// presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED?
- $this->_logError($response, $status);
+ $this->logError($response, $status);
return false;
}
- $this->_remove_from_stat_cache($dir);
+ $this->remove_from_stat_cache($dir);
// the following will do a soft delete, which would be useful if you deleted a file
// and then tried to do a stat on the deleted file. the above, in contrast, does
// a hard delete
- //$this->_update_stat_cache($dir, false);
+ //$this->update_stat_cache($dir, false);
return true;
}
@@ -2246,8 +2063,8 @@ class SFTP extends SSH2
/**
* Uploads a file to the SFTP server.
*
- * By default, \phpseclib\Net\SFTP::put() does not read from the local filesystem. $data is dumped directly into $remote_file.
- * So, for example, if you set $data to 'filename.ext' and then do \phpseclib\Net\SFTP::get(), you will get a file, twelve bytes
+ * By default, \phpseclib3\Net\SFTP::put() does not read from the local filesystem. $data is dumped directly into $remote_file.
+ * So, for example, if you set $data to 'filename.ext' and then do \phpseclib3\Net\SFTP::get(), you will get a file, twelve bytes
* long, containing 'filename.ext' as its contents.
*
* Setting $mode to self::SOURCE_LOCAL_FILE will change the above behavior. With self::SOURCE_LOCAL_FILE, $remote_file will
@@ -2279,28 +2096,31 @@ class SFTP extends SSH2
*
* Setting $local_start to > 0 or $mode | self::RESUME_START doesn't do anything unless $mode | self::SOURCE_LOCAL_FILE.
*
+ * {@internal ASCII mode for SFTPv4/5/6 can be supported by adding a new function - \phpseclib3\Net\SFTP::setMode().}
+ *
* @param string $remote_file
* @param string|resource $data
* @param int $mode
* @param int $start
* @param int $local_start
* @param callable|null $progressCallback
+ * @throws \UnexpectedValueException on receipt of unexpected packets
+ * @throws \BadFunctionCallException if you're uploading via a callback and the callback function is invalid
+ * @throws FileNotFoundException if you're uploading via a file and the file doesn't exist
* @return bool
- * @access public
- * @internal ASCII mode for SFTPv4/5/6 can be supported by adding a new function - \phpseclib\Net\SFTP::setMode().
*/
- function put($remote_file, $data, $mode = self::SOURCE_STRING, $start = -1, $local_start = -1, $progressCallback = null)
+ public function put($remote_file, $data, $mode = self::SOURCE_STRING, $start = -1, $local_start = -1, $progressCallback = null)
{
- if (!$this->_precheck()) {
+ if (!$this->precheck()) {
return false;
}
- $remote_file = $this->_realpath($remote_file);
+ $remote_file = $this->realpath($remote_file);
if ($remote_file === false) {
return false;
}
- $this->_remove_from_stat_cache($remote_file);
+ $this->remove_from_stat_cache($remote_file);
if ($this->version >= 5) {
$flags = NET_SFTP_OPEN_OPEN_OR_CREATE;
@@ -2308,43 +2128,43 @@ class SFTP extends SSH2
$flags = NET_SFTP_OPEN_WRITE | NET_SFTP_OPEN_CREATE;
// according to the SFTP specs, NET_SFTP_OPEN_APPEND should "force all writes to append data at the end of the file."
// in practice, it doesn't seem to do that.
- //$flags|= ($mode & SFTP::RESUME) ? NET_SFTP_OPEN_APPEND : NET_SFTP_OPEN_TRUNCATE;
+ //$flags|= ($mode & self::RESUME) ? NET_SFTP_OPEN_APPEND : NET_SFTP_OPEN_TRUNCATE;
}
if ($start >= 0) {
$offset = $start;
} elseif ($mode & (self::RESUME | self::RESUME_START)) {
// if NET_SFTP_OPEN_APPEND worked as it should _size() wouldn't need to be called
- $size = $this->size($remote_file);
- $offset = $size !== false ? $size : 0;
+ $stat = $this->stat($remote_file);
+ $offset = $stat !== false && $stat['size'] ? $stat['size'] : 0;
} else {
$offset = 0;
if ($this->version >= 5) {
$flags = NET_SFTP_OPEN_CREATE_TRUNCATE;
} else {
- $flags|= NET_SFTP_OPEN_TRUNCATE;
+ $flags |= NET_SFTP_OPEN_TRUNCATE;
}
}
- $packet = pack('Na*', strlen($remote_file), $remote_file);
- $packet.= $this->version >= 5 ?
+ $this->remove_from_stat_cache($remote_file);
+
+ $packet = Strings::packSSH2('s', $remote_file);
+ $packet .= $this->version >= 5 ?
pack('N3', 0, $flags, 0) :
pack('N2', $flags, 0);
- if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
- return false;
- }
+ $this->send_sftp_packet(NET_SFTP_OPEN, $packet);
- $response = $this->_get_sftp_packet();
+ $response = $this->get_sftp_packet();
switch ($this->packet_type) {
case NET_SFTP_HANDLE:
$handle = substr($response, 4);
break;
case NET_SFTP_STATUS:
- $this->_logError($response);
+ $this->logError($response);
return false;
default:
- user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
- return false;
+ throw new \UnexpectedValueException('Expected NET_SFTP_HANDLE or NET_SFTP_STATUS. '
+ . 'Got packet type: ' . $this->packet_type);
}
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.3
@@ -2352,7 +2172,7 @@ class SFTP extends SSH2
switch (true) {
case $mode & self::SOURCE_CALLBACK:
if (!is_callable($data)) {
- user_error("\$data should be is_callable() if you specify SOURCE_CALLBACK flag");
+ throw new \BadFunctionCallException("\$data should be is_callable() if you specify SOURCE_CALLBACK flag");
}
$dataCallback = $data;
// do nothing
@@ -2370,8 +2190,7 @@ class SFTP extends SSH2
break;
case $mode & self::SOURCE_LOCAL_FILE:
if (!is_file($data)) {
- user_error("$data is not a valid file");
- return false;
+ throw new FileNotFoundException("$data is not a valid file");
}
$fp = @fopen($data, 'rb');
if (!$fp) {
@@ -2385,12 +2204,11 @@ class SFTP extends SSH2
if ($local_start >= 0) {
fseek($fp, $local_start);
- $size-= $local_start;
+ $size -= $local_start;
} elseif ($mode & self::RESUME) {
fseek($fp, $offset);
- $size-= $offset;
+ $size -= $offset;
}
-
} elseif ($dataCallback) {
$size = 0;
} else {
@@ -2402,11 +2220,11 @@ class SFTP extends SSH2
$sftp_packet_size = $this->max_sftp_packet;
// make the SFTP packet be exactly the SFTP packet size by including the bytes in the NET_SFTP_WRITE packets "header"
- $sftp_packet_size-= strlen($handle) + 25;
+ $sftp_packet_size -= strlen($handle) + 25;
$i = $j = 0;
while ($dataCallback || ($size === 0 || $sent < $size)) {
if ($dataCallback) {
- $temp = call_user_func($dataCallback, $sftp_packet_size);
+ $temp = $dataCallback($sftp_packet_size);
if (is_null($temp)) {
break;
}
@@ -2419,22 +2237,23 @@ class SFTP extends SSH2
$subtemp = $offset + $sent;
$packet = pack('Na*N3a*', strlen($handle), $handle, $subtemp / 4294967296, $subtemp, strlen($temp), $temp);
- if (!$this->_send_sftp_packet(NET_SFTP_WRITE, $packet, $j)) {
+ try {
+ $this->send_sftp_packet(NET_SFTP_WRITE, $packet, $j);
+ } catch (\Exception $e) {
if ($mode & self::SOURCE_LOCAL_FILE) {
fclose($fp);
}
- return false;
+ throw $e;
}
- $sent+= strlen($temp);
+ $sent += strlen($temp);
if (is_callable($progressCallback)) {
- call_user_func($progressCallback, $sent);
+ $progressCallback($sent);
}
$i++;
$j++;
-
if ($i == NET_SFTP_UPLOAD_QUEUE_SIZE) {
- if (!$this->_read_put_responses($i)) {
+ if (!$this->read_put_responses($i)) {
$i = 0;
break;
}
@@ -2442,13 +2261,13 @@ class SFTP extends SSH2
}
}
- $result = $this->_close_handle($handle);
+ $result = $this->close_handle($handle);
- if (!$this->_read_put_responses($i)) {
+ if (!$this->read_put_responses($i)) {
if ($mode & self::SOURCE_LOCAL_FILE) {
fclose($fp);
}
- $this->_close_handle($handle);
+ $this->close_handle($handle);
return false;
}
@@ -2459,21 +2278,11 @@ class SFTP extends SSH2
if ($this->preserveTime) {
$stat = stat($data);
- if ($this->version < 4) {
- $attr = pack('N3', NET_SFTP_ATTR_ACCESSTIME, $stat['atime'], $stat['mtime']);
- } else {
- $attr = pack(
- 'N5',
- NET_SFTP_ATTR_ACCESSTIME | NET_SFTP_ATTR_MODIFYTIME,
- $stat['atime'] / 4294967296,
- $stat['atime'],
- $stat['mtime'] / 4294967296,
- $stat['mtime']
- );
- }
-
- if (!$this->_setstat($remote_file, $attr, false)) {
- user_error('Error setting file time');
+ $attr = $this->version < 4 ?
+ pack('N3', NET_SFTP_ATTR_ACCESSTIME, $stat['atime'], $stat['mtime']) :
+ Strings::packSSH2('NQ2', NET_SFTP_ATTR_ACCESSTIME | NET_SFTP_ATTR_MODIFYTIME, $stat['atime'], $stat['mtime']);
+ if (!$this->setstat($remote_file, $attr, false)) {
+ throw new \RuntimeException('Error setting file time');
}
}
}
@@ -2489,23 +2298,20 @@ class SFTP extends SSH2
*
* @param int $i
* @return bool
- * @access private
+ * @throws \UnexpectedValueException on receipt of unexpected packets
*/
- function _read_put_responses($i)
+ private function read_put_responses($i)
{
while ($i--) {
- $response = $this->_get_sftp_packet();
+ $response = $this->get_sftp_packet();
if ($this->packet_type != NET_SFTP_STATUS) {
- user_error('Expected SSH_FXP_STATUS');
- return false;
+ throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
+ . 'Got packet type: ' . $this->packet_type);
}
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
+ list($status) = Strings::unpackSSH2('N', $response);
if ($status != NET_SFTP_STATUS_OK) {
- $this->_logError($response, $status);
+ $this->logError($response, $status);
break;
}
}
@@ -2518,28 +2324,23 @@ class SFTP extends SSH2
*
* @param string $handle
* @return bool
- * @access private
+ * @throws \UnexpectedValueException on receipt of unexpected packets
*/
- function _close_handle($handle)
+ private function close_handle($handle)
{
- if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
- return false;
- }
+ $this->send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle));
// "The client MUST release all resources associated with the handle regardless of the status."
// -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.3
- $response = $this->_get_sftp_packet();
+ $response = $this->get_sftp_packet();
if ($this->packet_type != NET_SFTP_STATUS) {
- user_error('Expected SSH_FXP_STATUS');
- return false;
+ throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
+ . 'Got packet type: ' . $this->packet_type);
}
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
+ list($status) = Strings::unpackSSH2('N', $response);
if ($status != NET_SFTP_STATUS_OK) {
- $this->_logError($response, $status);
+ $this->logError($response, $status);
return false;
}
@@ -2556,43 +2357,41 @@ class SFTP extends SSH2
* $offset and $length can be used to download files in chunks.
*
* @param string $remote_file
- * @param string $local_file
+ * @param string|bool|resource|callable $local_file
* @param int $offset
* @param int $length
* @param callable|null $progressCallback
- * @return mixed
- * @access public
+ * @throws \UnexpectedValueException on receipt of unexpected packets
+ * @return string|bool
*/
- function get($remote_file, $local_file = false, $offset = 0, $length = -1, $progressCallback = null)
+ public function get($remote_file, $local_file = false, $offset = 0, $length = -1, $progressCallback = null)
{
- if (!$this->_precheck()) {
+ if (!$this->precheck()) {
return false;
}
- $remote_file = $this->_realpath($remote_file);
+ $remote_file = $this->realpath($remote_file);
if ($remote_file === false) {
return false;
}
- $packet = pack('Na*', strlen($remote_file), $remote_file);
- $packet.= $this->version >= 5 ?
+ $packet = Strings::packSSH2('s', $remote_file);
+ $packet .= $this->version >= 5 ?
pack('N3', 0, NET_SFTP_OPEN_OPEN_EXISTING, 0) :
pack('N2', NET_SFTP_OPEN_READ, 0);
- if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
- return false;
- }
+ $this->send_sftp_packet(NET_SFTP_OPEN, $packet);
- $response = $this->_get_sftp_packet();
+ $response = $this->get_sftp_packet();
switch ($this->packet_type) {
case NET_SFTP_HANDLE:
$handle = substr($response, 4);
break;
case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
- $this->_logError($response);
+ $this->logError($response);
return false;
default:
- user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
- return false;
+ throw new \UnexpectedValueException('Expected NET_SFTP_HANDLE or NET_SFTP_STATUS. '
+ . 'Got packet type: ' . $this->packet_type);
}
if (is_resource($local_file)) {
@@ -2623,15 +2422,17 @@ class SFTP extends SSH2
$packet_size = $length > 0 ? min($this->max_sftp_packet, $length - $read) : $this->max_sftp_packet;
- $packet = pack('Na*N3', strlen($handle), $handle, $tempoffset / 4294967296, $tempoffset, $packet_size);
- if (!$this->_send_sftp_packet(NET_SFTP_READ, $packet, $i)) {
+ $packet = Strings::packSSH2('sN3', $handle, $tempoffset / 4294967296, $tempoffset, $packet_size);
+ try {
+ $this->send_sftp_packet(NET_SFTP_READ, $packet, $i);
+ } catch (\Exception $e) {
if ($fclose_check) {
fclose($fp);
}
- return false;
+ throw $e;
}
$packet = null;
- $read+= $packet_size;
+ $read += $packet_size;
$i++;
}
@@ -2646,18 +2447,18 @@ class SFTP extends SSH2
$i--;
if ($clear_responses) {
- $this->_get_sftp_packet($packets_sent - $i);
+ $this->get_sftp_packet($packets_sent - $i);
continue;
} else {
- $response = $this->_get_sftp_packet($packets_sent - $i);
+ $response = $this->get_sftp_packet($packets_sent - $i);
}
switch ($this->packet_type) {
case NET_SFTP_DATA:
$temp = substr($response, 4);
- $offset+= strlen($temp);
+ $offset += strlen($temp);
if ($local_file === false) {
- $content.= $temp;
+ $content .= $temp;
} elseif (is_callable($local_file)) {
$local_file($temp);
} else {
@@ -2670,20 +2471,20 @@ class SFTP extends SSH2
break;
case NET_SFTP_STATUS:
// could, in theory, return false if !strlen($content) but we'll hold off for the time being
- $this->_logError($response);
+ $this->logError($response);
$clear_responses = true; // don't break out of the loop yet, so we can read the remaining responses
break;
default:
if ($fclose_check) {
fclose($fp);
}
- // maybe the file was successfully transferred, maybe it wasn't
if ($this->channel_close) {
$this->partial_init = false;
- $this->_init_sftp_connection();
+ $this->init_sftp_connection();
return false;
} else {
- user_error('Expected SSH_FX_DATA or SSH_FXP_STATUS');
+ throw new \UnexpectedValueException('Expected NET_SFTP_DATA or NET_SFTP_STATUS. '
+ . 'Got packet type: ' . $this->packet_type);
}
}
$response = null;
@@ -2703,7 +2504,7 @@ class SFTP extends SSH2
}
}
- if (!$this->_close_handle($handle)) {
+ if (!$this->close_handle($handle)) {
return false;
}
@@ -2717,11 +2518,11 @@ class SFTP extends SSH2
* @param string $path
* @param bool $recursive
* @return bool
- * @access public
+ * @throws \UnexpectedValueException on receipt of unexpected packets
*/
- function delete($path, $recursive = true)
+ public function delete($path, $recursive = true)
{
- if (!$this->_precheck()) {
+ if (!$this->precheck()) {
return false;
}
@@ -2734,39 +2535,35 @@ class SFTP extends SSH2
return false;
}
- $path = $this->_realpath($path);
+ $path = $this->realpath($path);
if ($path === false) {
return false;
}
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
- if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($path), $path))) {
- return false;
- }
+ $this->send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($path), $path));
- $response = $this->_get_sftp_packet();
+ $response = $this->get_sftp_packet();
if ($this->packet_type != NET_SFTP_STATUS) {
- user_error('Expected SSH_FXP_STATUS');
- return false;
+ throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
+ . 'Got packet type: ' . $this->packet_type);
}
// if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
+ list($status) = Strings::unpackSSH2('N', $response);
if ($status != NET_SFTP_STATUS_OK) {
- $this->_logError($response, $status);
+ $this->logError($response, $status);
if (!$recursive) {
return false;
}
+
$i = 0;
- $result = $this->_delete_recursive($path, $i);
- $this->_read_put_responses($i);
+ $result = $this->delete_recursive($path, $i);
+ $this->read_put_responses($i);
return $result;
}
- $this->_remove_from_stat_cache($path);
+ $this->remove_from_stat_cache($path);
return true;
}
@@ -2779,15 +2576,14 @@ class SFTP extends SSH2
* @param string $path
* @param int $i
* @return bool
- * @access private
*/
- function _delete_recursive($path, &$i)
+ private function delete_recursive($path, &$i)
{
- if (!$this->_read_put_responses($i)) {
+ if (!$this->read_put_responses($i)) {
return false;
}
$i = 0;
- $entries = $this->_list($path, true);
+ $entries = $this->readlist($path, true);
// The folder does not exist at all, so we cannot delete it.
if ($entries === NET_SFTP_STATUS_NO_SUCH_FILE) {
@@ -2797,7 +2593,7 @@ class SFTP extends SSH2
// Normally $entries would have at least . and .. but it might not if the directories
// permissions didn't allow reading. If this happens then default to an empty list of files.
if ($entries === false || is_int($entries)) {
- $entries = array();
+ $entries = [];
}
unset($entries['.'], $entries['..']);
@@ -2808,19 +2604,17 @@ class SFTP extends SSH2
$temp = $path . '/' . $filename;
if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
- if (!$this->_delete_recursive($temp, $i)) {
+ if (!$this->delete_recursive($temp, $i)) {
return false;
}
} else {
- if (!$this->_send_sftp_packet(NET_SFTP_REMOVE, pack('Na*', strlen($temp), $temp))) {
- return false;
- }
- $this->_remove_from_stat_cache($temp);
+ $this->send_sftp_packet(NET_SFTP_REMOVE, Strings::packSSH2('s', $temp));
+ $this->remove_from_stat_cache($temp);
$i++;
if ($i >= NET_SFTP_QUEUE_SIZE) {
- if (!$this->_read_put_responses($i)) {
+ if (!$this->read_put_responses($i)) {
return false;
}
$i = 0;
@@ -2828,15 +2622,13 @@ class SFTP extends SSH2
}
}
- if (!$this->_send_sftp_packet(NET_SFTP_RMDIR, pack('Na*', strlen($path), $path))) {
- return false;
- }
- $this->_remove_from_stat_cache($path);
+ $this->send_sftp_packet(NET_SFTP_RMDIR, Strings::packSSH2('s', $path));
+ $this->remove_from_stat_cache($path);
$i++;
if ($i >= NET_SFTP_QUEUE_SIZE) {
- if (!$this->_read_put_responses($i)) {
+ if (!$this->read_put_responses($i)) {
return false;
}
$i = 0;
@@ -2850,18 +2642,17 @@ class SFTP extends SSH2
*
* @param string $path
* @return bool
- * @access public
*/
- function file_exists($path)
+ public function file_exists($path)
{
if ($this->use_stat_cache) {
- if (!$this->_precheck()) {
+ if (!$this->precheck()) {
return false;
}
- $path = $this->_realpath($path);
+ $path = $this->realpath($path);
- $result = $this->_query_stat_cache($path);
+ $result = $this->query_stat_cache($path);
if (isset($result)) {
// return true if $result is an array or if it's an stdClass object
@@ -2877,11 +2668,10 @@ class SFTP extends SSH2
*
* @param string $path
* @return bool
- * @access public
*/
- function is_dir($path)
+ public function is_dir($path)
{
- $result = $this->_get_stat_cache_prop($path, 'type');
+ $result = $this->get_stat_cache_prop($path, 'type');
if ($result === false) {
return false;
}
@@ -2893,11 +2683,10 @@ class SFTP extends SSH2
*
* @param string $path
* @return bool
- * @access public
*/
- function is_file($path)
+ public function is_file($path)
{
- $result = $this->_get_stat_cache_prop($path, 'type');
+ $result = $this->get_stat_cache_prop($path, 'type');
if ($result === false) {
return false;
}
@@ -2909,11 +2698,10 @@ class SFTP extends SSH2
*
* @param string $path
* @return bool
- * @access public
*/
- function is_link($path)
+ public function is_link($path)
{
- $result = $this->_get_lstat_cache_prop($path, 'type');
+ $result = $this->get_lstat_cache_prop($path, 'type');
if ($result === false) {
return false;
}
@@ -2925,30 +2713,25 @@ class SFTP extends SSH2
*
* @param string $path
* @return bool
- * @access public
*/
- function is_readable($path)
+ public function is_readable($path)
{
- if (!$this->_precheck()) {
+ if (!$this->precheck()) {
return false;
}
- $path = $this->_realpath($path);
+ $packet = Strings::packSSH2('sNN', $this->realpath($path), NET_SFTP_OPEN_READ, 0);
+ $this->send_sftp_packet(NET_SFTP_OPEN, $packet);
- $packet = pack('Na*N2', strlen($path), $path, NET_SFTP_OPEN_READ, 0);
- if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
- return false;
- }
-
- $response = $this->_get_sftp_packet();
+ $response = $this->get_sftp_packet();
switch ($this->packet_type) {
case NET_SFTP_HANDLE:
return true;
case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
return false;
default:
- user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
- return false;
+ throw new \UnexpectedValueException('Expected NET_SFTP_HANDLE or NET_SFTP_STATUS. '
+ . 'Got packet type: ' . $this->packet_type);
}
}
@@ -2957,30 +2740,25 @@ class SFTP extends SSH2
*
* @param string $path
* @return bool
- * @access public
*/
- function is_writable($path)
+ public function is_writable($path)
{
- if (!$this->_precheck()) {
+ if (!$this->precheck()) {
return false;
}
- $path = $this->_realpath($path);
+ $packet = Strings::packSSH2('sNN', $this->realpath($path), NET_SFTP_OPEN_WRITE, 0);
+ $this->send_sftp_packet(NET_SFTP_OPEN, $packet);
- $packet = pack('Na*N2', strlen($path), $path, NET_SFTP_OPEN_WRITE, 0);
- if (!$this->_send_sftp_packet(NET_SFTP_OPEN, $packet)) {
- return false;
- }
-
- $response = $this->_get_sftp_packet();
+ $response = $this->get_sftp_packet();
switch ($this->packet_type) {
case NET_SFTP_HANDLE:
return true;
case NET_SFTP_STATUS: // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
return false;
default:
- user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
- return false;
+ throw new \UnexpectedValueException('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS. '
+ . 'Got packet type: ' . $this->packet_type);
}
}
@@ -2991,9 +2769,8 @@ class SFTP extends SSH2
*
* @param string $path
* @return bool
- * @access public
*/
- function is_writeable($path)
+ public function is_writeable($path)
{
return $this->is_writable($path);
}
@@ -3003,11 +2780,10 @@ class SFTP extends SSH2
*
* @param string $path
* @return mixed
- * @access public
*/
- function fileatime($path)
+ public function fileatime($path)
{
- return $this->_get_stat_cache_prop($path, 'atime');
+ return $this->get_stat_cache_prop($path, 'atime');
}
/**
@@ -3015,11 +2791,10 @@ class SFTP extends SSH2
*
* @param string $path
* @return mixed
- * @access public
*/
- function filemtime($path)
+ public function filemtime($path)
{
- return $this->_get_stat_cache_prop($path, 'mtime');
+ return $this->get_stat_cache_prop($path, 'mtime');
}
/**
@@ -3027,11 +2802,10 @@ class SFTP extends SSH2
*
* @param string $path
* @return mixed
- * @access public
*/
- function fileperms($path)
+ public function fileperms($path)
{
- return $this->_get_stat_cache_prop($path, 'permissions');
+ return $this->get_stat_cache_prop($path, 'mode');
}
/**
@@ -3039,11 +2813,10 @@ class SFTP extends SSH2
*
* @param string $path
* @return mixed
- * @access public
*/
- function fileowner($path)
+ public function fileowner($path)
{
- return $this->_get_stat_cache_prop($path, 'uid');
+ return $this->get_stat_cache_prop($path, 'uid');
}
/**
@@ -3051,35 +2824,54 @@ class SFTP extends SSH2
*
* @param string $path
* @return mixed
- * @access public
*/
- function filegroup($path)
+ public function filegroup($path)
{
- return $this->_get_stat_cache_prop($path, 'gid');
+ return $this->get_stat_cache_prop($path, 'gid');
+ }
+
+ /**
+ * Recursively go through rawlist() output to get the total filesize
+ *
+ * @return int
+ */
+ private static function recursiveFilesize(array $files)
+ {
+ $size = 0;
+ foreach ($files as $name => $file) {
+ if ($name == '.' || $name == '..') {
+ continue;
+ }
+ $size += is_array($file) ?
+ self::recursiveFilesize($file) :
+ $file->size;
+ }
+ return $size;
}
/**
* Gets file size
*
* @param string $path
+ * @param bool $recursive
* @return mixed
- * @access public
*/
- function filesize($path)
+ public function filesize($path, $recursive = false)
{
- return $this->_get_stat_cache_prop($path, 'size');
+ return !$recursive || $this->filetype($path) != 'dir' ?
+ $this->get_stat_cache_prop($path, 'size') :
+ self::recursiveFilesize($this->rawlist($path, true));
}
/**
* Gets file type
*
* @param string $path
- * @return mixed
- * @access public
+ * @return string|false
*/
- function filetype($path)
+ public function filetype($path)
{
- $type = $this->_get_stat_cache_prop($path, 'type');
+ $type = $this->get_stat_cache_prop($path, 'type');
if ($type === false) {
return false;
}
@@ -3110,11 +2902,10 @@ class SFTP extends SSH2
* @param string $path
* @param string $prop
* @return mixed
- * @access private
*/
- function _get_stat_cache_prop($path, $prop)
+ private function get_stat_cache_prop($path, $prop)
{
- return $this->_get_xstat_cache_prop($path, $prop, 'stat');
+ return $this->get_xstat_cache_prop($path, $prop, 'stat');
}
/**
@@ -3125,11 +2916,10 @@ class SFTP extends SSH2
* @param string $path
* @param string $prop
* @return mixed
- * @access private
*/
- function _get_lstat_cache_prop($path, $prop)
+ private function get_lstat_cache_prop($path, $prop)
{
- return $this->_get_xstat_cache_prop($path, $prop, 'lstat');
+ return $this->get_xstat_cache_prop($path, $prop, 'lstat');
}
/**
@@ -3139,20 +2929,19 @@ class SFTP extends SSH2
*
* @param string $path
* @param string $prop
- * @param mixed $type
+ * @param string $type
* @return mixed
- * @access private
*/
- function _get_xstat_cache_prop($path, $prop, $type)
+ private function get_xstat_cache_prop($path, $prop, $type)
{
- if (!$this->_precheck()) {
+ if (!$this->precheck()) {
return false;
}
if ($this->use_stat_cache) {
- $path = $this->_realpath($path);
+ $path = $this->realpath($path);
- $result = $this->_query_stat_cache($path);
+ $result = $this->query_stat_cache($path);
if (is_object($result) && isset($result->$type)) {
return $result->{$type}[$prop];
@@ -3176,22 +2965,22 @@ class SFTP extends SSH2
* @param string $oldname
* @param string $newname
* @return bool
- * @access public
+ * @throws \UnexpectedValueException on receipt of unexpected packets
*/
- function rename($oldname, $newname)
+ public function rename($oldname, $newname)
{
- if (!$this->_precheck()) {
+ if (!$this->precheck()) {
return false;
}
- $oldname = $this->_realpath($oldname);
- $newname = $this->_realpath($newname);
+ $oldname = $this->realpath($oldname);
+ $newname = $this->realpath($newname);
if ($oldname === false || $newname === false) {
return false;
}
// http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
- $packet = pack('Na*Na*', strlen($oldname), $oldname, strlen($newname), $newname);
+ $packet = Strings::packSSH2('ss', $oldname, $newname);
if ($this->version >= 5) {
/* quoting https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-05#section-6.5 ,
@@ -3202,33 +2991,28 @@ class SFTP extends SSH2
SSH_FXP_RENAME_NATIVE 0x00000004
(none of these are currently supported) */
- $packet.= "\0\0\0\0";
- }
- if (!$this->_send_sftp_packet(NET_SFTP_RENAME, $packet)) {
- return false;
+ $packet .= "\0\0\0\0";
}
+ $this->send_sftp_packet(NET_SFTP_RENAME, $packet);
- $response = $this->_get_sftp_packet();
+ $response = $this->get_sftp_packet();
if ($this->packet_type != NET_SFTP_STATUS) {
- user_error('Expected SSH_FXP_STATUS');
- return false;
+ throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
+ . 'Got packet type: ' . $this->packet_type);
}
// if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nstatus', $this->_string_shift($response, 4)));
+ list($status) = Strings::unpackSSH2('N', $response);
if ($status != NET_SFTP_STATUS_OK) {
- $this->_logError($response, $status);
+ $this->logError($response, $status);
return false;
}
// don't move the stat cache entry over since this operation could very well change the
// atime and mtime attributes
- //$this->_update_stat_cache($newname, $this->_query_stat_cache($oldname));
- $this->_remove_from_stat_cache($oldname);
- $this->_remove_from_stat_cache($newname);
+ //$this->update_stat_cache($newname, $this->query_stat_cache($oldname));
+ $this->remove_from_stat_cache($oldname);
+ $this->remove_from_stat_cache($newname);
return true;
}
@@ -3242,18 +3026,13 @@ class SFTP extends SSH2
* @param int $flags
* @param string $response
* @return array
- * @access private
*/
- function _parseTime($key, $flags, &$response)
+ private function parseTime($key, $flags, &$response)
{
- if (strlen($response) < 8) {
- user_error('Malformed file attributes');
- return array();
- }
- $attr = array();
- $attr[$key] = hexdec(bin2hex($this->_string_shift($response, 8)));
+ $attr = [];
+ list($attr[$key]) = Strings::unpackSSH2('Q', $response);
if ($flags & NET_SFTP_ATTR_SUBSECOND_TIMES) {
- $attr+= extract(unpack('N' . $key . '_nseconds', $this->_string_shift($response, 4)));
+ list($attr[$key . '-nseconds']) = Strings::unpackSSH2('N', $response);
}
return $attr;
}
@@ -3265,28 +3044,18 @@ class SFTP extends SSH2
*
* @param string $response
* @return array
- * @access private
*/
- function _parseAttributes(&$response)
+ protected function parseAttributes(&$response)
{
+ $attr = [];
+
if ($this->version >= 4) {
- $length = 5;
- $format = 'Nflags/Ctype';
+ list($flags, $attr['type']) = Strings::unpackSSH2('NC', $response);
} else {
- $length = 4;
- $format = 'Nflags';
+ list($flags) = Strings::unpackSSH2('N', $response);
}
- $attr = array();
- if (strlen($response) < $length) {
- user_error('Malformed file attributes');
- return array();
- }
- extract(unpack($format, $this->_string_shift($response, $length)));
- if (isset($type)) {
- $attr['type'] = $type;
- }
- foreach ($this->attributes as $key => $value) {
+ foreach (self::$attributes as $key => $value) {
switch ($flags & $key) {
case NET_SFTP_ATTR_UIDGID:
if ($this->version > 3) {
@@ -3325,90 +3094,42 @@ class SFTP extends SSH2
// IEEE 754 binary64 "double precision" on such platforms and
// as such can represent integers of at least 2^50 without loss
// of precision. Interpreted in filesize, 2^50 bytes = 1024 TiB.
- $attr['size'] = hexdec(bin2hex($this->_string_shift($response, 8)));
+ list($attr['size']) = Strings::unpackSSH2('Q', $response);
break;
- case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 or earlier)
- if (strlen($response) < 8) {
- user_error('Malformed file attributes');
- return $attr;
- }
- $attr+= unpack('Nuid/Ngid', $this->_string_shift($response, 8));
+ case NET_SFTP_ATTR_UIDGID: // 0x00000002 (SFTPv3 only)
+ list($attr['uid'], $attr['gid']) = Strings::unpackSSH2('NN', $response);
break;
- case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004
- if (strlen($response) < 4) {
- user_error('Malformed file attributes');
- return $attr;
- }
- $attr+= unpack('Npermissions', $this->_string_shift($response, 4));
- // mode == permissions; permissions was the original array key and is retained for bc purposes.
- // mode was added because that's the more industry standard terminology
- $attr+= array('mode' => $attr['permissions']);
- $fileType = $this->_parseMode($attr['permissions']);
- if ($fileType !== false) {
- $attr+= array('type' => $fileType);
+ case NET_SFTP_ATTR_PERMISSIONS: // 0x00000004
+ list($attr['mode']) = Strings::unpackSSH2('N', $response);
+ $fileType = $this->parseMode($attr['mode']);
+ if ($this->version < 4 && $fileType !== false) {
+ $attr += ['type' => $fileType];
}
break;
- case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008
+ case NET_SFTP_ATTR_ACCESSTIME: // 0x00000008
if ($this->version >= 4) {
- $attr+= $this->_parseTime('atime', $flags, $response);
+ $attr += $this->parseTime('atime', $flags, $response);
break;
}
- if (strlen($response) < 8) {
- user_error('Malformed file attributes');
- return $attr;
- }
- $attr+= unpack('Natime/Nmtime', $this->_string_shift($response, 8));
+ list($attr['atime'], $attr['mtime']) = Strings::unpackSSH2('NN', $response);
break;
case NET_SFTP_ATTR_CREATETIME: // 0x00000010 (SFTPv4+)
- $attr+= $this->_parseTime('createtime', $flags, $response);
+ $attr += $this->parseTime('createtime', $flags, $response);
break;
case NET_SFTP_ATTR_MODIFYTIME: // 0x00000020
- $attr+= $this->_parseTime('mtime', $flags, $response);
+ $attr += $this->parseTime('mtime', $flags, $response);
break;
case NET_SFTP_ATTR_ACL: // 0x00000040
// access control list
// see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-04#section-5.7
// currently unsupported
- if (strlen($response) < 4) {
- user_error('Malformed file attributes');
- return $attr;
- }
- extract(unpack('Ncount', $this->_string_shift($response, 4)));
+ list($count) = Strings::unpackSSH2('N', $response);
for ($i = 0; $i < $count; $i++) {
- if (strlen($response) < 16) {
- user_error('Malformed file attributes');
- return $attr;
- }
- extract(unpack('Ntype/Nflag/Nmask/Nlength', $this->_string_shift($response, 16)));
- if (strlen($response) < $length) {
- user_error('Malformed file attributes');
- return $attr;
- }
- $this->_string_shift($response, $length); // who
+ list($type, $flag, $mask, $who) = Strings::unpackSSH2('N3s', $result);
}
break;
case NET_SFTP_ATTR_OWNERGROUP: // 0x00000080
- if (strlen($response) < 4) {
- user_error('Malformed file attributes');
- return $attr;
- }
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
- if (strlen($response) < $length) {
- user_error('Malformed file attributes');
- return $attr;
- }
- $attr['owner'] = $this->_string_shift($response, $length);
-
- if (strlen($response) < 4) {
- user_error('Malformed file attributes');
- return $attr;
- }
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
- if (strlen($response) < $length) {
- user_error('Malformed file attributes');
- return $attr;
- }
- $attr['group'] = $this->_string_shift($response, $length);
+ list($attr['owner'], $attr['$group']) = Strings::unpackSSH2('ss', $response);
break;
case NET_SFTP_ATTR_SUBSECOND_TIMES: // 0x00000100
break;
@@ -3418,82 +3139,46 @@ class SFTP extends SSH2
// tells if you file is:
// readonly, system, hidden, case inensitive, archive, encrypted, compressed, sparse
// append only, immutable, sync
- if (strlen($response) < 8) {
- user_error('Malformed file attributes');
- return $attr;
- }
- extract(unpack('Nattrib-bits/Nattrib-bits-valid', $this->_string_shift($response, 8)));
+ list($attrib_bits, $attrib_bits_valid) = Strings::unpackSSH2('N2', $response);
+ // if we were actually gonna implement the above it ought to be
+ // $attr['attrib-bits'] and $attr['attrib-bits-valid']
+ // eg. - instead of _
break;
case NET_SFTP_ATTR_ALLOCATION_SIZE: // 0x00000400 (SFTPv6+)
// see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.4
- // represents the number of bytes htat the file consumes on the disk. will
+ // represents the number of bytes that the file consumes on the disk. will
// usually be larger than the 'size' field
- $attr['allocation-size'] = hexdec(bin2hex($this->_string_shift($response, 8)));
+ list($attr['allocation-size']) = Strings::unpackSSH2('Q', $response);
break;
case NET_SFTP_ATTR_TEXT_HINT: // 0x00000800
// https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.10
// currently unsupported
// tells if file is "known text", "guessed text", "known binary", "guessed binary"
- extract(unpack('Ctext-hint', $this->_string_shift($response)));
+ list($text_hint) = Strings::unpackSSH2('C', $response);
+ // the above should be $attr['text-hint']
break;
case NET_SFTP_ATTR_MIME_TYPE: // 0x00001000
// see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.11
- if (strlen($response) < 4) {
- user_error('Malformed file attributes');
- return $attr;
- }
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
- if (strlen($response) < $length) {
- user_error('Malformed file attributes');
- return $attr;
- }
- $attr['mime-type'] = $this->_string_shift($response, $length);
+ list($attr['mime-type']) = Strings::unpackSSH2('s', $response);
break;
case NET_SFTP_ATTR_LINK_COUNT: // 0x00002000
// see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.12
- if (strlen($response) < 4) {
- user_error('Malformed file attributes');
- return $attr;
- }
- $attr+= unpack('Nlink-count', $this->_string_shift($response, 4));
+ list($attr['link-count']) = Strings::unpackSSH2('N', $response);
break;
case NET_SFTP_ATTR_UNTRANSLATED_NAME:// 0x00004000
// see https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-7.13
- if (strlen($response) < 4) {
- user_error('Malformed file attributes');
- return $attr;
- }
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
- if (strlen($response) < $length) {
- user_error('Malformed file attributes');
- return $attr;
- }
- $attr['untranslated-name'] = $this->_string_shift($response, $length);
+ list($attr['untranslated-name']) = Strings::unpackSSH2('s', $response);
break;
case NET_SFTP_ATTR_CTIME: // 0x00008000
// 'ctime' contains the last time the file attributes were changed. The
// exact meaning of this field depends on the server.
- $attr+= $this->_parseTime('ctime', $flags, $response);
+ $attr += $this->parseTime('ctime', $flags, $response);
break;
- case NET_SFTP_ATTR_EXTENDED: // 0x80000000
- if (strlen($response) < 4) {
- user_error('Malformed file attributes');
- return $attr;
- }
- extract(unpack('Ncount', $this->_string_shift($response, 4)));
+ case NET_SFTP_ATTR_EXTENDED: // 0x80000000
+ list($count) = Strings::unpackSSH2('N', $response);
for ($i = 0; $i < $count; $i++) {
- if (strlen($response) < 4) {
- user_error('Malformed file attributes');
- return $attr;
- }
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
- $key = $this->_string_shift($response, $length);
- if (strlen($response) < 4) {
- user_error('Malformed file attributes');
- return $attr;
- }
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
- $attr[$key] = $this->_string_shift($response, $length);
+ list($key, $value) = Strings::unpackSSH2('ss', $response);
+ $attr[$key] = $value;
}
}
}
@@ -3507,9 +3192,8 @@ class SFTP extends SSH2
*
* @param int $mode
* @return int
- * @access private
*/
- function _parseMode($mode)
+ private function parseMode($mode)
{
// values come from http://lxr.free-electrons.com/source/include/uapi/linux/stat.h#L12
// see, also, http://linux.die.net/man/2/stat
@@ -3554,9 +3238,8 @@ class SFTP extends SSH2
*
* @param string $longname
* @return mixed
- * @access private
*/
- function _parseLongname($longname)
+ private function parseLongname($longname)
{
// http://en.wikipedia.org/wiki/Unix_file_types
// http://en.wikipedia.org/wiki/Filesystem_permissions#Notation_of_traditional_Unix_permissions
@@ -3585,73 +3268,49 @@ class SFTP extends SSH2
* @param string $data
* @param int $request_id
* @see self::_get_sftp_packet()
- * @see self::_send_channel_packet()
- * @return bool
- * @access private
+ * @see self::send_channel_packet()
+ * @return void
*/
- function _send_sftp_packet($type, $data, $request_id = 1)
+ private function send_sftp_packet($type, $data, $request_id = 1)
{
// in SSH2.php the timeout is cumulative per function call. eg. exec() will
// timeout after 10s. but for SFTP.php it's cumulative per packet
$this->curTimeout = $this->timeout;
+ $this->is_timeout = false;
$packet = $this->use_request_id ?
pack('NCNa*', strlen($data) + 5, $type, $request_id, $data) :
- pack('NCa*', strlen($data) + 1, $type, $data);
+ pack('NCa*', strlen($data) + 1, $type, $data);
- $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
- $result = $this->_send_channel_packet(self::CHANNEL, $packet);
- $stop = strtok(microtime(), ' ') + strtok('');
+ $start = microtime(true);
+ $this->send_channel_packet(self::CHANNEL, $packet);
+ $stop = microtime(true);
if (defined('NET_SFTP_LOGGING')) {
- $packet_type = '-> ' . $this->packet_types[$type] .
+ $packet_type = '-> ' . self::$packet_types[$type] .
' (' . round($stop - $start, 4) . 's)';
- if (NET_SFTP_LOGGING == self::LOG_REALTIME) {
- switch (PHP_SAPI) {
- case 'cli':
- $start = $stop = "\r\n";
- break;
- default:
- $start = '<pre>';
- $stop = '</pre>';
- }
- echo $start . $this->_format_log(array($data), array($packet_type)) . $stop;
- @flush();
- @ob_flush();
- } else {
- $this->packet_type_log[] = $packet_type;
- if (NET_SFTP_LOGGING == self::LOG_COMPLEX) {
- $this->packet_log[] = $data;
- }
- }
+ $this->append_log($packet_type, $data);
}
-
- return $result;
}
/**
* Resets the SFTP channel for re-use
- *
- * @access private
*/
- function _reset_sftp()
+ private function reset_sftp()
{
$this->use_request_id = false;
$this->pwd = false;
- $this->requestBuffer = array();
+ $this->requestBuffer = [];
$this->partial_init = false;
}
/**
* Resets a connection for re-use
- *
- * @param int $reason
- * @access private
*/
- function _reset_connection($reason)
+ protected function reset_connection()
{
- parent::_reset_connection($reason);
- $this->_reset_sftp();
+ parent::reset_connection();
+ $this->reset_sftp();
}
/**
@@ -3665,9 +3324,8 @@ class SFTP extends SSH2
*
* @see self::_send_sftp_packet()
* @return string
- * @access private
*/
- function _get_sftp_packet($request_id = null)
+ private function get_sftp_packet($request_id = null)
{
$this->channel_close = false;
@@ -3681,12 +3339,13 @@ class SFTP extends SSH2
// in SSH2.php the timeout is cumulative per function call. eg. exec() will
// timeout after 10s. but for SFTP.php it's cumulative per packet
$this->curTimeout = $this->timeout;
+ $this->is_timeout = false;
- $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
+ $start = microtime(true);
// SFTP packet length
while (strlen($this->packet_buffer) < 4) {
- $temp = $this->_get_channel_packet(self::CHANNEL, true);
+ $temp = $this->get_channel_packet(self::CHANNEL, true);
if ($temp === true) {
if ($this->channel_status[self::CHANNEL] === NET_SSH2_MSG_CHANNEL_CLOSE) {
$this->channel_close = true;
@@ -3695,95 +3354,97 @@ class SFTP extends SSH2
$this->packet_buffer = '';
return false;
}
- if ($temp === false) {
- return false;
- }
- $this->packet_buffer.= $temp;
+ $this->packet_buffer .= $temp;
}
if (strlen($this->packet_buffer) < 4) {
- return false;
+ throw new \RuntimeException('Packet is too small');
}
- extract(unpack('Nlength', $this->_string_shift($this->packet_buffer, 4)));
+ $length = unpack('Nlength', Strings::shift($this->packet_buffer, 4))['length'];
+
$tempLength = $length;
- $tempLength-= strlen($this->packet_buffer);
+ $tempLength -= strlen($this->packet_buffer);
// 256 * 1024 is what SFTP_MAX_MSG_LENGTH is set to in OpenSSH's sftp-common.h
if (!$this->allow_arbitrary_length_packets && !$this->use_request_id && $tempLength > 256 * 1024) {
- user_error('Invalid SFTP packet size');
- return false;
+ throw new \RuntimeException('Invalid Size');
}
// SFTP packet type and data payload
while ($tempLength > 0) {
- $temp = $this->_get_channel_packet(self::CHANNEL, true);
- if (is_bool($temp)) {
- if ($temp && $this->channel_status[self::CHANNEL] === NET_SSH2_MSG_CHANNEL_CLOSE) {
+ $temp = $this->get_channel_packet(self::CHANNEL, true);
+ if ($temp === true) {
+ if ($this->channel_status[self::CHANNEL] === NET_SSH2_MSG_CHANNEL_CLOSE) {
$this->channel_close = true;
}
$this->packet_type = false;
$this->packet_buffer = '';
return false;
}
- $this->packet_buffer.= $temp;
- $tempLength-= strlen($temp);
+ $this->packet_buffer .= $temp;
+ $tempLength -= strlen($temp);
}
- $stop = strtok(microtime(), ' ') + strtok('');
+ $stop = microtime(true);
- $this->packet_type = ord($this->_string_shift($this->packet_buffer));
+ $this->packet_type = ord(Strings::shift($this->packet_buffer));
if ($this->use_request_id) {
- extract(unpack('Npacket_id', $this->_string_shift($this->packet_buffer, 4))); // remove the request id
- $length-= 5; // account for the request id and the packet type
+ $packet_id = unpack('Npacket_id', Strings::shift($this->packet_buffer, 4))['packet_id']; // remove the request id
+ $length -= 5; // account for the request id and the packet type
} else {
- $length-= 1; // account for the packet type
+ $length -= 1; // account for the packet type
}
- $packet = $this->_string_shift($this->packet_buffer, $length);
+ $packet = Strings::shift($this->packet_buffer, $length);
if (defined('NET_SFTP_LOGGING')) {
- $packet_type = '<- ' . $this->packet_types[$this->packet_type] .
+ $packet_type = '<- ' . self::$packet_types[$this->packet_type] .
' (' . round($stop - $start, 4) . 's)';
- if (NET_SFTP_LOGGING == self::LOG_REALTIME) {
- switch (PHP_SAPI) {
- case 'cli':
- $start = $stop = "\r\n";
- break;
- default:
- $start = '<pre>';
- $stop = '</pre>';
- }
- echo $start . $this->_format_log(array($packet), array($packet_type)) . $stop;
- @flush();
- @ob_flush();
- } else {
- $this->packet_type_log[] = $packet_type;
- if (NET_SFTP_LOGGING == self::LOG_COMPLEX) {
- $this->packet_log[] = $packet;
- }
- }
+ $this->append_log($packet_type, $packet);
}
if (isset($request_id) && $this->use_request_id && $packet_id != $request_id) {
- $this->requestBuffer[$packet_id] = array(
+ $this->requestBuffer[$packet_id] = [
'packet_type' => $this->packet_type,
'packet' => $packet
- );
- return $this->_get_sftp_packet($request_id);
+ ];
+ return $this->get_sftp_packet($request_id);
}
return $packet;
}
/**
+ * Logs data packets
+ *
+ * Makes sure that only the last 1MB worth of packets will be logged
+ *
+ * @param string $message_number
+ * @param string $message
+ */
+ private function append_log($message_number, $message)
+ {
+ $this->append_log_helper(
+ NET_SFTP_LOGGING,
+ $message_number,
+ $message,
+ $this->packet_type_log,
+ $this->packet_log,
+ $this->log_size,
+ $this->realtime_log_file,
+ $this->realtime_log_wrap,
+ $this->realtime_log_size
+ );
+ }
+
+ /**
* Returns a log of the packets that have been sent and received.
*
- * Returns a string if NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX, an array if NET_SFTP_LOGGING == NET_SFTP_LOG_SIMPLE and false if !defined('NET_SFTP_LOGGING')
+ * Returns a string if NET_SFTP_LOGGING == self::LOG_COMPLEX, an array if NET_SFTP_LOGGING == self::LOG_SIMPLE and false if !defined('NET_SFTP_LOGGING')
*
- * @access public
- * @return string or Array
+ * @return array|string|false
*/
- function getSFTPLog()
+ public function getSFTPLog()
{
if (!defined('NET_SFTP_LOGGING')) {
return false;
@@ -3791,7 +3452,7 @@ class SFTP extends SSH2
switch (NET_SFTP_LOGGING) {
case self::LOG_COMPLEX:
- return $this->_format_log($this->packet_log, $this->packet_type_log);
+ return $this->format_log($this->packet_log, $this->packet_type_log);
break;
//case self::LOG_SIMPLE:
default:
@@ -3803,9 +3464,8 @@ class SFTP extends SSH2
* Returns all errors on the SFTP layer
*
* @return array
- * @access public
*/
- function getSFTPErrors()
+ public function getSFTPErrors()
{
return $this->sftp_errors;
}
@@ -3814,9 +3474,8 @@ class SFTP extends SSH2
* Returns the last error on the SFTP layer
*
* @return string
- * @access public
*/
- function getLastSFTPError()
+ public function getLastSFTPError()
{
return count($this->sftp_errors) ? $this->sftp_errors[count($this->sftp_errors) - 1] : '';
}
@@ -3825,19 +3484,18 @@ class SFTP extends SSH2
* Get supported SFTP versions
*
* @return array
- * @access public
*/
- function getSupportedVersions()
+ public function getSupportedVersions()
{
if (!($this->bitmap & SSH2::MASK_LOGIN)) {
return false;
}
if (!$this->partial_init) {
- $this->_partial_init_sftp_connection();
+ $this->partial_init_sftp_connection();
}
- $temp = array('version' => $this->defaultVersion);
+ $temp = ['version' => $this->defaultVersion];
if (isset($this->extensions['versions'])) {
$temp['extensions'] = $this->extensions['versions'];
}
@@ -3845,14 +3503,31 @@ class SFTP extends SSH2
}
/**
- * Get supported SFTP versions
+ * Get supported SFTP extensions
*
* @return array
- * @access public
*/
- function getNegotiatedVersion()
+ public function getSupportedExtensions()
+ {
+ if (!($this->bitmap & SSH2::MASK_LOGIN)) {
+ return false;
+ }
+
+ if (!$this->partial_init) {
+ $this->partial_init_sftp_connection();
+ }
+
+ return $this->extensions;
+ }
+
+ /**
+ * Get supported SFTP versions
+ *
+ * @return int|false
+ */
+ public function getNegotiatedVersion()
{
- if (!$this->_precheck()) {
+ if (!$this->precheck()) {
return false;
}
@@ -3867,9 +3542,8 @@ class SFTP extends SSH2
* unset the preferred version
*
* @param int $version
- * @access public
*/
- function setPreferredVersion($version)
+ public function setPreferredVersion($version)
{
$this->preferredVersion = $version;
}
@@ -3878,21 +3552,19 @@ class SFTP extends SSH2
* Disconnect
*
* @param int $reason
- * @return bool
- * @access private
+ * @return false
*/
- function _disconnect($reason)
+ protected function disconnect_helper($reason)
{
$this->pwd = false;
- parent::_disconnect($reason);
+ return parent::disconnect_helper($reason);
}
/**
* Enable Date Preservation
*
- * @access public
*/
- function enableDatePreservation()
+ public function enableDatePreservation()
{
$this->preserveTime = true;
}
@@ -3900,10 +3572,129 @@ class SFTP extends SSH2
/**
* Disable Date Preservation
*
- * @access public
*/
- function disableDatePreservation()
+ public function disableDatePreservation()
{
$this->preserveTime = false;
}
+
+ /**
+ * POSIX Rename
+ *
+ * Where rename() fails "if there already exists a file with the name specified by newpath"
+ * (draft-ietf-secsh-filexfer-02#section-6.5), posix_rename() overwrites the existing file in an atomic fashion.
+ * ie. "there is no observable instant in time where the name does not refer to either the old or the new file"
+ * (draft-ietf-secsh-filexfer-13#page-39).
+ *
+ * @param string $oldname
+ * @param string $newname
+ * @return bool
+ */
+ public function posix_rename($oldname, $newname)
+ {
+ if (!$this->precheck()) {
+ return false;
+ }
+
+ $oldname = $this->realpath($oldname);
+ $newname = $this->realpath($newname);
+ if ($oldname === false || $newname === false) {
+ return false;
+ }
+
+ if ($this->version >= 5) {
+ $packet = Strings::packSSH2('ssN', $oldname, $newname, 2); // 2 = SSH_FXP_RENAME_ATOMIC
+ $this->send_sftp_packet(NET_SFTP_RENAME, $packet);
+ } elseif (isset($this->extensions['posix-rename@openssh.com']) && $this->extensions['posix-rename@openssh.com'] === '1') {
+ $packet = Strings::packSSH2('sss', 'posix-rename@openssh.com', $oldname, $newname);
+ $this->send_sftp_packet(NET_SFTP_EXTENDED, $packet);
+ } else {
+ throw new \RuntimeException(
+ "Extension 'posix-rename@openssh.com' is not supported by the server. " .
+ "Call getSupportedVersions() to see a list of supported extension"
+ );
+ }
+
+ $response = $this->get_sftp_packet();
+ if ($this->packet_type != NET_SFTP_STATUS) {
+ throw new \UnexpectedValueException('Expected NET_SFTP_STATUS. '
+ . 'Got packet type: ' . $this->packet_type);
+ }
+
+ // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
+ list($status) = Strings::unpackSSH2('N', $response);
+ if ($status != NET_SFTP_STATUS_OK) {
+ $this->logError($response, $status);
+ return false;
+ }
+
+ // don't move the stat cache entry over since this operation could very well change the
+ // atime and mtime attributes
+ //$this->update_stat_cache($newname, $this->query_stat_cache($oldname));
+ $this->remove_from_stat_cache($oldname);
+ $this->remove_from_stat_cache($newname);
+
+ return true;
+ }
+
+ /**
+ * Returns general information about a file system.
+ *
+ * The function statvfs() returns information about a mounted filesystem.
+ * @see https://man7.org/linux/man-pages/man3/statvfs.3.html
+ *
+ * @param string $path
+ * @return false|array{bsize: int, frsize: int, blocks: int, bfree: int, bavail: int, files: int, ffree: int, favail: int, fsid: int, flag: int, namemax: int}
+ */
+ public function statvfs($path)
+ {
+ if (!$this->precheck()) {
+ return false;
+ }
+
+ if (!isset($this->extensions['statvfs@openssh.com']) || $this->extensions['statvfs@openssh.com'] !== '2') {
+ throw new \RuntimeException(
+ "Extension 'statvfs@openssh.com' is not supported by the server. " .
+ "Call getSupportedVersions() to see a list of supported extension"
+ );
+ }
+
+ $realpath = $this->realpath($path);
+ if ($realpath === false) {
+ return false;
+ }
+
+ $packet = Strings::packSSH2('ss', 'statvfs@openssh.com', $realpath);
+ $this->send_sftp_packet(NET_SFTP_EXTENDED, $packet);
+
+ $response = $this->get_sftp_packet();
+ if ($this->packet_type !== NET_SFTP_EXTENDED_REPLY) {
+ throw new \UnexpectedValueException(
+ 'Expected SSH_FXP_EXTENDED_REPLY. '
+ . 'Got packet type: ' . $this->packet_type
+ );
+ }
+
+ /**
+ * These requests return a SSH_FXP_STATUS reply on failure. On success they
+ * return the following SSH_FXP_EXTENDED_REPLY reply:
+ *
+ * uint32 id
+ * uint64 f_bsize file system block size
+ * uint64 f_frsize fundamental fs block size
+ * uint64 f_blocks number of blocks (unit f_frsize)
+ * uint64 f_bfree free blocks in file system
+ * uint64 f_bavail free blocks for non-root
+ * uint64 f_files total file inodes
+ * uint64 f_ffree free file inodes
+ * uint64 f_favail free file inodes for to non-root
+ * uint64 f_fsid file system id
+ * uint64 f_flag bit mask of f_flag values
+ * uint64 f_namemax maximum filename length
+ */
+ return array_combine(
+ ['bsize', 'frsize', 'blocks', 'bfree', 'bavail', 'files', 'ffree', 'favail', 'fsid', 'flag', 'namemax'],
+ Strings::unpackSSH2('QQQQQQQQQQQ', $response)
+ );
+ }
}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Net/SFTP/Stream.php b/vendor/phpseclib/phpseclib/phpseclib/Net/SFTP/Stream.php
index ec9e5841a..a1f2fa245 100644
--- a/vendor/phpseclib/phpseclib/phpseclib/Net/SFTP/Stream.php
+++ b/vendor/phpseclib/phpseclib/phpseclib/Net/SFTP/Stream.php
@@ -7,25 +7,22 @@
*
* PHP version 5
*
- * @category Net
- * @package SFTP
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2013 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
-namespace phpseclib\Net\SFTP;
+namespace phpseclib3\Net\SFTP;
-use phpseclib\Crypt\RSA;
-use phpseclib\Net\SFTP;
+use phpseclib3\Crypt\Common\PrivateKey;
+use phpseclib3\Net\SFTP;
+use phpseclib3\Net\SSH2;
/**
* SFTP Stream Wrapper
*
- * @package SFTP
* @author Jim Wigginton <terrafrost@php.net>
- * @access public
*/
class Stream
{
@@ -36,90 +33,80 @@ class Stream
*
* @var array
*/
- static $instances;
+ public static $instances;
/**
* SFTP instance
*
* @var object
- * @access private
*/
- var $sftp;
+ private $sftp;
/**
* Path
*
* @var string
- * @access private
*/
- var $path;
+ private $path;
/**
* Mode
*
* @var string
- * @access private
*/
- var $mode;
+ private $mode;
/**
* Position
*
* @var int
- * @access private
*/
- var $pos;
+ private $pos;
/**
* Size
*
* @var int
- * @access private
*/
- var $size;
+ private $size;
/**
* Directory entries
*
* @var array
- * @access private
*/
- var $entries;
+ private $entries;
/**
* EOF flag
*
* @var bool
- * @access private
*/
- var $eof;
+ private $eof;
/**
* Context resource
*
- * Technically this needs to be publically accessible so PHP can set it directly
+ * Technically this needs to be publicly accessible so PHP can set it directly
*
* @var resource
- * @access public
*/
- var $context;
+ public $context;
/**
* Notification callback function
*
* @var callable
- * @access public
*/
- var $notification;
+ private $notification;
/**
* Registers this class as a URL wrapper.
*
* @param string $protocol The wrapper name to be registered.
* @return bool True on success, false otherwise.
- * @access public
*/
- static function register($protocol = 'sftp')
+ public static function register($protocol = 'sftp')
{
if (in_array($protocol, stream_get_wrappers(), true)) {
return false;
@@ -130,9 +117,8 @@ class Stream
/**
* The Constructor
*
- * @access public
*/
- function __construct()
+ public function __construct()
{
if (defined('NET_SFTP_STREAM_LOGGING')) {
echo "__construct()\r\n";
@@ -149,21 +135,28 @@ class Stream
*
* @param string $path
* @return string
- * @access private
*/
- function _parse_path($path)
+ protected function parse_path($path)
{
$orig = $path;
- extract(parse_url($path) + array('port' => 22));
+ $url = parse_url($path) + ['port' => 22];
+
+ $keys = ['scheme', 'host', 'port', 'user', 'pass', 'path', 'query', 'fragment'];
+ foreach ($keys as $key) {
+ if (isset($url[$key])) {
+ $$key = $url[$key];
+ }
+ }
+
if (isset($query)) {
- $path.= '?' . $query;
+ $path .= '?' . $query;
} elseif (preg_match('/(\?|\?#)$/', $orig)) {
- $path.= '?';
+ $path .= '?';
}
if (isset($fragment)) {
- $path.= '#' . $fragment;
+ $path .= '#' . $fragment;
} elseif ($orig[strlen($orig) - 1] == '#') {
- $path.= '#';
+ $path .= '#';
}
if (!isset($host)) {
@@ -177,13 +170,12 @@ class Stream
}
}
- if ($host[0] == '$') {
- $host = substr($host, 1);
- global ${$host};
- if (($$host instanceof SFTP) === false) {
+ if (preg_match('/^{[a-z0-9]+}$/i', $host)) {
+ $host = SSH2::getConnectionByResourceId($host);
+ if ($host === false) {
return false;
}
- $this->sftp = $$host;
+ $this->sftp = $host;
} else {
if (isset($this->context)) {
$context = stream_context_get_options($this->context);
@@ -204,7 +196,7 @@ class Stream
if (isset($context[$scheme]['password'])) {
$pass = $context[$scheme]['password'];
}
- if (isset($context[$scheme]['privkey']) && $context[$scheme]['privkey'] instanceof RSA) {
+ if (isset($context[$scheme]['privkey']) && $context[$scheme]['privkey'] instanceof PrivateKey) {
$pass = $context[$scheme]['privkey'];
}
@@ -212,7 +204,7 @@ class Stream
return false;
}
- // casting $pass to a string is necessary in the event that it's a \phpseclib\Crypt\RSA object
+ // casting $pass to a string is necessary in the event that it's a \phpseclib3\Crypt\RSA object
if (isset(self::$instances[$host][$port][$user][(string) $pass])) {
$this->sftp = self::$instances[$host][$port][$user][(string) $pass];
} else {
@@ -255,18 +247,17 @@ class Stream
* @param int $options
* @param string $opened_path
* @return bool
- * @access public
*/
- function _stream_open($path, $mode, $options, &$opened_path)
+ private function _stream_open($path, $mode, $options, &$opened_path)
{
- $path = $this->_parse_path($path);
+ $path = $this->parse_path($path);
if ($path === false) {
return false;
}
$this->path = $path;
- $this->size = $this->sftp->size($path);
+ $this->size = $this->sftp->filesize($path);
$this->mode = preg_replace('#[bt]$#', '', $mode);
$this->eof = false;
@@ -297,9 +288,8 @@ class Stream
*
* @param int $count
* @return mixed
- * @access public
*/
- function _stream_read($count)
+ private function _stream_read($count)
{
switch ($this->mode) {
case 'w':
@@ -329,7 +319,7 @@ class Stream
$this->eof = true;
return false;
}
- $this->pos+= strlen($result);
+ $this->pos += strlen($result);
return $result;
}
@@ -338,10 +328,9 @@ class Stream
* Write to stream
*
* @param string $data
- * @return mixed
- * @access public
+ * @return int|false
*/
- function _stream_write($data)
+ private function _stream_write($data)
{
switch ($this->mode) {
case 'r':
@@ -361,7 +350,7 @@ class Stream
if ($result === false) {
return false;
}
- $this->pos+= strlen($data);
+ $this->pos += strlen($data);
if ($this->pos > $this->size) {
$this->size = $this->pos;
}
@@ -373,9 +362,8 @@ class Stream
* Retrieve the current position of a stream
*
* @return int
- * @access public
*/
- function _stream_tell()
+ private function _stream_tell()
{
return $this->pos;
}
@@ -391,9 +379,8 @@ class Stream
* will return false. do fread($fp, 1) and feof() will then return true.
*
* @return bool
- * @access public
*/
- function _stream_eof()
+ private function _stream_eof()
{
return $this->eof;
}
@@ -404,9 +391,8 @@ class Stream
* @param int $offset
* @param int $whence
* @return bool
- * @access public
*/
- function _stream_seek($offset, $whence)
+ private function _stream_seek($offset, $whence)
{
switch ($whence) {
case SEEK_SET:
@@ -415,10 +401,10 @@ class Stream
}
break;
case SEEK_CUR:
- $offset+= $this->pos;
+ $offset += $this->pos;
break;
case SEEK_END:
- $offset+= $this->size;
+ $offset += $this->size;
}
$this->pos = $offset;
@@ -433,11 +419,10 @@ class Stream
* @param int $option
* @param mixed $var
* @return bool
- * @access public
*/
- function _stream_metadata($path, $option, $var)
+ private function _stream_metadata($path, $option, $var)
{
- $path = $this->_parse_path($path);
+ $path = $this->parse_path($path);
if ($path === false) {
return false;
}
@@ -467,9 +452,8 @@ class Stream
*
* @param int $cast_as
* @return resource
- * @access public
*/
- function _stream_cast($cast_as)
+ private function _stream_cast($cast_as)
{
return $this->sftp->fsock;
}
@@ -479,9 +463,8 @@ class Stream
*
* @param int $operation
* @return bool
- * @access public
*/
- function _stream_lock($operation)
+ private function _stream_lock($operation)
{
return false;
}
@@ -490,15 +473,14 @@ class Stream
* Renames a file or directory
*
* Attempts to rename oldname to newname, moving it between directories if necessary.
- * If newname exists, it will be overwritten. This is a departure from what \phpseclib\Net\SFTP
+ * If newname exists, it will be overwritten. This is a departure from what \phpseclib3\Net\SFTP
* does.
*
* @param string $path_from
* @param string $path_to
* @return bool
- * @access public
*/
- function _rename($path_from, $path_to)
+ private function _rename($path_from, $path_to)
{
$path1 = parse_url($path_from);
$path2 = parse_url($path_to);
@@ -507,7 +489,7 @@ class Stream
return false;
}
- $path_from = $this->_parse_path($path_from);
+ $path_from = $this->parse_path($path_from);
$path_to = parse_url($path_to);
if ($path_from === false) {
return false;
@@ -548,11 +530,10 @@ class Stream
* @param string $path
* @param int $options
* @return bool
- * @access public
*/
- function _dir_opendir($path, $options)
+ private function _dir_opendir($path, $options)
{
- $path = $this->_parse_path($path);
+ $path = $this->parse_path($path);
if ($path === false) {
return false;
}
@@ -565,9 +546,8 @@ class Stream
* Read entry from directory handle
*
* @return mixed
- * @access public
*/
- function _dir_readdir()
+ private function _dir_readdir()
{
if (isset($this->entries[$this->pos])) {
return $this->entries[$this->pos++];
@@ -579,9 +559,8 @@ class Stream
* Rewind directory handle
*
* @return bool
- * @access public
*/
- function _dir_rewinddir()
+ private function _dir_rewinddir()
{
$this->pos = 0;
return true;
@@ -591,9 +570,8 @@ class Stream
* Close directory handle
*
* @return bool
- * @access public
*/
- function _dir_closedir()
+ private function _dir_closedir()
{
return true;
}
@@ -607,11 +585,10 @@ class Stream
* @param int $mode
* @param int $options
* @return bool
- * @access public
*/
- function _mkdir($path, $mode, $options)
+ private function _mkdir($path, $mode, $options)
{
- $path = $this->_parse_path($path);
+ $path = $this->parse_path($path);
if ($path === false) {
return false;
}
@@ -630,11 +607,10 @@ class Stream
* @param string $path
* @param int $options
* @return bool
- * @access public
*/
- function _rmdir($path, $options)
+ private function _rmdir($path, $options)
{
- $path = $this->_parse_path($path);
+ $path = $this->parse_path($path);
if ($path === false) {
return false;
}
@@ -645,12 +621,11 @@ class Stream
/**
* Flushes the output
*
- * See <http://php.net/fflush>. Always returns true because \phpseclib\Net\SFTP doesn't cache stuff before writing
+ * See <http://php.net/fflush>. Always returns true because \phpseclib3\Net\SFTP doesn't cache stuff before writing
*
* @return bool
- * @access public
*/
- function _stream_flush()
+ private function _stream_flush()
{
return true;
}
@@ -659,9 +634,8 @@ class Stream
* Retrieve information about a file resource
*
* @return mixed
- * @access public
*/
- function _stream_stat()
+ private function _stream_stat()
{
$results = $this->sftp->stat($this->path);
if ($results === false) {
@@ -675,11 +649,10 @@ class Stream
*
* @param string $path
* @return bool
- * @access public
*/
- function _unlink($path)
+ private function _unlink($path)
{
- $path = $this->_parse_path($path);
+ $path = $this->parse_path($path);
if ($path === false) {
return false;
}
@@ -690,18 +663,17 @@ class Stream
/**
* Retrieve information about a file
*
- * Ignores the STREAM_URL_STAT_QUIET flag because the entirety of \phpseclib\Net\SFTP\Stream is quiet by default
+ * Ignores the STREAM_URL_STAT_QUIET flag because the entirety of \phpseclib3\Net\SFTP\Stream is quiet by default
* might be worthwhile to reconstruct bits 12-16 (ie. the file type) if mode doesn't have them but we'll
* cross that bridge when and if it's reached
*
* @param string $path
* @param int $flags
* @return mixed
- * @access public
*/
- function _url_stat($path, $flags)
+ private function _url_stat($path, $flags)
{
- $path = $this->_parse_path($path);
+ $path = $this->parse_path($path);
if ($path === false) {
return false;
}
@@ -719,9 +691,8 @@ class Stream
*
* @param int $new_size
* @return bool
- * @access public
*/
- function _stream_truncate($new_size)
+ private function _stream_truncate($new_size)
{
if (!$this->sftp->truncate($this->path, $new_size)) {
return false;
@@ -737,15 +708,14 @@ class Stream
* Change stream options
*
* STREAM_OPTION_WRITE_BUFFER isn't supported for the same reason stream_flush isn't.
- * The other two aren't supported because of limitations in \phpseclib\Net\SFTP.
+ * The other two aren't supported because of limitations in \phpseclib3\Net\SFTP.
*
* @param int $option
* @param int $arg1
* @param int $arg2
* @return bool
- * @access public
*/
- function _stream_set_option($option, $arg1, $arg2)
+ private function _stream_set_option($option, $arg1, $arg2)
{
return false;
}
@@ -753,9 +723,8 @@ class Stream
/**
* Close an resource
*
- * @access public
*/
- function _stream_close()
+ private function _stream_close()
{
}
@@ -772,9 +741,8 @@ class Stream
* @param string $name
* @param array $arguments
* @return mixed
- * @access public
*/
- function __call($name, $arguments)
+ public function __call($name, array $arguments)
{
if (defined('NET_SFTP_STREAM_LOGGING')) {
echo $name . '(';
@@ -791,6 +759,6 @@ class Stream
if (!method_exists($this, $name)) {
return false;
}
- return call_user_func_array(array($this, $name), $arguments);
+ return $this->$name(...$arguments);
}
}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Net/SSH1.php b/vendor/phpseclib/phpseclib/phpseclib/Net/SSH1.php
deleted file mode 100644
index fc8d2acd8..000000000
--- a/vendor/phpseclib/phpseclib/phpseclib/Net/SSH1.php
+++ /dev/null
@@ -1,1662 +0,0 @@
-<?php
-
-/**
- * Pure-PHP implementation of SSHv1.
- *
- * PHP version 5
- *
- * Here's a short example of how to use this library:
- * <code>
- * <?php
- * include 'vendor/autoload.php';
- *
- * $ssh = new \phpseclib\Net\SSH1('www.domain.tld');
- * if (!$ssh->login('username', 'password')) {
- * exit('Login Failed');
- * }
- *
- * echo $ssh->exec('ls -la');
- * ?>
- * </code>
- *
- * Here's another short example:
- * <code>
- * <?php
- * include 'vendor/autoload.php';
- *
- * $ssh = new \phpseclib\Net\SSH1('www.domain.tld');
- * if (!$ssh->login('username', 'password')) {
- * exit('Login Failed');
- * }
- *
- * echo $ssh->read('username@username:~$');
- * $ssh->write("ls -la\n");
- * echo $ssh->read('username@username:~$');
- * ?>
- * </code>
- *
- * More information on the SSHv1 specification can be found by reading
- * {@link http://www.snailbook.com/docs/protocol-1.5.txt protocol-1.5.txt}.
- *
- * @category Net
- * @package SSH1
- * @author Jim Wigginton <terrafrost@php.net>
- * @copyright 2007 Jim Wigginton
- * @license http://www.opensource.org/licenses/mit-license.html MIT License
- * @link http://phpseclib.sourceforge.net
- */
-
-namespace phpseclib\Net;
-
-use phpseclib\Crypt\DES;
-use phpseclib\Crypt\Random;
-use phpseclib\Crypt\TripleDES;
-use phpseclib\Math\BigInteger;
-
-/**
- * Pure-PHP implementation of SSHv1.
- *
- * @package SSH1
- * @author Jim Wigginton <terrafrost@php.net>
- * @access public
- */
-class SSH1
-{
- /**#@+
- * Encryption Methods
- *
- * @see \phpseclib\Net\SSH1::getSupportedCiphers()
- * @access public
- */
- /**
- * No encryption
- *
- * Not supported.
- */
- const CIPHER_NONE = 0;
- /**
- * IDEA in CFB mode
- *
- * Not supported.
- */
- const CIPHER_IDEA = 1;
- /**
- * DES in CBC mode
- */
- const CIPHER_DES = 2;
- /**
- * Triple-DES in CBC mode
- *
- * All implementations are required to support this
- */
- const CIPHER_3DES = 3;
- /**
- * TRI's Simple Stream encryption CBC
- *
- * Not supported nor is it defined in the official SSH1 specs. OpenSSH, however, does define it (see cipher.h),
- * although it doesn't use it (see cipher.c)
- */
- const CIPHER_BROKEN_TSS = 4;
- /**
- * RC4
- *
- * Not supported.
- *
- * @internal According to the SSH1 specs:
- *
- * "The first 16 bytes of the session key are used as the key for
- * the server to client direction. The remaining 16 bytes are used
- * as the key for the client to server direction. This gives
- * independent 128-bit keys for each direction."
- *
- * This library currently only supports encryption when the same key is being used for both directions. This is
- * because there's only one $crypto object. Two could be added ($encrypt and $decrypt, perhaps).
- */
- const CIPHER_RC4 = 5;
- /**
- * Blowfish
- *
- * Not supported nor is it defined in the official SSH1 specs. OpenSSH, however, defines it (see cipher.h) and
- * uses it (see cipher.c)
- */
- const CIPHER_BLOWFISH = 6;
- /**#@-*/
-
- /**#@+
- * Authentication Methods
- *
- * @see \phpseclib\Net\SSH1::getSupportedAuthentications()
- * @access public
- */
- /**
- * .rhosts or /etc/hosts.equiv
- */
- const AUTH_RHOSTS = 1;
- /**
- * pure RSA authentication
- */
- const AUTH_RSA = 2;
- /**
- * password authentication
- *
- * This is the only method that is supported by this library.
- */
- const AUTH_PASSWORD = 3;
- /**
- * .rhosts with RSA host authentication
- */
- const AUTH_RHOSTS_RSA = 4;
- /**#@-*/
-
- /**#@+
- * Terminal Modes
- *
- * @link http://3sp.com/content/developer/maverick-net/docs/Maverick.SSH.PseudoTerminalModesMembers.html
- * @access private
- */
- const TTY_OP_END = 0;
- /**#@-*/
-
- /**
- * The Response Type
- *
- * @see \phpseclib\Net\SSH1::_get_binary_packet()
- * @access private
- */
- const RESPONSE_TYPE = 1;
-
- /**
- * The Response Data
- *
- * @see \phpseclib\Net\SSH1::_get_binary_packet()
- * @access private
- */
- const RESPONSE_DATA = 2;
-
- /**#@+
- * Execution Bitmap Masks
- *
- * @see \phpseclib\Net\SSH1::bitmap
- * @access private
- */
- const MASK_CONSTRUCTOR = 0x00000001;
- const MASK_CONNECTED = 0x00000002;
- const MASK_LOGIN = 0x00000004;
- const MASK_SHELL = 0x00000008;
- /**#@-*/
-
- /**#@+
- * @access public
- * @see \phpseclib\Net\SSH1::getLog()
- */
- /**
- * Returns the message numbers
- */
- const LOG_SIMPLE = 1;
- /**
- * Returns the message content
- */
- const LOG_COMPLEX = 2;
- /**
- * Outputs the content real-time
- */
- const LOG_REALTIME = 3;
- /**
- * Dumps the content real-time to a file
- */
- const LOG_REALTIME_FILE = 4;
- /**
- * Make sure that the log never gets larger than this
- */
- const LOG_MAX_SIZE = 1048576; // 1024 * 1024
- /**#@-*/
-
- /**#@+
- * @access public
- * @see \phpseclib\Net\SSH1::read()
- */
- /**
- * Returns when a string matching $expect exactly is found
- */
- const READ_SIMPLE = 1;
- /**
- * Returns when a string matching the regular expression $expect is found
- */
- const READ_REGEX = 2;
- /**#@-*/
-
- /**
- * The SSH identifier
- *
- * @var string
- * @access private
- */
- var $identifier = 'SSH-1.5-phpseclib';
-
- /**
- * The Socket Object
- *
- * @var object
- * @access private
- */
- var $fsock;
-
- /**
- * The cryptography object
- *
- * @var object
- * @access private
- */
- var $crypto = false;
-
- /**
- * Execution Bitmap
- *
- * The bits that are set represent functions that have been called already. This is used to determine
- * if a requisite function has been successfully executed. If not, an error should be thrown.
- *
- * @var int
- * @access private
- */
- var $bitmap = 0;
-
- /**
- * The Server Key Public Exponent
- *
- * Logged for debug purposes
- *
- * @see self::getServerKeyPublicExponent()
- * @var string
- * @access private
- */
- var $server_key_public_exponent;
-
- /**
- * The Server Key Public Modulus
- *
- * Logged for debug purposes
- *
- * @see self::getServerKeyPublicModulus()
- * @var string
- * @access private
- */
- var $server_key_public_modulus;
-
- /**
- * The Host Key Public Exponent
- *
- * Logged for debug purposes
- *
- * @see self::getHostKeyPublicExponent()
- * @var string
- * @access private
- */
- var $host_key_public_exponent;
-
- /**
- * The Host Key Public Modulus
- *
- * Logged for debug purposes
- *
- * @see self::getHostKeyPublicModulus()
- * @var string
- * @access private
- */
- var $host_key_public_modulus;
-
- /**
- * Supported Ciphers
- *
- * Logged for debug purposes
- *
- * @see self::getSupportedCiphers()
- * @var array
- * @access private
- */
- var $supported_ciphers = array(
- self::CIPHER_NONE => 'No encryption',
- self::CIPHER_IDEA => 'IDEA in CFB mode',
- self::CIPHER_DES => 'DES in CBC mode',
- self::CIPHER_3DES => 'Triple-DES in CBC mode',
- self::CIPHER_BROKEN_TSS => 'TRI\'s Simple Stream encryption CBC',
- self::CIPHER_RC4 => 'RC4',
- self::CIPHER_BLOWFISH => 'Blowfish'
- );
-
- /**
- * Supported Authentications
- *
- * Logged for debug purposes
- *
- * @see self::getSupportedAuthentications()
- * @var array
- * @access private
- */
- var $supported_authentications = array(
- self::AUTH_RHOSTS => '.rhosts or /etc/hosts.equiv',
- self::AUTH_RSA => 'pure RSA authentication',
- self::AUTH_PASSWORD => 'password authentication',
- self::AUTH_RHOSTS_RSA => '.rhosts with RSA host authentication'
- );
-
- /**
- * Server Identification
- *
- * @see self::getServerIdentification()
- * @var string
- * @access private
- */
- var $server_identification = '';
-
- /**
- * Protocol Flags
- *
- * @see self::__construct()
- * @var array
- * @access private
- */
- var $protocol_flags = array();
-
- /**
- * Protocol Flag Log
- *
- * @see self::getLog()
- * @var array
- * @access private
- */
- var $protocol_flags_log = array();
-
- /**
- * Message Log
- *
- * @see self::getLog()
- * @var array
- * @access private
- */
- var $message_log = array();
-
- /**
- * Real-time log file pointer
- *
- * @see self::_append_log()
- * @var resource
- * @access private
- */
- var $realtime_log_file;
-
- /**
- * Real-time log file size
- *
- * @see self::_append_log()
- * @var int
- * @access private
- */
- var $realtime_log_size;
-
- /**
- * Real-time log file wrap boolean
- *
- * @see self::_append_log()
- * @var bool
- * @access private
- */
- var $realtime_log_wrap;
-
- /**
- * Interactive Buffer
- *
- * @see self::read()
- * @var array
- * @access private
- */
- var $interactiveBuffer = '';
-
- /**
- * Current log size
- *
- * Should never exceed self::LOG_MAX_SIZE
- *
- * @see self::_send_binary_packet()
- * @see self::_get_binary_packet()
- * @var int
- * @access private
- */
- var $log_size;
-
- /**
- * Timeout
- *
- * @see self::setTimeout()
- * @access private
- */
- var $timeout;
-
- /**
- * Current Timeout
- *
- * @see self::_get_channel_packet()
- * @access private
- */
- var $curTimeout;
-
- /**
- * Log Boundary
- *
- * @see self::_format_log()
- * @access private
- */
- var $log_boundary = ':';
-
- /**
- * Log Long Width
- *
- * @see self::_format_log()
- * @access private
- */
- var $log_long_width = 65;
-
- /**
- * Log Short Width
- *
- * @see self::_format_log()
- * @access private
- */
- var $log_short_width = 16;
-
- /**
- * Hostname
- *
- * @see self::__construct()
- * @see self::_connect()
- * @var string
- * @access private
- */
- var $host;
-
- /**
- * Port Number
- *
- * @see self::__construct()
- * @see self::_connect()
- * @var int
- * @access private
- */
- var $port;
-
- /**
- * Timeout for initial connection
- *
- * Set by the constructor call. Calling setTimeout() is optional. If it's not called functions like
- * exec() won't timeout unless some PHP setting forces it too. The timeout specified in the constructor,
- * however, is non-optional. There will be a timeout, whether or not you set it. If you don't it'll be
- * 10 seconds. It is used by fsockopen() in that function.
- *
- * @see self::__construct()
- * @see self::_connect()
- * @var int
- * @access private
- */
- var $connectionTimeout;
-
- /**
- * Default cipher
- *
- * @see self::__construct()
- * @see self::_connect()
- * @var int
- * @access private
- */
- var $cipher;
-
- /**
- * Default Constructor.
- *
- * Connects to an SSHv1 server
- *
- * @param string $host
- * @param int $port
- * @param int $timeout
- * @param int $cipher
- * @return \phpseclib\Net\SSH1
- * @access public
- */
- function __construct($host, $port = 22, $timeout = 10, $cipher = self::CIPHER_3DES)
- {
- $this->protocol_flags = array(
- 1 => 'NET_SSH1_MSG_DISCONNECT',
- 2 => 'NET_SSH1_SMSG_PUBLIC_KEY',
- 3 => 'NET_SSH1_CMSG_SESSION_KEY',
- 4 => 'NET_SSH1_CMSG_USER',
- 9 => 'NET_SSH1_CMSG_AUTH_PASSWORD',
- 10 => 'NET_SSH1_CMSG_REQUEST_PTY',
- 12 => 'NET_SSH1_CMSG_EXEC_SHELL',
- 13 => 'NET_SSH1_CMSG_EXEC_CMD',
- 14 => 'NET_SSH1_SMSG_SUCCESS',
- 15 => 'NET_SSH1_SMSG_FAILURE',
- 16 => 'NET_SSH1_CMSG_STDIN_DATA',
- 17 => 'NET_SSH1_SMSG_STDOUT_DATA',
- 18 => 'NET_SSH1_SMSG_STDERR_DATA',
- 19 => 'NET_SSH1_CMSG_EOF',
- 20 => 'NET_SSH1_SMSG_EXITSTATUS',
- 33 => 'NET_SSH1_CMSG_EXIT_CONFIRMATION'
- );
-
- $this->_define_array($this->protocol_flags);
-
- $this->host = $host;
- $this->port = $port;
- $this->connectionTimeout = $timeout;
- $this->cipher = $cipher;
- }
-
- /**
- * Connect to an SSHv1 server
- *
- * @return bool
- * @access private
- */
- function _connect()
- {
- $this->fsock = @fsockopen($this->host, $this->port, $errno, $errstr, $this->connectionTimeout);
- if (!$this->fsock) {
- user_error(rtrim("Cannot connect to {$this->host}:{$this->port}. Error $errno. $errstr"));
- return false;
- }
-
- $this->server_identification = $init_line = fgets($this->fsock, 255);
-
- if (defined('NET_SSH1_LOGGING')) {
- $this->_append_log('<-', $this->server_identification);
- $this->_append_log('->', $this->identifier . "\r\n");
- }
-
- if (!preg_match('#SSH-([0-9\.]+)-(.+)#', $init_line, $parts)) {
- user_error('Can only connect to SSH servers');
- return false;
- }
- if ($parts[1][0] != 1) {
- user_error("Cannot connect to SSH $parts[1] servers");
- return false;
- }
-
- fputs($this->fsock, $this->identifier."\r\n");
-
- $response = $this->_get_binary_packet();
- if ($response[self::RESPONSE_TYPE] != NET_SSH1_SMSG_PUBLIC_KEY) {
- user_error('Expected SSH_SMSG_PUBLIC_KEY');
- return false;
- }
-
- $anti_spoofing_cookie = $this->_string_shift($response[self::RESPONSE_DATA], 8);
-
- $this->_string_shift($response[self::RESPONSE_DATA], 4);
-
- if (strlen($response[self::RESPONSE_DATA]) < 2) {
- return false;
- }
- $temp = unpack('nlen', $this->_string_shift($response[self::RESPONSE_DATA], 2));
- $server_key_public_exponent = new BigInteger($this->_string_shift($response[self::RESPONSE_DATA], ceil($temp['len'] / 8)), 256);
- $this->server_key_public_exponent = $server_key_public_exponent;
-
- if (strlen($response[self::RESPONSE_DATA]) < 2) {
- return false;
- }
- $temp = unpack('nlen', $this->_string_shift($response[self::RESPONSE_DATA], 2));
- $server_key_public_modulus = new BigInteger($this->_string_shift($response[self::RESPONSE_DATA], ceil($temp['len'] / 8)), 256);
-
- $this->server_key_public_modulus = $server_key_public_modulus;
-
- $this->_string_shift($response[self::RESPONSE_DATA], 4);
-
- if (strlen($response[self::RESPONSE_DATA]) < 2) {
- return false;
- }
- $temp = unpack('nlen', $this->_string_shift($response[self::RESPONSE_DATA], 2));
- $host_key_public_exponent = new BigInteger($this->_string_shift($response[self::RESPONSE_DATA], ceil($temp['len'] / 8)), 256);
- $this->host_key_public_exponent = $host_key_public_exponent;
-
- if (strlen($response[self::RESPONSE_DATA]) < 2) {
- return false;
- }
- $temp = unpack('nlen', $this->_string_shift($response[self::RESPONSE_DATA], 2));
- $host_key_public_modulus = new BigInteger($this->_string_shift($response[self::RESPONSE_DATA], ceil($temp['len'] / 8)), 256);
-
- $this->host_key_public_modulus = $host_key_public_modulus;
-
- $this->_string_shift($response[self::RESPONSE_DATA], 4);
-
- // get a list of the supported ciphers
- if (strlen($response[self::RESPONSE_DATA]) < 4) {
- return false;
- }
- extract(unpack('Nsupported_ciphers_mask', $this->_string_shift($response[self::RESPONSE_DATA], 4)));
-
- foreach ($this->supported_ciphers as $mask => $name) {
- if (($supported_ciphers_mask & (1 << $mask)) == 0) {
- unset($this->supported_ciphers[$mask]);
- }
- }
-
- // get a list of the supported authentications
- if (strlen($response[self::RESPONSE_DATA]) < 4) {
- return false;
- }
- extract(unpack('Nsupported_authentications_mask', $this->_string_shift($response[self::RESPONSE_DATA], 4)));
- foreach ($this->supported_authentications as $mask => $name) {
- if (($supported_authentications_mask & (1 << $mask)) == 0) {
- unset($this->supported_authentications[$mask]);
- }
- }
-
- $session_id = pack('H*', md5($host_key_public_modulus->toBytes() . $server_key_public_modulus->toBytes() . $anti_spoofing_cookie));
-
- $session_key = Random::string(32);
- $double_encrypted_session_key = $session_key ^ str_pad($session_id, 32, chr(0));
-
- if ($server_key_public_modulus->compare($host_key_public_modulus) < 0) {
- $double_encrypted_session_key = $this->_rsa_crypt(
- $double_encrypted_session_key,
- array(
- $server_key_public_exponent,
- $server_key_public_modulus
- )
- );
- $double_encrypted_session_key = $this->_rsa_crypt(
- $double_encrypted_session_key,
- array(
- $host_key_public_exponent,
- $host_key_public_modulus
- )
- );
- } else {
- $double_encrypted_session_key = $this->_rsa_crypt(
- $double_encrypted_session_key,
- array(
- $host_key_public_exponent,
- $host_key_public_modulus
- )
- );
- $double_encrypted_session_key = $this->_rsa_crypt(
- $double_encrypted_session_key,
- array(
- $server_key_public_exponent,
- $server_key_public_modulus
- )
- );
- }
-
- $cipher = isset($this->supported_ciphers[$this->cipher]) ? $this->cipher : self::CIPHER_3DES;
- $data = pack('C2a*na*N', NET_SSH1_CMSG_SESSION_KEY, $cipher, $anti_spoofing_cookie, 8 * strlen($double_encrypted_session_key), $double_encrypted_session_key, 0);
-
- if (!$this->_send_binary_packet($data)) {
- user_error('Error sending SSH_CMSG_SESSION_KEY');
- return false;
- }
-
- switch ($cipher) {
- //case self::CIPHER_NONE:
- // $this->crypto = new \phpseclib\Crypt\Null();
- // break;
- case self::CIPHER_DES:
- $this->crypto = new DES();
- $this->crypto->disablePadding();
- $this->crypto->enableContinuousBuffer();
- $this->crypto->setKey(substr($session_key, 0, 8));
- break;
- case self::CIPHER_3DES:
- $this->crypto = new TripleDES(TripleDES::MODE_3CBC);
- $this->crypto->disablePadding();
- $this->crypto->enableContinuousBuffer();
- $this->crypto->setKey(substr($session_key, 0, 24));
- break;
- //case self::CIPHER_RC4:
- // $this->crypto = new RC4();
- // $this->crypto->enableContinuousBuffer();
- // $this->crypto->setKey(substr($session_key, 0, 16));
- // break;
- }
-
- $response = $this->_get_binary_packet();
-
- if ($response[self::RESPONSE_TYPE] != NET_SSH1_SMSG_SUCCESS) {
- user_error('Expected SSH_SMSG_SUCCESS');
- return false;
- }
-
- $this->bitmap = self::MASK_CONNECTED;
-
- return true;
- }
-
- /**
- * Login
- *
- * @param string $username
- * @param string $password
- * @return bool
- * @access public
- */
- function login($username, $password = '')
- {
- if (!($this->bitmap & self::MASK_CONSTRUCTOR)) {
- $this->bitmap |= self::MASK_CONSTRUCTOR;
- if (!$this->_connect()) {
- return false;
- }
- }
-
- if (!($this->bitmap & self::MASK_CONNECTED)) {
- return false;
- }
-
- $data = pack('CNa*', NET_SSH1_CMSG_USER, strlen($username), $username);
-
- if (!$this->_send_binary_packet($data)) {
- user_error('Error sending SSH_CMSG_USER');
- return false;
- }
-
- $response = $this->_get_binary_packet();
-
- if ($response === true) {
- return false;
- }
- if ($response[self::RESPONSE_TYPE] == NET_SSH1_SMSG_SUCCESS) {
- $this->bitmap |= self::MASK_LOGIN;
- return true;
- } elseif ($response[self::RESPONSE_TYPE] != NET_SSH1_SMSG_FAILURE) {
- user_error('Expected SSH_SMSG_SUCCESS or SSH_SMSG_FAILURE');
- return false;
- }
-
- $data = pack('CNa*', NET_SSH1_CMSG_AUTH_PASSWORD, strlen($password), $password);
-
- if (!$this->_send_binary_packet($data)) {
- user_error('Error sending SSH_CMSG_AUTH_PASSWORD');
- return false;
- }
-
- // remove the username and password from the last logged packet
- if (defined('NET_SSH1_LOGGING') && NET_SSH1_LOGGING == self::LOG_COMPLEX) {
- $data = pack('CNa*', NET_SSH1_CMSG_AUTH_PASSWORD, strlen('password'), 'password');
- $this->message_log[count($this->message_log) - 1] = $data;
- }
-
- $response = $this->_get_binary_packet();
-
- if ($response === true) {
- return false;
- }
- if ($response[self::RESPONSE_TYPE] == NET_SSH1_SMSG_SUCCESS) {
- $this->bitmap |= self::MASK_LOGIN;
- return true;
- } elseif ($response[self::RESPONSE_TYPE] == NET_SSH1_SMSG_FAILURE) {
- return false;
- } else {
- user_error('Expected SSH_SMSG_SUCCESS or SSH_SMSG_FAILURE');
- return false;
- }
- }
-
- /**
- * Set Timeout
- *
- * $ssh->exec('ping 127.0.0.1'); on a Linux host will never return and will run indefinitely. setTimeout() makes it so it'll timeout.
- * Setting $timeout to false or 0 will mean there is no timeout.
- *
- * @param mixed $timeout
- */
- function setTimeout($timeout)
- {
- $this->timeout = $this->curTimeout = $timeout;
- }
-
- /**
- * Executes a command on a non-interactive shell, returns the output, and quits.
- *
- * An SSH1 server will close the connection after a command has been executed on a non-interactive shell. SSH2
- * servers don't, however, this isn't an SSH2 client. The way this works, on the server, is by initiating a
- * shell with the -s option, as discussed in the following links:
- *
- * {@link http://www.faqs.org/docs/bashman/bashref_65.html http://www.faqs.org/docs/bashman/bashref_65.html}
- * {@link http://www.faqs.org/docs/bashman/bashref_62.html http://www.faqs.org/docs/bashman/bashref_62.html}
- *
- * To execute further commands, a new \phpseclib\Net\SSH1 object will need to be created.
- *
- * Returns false on failure and the output, otherwise.
- *
- * @see self::interactiveRead()
- * @see self::interactiveWrite()
- * @param string $cmd
- * @param bool $block
- * @return mixed
- * @access public
- */
- function exec($cmd, $block = true)
- {
- if (!($this->bitmap & self::MASK_LOGIN)) {
- user_error('Operation disallowed prior to login()');
- return false;
- }
-
- $data = pack('CNa*', NET_SSH1_CMSG_EXEC_CMD, strlen($cmd), $cmd);
-
- if (!$this->_send_binary_packet($data)) {
- user_error('Error sending SSH_CMSG_EXEC_CMD');
- return false;
- }
-
- if (!$block) {
- return true;
- }
-
- $output = '';
- $response = $this->_get_binary_packet();
-
- if ($response !== false) {
- do {
- $output.= substr($response[self::RESPONSE_DATA], 4);
- $response = $this->_get_binary_packet();
- } while (is_array($response) && $response[self::RESPONSE_TYPE] != NET_SSH1_SMSG_EXITSTATUS);
- }
-
- $data = pack('C', NET_SSH1_CMSG_EXIT_CONFIRMATION);
-
- // i don't think it's really all that important if this packet gets sent or not.
- $this->_send_binary_packet($data);
-
- fclose($this->fsock);
-
- // reset the execution bitmap - a new \phpseclib\Net\SSH1 object needs to be created.
- $this->bitmap = 0;
-
- return $output;
- }
-
- /**
- * Creates an interactive shell
- *
- * @see self::interactiveRead()
- * @see self::interactiveWrite()
- * @return bool
- * @access private
- */
- function _initShell()
- {
- // connect using the sample parameters in protocol-1.5.txt.
- // according to wikipedia.org's entry on text terminals, "the fundamental type of application running on a text
- // terminal is a command line interpreter or shell". thus, opening a terminal session to run the shell.
- $data = pack('CNa*N4C', NET_SSH1_CMSG_REQUEST_PTY, strlen('vt100'), 'vt100', 24, 80, 0, 0, self::TTY_OP_END);
-
- if (!$this->_send_binary_packet($data)) {
- user_error('Error sending SSH_CMSG_REQUEST_PTY');
- return false;
- }
-
- $response = $this->_get_binary_packet();
-
- if ($response === true) {
- return false;
- }
- if ($response[self::RESPONSE_TYPE] != NET_SSH1_SMSG_SUCCESS) {
- user_error('Expected SSH_SMSG_SUCCESS');
- return false;
- }
-
- $data = pack('C', NET_SSH1_CMSG_EXEC_SHELL);
-
- if (!$this->_send_binary_packet($data)) {
- user_error('Error sending SSH_CMSG_EXEC_SHELL');
- return false;
- }
-
- $this->bitmap |= self::MASK_SHELL;
-
- //stream_set_blocking($this->fsock, 0);
-
- return true;
- }
-
- /**
- * Inputs a command into an interactive shell.
- *
- * @see self::interactiveWrite()
- * @param string $cmd
- * @return bool
- * @access public
- */
- function write($cmd)
- {
- return $this->interactiveWrite($cmd);
- }
-
- /**
- * Returns the output of an interactive shell when there's a match for $expect
- *
- * $expect can take the form of a string literal or, if $mode == self::READ_REGEX,
- * a regular expression.
- *
- * @see self::write()
- * @param string $expect
- * @param int $mode
- * @return bool
- * @access public
- */
- function read($expect, $mode = self::READ_SIMPLE)
- {
- if (!($this->bitmap & self::MASK_LOGIN)) {
- user_error('Operation disallowed prior to login()');
- return false;
- }
-
- if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) {
- user_error('Unable to initiate an interactive shell session');
- return false;
- }
-
- $match = $expect;
- while (true) {
- if ($mode == self::READ_REGEX) {
- preg_match($expect, $this->interactiveBuffer, $matches);
- $match = isset($matches[0]) ? $matches[0] : '';
- }
- $pos = strlen($match) ? strpos($this->interactiveBuffer, $match) : false;
- if ($pos !== false) {
- return $this->_string_shift($this->interactiveBuffer, $pos + strlen($match));
- }
- $response = $this->_get_binary_packet();
-
- if ($response === true) {
- return $this->_string_shift($this->interactiveBuffer, strlen($this->interactiveBuffer));
- }
- $this->interactiveBuffer.= substr($response[self::RESPONSE_DATA], 4);
- }
- }
-
- /**
- * Inputs a command into an interactive shell.
- *
- * @see self::interactiveRead()
- * @param string $cmd
- * @return bool
- * @access public
- */
- function interactiveWrite($cmd)
- {
- if (!($this->bitmap & self::MASK_LOGIN)) {
- user_error('Operation disallowed prior to login()');
- return false;
- }
-
- if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) {
- user_error('Unable to initiate an interactive shell session');
- return false;
- }
-
- $data = pack('CNa*', NET_SSH1_CMSG_STDIN_DATA, strlen($cmd), $cmd);
-
- if (!$this->_send_binary_packet($data)) {
- user_error('Error sending SSH_CMSG_STDIN');
- return false;
- }
-
- return true;
- }
-
- /**
- * Returns the output of an interactive shell when no more output is available.
- *
- * Requires PHP 4.3.0 or later due to the use of the stream_select() function. If you see stuff like
- * "^[[00m", you're seeing ANSI escape codes. According to
- * {@link http://support.microsoft.com/kb/101875 How to Enable ANSI.SYS in a Command Window}, "Windows NT
- * does not support ANSI escape sequences in Win32 Console applications", so if you're a Windows user,
- * there's not going to be much recourse.
- *
- * @see self::interactiveRead()
- * @return string
- * @access public
- */
- function interactiveRead()
- {
- if (!($this->bitmap & self::MASK_LOGIN)) {
- user_error('Operation disallowed prior to login()');
- return false;
- }
-
- if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) {
- user_error('Unable to initiate an interactive shell session');
- return false;
- }
-
- $read = array($this->fsock);
- $write = $except = null;
- if (stream_select($read, $write, $except, 0)) {
- $response = $this->_get_binary_packet();
- return substr($response[self::RESPONSE_DATA], 4);
- } else {
- return '';
- }
- }
-
- /**
- * Disconnect
- *
- * @access public
- */
- function disconnect()
- {
- $this->_disconnect();
- }
-
- /**
- * Destructor.
- *
- * Will be called, automatically, if you're supporting just PHP5. If you're supporting PHP4, you'll need to call
- * disconnect().
- *
- * @access public
- */
- function __destruct()
- {
- $this->_disconnect();
- }
-
- /**
- * Disconnect
- *
- * @param string $msg
- * @access private
- */
- function _disconnect($msg = 'Client Quit')
- {
- if ($this->bitmap) {
- $data = pack('C', NET_SSH1_CMSG_EOF);
- $this->_send_binary_packet($data);
- /*
- $response = $this->_get_binary_packet();
- if ($response === true) {
- $response = array(self::RESPONSE_TYPE => -1);
- }
- switch ($response[self::RESPONSE_TYPE]) {
- case NET_SSH1_SMSG_EXITSTATUS:
- $data = pack('C', NET_SSH1_CMSG_EXIT_CONFIRMATION);
- break;
- default:
- $data = pack('CNa*', NET_SSH1_MSG_DISCONNECT, strlen($msg), $msg);
- }
- */
- $data = pack('CNa*', NET_SSH1_MSG_DISCONNECT, strlen($msg), $msg);
-
- $this->_send_binary_packet($data);
- fclose($this->fsock);
- $this->bitmap = 0;
- }
- }
-
- /**
- * Gets Binary Packets
- *
- * See 'The Binary Packet Protocol' of protocol-1.5.txt for more info.
- *
- * Also, this function could be improved upon by adding detection for the following exploit:
- * http://www.securiteam.com/securitynews/5LP042K3FY.html
- *
- * @see self::_send_binary_packet()
- * @return array
- * @access private
- */
- function _get_binary_packet()
- {
- if (feof($this->fsock)) {
- //user_error('connection closed prematurely');
- return false;
- }
-
- if ($this->curTimeout) {
- $read = array($this->fsock);
- $write = $except = null;
-
- $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
- $sec = floor($this->curTimeout);
- $usec = 1000000 * ($this->curTimeout - $sec);
- // on windows this returns a "Warning: Invalid CRT parameters detected" error
- if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) {
- //$this->_disconnect('Timeout');
- return true;
- }
- $elapsed = strtok(microtime(), ' ') + strtok('') - $start;
- $this->curTimeout-= $elapsed;
- }
-
- $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
- $data = fread($this->fsock, 4);
- if (strlen($data) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $data);
-
- $padding_length = 8 - ($temp['length'] & 7);
- $length = $temp['length'] + $padding_length;
- $raw = '';
-
- while ($length > 0) {
- $temp = fread($this->fsock, $length);
- if (strlen($temp) != $length) {
- return false;
- }
- $raw.= $temp;
- $length-= strlen($temp);
- }
- $stop = strtok(microtime(), ' ') + strtok('');
-
- if (strlen($raw) && $this->crypto !== false) {
- $raw = $this->crypto->decrypt($raw);
- }
-
- $padding = substr($raw, 0, $padding_length);
- $type = $raw[$padding_length];
- $data = substr($raw, $padding_length + 1, -4);
-
- if (strlen($raw) < 4) {
- return false;
- }
- $temp = unpack('Ncrc', substr($raw, -4));
-
- //if ( $temp['crc'] != $this->_crc($padding . $type . $data) ) {
- // user_error('Bad CRC in packet from server');
- // return false;
- //}
-
- $type = ord($type);
-
- if (defined('NET_SSH1_LOGGING')) {
- $temp = isset($this->protocol_flags[$type]) ? $this->protocol_flags[$type] : 'UNKNOWN';
- $temp = '<- ' . $temp .
- ' (' . round($stop - $start, 4) . 's)';
- $this->_append_log($temp, $data);
- }
-
- return array(
- self::RESPONSE_TYPE => $type,
- self::RESPONSE_DATA => $data
- );
- }
-
- /**
- * Sends Binary Packets
- *
- * Returns true on success, false on failure.
- *
- * @see self::_get_binary_packet()
- * @param string $data
- * @return bool
- * @access private
- */
- function _send_binary_packet($data)
- {
- if (feof($this->fsock)) {
- //user_error('connection closed prematurely');
- return false;
- }
-
- $length = strlen($data) + 4;
-
- $padding = Random::string(8 - ($length & 7));
-
- $orig = $data;
- $data = $padding . $data;
- $data.= pack('N', $this->_crc($data));
-
- if ($this->crypto !== false) {
- $data = $this->crypto->encrypt($data);
- }
-
- $packet = pack('Na*', $length, $data);
-
- $start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
- $result = strlen($packet) == fputs($this->fsock, $packet);
- $stop = strtok(microtime(), ' ') + strtok('');
-
- if (defined('NET_SSH1_LOGGING')) {
- $temp = isset($this->protocol_flags[ord($orig[0])]) ? $this->protocol_flags[ord($orig[0])] : 'UNKNOWN';
- $temp = '-> ' . $temp .
- ' (' . round($stop - $start, 4) . 's)';
- $this->_append_log($temp, $orig);
- }
-
- return $result;
- }
-
- /**
- * Cyclic Redundancy Check (CRC)
- *
- * PHP's crc32 function is implemented slightly differently than the one that SSH v1 uses, so
- * we've reimplemented it. A more detailed discussion of the differences can be found after
- * $crc_lookup_table's initialization.
- *
- * @see self::_get_binary_packet()
- * @see self::_send_binary_packet()
- * @param string $data
- * @return int
- * @access private
- */
- function _crc($data)
- {
- static $crc_lookup_table = array(
- 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA,
- 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
- 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988,
- 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
- 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE,
- 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
- 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC,
- 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
- 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172,
- 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
- 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940,
- 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
- 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116,
- 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
- 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924,
- 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
- 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A,
- 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
- 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818,
- 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
- 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E,
- 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
- 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C,
- 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
- 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2,
- 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
- 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0,
- 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
- 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086,
- 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
- 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4,
- 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
- 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A,
- 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
- 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8,
- 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
- 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE,
- 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
- 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC,
- 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
- 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252,
- 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
- 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60,
- 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
- 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236,
- 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
- 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04,
- 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
- 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A,
- 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
- 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38,
- 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
- 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E,
- 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
- 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C,
- 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
- 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2,
- 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
- 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0,
- 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
- 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6,
- 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
- 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94,
- 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
- );
-
- // For this function to yield the same output as PHP's crc32 function, $crc would have to be
- // set to 0xFFFFFFFF, initially - not 0x00000000 as it currently is.
- $crc = 0x00000000;
- $length = strlen($data);
-
- for ($i=0; $i<$length; $i++) {
- // We AND $crc >> 8 with 0x00FFFFFF because we want the eight newly added bits to all
- // be zero. PHP, unfortunately, doesn't always do this. 0x80000000 >> 8, as an example,
- // yields 0xFF800000 - not 0x00800000. The following link elaborates:
- // http://www.php.net/manual/en/language.operators.bitwise.php#57281
- $crc = (($crc >> 8) & 0x00FFFFFF) ^ $crc_lookup_table[($crc & 0xFF) ^ ord($data[$i])];
- }
-
- // In addition to having to set $crc to 0xFFFFFFFF, initially, the return value must be XOR'd with
- // 0xFFFFFFFF for this function to return the same thing that PHP's crc32 function would.
- return $crc;
- }
-
- /**
- * String Shift
- *
- * Inspired by array_shift
- *
- * @param string $string
- * @param int $index
- * @return string
- * @access private
- */
- function _string_shift(&$string, $index = 1)
- {
- $substr = substr($string, 0, $index);
- $string = substr($string, $index);
- return $substr;
- }
-
- /**
- * RSA Encrypt
- *
- * Returns mod(pow($m, $e), $n), where $n should be the product of two (large) primes $p and $q and where $e
- * should be a number with the property that gcd($e, ($p - 1) * ($q - 1)) == 1. Could just make anything that
- * calls this call modexp, instead, but I think this makes things clearer, maybe...
- *
- * @see self::__construct()
- * @param BigInteger $m
- * @param array $key
- * @return BigInteger
- * @access private
- */
- function _rsa_crypt($m, $key)
- {
- /*
- $rsa = new RSA();
- $rsa->loadKey($key, RSA::PUBLIC_FORMAT_RAW);
- $rsa->setEncryptionMode(RSA::ENCRYPTION_PKCS1);
- return $rsa->encrypt($m);
- */
-
- // To quote from protocol-1.5.txt:
- // The most significant byte (which is only partial as the value must be
- // less than the public modulus, which is never a power of two) is zero.
- //
- // The next byte contains the value 2 (which stands for public-key
- // encrypted data in the PKCS standard [PKCS#1]). Then, there are non-
- // zero random bytes to fill any unused space, a zero byte, and the data
- // to be encrypted in the least significant bytes, the last byte of the
- // data in the least significant byte.
-
- // Presumably the part of PKCS#1 they're refering to is "Section 7.2.1 Encryption Operation",
- // under "7.2 RSAES-PKCS1-v1.5" and "7 Encryption schemes" of the following URL:
- // ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1.pdf
- $modulus = $key[1]->toBytes();
- $length = strlen($modulus) - strlen($m) - 3;
- $random = '';
- while (strlen($random) != $length) {
- $block = Random::string($length - strlen($random));
- $block = str_replace("\x00", '', $block);
- $random.= $block;
- }
- $temp = chr(0) . chr(2) . $random . chr(0) . $m;
-
- $m = new BigInteger($temp, 256);
- $m = $m->modPow($key[0], $key[1]);
-
- return $m->toBytes();
- }
-
- /**
- * Define Array
- *
- * Takes any number of arrays whose indices are integers and whose values are strings and defines a bunch of
- * named constants from it, using the value as the name of the constant and the index as the value of the constant.
- * If any of the constants that would be defined already exists, none of the constants will be defined.
- *
- * @access private
- */
- function _define_array()
- {
- $args = func_get_args();
- foreach ($args as $arg) {
- foreach ($arg as $key => $value) {
- if (!defined($value)) {
- define($value, $key);
- } else {
- break 2;
- }
- }
- }
- }
-
- /**
- * Returns a log of the packets that have been sent and received.
- *
- * Returns a string if NET_SSH1_LOGGING == self::LOG_COMPLEX, an array if NET_SSH1_LOGGING == self::LOG_SIMPLE and false if !defined('NET_SSH1_LOGGING')
- *
- * @access public
- * @return array|false|string
- */
- function getLog()
- {
- if (!defined('NET_SSH1_LOGGING')) {
- return false;
- }
-
- switch (NET_SSH1_LOGGING) {
- case self::LOG_SIMPLE:
- return $this->protocol_flags_log;
- break;
- case self::LOG_COMPLEX:
- return $this->_format_log($this->message_log, $this->protocol_flags_log);
- break;
- default:
- return false;
- }
- }
-
- /**
- * Formats a log for printing
- *
- * @param array $message_log
- * @param array $message_number_log
- * @access private
- * @return string
- */
- function _format_log($message_log, $message_number_log)
- {
- $output = '';
- for ($i = 0; $i < count($message_log); $i++) {
- $output.= $message_number_log[$i] . "\r\n";
- $current_log = $message_log[$i];
- $j = 0;
- do {
- if (strlen($current_log)) {
- $output.= str_pad(dechex($j), 7, '0', STR_PAD_LEFT) . '0 ';
- }
- $fragment = $this->_string_shift($current_log, $this->log_short_width);
- $hex = substr(preg_replace_callback('#.#s', array($this, '_format_log_helper'), $fragment), strlen($this->log_boundary));
- // replace non ASCII printable characters with dots
- // http://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters
- // also replace < with a . since < messes up the output on web browsers
- $raw = preg_replace('#[^\x20-\x7E]|<#', '.', $fragment);
- $output.= str_pad($hex, $this->log_long_width - $this->log_short_width, ' ') . $raw . "\r\n";
- $j++;
- } while (strlen($current_log));
- $output.= "\r\n";
- }
-
- return $output;
- }
-
- /**
- * Helper function for _format_log
- *
- * For use with preg_replace_callback()
- *
- * @param array $matches
- * @access private
- * @return string
- */
- function _format_log_helper($matches)
- {
- return $this->log_boundary . str_pad(dechex(ord($matches[0])), 2, '0', STR_PAD_LEFT);
- }
-
- /**
- * Return the server key public exponent
- *
- * Returns, by default, the base-10 representation. If $raw_output is set to true, returns, instead,
- * the raw bytes. This behavior is similar to PHP's md5() function.
- *
- * @param bool $raw_output
- * @return string
- * @access public
- */
- function getServerKeyPublicExponent($raw_output = false)
- {
- return $raw_output ? $this->server_key_public_exponent->toBytes() : $this->server_key_public_exponent->toString();
- }
-
- /**
- * Return the server key public modulus
- *
- * Returns, by default, the base-10 representation. If $raw_output is set to true, returns, instead,
- * the raw bytes. This behavior is similar to PHP's md5() function.
- *
- * @param bool $raw_output
- * @return string
- * @access public
- */
- function getServerKeyPublicModulus($raw_output = false)
- {
- return $raw_output ? $this->server_key_public_modulus->toBytes() : $this->server_key_public_modulus->toString();
- }
-
- /**
- * Return the host key public exponent
- *
- * Returns, by default, the base-10 representation. If $raw_output is set to true, returns, instead,
- * the raw bytes. This behavior is similar to PHP's md5() function.
- *
- * @param bool $raw_output
- * @return string
- * @access public
- */
- function getHostKeyPublicExponent($raw_output = false)
- {
- return $raw_output ? $this->host_key_public_exponent->toBytes() : $this->host_key_public_exponent->toString();
- }
-
- /**
- * Return the host key public modulus
- *
- * Returns, by default, the base-10 representation. If $raw_output is set to true, returns, instead,
- * the raw bytes. This behavior is similar to PHP's md5() function.
- *
- * @param bool $raw_output
- * @return string
- * @access public
- */
- function getHostKeyPublicModulus($raw_output = false)
- {
- return $raw_output ? $this->host_key_public_modulus->toBytes() : $this->host_key_public_modulus->toString();
- }
-
- /**
- * Return a list of ciphers supported by SSH1 server.
- *
- * Just because a cipher is supported by an SSH1 server doesn't mean it's supported by this library. If $raw_output
- * is set to true, returns, instead, an array of constants. ie. instead of array('Triple-DES in CBC mode'), you'll
- * get array(self::CIPHER_3DES).
- *
- * @param bool $raw_output
- * @return array
- * @access public
- */
- function getSupportedCiphers($raw_output = false)
- {
- return $raw_output ? array_keys($this->supported_ciphers) : array_values($this->supported_ciphers);
- }
-
- /**
- * Return a list of authentications supported by SSH1 server.
- *
- * Just because a cipher is supported by an SSH1 server doesn't mean it's supported by this library. If $raw_output
- * is set to true, returns, instead, an array of constants. ie. instead of array('password authentication'), you'll
- * get array(self::AUTH_PASSWORD).
- *
- * @param bool $raw_output
- * @return array
- * @access public
- */
- function getSupportedAuthentications($raw_output = false)
- {
- return $raw_output ? array_keys($this->supported_authentications) : array_values($this->supported_authentications);
- }
-
- /**
- * Return the server identification.
- *
- * @return string
- * @access public
- */
- function getServerIdentification()
- {
- return rtrim($this->server_identification);
- }
-
- /**
- * Logs data packets
- *
- * Makes sure that only the last 1MB worth of packets will be logged
- *
- * @param int $protocol_flags
- * @param string $message
- * @access private
- */
- function _append_log($protocol_flags, $message)
- {
- switch (NET_SSH1_LOGGING) {
- // useful for benchmarks
- case self::LOG_SIMPLE:
- $this->protocol_flags_log[] = $protocol_flags;
- break;
- // the most useful log for SSH1
- case self::LOG_COMPLEX:
- $this->protocol_flags_log[] = $protocol_flags;
- $this->_string_shift($message);
- $this->log_size+= strlen($message);
- $this->message_log[] = $message;
- while ($this->log_size > self::LOG_MAX_SIZE) {
- $this->log_size-= strlen(array_shift($this->message_log));
- array_shift($this->protocol_flags_log);
- }
- break;
- // dump the output out realtime; packets may be interspersed with non packets,
- // passwords won't be filtered out and select other packets may not be correctly
- // identified
- case self::LOG_REALTIME:
- echo "<pre>\r\n" . $this->_format_log(array($message), array($protocol_flags)) . "\r\n</pre>\r\n";
- @flush();
- @ob_flush();
- break;
- // basically the same thing as self::LOG_REALTIME with the caveat that self::LOG_REALTIME_FILE
- // needs to be defined and that the resultant log file will be capped out at self::LOG_MAX_SIZE.
- // the earliest part of the log file is denoted by the first <<< START >>> and is not going to necessarily
- // at the beginning of the file
- case self::LOG_REALTIME_FILE:
- if (!isset($this->realtime_log_file)) {
- // PHP doesn't seem to like using constants in fopen()
- $filename = self::LOG_REALTIME_FILE;
- $fp = fopen($filename, 'w');
- $this->realtime_log_file = $fp;
- }
- if (!is_resource($this->realtime_log_file)) {
- break;
- }
- $entry = $this->_format_log(array($message), array($protocol_flags));
- if ($this->realtime_log_wrap) {
- $temp = "<<< START >>>\r\n";
- $entry.= $temp;
- fseek($this->realtime_log_file, ftell($this->realtime_log_file) - strlen($temp));
- }
- $this->realtime_log_size+= strlen($entry);
- if ($this->realtime_log_size > self::LOG_MAX_SIZE) {
- fseek($this->realtime_log_file, 0);
- $this->realtime_log_size = strlen($entry);
- $this->realtime_log_wrap = true;
- }
- fputs($this->realtime_log_file, $entry);
- }
- }
-}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/Net/SSH2.php b/vendor/phpseclib/phpseclib/phpseclib/Net/SSH2.php
index 775d894fb..1c8a0e265 100644
--- a/vendor/phpseclib/phpseclib/phpseclib/Net/SSH2.php
+++ b/vendor/phpseclib/phpseclib/phpseclib/Net/SSH2.php
@@ -10,7 +10,7 @@
* <?php
* include 'vendor/autoload.php';
*
- * $ssh = new \phpseclib\Net\SSH2('www.domain.tld');
+ * $ssh = new \phpseclib3\Net\SSH2('www.domain.tld');
* if (!$ssh->login('username', 'password')) {
* exit('Login Failed');
* }
@@ -24,11 +24,9 @@
* <?php
* include 'vendor/autoload.php';
*
- * $key = new \phpseclib\Crypt\RSA();
- * //$key->setPassword('whatever');
- * $key->loadKey(file_get_contents('privatekey'));
+ * $key = \phpseclib3\Crypt\PublicKeyLoader::load('...', '(optional) password');
*
- * $ssh = new \phpseclib\Net\SSH2('www.domain.tld');
+ * $ssh = new \phpseclib3\Net\SSH2('www.domain.tld');
* if (!$ssh->login('username', $key)) {
* exit('Login Failed');
* }
@@ -39,41 +37,52 @@
* ?>
* </code>
*
- * @category Net
- * @package SSH2
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2007 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
*/
-namespace phpseclib\Net;
-
-use phpseclib\Crypt\Base;
-use phpseclib\Crypt\Blowfish;
-use phpseclib\Crypt\Hash;
-use phpseclib\Crypt\Random;
-use phpseclib\Crypt\RC4;
-use phpseclib\Crypt\Rijndael;
-use phpseclib\Crypt\RSA;
-use phpseclib\Crypt\TripleDES;
-use phpseclib\Crypt\Twofish;
-use phpseclib\Math\BigInteger; // Used to do Diffie-Hellman key exchange and DSA/RSA signature verification.
-use phpseclib\System\SSH\Agent;
+namespace phpseclib3\Net;
+
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Blowfish;
+use phpseclib3\Crypt\ChaCha20;
+use phpseclib3\Crypt\Common\AsymmetricKey;
+use phpseclib3\Crypt\Common\PrivateKey;
+use phpseclib3\Crypt\Common\PublicKey;
+use phpseclib3\Crypt\Common\SymmetricKey;
+use phpseclib3\Crypt\DH;
+use phpseclib3\Crypt\DSA;
+use phpseclib3\Crypt\EC;
+use phpseclib3\Crypt\Hash;
+use phpseclib3\Crypt\Random;
+use phpseclib3\Crypt\RC4;
+use phpseclib3\Crypt\Rijndael;
+use phpseclib3\Crypt\RSA;
+use phpseclib3\Crypt\TripleDES; // Used to do Diffie-Hellman key exchange and DSA/RSA signature verification.
+use phpseclib3\Crypt\Twofish;
+use phpseclib3\Exception\ConnectionClosedException;
+use phpseclib3\Exception\InsufficientSetupException;
+use phpseclib3\Exception\InvalidPacketLengthException;
+use phpseclib3\Exception\NoSupportedAlgorithmsException;
+use phpseclib3\Exception\TimeoutException;
+use phpseclib3\Exception\UnableToConnectException;
+use phpseclib3\Exception\UnsupportedAlgorithmException;
+use phpseclib3\Exception\UnsupportedCurveException;
+use phpseclib3\Math\BigInteger;
+use phpseclib3\System\SSH\Agent;
/**
* Pure-PHP implementation of SSHv2.
*
- * @package SSH2
* @author Jim Wigginton <terrafrost@php.net>
- * @access public
*/
class SSH2
{
/**#@+
* Compression Types
*
- * @access private
*/
/**
* No compression
@@ -89,53 +98,45 @@ class SSH2
const NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH = 3;
/**#@-*/
- /**#@+
- * Execution Bitmap Masks
- *
- * @see \phpseclib\Net\SSH2::bitmap
- * @access private
- */
+ // Execution Bitmap Masks
const MASK_CONSTRUCTOR = 0x00000001;
const MASK_CONNECTED = 0x00000002;
const MASK_LOGIN_REQ = 0x00000004;
const MASK_LOGIN = 0x00000008;
const MASK_SHELL = 0x00000010;
- const MASK_WINDOW_ADJUST = 0x00000020;
- /**#@-*/
+ const MASK_DISCONNECT = 0x00000020;
- /**#@+
+ /*
* Channel constants
*
* RFC4254 refers not to client and server channels but rather to sender and recipient channels. we don't refer
* to them in that way because RFC4254 toggles the meaning. the client sends a SSH_MSG_CHANNEL_OPEN message with
* a sender channel and the server sends a SSH_MSG_CHANNEL_OPEN_CONFIRMATION in response, with a sender and a
- * recepient channel. at first glance, you might conclude that SSH_MSG_CHANNEL_OPEN_CONFIRMATION's sender channel
- * would be the same thing as SSH_MSG_CHANNEL_OPEN's sender channel, but it's not, per this snipet:
+ * recipient channel. at first glance, you might conclude that SSH_MSG_CHANNEL_OPEN_CONFIRMATION's sender channel
+ * would be the same thing as SSH_MSG_CHANNEL_OPEN's sender channel, but it's not, per this snippet:
* The 'recipient channel' is the channel number given in the original
* open request, and 'sender channel' is the channel number allocated by
* the other side.
*
- * @see \phpseclib\Net\SSH2::_send_channel_packet()
- * @see \phpseclib\Net\SSH2::_get_channel_packet()
- * @access private
- */
+ * @see \phpseclib3\Net\SSH2::send_channel_packet()
+ * @see \phpseclib3\Net\SSH2::get_channel_packet()
+ */
const CHANNEL_EXEC = 1; // PuTTy uses 0x100
const CHANNEL_SHELL = 2;
const CHANNEL_SUBSYSTEM = 3;
const CHANNEL_AGENT_FORWARD = 4;
const CHANNEL_KEEP_ALIVE = 5;
- /**#@-*/
- /**#@+
- * @access public
- * @see \phpseclib\Net\SSH2::getLog()
- */
/**
* Returns the message numbers
+ *
+ * @see \phpseclib3\Net\SSH2::getLog()
*/
const LOG_SIMPLE = 1;
/**
* Returns the message content
+ *
+ * @see \phpseclib3\Net\SSH2::getLog()
*/
const LOG_COMPLEX = 2;
/**
@@ -147,25 +148,30 @@ class SSH2
*/
const LOG_REALTIME_FILE = 4;
/**
+ * Outputs the message numbers real-time
+ */
+ const LOG_SIMPLE_REALTIME = 5;
+ /*
* Dumps the message numbers real-time
*/
const LOG_REALTIME_SIMPLE = 5;
/**
* Make sure that the log never gets larger than this
+ *
+ * @see \phpseclib3\Net\SSH2::getLog()
*/
const LOG_MAX_SIZE = 1048576; // 1024 * 1024
- /**#@-*/
- /**#@+
- * @access public
- * @see \phpseclib\Net\SSH2::read()
- */
/**
* Returns when a string matching $expect exactly is found
+ *
+ * @see \phpseclib3\Net\SSH2::read()
*/
const READ_SIMPLE = 1;
/**
* Returns when a string matching the regular expression $expect is found
+ *
+ * @see \phpseclib3\Net\SSH2::read()
*/
const READ_REGEX = 2;
/**
@@ -173,25 +179,24 @@ class SSH2
*
* Some data packets may only contain a single character so it may be necessary
* to call read() multiple times when using this option
+ *
+ * @see \phpseclib3\Net\SSH2::read()
*/
const READ_NEXT = 3;
- /**#@-*/
/**
* The SSH identifier
*
* @var string
- * @access private
*/
- var $identifier;
+ private $identifier;
/**
* The Socket Object
*
- * @var object
- * @access private
+ * @var resource|closed-resource|null
*/
- var $fsock;
+ public $fsock;
/**
* Execution Bitmap
@@ -200,82 +205,73 @@ class SSH2
* if a requisite function has been successfully executed. If not, an error should be thrown.
*
* @var int
- * @access private
*/
- var $bitmap = 0;
+ protected $bitmap = 0;
/**
* Error information
*
* @see self::getErrors()
* @see self::getLastError()
- * @var string
- * @access private
+ * @var array
*/
- var $errors = array();
+ private $errors = [];
/**
* Server Identifier
*
* @see self::getServerIdentification()
- * @var array|false
- * @access private
+ * @var string|false
*/
- var $server_identifier = false;
+ protected $server_identifier = false;
/**
* Key Exchange Algorithms
*
* @see self::getKexAlgorithims()
* @var array|false
- * @access private
*/
- var $kex_algorithms = false;
+ private $kex_algorithms = false;
/**
* Key Exchange Algorithm
*
* @see self::getMethodsNegotiated()
* @var string|false
- * @access private
*/
- var $kex_algorithm = false;
+ private $kex_algorithm = false;
/**
* Minimum Diffie-Hellman Group Bit Size in RFC 4419 Key Exchange Methods
*
* @see self::_key_exchange()
* @var int
- * @access private
*/
- var $kex_dh_group_size_min = 1536;
+ private $kex_dh_group_size_min = 1536;
/**
* Preferred Diffie-Hellman Group Bit Size in RFC 4419 Key Exchange Methods
*
* @see self::_key_exchange()
* @var int
- * @access private
*/
- var $kex_dh_group_size_preferred = 2048;
+ private $kex_dh_group_size_preferred = 2048;
/**
* Maximum Diffie-Hellman Group Bit Size in RFC 4419 Key Exchange Methods
*
* @see self::_key_exchange()
* @var int
- * @access private
*/
- var $kex_dh_group_size_max = 4096;
+ private $kex_dh_group_size_max = 4096;
/**
* Server Host Key Algorithms
*
* @see self::getServerHostKeyAlgorithms()
* @var array|false
- * @access private
*/
- var $server_host_key_algorithms = false;
+ private $server_host_key_algorithms = false;
/**
* Supported Private Key Algorithms
@@ -287,88 +283,79 @@ class SSH2
* @see self::privatekey_login()
* @var array|false
*/
- var $supported_private_key_algorithms = false;
+ private $supported_private_key_algorithms = false;
/**
* Encryption Algorithms: Client to Server
*
* @see self::getEncryptionAlgorithmsClient2Server()
* @var array|false
- * @access private
*/
- var $encryption_algorithms_client_to_server = false;
+ private $encryption_algorithms_client_to_server = false;
/**
* Encryption Algorithms: Server to Client
*
* @see self::getEncryptionAlgorithmsServer2Client()
* @var array|false
- * @access private
*/
- var $encryption_algorithms_server_to_client = false;
+ private $encryption_algorithms_server_to_client = false;
/**
* MAC Algorithms: Client to Server
*
* @see self::getMACAlgorithmsClient2Server()
* @var array|false
- * @access private
*/
- var $mac_algorithms_client_to_server = false;
+ private $mac_algorithms_client_to_server = false;
/**
* MAC Algorithms: Server to Client
*
* @see self::getMACAlgorithmsServer2Client()
* @var array|false
- * @access private
*/
- var $mac_algorithms_server_to_client = false;
+ private $mac_algorithms_server_to_client = false;
/**
* Compression Algorithms: Client to Server
*
* @see self::getCompressionAlgorithmsClient2Server()
* @var array|false
- * @access private
*/
- var $compression_algorithms_client_to_server = false;
+ private $compression_algorithms_client_to_server = false;
/**
* Compression Algorithms: Server to Client
*
* @see self::getCompressionAlgorithmsServer2Client()
* @var array|false
- * @access private
*/
- var $compression_algorithms_server_to_client = false;
+ private $compression_algorithms_server_to_client = false;
/**
* Languages: Server to Client
*
* @see self::getLanguagesServer2Client()
* @var array|false
- * @access private
*/
- var $languages_server_to_client = false;
+ private $languages_server_to_client = false;
/**
* Languages: Client to Server
*
* @see self::getLanguagesClient2Server()
* @var array|false
- * @access private
*/
- var $languages_client_to_server = false;
+ private $languages_client_to_server = false;
/**
* Preferred Algorithms
*
* @see self::setPreferredAlgorithms()
* @var array
- * @access private
*/
- var $preferred = array();
+ private $preferred = [];
/**
* Block Size for Server to Client Encryption
@@ -383,9 +370,8 @@ class SSH2
* @see self::__construct()
* @see self::_send_binary_packet()
* @var int
- * @access private
*/
- var $encrypt_block_size = 8;
+ private $encrypt_block_size = 8;
/**
* Block Size for Client to Server Encryption
@@ -393,52 +379,98 @@ class SSH2
* @see self::__construct()
* @see self::_get_binary_packet()
* @var int
- * @access private
*/
- var $decrypt_block_size = 8;
+ private $decrypt_block_size = 8;
/**
* Server to Client Encryption Object
*
* @see self::_get_binary_packet()
- * @var object
- * @access private
+ * @var SymmetricKey|false
*/
- var $decrypt = false;
+ private $decrypt = false;
/**
* Decryption Algorithm Name
*
* @var string|null
- * @access private
*/
- var $decryptName;
+ private $decryptName;
+
+ /**
+ * Decryption Invocation Counter
+ *
+ * Used by GCM
+ *
+ * @var string|null
+ */
+ private $decryptInvocationCounter;
+
+ /**
+ * Fixed Part of Nonce
+ *
+ * Used by GCM
+ *
+ * @var string|null
+ */
+ private $decryptFixedPart;
+
+ /**
+ * Server to Client Length Encryption Object
+ *
+ * @see self::_get_binary_packet()
+ * @var object
+ */
+ private $lengthDecrypt = false;
/**
* Client to Server Encryption Object
*
* @see self::_send_binary_packet()
- * @var object
- * @access private
+ * @var SymmetricKey|false
*/
- var $encrypt = false;
+ private $encrypt = false;
/**
* Encryption Algorithm Name
*
* @var string|null
- * @access private
*/
- var $encryptName;
+ private $encryptName;
+
+ /**
+ * Encryption Invocation Counter
+ *
+ * Used by GCM
+ *
+ * @var string|null
+ */
+ private $encryptInvocationCounter;
+
+ /**
+ * Fixed Part of Nonce
+ *
+ * Used by GCM
+ *
+ * @var string|null
+ */
+ private $encryptFixedPart;
+
+ /**
+ * Client to Server Length Encryption Object
+ *
+ * @see self::_send_binary_packet()
+ * @var object
+ */
+ private $lengthEncrypt = false;
/**
* Client to Server HMAC Object
*
* @see self::_send_binary_packet()
* @var object
- * @access private
*/
- var $hmac_create = false;
+ private $hmac_create = false;
/**
* Client to Server HMAC Name
@@ -448,20 +480,33 @@ class SSH2
private $hmac_create_name;
/**
+ * Client to Server ETM
+ *
+ * @var int|false
+ */
+ private $hmac_create_etm;
+
+ /**
* Server to Client HMAC Object
*
* @see self::_get_binary_packet()
* @var object
- * @access private
*/
- var $hmac_check = false;
+ private $hmac_check = false;
/**
* Server to Client HMAC Name
*
* @var string|false
*/
- var $hmac_check_name;
+ private $hmac_check_name;
+
+ /**
+ * Server to Client ETM
+ *
+ * @var int|false
+ */
+ private $hmac_check_etm;
/**
* Size of server to client HMAC
@@ -472,18 +517,16 @@ class SSH2
*
* @see self::_get_binary_packet()
* @var int
- * @access private
*/
- var $hmac_size = false;
+ private $hmac_size = false;
/**
* Server Public Host Key
*
* @see self::getServerPublicHostKey()
* @var string
- * @access private
*/
- var $server_public_host_key;
+ private $server_public_host_key;
/**
* Session identifier
@@ -496,9 +539,8 @@ class SSH2
*
* @see self::_key_exchange()
* @var string
- * @access private
*/
- var $session_id = false;
+ private $session_id = false;
/**
* Exchange hash
@@ -507,9 +549,8 @@ class SSH2
*
* @see self::_key_exchange()
* @var string
- * @access private
*/
- var $exchange_hash = false;
+ private $exchange_hash = false;
/**
* Message Numbers
@@ -518,7 +559,7 @@ class SSH2
* @var array
* @access private
*/
- var $message_numbers = array();
+ private static $message_numbers = [];
/**
* Disconnection Message 'reason codes' defined in RFC4253
@@ -527,7 +568,7 @@ class SSH2
* @var array
* @access private
*/
- var $disconnect_reasons = array();
+ private static $disconnect_reasons = [];
/**
* SSH_MSG_CHANNEL_OPEN_FAILURE 'reason codes', defined in RFC4254
@@ -536,7 +577,7 @@ class SSH2
* @var array
* @access private
*/
- var $channel_open_failure_reasons = array();
+ private static $channel_open_failure_reasons = [];
/**
* Terminal Modes
@@ -546,7 +587,7 @@ class SSH2
* @var array
* @access private
*/
- var $terminal_modes = array();
+ private static $terminal_modes = [];
/**
* SSH_MSG_CHANNEL_EXTENDED_DATA's data_type_codes
@@ -556,7 +597,7 @@ class SSH2
* @var array
* @access private
*/
- var $channel_extended_data_type_codes = array();
+ private static $channel_extended_data_type_codes = [];
/**
* Send Sequence Number
@@ -565,9 +606,8 @@ class SSH2
*
* @see self::_send_binary_packet()
* @var int
- * @access private
*/
- var $send_seq_no = 0;
+ private $send_seq_no = 0;
/**
* Get Sequence Number
@@ -576,74 +616,86 @@ class SSH2
*
* @see self::_get_binary_packet()
* @var int
- * @access private
*/
- var $get_seq_no = 0;
+ private $get_seq_no = 0;
/**
* Server Channels
*
* Maps client channels to server channels
*
- * @see self::_get_channel_packet()
+ * @see self::get_channel_packet()
* @see self::exec()
* @var array
- * @access private
*/
- var $server_channels = array();
+ protected $server_channels = [];
/**
- * Channel Buffers
+ * Channel Read Buffers
*
* If a client requests a packet from one channel but receives two packets from another those packets should
* be placed in a buffer
*
- * @see self::_get_channel_packet()
+ * @see self::get_channel_packet()
* @see self::exec()
* @var array
- * @access private
*/
- var $channel_buffers = array();
+ private $channel_buffers = [];
+
+ /**
+ * Channel Write Buffers
+ *
+ * If a client sends a packet and receives a timeout error mid-transmission, buffer the data written so it
+ * can be de-duplicated upon resuming write
+ *
+ * @see self::send_channel_packet()
+ * @var array
+ */
+ private $channel_buffers_write = [];
/**
* Channel Status
*
* Contains the type of the last sent message
*
- * @see self::_get_channel_packet()
+ * @see self::get_channel_packet()
* @var array
- * @access private
*/
- var $channel_status = array();
+ protected $channel_status = [];
+
+ /**
+ * The identifier of the interactive channel which was opened most recently
+ *
+ * @see self::getInteractiveChannelId()
+ * @var int
+ */
+ private $channel_id_last_interactive = 0;
/**
* Packet Size
*
* Maximum packet size indexed by channel
*
- * @see self::_send_channel_packet()
+ * @see self::send_channel_packet()
* @var array
- * @access private
*/
- var $packet_size_client_to_server = array();
+ private $packet_size_client_to_server = [];
/**
* Message Number Log
*
* @see self::getLog()
* @var array
- * @access private
*/
- var $message_number_log = array();
+ private $message_number_log = [];
/**
* Message Log
*
* @see self::getLog()
* @var array
- * @access private
*/
- var $message_log = array();
+ private $message_log = [];
/**
* The Window Size
@@ -651,11 +703,10 @@ class SSH2
* Bytes the other party can send before it must wait for the window to be adjusted (0x7FFFFFFF = 2GB)
*
* @var int
- * @see self::_send_channel_packet()
+ * @see self::send_channel_packet()
* @see self::exec()
- * @access private
*/
- var $window_size = 0x7FFFFFFF;
+ protected $window_size = 0x7FFFFFFF;
/**
* What we resize the window to
@@ -667,31 +718,28 @@ class SSH2
* @var int
* @see self::_send_channel_packet()
* @see self::exec()
- * @access private
*/
- var $window_resize = 0x40000000;
+ private $window_resize = 0x40000000;
/**
* Window size, server to client
*
* Window size indexed by channel
*
- * @see self::_send_channel_packet()
+ * @see self::send_channel_packet()
* @var array
- * @access private
*/
- var $window_size_server_to_client = array();
+ protected $window_size_server_to_client = [];
/**
* Window size, client to server
*
* Window size indexed by channel
*
- * @see self::_get_channel_packet()
+ * @see self::get_channel_packet()
* @var array
- * @access private
*/
- var $window_size_client_to_server = array();
+ private $window_size_client_to_server = [];
/**
* Server signature
@@ -700,9 +748,8 @@ class SSH2
*
* @see self::getServerPublicHostKey()
* @var string
- * @access private
*/
- var $signature = '';
+ private $signature = '';
/**
* Server signature format
@@ -711,18 +758,16 @@ class SSH2
*
* @see self::getServerPublicHostKey()
* @var string
- * @access private
*/
- var $signature_format = '';
+ private $signature_format = '';
/**
* Interactive Buffer
*
* @see self::read()
- * @var array
- * @access private
+ * @var string
*/
- var $interactiveBuffer = '';
+ private $interactiveBuffer = '';
/**
* Current log size
@@ -732,143 +777,113 @@ class SSH2
* @see self::_send_binary_packet()
* @see self::_get_binary_packet()
* @var int
- * @access private
*/
- var $log_size;
+ private $log_size;
/**
* Timeout
*
* @see self::setTimeout()
- * @access private
*/
- var $timeout;
+ protected $timeout;
/**
* Current Timeout
*
- * @see self::_get_channel_packet()
- * @access private
+ * @see self::get_channel_packet()
*/
- var $curTimeout;
+ protected $curTimeout;
/**
* Keep Alive Interval
*
* @see self::setKeepAlive()
- * @access private
*/
- var $keepAlive;
+ private $keepAlive;
/**
* Real-time log file pointer
*
* @see self::_append_log()
- * @var resource
- * @access private
+ * @var resource|closed-resource
*/
- var $realtime_log_file;
+ private $realtime_log_file;
/**
* Real-time log file size
*
* @see self::_append_log()
* @var int
- * @access private
*/
- var $realtime_log_size;
+ private $realtime_log_size;
/**
* Has the signature been validated?
*
* @see self::getServerPublicHostKey()
* @var bool
- * @access private
*/
- var $signature_validated = false;
+ private $signature_validated = false;
/**
* Real-time log file wrap boolean
*
* @see self::_append_log()
- * @access private
+ * @var bool
*/
- var $realtime_log_wrap;
+ private $realtime_log_wrap;
/**
* Flag to suppress stderr from output
*
* @see self::enableQuietMode()
- * @access private
*/
- var $quiet_mode = false;
+ private $quiet_mode = false;
/**
- * Time of first network activity
+ * Time of last read/write network activity
*
- * @var int
- * @access private
+ * @var float
*/
- var $last_packet;
+ private $last_packet = null;
/**
* Exit status returned from ssh if any
*
* @var int
- * @access private
*/
- var $exit_status;
+ private $exit_status;
/**
* Flag to request a PTY when using exec()
*
* @var bool
* @see self::enablePTY()
- * @access private
*/
- var $request_pty = false;
-
- /**
- * Flag set while exec() is running when using enablePTY()
- *
- * @var bool
- * @access private
- */
- var $in_request_pty_exec = false;
-
- /**
- * Flag set after startSubsystem() is called
- *
- * @var bool
- * @access private
- */
- var $in_subsystem;
+ private $request_pty = false;
/**
* Contents of stdError
*
* @var string
- * @access private
*/
- var $stdErrorLog;
+ private $stdErrorLog;
/**
* The Last Interactive Response
*
* @see self::_keyboard_interactive_process()
* @var string
- * @access private
*/
- var $last_interactive_response = '';
+ private $last_interactive_response = '';
/**
* Keyboard Interactive Request / Responses
*
* @see self::_keyboard_interactive_process()
* @var array
- * @access private
*/
- var $keyboard_requests_responses = array();
+ private $keyboard_requests_responses = [];
/**
* Banner Message
@@ -879,45 +894,40 @@ class SSH2
* @see self::_filter()
* @see self::getBannerMessage()
* @var string
- * @access private
*/
- var $banner_message = '';
+ private $banner_message = '';
/**
* Did read() timeout or return normally?
*
* @see self::isTimeout()
* @var bool
- * @access private
*/
- var $is_timeout = false;
+ protected $is_timeout = false;
/**
* Log Boundary
*
* @see self::_format_log()
* @var string
- * @access private
*/
- var $log_boundary = ':';
+ private $log_boundary = ':';
/**
* Log Long Width
*
* @see self::_format_log()
* @var int
- * @access private
*/
- var $log_long_width = 65;
+ private $log_long_width = 65;
/**
* Log Short Width
*
* @see self::_format_log()
* @var int
- * @access private
*/
- var $log_short_width = 16;
+ private $log_short_width = 16;
/**
* Hostname
@@ -925,9 +935,8 @@ class SSH2
* @see self::__construct()
* @see self::_connect()
* @var string
- * @access private
*/
- var $host;
+ private $host;
/**
* Port Number
@@ -935,9 +944,8 @@ class SSH2
* @see self::__construct()
* @see self::_connect()
* @var int
- * @access private
*/
- var $port;
+ private $port;
/**
* Number of columns for terminal window size
@@ -946,9 +954,8 @@ class SSH2
* @see self::setWindowColumns()
* @see self::setWindowSize()
* @var int
- * @access private
*/
- var $windowColumns = 80;
+ private $windowColumns = 80;
/**
* Number of columns for terminal window size
@@ -957,9 +964,8 @@ class SSH2
* @see self::setWindowRows()
* @see self::setWindowSize()
* @var int
- * @access private
*/
- var $windowRows = 24;
+ private $windowRows = 24;
/**
* Crypto Engine
@@ -967,164 +973,175 @@ class SSH2
* @see self::setCryptoEngine()
* @see self::_key_exchange()
* @var int
- * @access private
*/
- var $crypto_engine = false;
+ private static $crypto_engine = false;
/**
* A System_SSH_Agent for use in the SSH2 Agent Forwarding scenario
*
- * @var System_SSH_Agent
- * @access private
+ * @var Agent
*/
- var $agent;
+ private $agent;
+
+ /**
+ * Connection storage to replicates ssh2 extension functionality:
+ * {@link http://php.net/manual/en/wrappers.ssh2.php#refsect1-wrappers.ssh2-examples}
+ *
+ * @var array<string, SSH2|\WeakReference<SSH2>>
+ */
+ private static $connections;
/**
* Send the identification string first?
*
* @var bool
- * @access private
*/
- var $send_id_string_first = true;
+ private $send_id_string_first = true;
/**
* Send the key exchange initiation packet first?
*
* @var bool
- * @access private
*/
- var $send_kex_first = true;
+ private $send_kex_first = true;
/**
* Some versions of OpenSSH incorrectly calculate the key size
*
* @var bool
- * @access private
*/
- var $bad_key_size_fix = false;
+ private $bad_key_size_fix = false;
/**
* Should we try to re-connect to re-establish keys?
*
* @var bool
- * @access private
*/
- var $login_credentials_finalized = false;
+ private $login_credentials_finalized = false;
/**
* Binary Packet Buffer
*
- * @var string|false
- * @access private
+ * @var object|null
*/
- var $binary_packet_buffer = false;
+ private $binary_packet_buffer = null;
/**
* Preferred Signature Format
*
* @var string|false
- * @access private
*/
- var $preferred_signature_format = false;
+ protected $preferred_signature_format = false;
/**
* Authentication Credentials
*
* @var array
- * @access private
*/
- var $auth = array();
+ protected $auth = [];
+
+ /**
+ * Terminal
+ *
+ * @var string
+ */
+ private $term = 'vt100';
/**
* The authentication methods that may productively continue authentication.
*
* @see https://tools.ietf.org/html/rfc4252#section-5.1
* @var array|null
- * @access private
*/
- var $auth_methods_to_continue = null;
+ private $auth_methods_to_continue = null;
/**
* Compression method
*
* @var int
- * @access private
*/
- var $compress = self::NET_SSH2_COMPRESSION_NONE;
+ private $compress = self::NET_SSH2_COMPRESSION_NONE;
/**
* Decompression method
*
- * @var resource|object
- * @access private
+ * @var int
*/
- var $decompress = self::NET_SSH2_COMPRESSION_NONE;
+ private $decompress = self::NET_SSH2_COMPRESSION_NONE;
/**
* Compression context
*
- * @var int
- * @access private
+ * @var resource|false|null
*/
- var $compress_context;
+ private $compress_context;
/**
* Decompression context
*
* @var resource|object
- * @access private
*/
- var $decompress_context;
+ private $decompress_context;
/**
* Regenerate Compression Context
*
* @var bool
- * @access private
*/
- var $regenerate_compression_context = false;
+ private $regenerate_compression_context = false;
/**
* Regenerate Decompression Context
*
* @var bool
- * @access private
*/
- var $regenerate_decompression_context = false;
+ private $regenerate_decompression_context = false;
/**
* Smart multi-factor authentication flag
*
* @var bool
- * @access private
*/
- var $smartMFA = true;
+ private $smartMFA = true;
+
+ /**
+ * How many channels are currently opened
+ *
+ * @var int
+ */
+ private $channelCount = 0;
+
+ /**
+ * Does the server support multiple channels? If not then error out
+ * when multiple channels are attempted to be opened
+ *
+ * @var bool
+ */
+ private $errorOnMultipleChannels;
/**
* Bytes Transferred Since Last Key Exchange
- *
+ *
* Includes outbound and inbound totals
*
* @var int
- * @access private
*/
- var $bytesTransferredSinceLastKEX = 0;
+ private $bytesTransferredSinceLastKEX = 0;
/**
* After how many transferred byte should phpseclib initiate a key re-exchange?
- *
+ *
* @var int
- * @access private
*/
- var $doKeyReexchangeAfterXBytes = 1073741824;
+ private $doKeyReexchangeAfterXBytes = 1024 * 1024 * 1024;
/**
* Has a key re-exchange been initialized?
- *
+ *
* @var bool
* @access private
*/
- var $keyExchangeInProgress = false;
+ private $keyExchangeInProgress = false;
/**
* KEX Buffer
@@ -1138,7 +1155,7 @@ class SSH2
* @var array
* @access private
*/
- var $kex_buffer = array();
+ private $kex_buffer = [];
/**
* Strict KEX Flag
@@ -1151,7 +1168,7 @@ class SSH2
* @var array
* @access private
*/
- var $strict_kex_flag = false;
+ private $strict_kex_flag = false;
/**
* Default Constructor.
@@ -1164,90 +1181,98 @@ class SSH2
* @param int $port
* @param int $timeout
* @see self::login()
- * @return \phpseclib\Net\SSH2
- * @access public
*/
- function __construct($host, $port = 22, $timeout = 10)
+ public function __construct($host, $port = 22, $timeout = 10)
{
- $this->message_numbers = array(
- 1 => 'NET_SSH2_MSG_DISCONNECT',
- 2 => 'NET_SSH2_MSG_IGNORE',
- 3 => 'NET_SSH2_MSG_UNIMPLEMENTED',
- 4 => 'NET_SSH2_MSG_DEBUG',
- 5 => 'NET_SSH2_MSG_SERVICE_REQUEST',
- 6 => 'NET_SSH2_MSG_SERVICE_ACCEPT',
- 7 => 'NET_SSH2_MSG_EXT_INFO', // RFC 8308
- 20 => 'NET_SSH2_MSG_KEXINIT',
- 21 => 'NET_SSH2_MSG_NEWKEYS',
- 30 => 'NET_SSH2_MSG_KEXDH_INIT',
- 31 => 'NET_SSH2_MSG_KEXDH_REPLY',
- 50 => 'NET_SSH2_MSG_USERAUTH_REQUEST',
- 51 => 'NET_SSH2_MSG_USERAUTH_FAILURE',
- 52 => 'NET_SSH2_MSG_USERAUTH_SUCCESS',
- 53 => 'NET_SSH2_MSG_USERAUTH_BANNER',
-
- 80 => 'NET_SSH2_MSG_GLOBAL_REQUEST',
- 81 => 'NET_SSH2_MSG_REQUEST_SUCCESS',
- 82 => 'NET_SSH2_MSG_REQUEST_FAILURE',
- 90 => 'NET_SSH2_MSG_CHANNEL_OPEN',
- 91 => 'NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION',
- 92 => 'NET_SSH2_MSG_CHANNEL_OPEN_FAILURE',
- 93 => 'NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST',
- 94 => 'NET_SSH2_MSG_CHANNEL_DATA',
- 95 => 'NET_SSH2_MSG_CHANNEL_EXTENDED_DATA',
- 96 => 'NET_SSH2_MSG_CHANNEL_EOF',
- 97 => 'NET_SSH2_MSG_CHANNEL_CLOSE',
- 98 => 'NET_SSH2_MSG_CHANNEL_REQUEST',
- 99 => 'NET_SSH2_MSG_CHANNEL_SUCCESS',
- 100 => 'NET_SSH2_MSG_CHANNEL_FAILURE'
- );
- $this->disconnect_reasons = array(
- 1 => 'NET_SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT',
- 2 => 'NET_SSH2_DISCONNECT_PROTOCOL_ERROR',
- 3 => 'NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED',
- 4 => 'NET_SSH2_DISCONNECT_RESERVED',
- 5 => 'NET_SSH2_DISCONNECT_MAC_ERROR',
- 6 => 'NET_SSH2_DISCONNECT_COMPRESSION_ERROR',
- 7 => 'NET_SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE',
- 8 => 'NET_SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED',
- 9 => 'NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE',
- 10 => 'NET_SSH2_DISCONNECT_CONNECTION_LOST',
- 11 => 'NET_SSH2_DISCONNECT_BY_APPLICATION',
- 12 => 'NET_SSH2_DISCONNECT_TOO_MANY_CONNECTIONS',
- 13 => 'NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER',
- 14 => 'NET_SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE',
- 15 => 'NET_SSH2_DISCONNECT_ILLEGAL_USER_NAME'
- );
- $this->channel_open_failure_reasons = array(
- 1 => 'NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED'
- );
- $this->terminal_modes = array(
- 0 => 'NET_SSH2_TTY_OP_END'
- );
- $this->channel_extended_data_type_codes = array(
- 1 => 'NET_SSH2_EXTENDED_DATA_STDERR'
- );
+ if (empty(self::$message_numbers)) {
+ self::$message_numbers = [
+ 1 => 'NET_SSH2_MSG_DISCONNECT',
+ 2 => 'NET_SSH2_MSG_IGNORE',
+ 3 => 'NET_SSH2_MSG_UNIMPLEMENTED',
+ 4 => 'NET_SSH2_MSG_DEBUG',
+ 5 => 'NET_SSH2_MSG_SERVICE_REQUEST',
+ 6 => 'NET_SSH2_MSG_SERVICE_ACCEPT',
+ 7 => 'NET_SSH2_MSG_EXT_INFO', // RFC 8308
+ 20 => 'NET_SSH2_MSG_KEXINIT',
+ 21 => 'NET_SSH2_MSG_NEWKEYS',
+ 30 => 'NET_SSH2_MSG_KEXDH_INIT',
+ 31 => 'NET_SSH2_MSG_KEXDH_REPLY',
+ 50 => 'NET_SSH2_MSG_USERAUTH_REQUEST',
+ 51 => 'NET_SSH2_MSG_USERAUTH_FAILURE',
+ 52 => 'NET_SSH2_MSG_USERAUTH_SUCCESS',
+ 53 => 'NET_SSH2_MSG_USERAUTH_BANNER',
+
+ 80 => 'NET_SSH2_MSG_GLOBAL_REQUEST',
+ 81 => 'NET_SSH2_MSG_REQUEST_SUCCESS',
+ 82 => 'NET_SSH2_MSG_REQUEST_FAILURE',
+ 90 => 'NET_SSH2_MSG_CHANNEL_OPEN',
+ 91 => 'NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION',
+ 92 => 'NET_SSH2_MSG_CHANNEL_OPEN_FAILURE',
+ 93 => 'NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST',
+ 94 => 'NET_SSH2_MSG_CHANNEL_DATA',
+ 95 => 'NET_SSH2_MSG_CHANNEL_EXTENDED_DATA',
+ 96 => 'NET_SSH2_MSG_CHANNEL_EOF',
+ 97 => 'NET_SSH2_MSG_CHANNEL_CLOSE',
+ 98 => 'NET_SSH2_MSG_CHANNEL_REQUEST',
+ 99 => 'NET_SSH2_MSG_CHANNEL_SUCCESS',
+ 100 => 'NET_SSH2_MSG_CHANNEL_FAILURE'
+ ];
+ self::$disconnect_reasons = [
+ 1 => 'NET_SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT',
+ 2 => 'NET_SSH2_DISCONNECT_PROTOCOL_ERROR',
+ 3 => 'NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED',
+ 4 => 'NET_SSH2_DISCONNECT_RESERVED',
+ 5 => 'NET_SSH2_DISCONNECT_MAC_ERROR',
+ 6 => 'NET_SSH2_DISCONNECT_COMPRESSION_ERROR',
+ 7 => 'NET_SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE',
+ 8 => 'NET_SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED',
+ 9 => 'NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE',
+ 10 => 'NET_SSH2_DISCONNECT_CONNECTION_LOST',
+ 11 => 'NET_SSH2_DISCONNECT_BY_APPLICATION',
+ 12 => 'NET_SSH2_DISCONNECT_TOO_MANY_CONNECTIONS',
+ 13 => 'NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER',
+ 14 => 'NET_SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE',
+ 15 => 'NET_SSH2_DISCONNECT_ILLEGAL_USER_NAME'
+ ];
+ self::$channel_open_failure_reasons = [
+ 1 => 'NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED'
+ ];
+ self::$terminal_modes = [
+ 0 => 'NET_SSH2_TTY_OP_END'
+ ];
+ self::$channel_extended_data_type_codes = [
+ 1 => 'NET_SSH2_EXTENDED_DATA_STDERR'
+ ];
+
+ self::define_array(
+ self::$message_numbers,
+ self::$disconnect_reasons,
+ self::$channel_open_failure_reasons,
+ self::$terminal_modes,
+ self::$channel_extended_data_type_codes,
+ [60 => 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'],
+ [60 => 'NET_SSH2_MSG_USERAUTH_PK_OK'],
+ [60 => 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST',
+ 61 => 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE'],
+ // RFC 4419 - diffie-hellman-group-exchange-sha{1,256}
+ [30 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST_OLD',
+ 31 => 'NET_SSH2_MSG_KEXDH_GEX_GROUP',
+ 32 => 'NET_SSH2_MSG_KEXDH_GEX_INIT',
+ 33 => 'NET_SSH2_MSG_KEXDH_GEX_REPLY',
+ 34 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST'],
+ // RFC 5656 - Elliptic Curves (for curve25519-sha256@libssh.org)
+ [30 => 'NET_SSH2_MSG_KEX_ECDH_INIT',
+ 31 => 'NET_SSH2_MSG_KEX_ECDH_REPLY']
+ );
+ }
- $this->_define_array(
- $this->message_numbers,
- $this->disconnect_reasons,
- $this->channel_open_failure_reasons,
- $this->terminal_modes,
- $this->channel_extended_data_type_codes,
- array(60 => 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ'),
- array(60 => 'NET_SSH2_MSG_USERAUTH_PK_OK'),
- array(60 => 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST',
- 61 => 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE'),
- // RFC 4419 - diffie-hellman-group-exchange-sha{1,256}
- array(30 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST_OLD',
- 31 => 'NET_SSH2_MSG_KEXDH_GEX_GROUP',
- 32 => 'NET_SSH2_MSG_KEXDH_GEX_INIT',
- 33 => 'NET_SSH2_MSG_KEXDH_GEX_REPLY',
- 34 => 'NET_SSH2_MSG_KEXDH_GEX_REQUEST'),
- // RFC 5656 - Elliptic Curves (for curve25519-sha256@libssh.org)
- array(30 => 'NET_SSH2_MSG_KEX_ECDH_INIT',
- 31 => 'NET_SSH2_MSG_KEX_ECDH_REPLY')
- );
+ /**
+ * Typehint is required due to a bug in Psalm: https://github.com/vimeo/psalm/issues/7508
+ * @var \WeakReference<SSH2>|SSH2
+ */
+ self::$connections[$this->getResourceId()] = class_exists('WeakReference')
+ ? \WeakReference::create($this)
+ : $this;
$this->timeout = $timeout;
@@ -1256,7 +1281,7 @@ class SSH2
return;
}
- if (is_string($host)) {
+ if (Strings::is_stringable($host)) {
$this->host = $host;
$this->port = $port;
}
@@ -1266,14 +1291,13 @@ class SSH2
* Set Crypto Engine Mode
*
* Possible $engine values:
- * CRYPT_MODE_INTERNAL, CRYPT_MODE_MCRYPT
+ * OpenSSL, mcrypt, Eval, PHP
*
* @param int $engine
- * @access public
*/
- function setCryptoEngine($engine)
+ public static function setCryptoEngine($engine)
{
- $this->crypto_engine = $engine;
+ self::$crypto_engine = $engine;
}
/**
@@ -1283,9 +1307,8 @@ class SSH2
* both sides MUST send an identification string". It does not say which side sends it first. In
* theory it shouldn't matter but it is a fact of life that some SSH servers are simply buggy
*
- * @access public
*/
- function sendIdentificationStringFirst()
+ public function sendIdentificationStringFirst()
{
$this->send_id_string_first = true;
}
@@ -1297,9 +1320,8 @@ class SSH2
* both sides MUST send an identification string". It does not say which side sends it first. In
* theory it shouldn't matter but it is a fact of life that some SSH servers are simply buggy
*
- * @access public
*/
- function sendIdentificationStringLast()
+ public function sendIdentificationStringLast()
{
$this->send_id_string_first = false;
}
@@ -1311,9 +1333,8 @@ class SSH2
* sending the [SSH_MSG_KEXINIT] packet". It does not say which side sends it first. In theory
* it shouldn't matter but it is a fact of life that some SSH servers are simply buggy
*
- * @access public
*/
- function sendKEXINITFirst()
+ public function sendKEXINITFirst()
{
$this->send_kex_first = true;
}
@@ -1325,31 +1346,54 @@ class SSH2
* sending the [SSH_MSG_KEXINIT] packet". It does not say which side sends it first. In theory
* it shouldn't matter but it is a fact of life that some SSH servers are simply buggy
*
- * @access public
*/
- function sendKEXINITLast()
+ public function sendKEXINITLast()
{
$this->send_kex_first = false;
}
/**
+ * stream_select wrapper
+ *
+ * Quoting https://stackoverflow.com/a/14262151/569976,
+ * "The general approach to `EINTR` is to simply handle the error and retry the operation again"
+ *
+ * This wrapper does that loop
+ */
+ private static function stream_select(&$read, &$write, &$except, $seconds, $microseconds = null)
+ {
+ $remaining = $seconds + $microseconds / 1000000;
+ $start = microtime(true);
+ while (true) {
+ $result = @stream_select($read, $write, $except, $seconds, $microseconds);
+ if ($result !== false) {
+ return $result;
+ }
+ $elapsed = microtime(true) - $start;
+ $seconds = (int) ($remaining - floor($elapsed));
+ $microseconds = (int) (1000000 * ($remaining - $seconds));
+ if ($elapsed >= $remaining) {
+ return false;
+ }
+ }
+ }
+
+ /**
* Connect to an SSHv2 server
*
- * @return bool
- * @access private
+ * @throws \UnexpectedValueException on receipt of unexpected packets
+ * @throws \RuntimeException on other errors
*/
- function _connect()
+ private function connect()
{
if ($this->bitmap & self::MASK_CONSTRUCTOR) {
- return false;
+ return;
}
$this->bitmap |= self::MASK_CONSTRUCTOR;
$this->curTimeout = $this->timeout;
- $this->last_packet = microtime(true);
-
if (!is_resource($this->fsock)) {
$start = microtime(true);
// with stream_select a timeout of 0 means that no timeout takes place;
@@ -1358,24 +1402,31 @@ class SSH2
$this->fsock = @fsockopen($this->host, $this->port, $errno, $errstr, $this->curTimeout == 0 ? 100000 : $this->curTimeout);
if (!$this->fsock) {
$host = $this->host . ':' . $this->port;
- user_error(rtrim("Cannot connect to $host. Error $errno. $errstr"));
- return false;
+ throw new UnableToConnectException(rtrim("Cannot connect to $host. Error $errno. $errstr"));
}
$elapsed = microtime(true) - $start;
if ($this->curTimeout) {
- $this->curTimeout-= $elapsed;
+ $this->curTimeout -= $elapsed;
if ($this->curTimeout < 0) {
- $this->is_timeout = true;
- return false;
+ throw new \RuntimeException('Connection timed out whilst attempting to open socket connection');
}
}
+
+ if (defined('NET_SSH2_LOGGING')) {
+ $this->append_log('(fsockopen took ' . round($elapsed, 4) . 's)', '');
+ }
}
- $this->identifier = $this->_generate_identifier();
+ $this->identifier = $this->generate_identifier();
if ($this->send_id_string_first) {
+ $start = microtime(true);
fputs($this->fsock, $this->identifier . "\r\n");
+ $elapsed = round(microtime(true) - $start, 4);
+ if (defined('NET_SSH2_LOGGING')) {
+ $this->append_log("-> (network: $elapsed)", $this->identifier . "\r\n");
+ }
}
/* According to the SSH2 specs,
@@ -1386,33 +1437,30 @@ class SSH2
in ISO-10646 UTF-8 [RFC3629] (language is not specified). Clients
MUST be able to process such lines." */
$data = '';
+ $totalElapsed = 0;
while (!feof($this->fsock) && !preg_match('#(.*)^(SSH-(\d\.\d+).*)#ms', $data, $matches)) {
$line = '';
while (true) {
if ($this->curTimeout) {
if ($this->curTimeout < 0) {
- $this->is_timeout = true;
- return false;
+ throw new \RuntimeException('Connection timed out whilst receiving server identification string');
}
- $read = array($this->fsock);
+ $read = [$this->fsock];
$write = $except = null;
$start = microtime(true);
$sec = (int) floor($this->curTimeout);
$usec = (int) (1000000 * ($this->curTimeout - $sec));
- // on windows this returns a "Warning: Invalid CRT parameters detected" error
- // the !count() is done as a workaround for <https://bugs.php.net/42682>
- if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) {
- $this->is_timeout = true;
- return false;
+ if (static::stream_select($read, $write, $except, $sec, $usec) === false) {
+ throw new \RuntimeException('Connection timed out whilst receiving server identification string');
}
$elapsed = microtime(true) - $start;
- $this->curTimeout-= $elapsed;
+ $totalElapsed += $elapsed;
+ $this->curTimeout -= $elapsed;
}
$temp = stream_get_line($this->fsock, 255, "\n");
-
if ($temp === false) {
- return false;
+ throw new \RuntimeException('Error reading SSH identification string; are you sure you\'re connecting to an SSH server?');
}
$line .= $temp;
@@ -1425,60 +1473,69 @@ class SSH2
break;
}
- $data.= $line;
+ $data .= $line;
+ }
+
+ if (defined('NET_SSH2_LOGGING')) {
+ $this->append_log('<- (network: ' . round($totalElapsed, 4) . ')', $data);
}
if (feof($this->fsock)) {
$this->bitmap = 0;
- user_error('Connection closed by server');
- return false;
+ throw new ConnectionClosedException('Connection closed by server; are you sure you\'re connected to an SSH server?');
}
$extra = $matches[1];
- if (defined('NET_SSH2_LOGGING')) {
- $this->_append_log('<-', $matches[0]);
- $this->_append_log('->', $this->identifier . "\r\n");
- }
-
- $this->server_identifier = trim($data, "\r\n");
-
+ // earlier the SSH specs were quoted.
+ // "The server MAY send other lines of data before sending the version string." they said.
+ // the implication of this is that the lines of data before the server string are *not* a part of it
+ // getting this right is important because the correct server identifier needs to be fed into the
+ // exchange hash for the shared keys to be calculated correctly
+ $data = explode("\r\n", trim($data, "\r\n"));
+ $this->server_identifier = $data[count($data) - 1];
if (strlen($extra)) {
$this->errors[] = $data;
}
if (version_compare($matches[3], '1.99', '<')) {
- user_error("Cannot connect to SSH $matches[3] servers");
- return false;
+ $this->bitmap = 0;
+ throw new UnableToConnectException("Cannot connect to SSH $matches[3] servers");
}
+ // Ubuntu's OpenSSH from 5.8 to 6.9 didn't work with multiple channels. see
+ // https://bugs.launchpad.net/ubuntu/+source/openssh/+bug/1334916 for more info.
+ // https://lists.ubuntu.com/archives/oneiric-changes/2011-July/005772.html discusses
+ // when consolekit was incorporated.
+ // https://marc.info/?l=openssh-unix-dev&m=163409903417589&w=2 discusses some of the
+ // issues with how Ubuntu incorporated consolekit
+ $pattern = '#^SSH-2\.0-OpenSSH_([\d.]+)[^ ]* Ubuntu-.*$#';
+ $match = preg_match($pattern, $this->server_identifier, $matches);
+ $match = $match && version_compare('5.8', $matches[1], '<=');
+ $match = $match && version_compare('6.9', $matches[1], '>=');
+ $this->errorOnMultipleChannels = $match;
+
if (!$this->send_id_string_first) {
+ $start = microtime(true);
fputs($this->fsock, $this->identifier . "\r\n");
- }
-
- if (!$this->send_kex_first) {
- $response = $this->_get_binary_packet();
- if ($response === false) {
- $this->bitmap = 0;
- user_error('Connection closed by server');
- return false;
+ $elapsed = round(microtime(true) - $start, 4);
+ if (defined('NET_SSH2_LOGGING')) {
+ $this->append_log("-> (network: $elapsed)", $this->identifier . "\r\n");
}
+ }
- if (!strlen($response) || ord($response[0]) != NET_SSH2_MSG_KEXINIT) {
- user_error('Expected SSH_MSG_KEXINIT');
- return false;
- }
+ $this->last_packet = microtime(true);
- if (!$this->_key_exchange($response)) {
- return false;
- }
+ if (!$this->send_kex_first) {
+ $response = $this->get_binary_packet_or_close(NET_SSH2_MSG_KEXINIT);
+ $this->key_exchange($response);
}
- if ($this->send_kex_first && !$this->_key_exchange()) {
- return false;
+ if ($this->send_kex_first) {
+ $this->key_exchange();
}
- $this->bitmap|= self::MASK_CONNECTED;
+ $this->bitmap |= self::MASK_CONNECTED;
return true;
}
@@ -1488,15 +1545,14 @@ class SSH2
*
* You should overwrite this method in your own class if you want to use another identifier
*
- * @access protected
* @return string
*/
- function _generate_identifier()
+ private function generate_identifier()
{
- $identifier = 'SSH-2.0-phpseclib_2.0';
+ $identifier = 'SSH-2.0-phpseclib_3.0';
- $ext = array();
- if (function_exists('sodium_crypto_box_publickey_from_secretkey')) {
+ $ext = [];
+ if (extension_loaded('sodium')) {
$ext[] = 'libsodium';
}
@@ -1522,10 +1578,13 @@ class SSH2
/**
* Key Exchange
*
- * @param string $kexinit_payload_server optional
- * @access private
+ * @return bool
+ * @param string|bool $kexinit_payload_server optional
+ * @throws \UnexpectedValueException on receipt of unexpected packets
+ * @throws \RuntimeException on other errors
+ * @throws NoSupportedAlgorithmsException when none of the algorithms phpseclib has loaded are compatible
*/
- function _key_exchange($kexinit_payload_server = false)
+ private function key_exchange($kexinit_payload_server = false)
{
$this->bytesTransferredSinceLastKEX = 0;
@@ -1537,30 +1596,30 @@ class SSH2
$kex_algorithms = isset($preferred['kex']) ?
$preferred['kex'] :
- $this->getSupportedKEXAlgorithms();
+ SSH2::getSupportedKEXAlgorithms();
$server_host_key_algorithms = isset($preferred['hostkey']) ?
$preferred['hostkey'] :
- $this->getSupportedHostKeyAlgorithms();
+ SSH2::getSupportedHostKeyAlgorithms();
$s2c_encryption_algorithms = isset($preferred['server_to_client']['crypt']) ?
$preferred['server_to_client']['crypt'] :
- $this->getSupportedEncryptionAlgorithms();
+ SSH2::getSupportedEncryptionAlgorithms();
$c2s_encryption_algorithms = isset($preferred['client_to_server']['crypt']) ?
$preferred['client_to_server']['crypt'] :
- $this->getSupportedEncryptionAlgorithms();
+ SSH2::getSupportedEncryptionAlgorithms();
$s2c_mac_algorithms = isset($preferred['server_to_client']['mac']) ?
$preferred['server_to_client']['mac'] :
- $this->getSupportedMACAlgorithms();
+ SSH2::getSupportedMACAlgorithms();
$c2s_mac_algorithms = isset($preferred['client_to_server']['mac']) ?
$preferred['client_to_server']['mac'] :
- $this->getSupportedMACAlgorithms();
+ SSH2::getSupportedMACAlgorithms();
$s2c_compression_algorithms = isset($preferred['server_to_client']['comp']) ?
$preferred['server_to_client']['comp'] :
- $this->getSupportedCompressionAlgorithms();
+ SSH2::getSupportedCompressionAlgorithms();
$c2s_compression_algorithms = isset($preferred['client_to_server']['comp']) ?
$preferred['client_to_server']['comp'] :
- $this->getSupportedCompressionAlgorithms();
+ SSH2::getSupportedCompressionAlgorithms();
- $kex_algorithms = array_merge($kex_algorithms, array('ext-info-c', 'kex-strict-c-v00@openssh.com'));
+ $kex_algorithms = array_merge($kex_algorithms, ['ext-info-c', 'kex-strict-c-v00@openssh.com']);
// some SSH servers have buggy implementations of some of the above algorithms
switch (true) {
@@ -1569,78 +1628,61 @@ class SSH2
if (!isset($preferred['server_to_client']['mac'])) {
$s2c_mac_algorithms = array_values(array_diff(
$s2c_mac_algorithms,
- array('hmac-sha1-96', 'hmac-md5-96')
+ ['hmac-sha1-96', 'hmac-md5-96']
));
}
if (!isset($preferred['client_to_server']['mac'])) {
$c2s_mac_algorithms = array_values(array_diff(
$c2s_mac_algorithms,
- array('hmac-sha1-96', 'hmac-md5-96')
+ ['hmac-sha1-96', 'hmac-md5-96']
+ ));
+ }
+ break;
+ case substr($this->server_identifier, 0, 24) == 'SSH-2.0-TurboFTP_SERVER_':
+ if (!isset($preferred['server_to_client']['crypt'])) {
+ $s2c_encryption_algorithms = array_values(array_diff(
+ $s2c_encryption_algorithms,
+ ['aes128-gcm@openssh.com', 'aes256-gcm@openssh.com']
+ ));
+ }
+ if (!isset($preferred['client_to_server']['crypt'])) {
+ $c2s_encryption_algorithms = array_values(array_diff(
+ $c2s_encryption_algorithms,
+ ['aes128-gcm@openssh.com', 'aes256-gcm@openssh.com']
));
}
}
- $str_kex_algorithms = implode(',', $kex_algorithms);
- $str_server_host_key_algorithms = implode(',', $server_host_key_algorithms);
- $encryption_algorithms_server_to_client = implode(',', $s2c_encryption_algorithms);
- $encryption_algorithms_client_to_server = implode(',', $c2s_encryption_algorithms);
- $mac_algorithms_server_to_client = implode(',', $s2c_mac_algorithms);
- $mac_algorithms_client_to_server = implode(',', $c2s_mac_algorithms);
- $compression_algorithms_server_to_client = implode(',', $s2c_compression_algorithms);
- $compression_algorithms_client_to_server = implode(',', $c2s_compression_algorithms);
-
$client_cookie = Random::string(16);
- $kexinit_payload_client = pack(
- 'Ca*Na*Na*Na*Na*Na*Na*Na*Na*Na*Na*CN',
- NET_SSH2_MSG_KEXINIT,
- $client_cookie,
- strlen($str_kex_algorithms),
- $str_kex_algorithms,
- strlen($str_server_host_key_algorithms),
- $str_server_host_key_algorithms,
- strlen($encryption_algorithms_client_to_server),
- $encryption_algorithms_client_to_server,
- strlen($encryption_algorithms_server_to_client),
- $encryption_algorithms_server_to_client,
- strlen($mac_algorithms_client_to_server),
- $mac_algorithms_client_to_server,
- strlen($mac_algorithms_server_to_client),
- $mac_algorithms_server_to_client,
- strlen($compression_algorithms_client_to_server),
- $compression_algorithms_client_to_server,
- strlen($compression_algorithms_server_to_client),
- $compression_algorithms_server_to_client,
- 0,
- '',
- 0,
- '',
- 0,
- 0
+ $kexinit_payload_client = pack('Ca*', NET_SSH2_MSG_KEXINIT, $client_cookie);
+ $kexinit_payload_client .= Strings::packSSH2(
+ 'L10bN',
+ $kex_algorithms,
+ $server_host_key_algorithms,
+ $c2s_encryption_algorithms,
+ $s2c_encryption_algorithms,
+ $c2s_mac_algorithms,
+ $s2c_mac_algorithms,
+ $c2s_compression_algorithms,
+ $s2c_compression_algorithms,
+ [], // language, client to server
+ [], // language, server to client
+ false, // first_kex_packet_follows
+ 0 // reserved for future extension
);
if ($kexinit_payload_server === false && $send_kex) {
- if (!$this->_send_binary_packet($kexinit_payload_client)) {
- return false;
- }
+ $this->send_binary_packet($kexinit_payload_client);
while (true) {
- $kexinit_payload_server = $this->_get_binary_packet();
- if ($kexinit_payload_server === false) {
- $this->bitmap = 0;
- user_error('Connection closed by server');
- return false;
- }
-
- if (strlen($kexinit_payload_server)) {
- switch (ord($kexinit_payload_server[0])) {
- case NET_SSH2_MSG_KEXINIT:
- break 2;
- case NET_SSH2_MSG_DISCONNECT:
- return $this->_handleDisconnect($kexinit_payload_server);
- }
+ $kexinit_payload_server = $this->get_binary_packet();
+ switch (ord($kexinit_payload_server[0])) {
+ case NET_SSH2_MSG_KEXINIT:
+ break 2;
+ case NET_SSH2_MSG_DISCONNECT:
+ return $this->handleDisconnect($kexinit_payload_server);
}
-
$this->kex_buffer[] = $kexinit_payload_server;
}
@@ -1648,159 +1690,136 @@ class SSH2
}
$response = $kexinit_payload_server;
- $this->_string_shift($response, 1); // skip past the message number (it should be SSH_MSG_KEXINIT)
- $server_cookie = $this->_string_shift($response, 16);
-
- if (strlen($response) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $this->_string_shift($response, 4));
- $this->kex_algorithms = explode(',', $this->_string_shift($response, $temp['length']));
+ Strings::shift($response, 1); // skip past the message number (it should be SSH_MSG_KEXINIT)
+ $server_cookie = Strings::shift($response, 16);
+
+ list(
+ $this->kex_algorithms,
+ $this->server_host_key_algorithms,
+ $this->encryption_algorithms_client_to_server,
+ $this->encryption_algorithms_server_to_client,
+ $this->mac_algorithms_client_to_server,
+ $this->mac_algorithms_server_to_client,
+ $this->compression_algorithms_client_to_server,
+ $this->compression_algorithms_server_to_client,
+ $this->languages_client_to_server,
+ $this->languages_server_to_client,
+ $first_kex_packet_follows
+ ) = Strings::unpackSSH2('L10C', $response);
if (in_array('kex-strict-s-v00@openssh.com', $this->kex_algorithms)) {
if ($this->session_id === false) {
// [kex-strict-s-v00@openssh.com is] only valid in the initial SSH2_MSG_KEXINIT and MUST be ignored
// if [it is] present in subsequent SSH2_MSG_KEXINIT packets
$this->strict_kex_flag = true;
if (count($this->kex_buffer)) {
- user_error('Possible Terrapin Attack detected');
- return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
+ throw new \UnexpectedValueException('Possible Terrapin Attack detected');
}
}
}
- if (strlen($response) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $this->_string_shift($response, 4));
- $this->server_host_key_algorithms = explode(',', $this->_string_shift($response, $temp['length']));
-
$this->supported_private_key_algorithms = $this->server_host_key_algorithms;
- if (strlen($response) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $this->_string_shift($response, 4));
- $this->encryption_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length']));
-
- if (strlen($response) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $this->_string_shift($response, 4));
- $this->encryption_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length']));
-
- if (strlen($response) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $this->_string_shift($response, 4));
- $this->mac_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length']));
-
- if (strlen($response) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $this->_string_shift($response, 4));
- $this->mac_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length']));
-
- if (strlen($response) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $this->_string_shift($response, 4));
- $this->compression_algorithms_client_to_server = explode(',', $this->_string_shift($response, $temp['length']));
-
- if (strlen($response) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $this->_string_shift($response, 4));
- $this->compression_algorithms_server_to_client = explode(',', $this->_string_shift($response, $temp['length']));
-
- if (strlen($response) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $this->_string_shift($response, 4));
- $this->languages_client_to_server = explode(',', $this->_string_shift($response, $temp['length']));
-
- if (strlen($response) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $this->_string_shift($response, 4));
- $this->languages_server_to_client = explode(',', $this->_string_shift($response, $temp['length']));
-
- if (!strlen($response)) {
- return false;
- }
- extract(unpack('Cfirst_kex_packet_follows', $this->_string_shift($response, 1)));
- $first_kex_packet_follows = $first_kex_packet_follows != 0;
-
- if ($send_kex && !$this->_send_binary_packet($kexinit_payload_client)) {
- return false;
+ if ($send_kex) {
+ $this->send_binary_packet($kexinit_payload_client);
}
// we need to decide upon the symmetric encryption algorithms before we do the diffie-hellman key exchange
+
// we don't initialize any crypto-objects, yet - we do that, later. for now, we need the lengths to make the
// diffie-hellman key exchange as fast as possible
- $decrypt = $this->_array_intersect_first($s2c_encryption_algorithms, $this->encryption_algorithms_server_to_client);
- $decryptKeyLength = $this->_encryption_algorithm_to_key_size($decrypt);
- if ($decryptKeyLength === null) {
- user_error('No compatible server to client encryption algorithms found');
- return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
+ $decrypt = self::array_intersect_first($s2c_encryption_algorithms, $this->encryption_algorithms_server_to_client);
+ if (!$decrypt || ($decryptKeyLength = $this->encryption_algorithm_to_key_size($decrypt)) === null) {
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
+ throw new NoSupportedAlgorithmsException('No compatible server to client encryption algorithms found');
}
- $encrypt = $this->_array_intersect_first($c2s_encryption_algorithms, $this->encryption_algorithms_client_to_server);
- $encryptKeyLength = $this->_encryption_algorithm_to_key_size($encrypt);
- if ($encryptKeyLength === null) {
- user_error('No compatible client to server encryption algorithms found');
- return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
+ $encrypt = self::array_intersect_first($c2s_encryption_algorithms, $this->encryption_algorithms_client_to_server);
+ if (!$encrypt || ($encryptKeyLength = $this->encryption_algorithm_to_key_size($encrypt)) === null) {
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
+ throw new NoSupportedAlgorithmsException('No compatible client to server encryption algorithms found');
}
// through diffie-hellman key exchange a symmetric key is obtained
- $this->kex_algorithm = $kex_algorithm = $this->_array_intersect_first($kex_algorithms, $this->kex_algorithms);
- if ($kex_algorithm === false) {
- user_error('No compatible key exchange algorithms found');
- return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
+ $this->kex_algorithm = self::array_intersect_first($kex_algorithms, $this->kex_algorithms);
+ if ($this->kex_algorithm === false) {
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
+ throw new NoSupportedAlgorithmsException('No compatible key exchange algorithms found');
}
- $server_host_key_algorithm = $this->_array_intersect_first($server_host_key_algorithms, $this->server_host_key_algorithms);
+ $server_host_key_algorithm = self::array_intersect_first($server_host_key_algorithms, $this->server_host_key_algorithms);
if ($server_host_key_algorithm === false) {
- user_error('No compatible server host key algorithms found');
- return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
+ throw new NoSupportedAlgorithmsException('No compatible server host key algorithms found');
}
- $mac_algorithm_in = $this->_array_intersect_first($s2c_mac_algorithms, $this->mac_algorithms_server_to_client);
+ $mac_algorithm_out = self::array_intersect_first($c2s_mac_algorithms, $this->mac_algorithms_client_to_server);
+ if ($mac_algorithm_out === false) {
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
+ throw new NoSupportedAlgorithmsException('No compatible client to server message authentication algorithms found');
+ }
+
+ $mac_algorithm_in = self::array_intersect_first($s2c_mac_algorithms, $this->mac_algorithms_server_to_client);
if ($mac_algorithm_in === false) {
- user_error('No compatible server to client message authentication algorithms found');
- return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
+ throw new NoSupportedAlgorithmsException('No compatible server to client message authentication algorithms found');
}
- $compression_map = array(
+ $compression_map = [
'none' => self::NET_SSH2_COMPRESSION_NONE,
'zlib' => self::NET_SSH2_COMPRESSION_ZLIB,
'zlib@openssh.com' => self::NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH
- );
+ ];
+
+ $compression_algorithm_in = self::array_intersect_first($s2c_compression_algorithms, $this->compression_algorithms_server_to_client);
+ if ($compression_algorithm_in === false) {
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
+ throw new NoSupportedAlgorithmsException('No compatible server to client compression algorithms found');
+ }
+ $this->decompress = $compression_map[$compression_algorithm_in];
- $compression_algorithm_out = $this->_array_intersect_first($c2s_compression_algorithms, $this->compression_algorithms_client_to_server);
+ $compression_algorithm_out = self::array_intersect_first($c2s_compression_algorithms, $this->compression_algorithms_client_to_server);
if ($compression_algorithm_out === false) {
- user_error('No compatible client to server compression algorithms found');
- return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
+ throw new NoSupportedAlgorithmsException('No compatible client to server compression algorithms found');
}
$this->compress = $compression_map[$compression_algorithm_out];
- $compression_algorithm_in = $this->_array_intersect_first($s2c_compression_algorithms, $this->compression_algorithms_server_to_client);
- if ($compression_algorithm_in === false) {
- user_error('No compatible server to client compression algorithms found');
- return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
+ switch ($this->kex_algorithm) {
+ case 'diffie-hellman-group15-sha512':
+ case 'diffie-hellman-group16-sha512':
+ case 'diffie-hellman-group17-sha512':
+ case 'diffie-hellman-group18-sha512':
+ case 'ecdh-sha2-nistp521':
+ $kexHash = new Hash('sha512');
+ break;
+ case 'ecdh-sha2-nistp384':
+ $kexHash = new Hash('sha384');
+ break;
+ case 'diffie-hellman-group-exchange-sha256':
+ case 'diffie-hellman-group14-sha256':
+ case 'ecdh-sha2-nistp256':
+ case 'curve25519-sha256@libssh.org':
+ case 'curve25519-sha256':
+ $kexHash = new Hash('sha256');
+ break;
+ default:
+ $kexHash = new Hash('sha1');
}
- $this->decompress = $compression_map[$compression_algorithm_in];
// Only relevant in diffie-hellman-group-exchange-sha{1,256}, otherwise empty.
+
$exchange_hash_rfc4419 = '';
- if ($kex_algorithm === 'curve25519-sha256@libssh.org') {
- $x = Random::string(32);
- $eBytes = sodium_crypto_box_publickey_from_secretkey($x);
+ if (strpos($this->kex_algorithm, 'curve25519-sha256') === 0 || strpos($this->kex_algorithm, 'ecdh-sha2-nistp') === 0) {
+ $curve = strpos($this->kex_algorithm, 'curve25519-sha256') === 0 ?
+ 'Curve25519' :
+ substr($this->kex_algorithm, 10);
+ $ourPrivate = EC::createKey($curve);
+ $ourPublicBytes = $ourPrivate->getPublicKey()->getEncodedCoordinates();
$clientKexInitMessage = 'NET_SSH2_MSG_KEX_ECDH_INIT';
$serverKexReplyMessage = 'NET_SSH2_MSG_KEX_ECDH_REPLY';
- $kexHash = new Hash('sha256');
} else {
- if (strpos($kex_algorithm, 'diffie-hellman-group-exchange') === 0) {
+ if (strpos($this->kex_algorithm, 'diffie-hellman-group-exchange') === 0) {
$dh_group_sizes_packed = pack(
'NNN',
$this->kex_dh_group_size_min,
@@ -1812,210 +1831,94 @@ class SSH2
NET_SSH2_MSG_KEXDH_GEX_REQUEST,
$dh_group_sizes_packed
);
- if (!$this->_send_binary_packet($packet)) {
- return false;
- }
- $this->_updateLogHistory('UNKNOWN (34)', 'NET_SSH2_MSG_KEXDH_GEX_REQUEST');
+ $this->send_binary_packet($packet);
+ $this->updateLogHistory('UNKNOWN (34)', 'NET_SSH2_MSG_KEXDH_GEX_REQUEST');
- $response = $this->_get_binary_packet();
- if ($response === false) {
- $this->bitmap = 0;
- user_error('Connection closed by server');
- return false;
- }
- extract(unpack('Ctype', $this->_string_shift($response, 1)));
- if ($type != NET_SSH2_MSG_KEXDH_GEX_GROUP) {
- user_error('Expected SSH_MSG_KEX_DH_GEX_GROUP');
- return false;
- }
- $this->_updateLogHistory('NET_SSH2_MSG_KEXDH_REPLY', 'NET_SSH2_MSG_KEXDH_GEX_GROUP');
-
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('NprimeLength', $this->_string_shift($response, 4)));
- $primeBytes = $this->_string_shift($response, $primeLength);
+ $response = $this->get_binary_packet_or_close(NET_SSH2_MSG_KEXDH_GEX_GROUP);
+ list($type, $primeBytes, $gBytes) = Strings::unpackSSH2('Css', $response);
+ $this->updateLogHistory('NET_SSH2_MSG_KEXDH_REPLY', 'NET_SSH2_MSG_KEXDH_GEX_GROUP');
$prime = new BigInteger($primeBytes, -256);
-
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('NgLength', $this->_string_shift($response, 4)));
- $gBytes = $this->_string_shift($response, $gLength);
$g = new BigInteger($gBytes, -256);
- $exchange_hash_rfc4419 = pack(
- 'a*Na*Na*',
- $dh_group_sizes_packed,
- $primeLength,
+ $exchange_hash_rfc4419 = $dh_group_sizes_packed . Strings::packSSH2(
+ 'ss',
$primeBytes,
- $gLength,
$gBytes
);
+ $params = DH::createParameters($prime, $g);
$clientKexInitMessage = 'NET_SSH2_MSG_KEXDH_GEX_INIT';
$serverKexReplyMessage = 'NET_SSH2_MSG_KEXDH_GEX_REPLY';
} else {
- switch ($kex_algorithm) {
- // see http://tools.ietf.org/html/rfc2409#section-6.2 and
- // http://tools.ietf.org/html/rfc2412, appendex E
- case 'diffie-hellman-group1-sha1':
- $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
- '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
- '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
- 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF';
- break;
- // see http://tools.ietf.org/html/rfc3526#section-3
- case 'diffie-hellman-group14-sha1':
- $prime = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' .
- '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' .
- '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' .
- 'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05' .
- '98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB' .
- '9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B' .
- 'E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718' .
- '3995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF';
- break;
- }
- // For both diffie-hellman-group1-sha1 and diffie-hellman-group14-sha1
- // the generator field element is 2 (decimal) and the hash function is sha1.
- $g = new BigInteger(2);
- $prime = new BigInteger($prime, 16);
+ $params = DH::createParameters($this->kex_algorithm);
$clientKexInitMessage = 'NET_SSH2_MSG_KEXDH_INIT';
$serverKexReplyMessage = 'NET_SSH2_MSG_KEXDH_REPLY';
}
- switch ($kex_algorithm) {
- case 'diffie-hellman-group-exchange-sha256':
- $kexHash = new Hash('sha256');
- break;
- default:
- $kexHash = new Hash('sha1');
- }
-
- /* To increase the speed of the key exchange, both client and server may
- reduce the size of their private exponents. It should be at least
- twice as long as the key material that is generated from the shared
- secret. For more details, see the paper by van Oorschot and Wiener
- [VAN-OORSCHOT].
+ $keyLength = min($kexHash->getLengthInBytes(), max($encryptKeyLength, $decryptKeyLength));
- -- http://tools.ietf.org/html/rfc4419#section-6.2 */
- $one = new BigInteger(1);
- $keyLength = min($kexHash->getLength(), max($encryptKeyLength, $decryptKeyLength));
- $max = $one->bitwise_leftShift(16 * $keyLength); // 2 * 8 * $keyLength
- $max = $max->subtract($one);
+ $ourPrivate = DH::createKey($params, 16 * $keyLength); // 2 * 8 * $keyLength
+ $ourPublic = $ourPrivate->getPublicKey()->toBigInteger();
+ $ourPublicBytes = $ourPublic->toBytes(true);
+ }
- $x = $one->random($one, $max);
- $e = $g->modPow($x, $prime);
+ $data = pack('CNa*', constant($clientKexInitMessage), strlen($ourPublicBytes), $ourPublicBytes);
- $eBytes = $e->toBytes(true);
- }
- $data = pack('CNa*', constant($clientKexInitMessage), strlen($eBytes), $eBytes);
+ $this->send_binary_packet($data);
- if (!$this->_send_binary_packet($data)) {
- $this->bitmap = 0;
- user_error('Connection closed by server');
- return false;
- }
switch ($clientKexInitMessage) {
case 'NET_SSH2_MSG_KEX_ECDH_INIT':
- $this->_updateLogHistory('NET_SSH2_MSG_KEXDH_INIT', 'NET_SSH2_MSG_KEX_ECDH_INIT');
+ $this->updateLogHistory('NET_SSH2_MSG_KEXDH_INIT', 'NET_SSH2_MSG_KEX_ECDH_INIT');
break;
case 'NET_SSH2_MSG_KEXDH_GEX_INIT':
- $this->_updateLogHistory('UNKNOWN (32)', 'NET_SSH2_MSG_KEXDH_GEX_INIT');
+ $this->updateLogHistory('UNKNOWN (32)', 'NET_SSH2_MSG_KEXDH_GEX_INIT');
}
- $response = $this->_get_binary_packet();
- if ($response === false) {
- $this->bitmap = 0;
- user_error('Connection closed by server');
- return false;
- }
- if (!strlen($response)) {
- return false;
- }
- extract(unpack('Ctype', $this->_string_shift($response, 1)));
+ $response = $this->get_binary_packet_or_close(constant($serverKexReplyMessage));
+
+ list(
+ $type,
+ $server_public_host_key,
+ $theirPublicBytes,
+ $this->signature
+ ) = Strings::unpackSSH2('Csss', $response);
- if ($type != constant($serverKexReplyMessage)) {
- user_error("Expected $serverKexReplyMessage");
- return false;
- }
switch ($serverKexReplyMessage) {
case 'NET_SSH2_MSG_KEX_ECDH_REPLY':
- $this->_updateLogHistory('NET_SSH2_MSG_KEXDH_REPLY', 'NET_SSH2_MSG_KEX_ECDH_REPLY');
+ $this->updateLogHistory('NET_SSH2_MSG_KEXDH_REPLY', 'NET_SSH2_MSG_KEX_ECDH_REPLY');
break;
case 'NET_SSH2_MSG_KEXDH_GEX_REPLY':
- $this->_updateLogHistory('UNKNOWN (33)', 'NET_SSH2_MSG_KEXDH_GEX_REPLY');
- }
-
- if (strlen($response) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $this->_string_shift($response, 4));
- $this->server_public_host_key = $server_public_host_key = $this->_string_shift($response, $temp['length']);
-
- if (strlen($server_public_host_key) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
- $public_key_format = $this->_string_shift($server_public_host_key, $temp['length']);
-
- if (strlen($response) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $this->_string_shift($response, 4));
- $fBytes = $this->_string_shift($response, $temp['length']);
-
- if (strlen($response) < 4) {
- return false;
+ $this->updateLogHistory('UNKNOWN (33)', 'NET_SSH2_MSG_KEXDH_GEX_REPLY');
}
- $temp = unpack('Nlength', $this->_string_shift($response, 4));
- $this->signature = $this->_string_shift($response, $temp['length']);
+ $this->server_public_host_key = $server_public_host_key;
+ list($public_key_format) = Strings::unpackSSH2('s', $server_public_host_key);
if (strlen($this->signature) < 4) {
- return false;
+ throw new \LengthException('The signature needs at least four bytes');
}
- $temp = unpack('Nlength', $this->_string_shift($this->signature, 4));
- $this->signature_format = $this->_string_shift($this->signature, $temp['length']);
+ $temp = unpack('Nlength', substr($this->signature, 0, 4));
+ $this->signature_format = substr($this->signature, 4, $temp['length']);
- if ($kex_algorithm === 'curve25519-sha256@libssh.org') {
- if (strlen($fBytes) !== 32) {
- user_error('Received curve25519 public key of invalid length.');
- return false;
- }
- $key = new BigInteger(sodium_crypto_scalarmult($x, $fBytes), 256);
- // sodium_compat doesn't emulate sodium_memzero
- // also, with v1 of libsodium API the extension identifies itself as
- // libsodium whereas v2 of the libsodium API (what PHP 7.2+ includes)
- // identifies itself as sodium. sodium_compat uses the v1 API to
- // emulate the v2 API if it's the v1 API that's available
- if (extension_loaded('sodium') || extension_loaded('libsodium')) {
- sodium_memzero($x);
- }
- } else {
- $f = new BigInteger($fBytes, -256);
- $key = $f->modPow($x, $prime);
+ $keyBytes = DH::computeSecret($ourPrivate, $theirPublicBytes);
+ if (($keyBytes & "\xFF\x80") === "\x00\x00") {
+ $keyBytes = substr($keyBytes, 1);
+ } elseif (($keyBytes[0] & "\x80") === "\x80") {
+ $keyBytes = "\0$keyBytes";
}
- $keyBytes = $key->toBytes(true);
- $this->exchange_hash = pack(
- 'Na*Na*Na*Na*Na*a*Na*Na*Na*',
- strlen($this->identifier),
+ $this->exchange_hash = Strings::packSSH2(
+ 's5',
$this->identifier,
- strlen($this->server_identifier),
$this->server_identifier,
- strlen($kexinit_payload_client),
$kexinit_payload_client,
- strlen($kexinit_payload_server),
$kexinit_payload_server,
- strlen($this->server_public_host_key),
- $this->server_public_host_key,
- $exchange_hash_rfc4419,
- strlen($eBytes),
- $eBytes,
- strlen($fBytes),
- $fBytes,
- strlen($keyBytes),
+ $this->server_public_host_key
+ );
+ $this->exchange_hash .= $exchange_hash_rfc4419;
+ $this->exchange_hash .= Strings::packSSH2(
+ 's3',
+ $ourPublicBytes,
+ $theirPublicBytes,
$keyBytes
);
@@ -2026,52 +1929,27 @@ class SSH2
}
switch ($server_host_key_algorithm) {
- case 'ssh-dss':
- $expected_key_format = 'ssh-dss';
- break;
- //case 'rsa-sha2-256':
- //case 'rsa-sha2-512':
+ case 'rsa-sha2-256':
+ case 'rsa-sha2-512':
//case 'ssh-rsa':
- default:
$expected_key_format = 'ssh-rsa';
+ break;
+ default:
+ $expected_key_format = $server_host_key_algorithm;
}
-
if ($public_key_format != $expected_key_format || $this->signature_format != $server_host_key_algorithm) {
switch (true) {
case $this->signature_format == $server_host_key_algorithm:
case $server_host_key_algorithm != 'rsa-sha2-256' && $server_host_key_algorithm != 'rsa-sha2-512':
case $this->signature_format != 'ssh-rsa':
- user_error('Server Host Key Algorithm Mismatch');
- return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
+ throw new \RuntimeException('Server Host Key Algorithm Mismatch (' . $this->signature_format . ' vs ' . $server_host_key_algorithm . ')');
}
}
- $packet = pack(
- 'C',
- NET_SSH2_MSG_NEWKEYS
- );
-
- if (!$this->_send_binary_packet($packet)) {
- return false;
- }
-
- $response = $this->_get_binary_packet();
-
- if ($response === false) {
- $this->bitmap = 0;
- user_error('Connection closed by server');
- return false;
- }
-
- if (!strlen($response)) {
- return false;
- }
- extract(unpack('Ctype', $this->_string_shift($response, 1)));
-
- if ($type != NET_SSH2_MSG_NEWKEYS) {
- user_error('Expected SSH_MSG_NEWKEYS');
- return false;
- }
+ $packet = pack('C', NET_SSH2_MSG_NEWKEYS);
+ $this->send_binary_packet($packet);
+ $this->get_binary_packet_or_close(NET_SSH2_MSG_NEWKEYS);
$this->keyExchangeInProgress = false;
@@ -2081,63 +1959,94 @@ class SSH2
$keyBytes = pack('Na*', strlen($keyBytes), $keyBytes);
- $this->encrypt = $this->_encryption_algorithm_to_crypt_instance($encrypt);
+ $this->encrypt = self::encryption_algorithm_to_crypt_instance($encrypt);
if ($this->encrypt) {
- if ($this->crypto_engine) {
- $this->encrypt->setPreferredEngine($this->crypto_engine);
+ if (self::$crypto_engine) {
+ $this->encrypt->setPreferredEngine(self::$crypto_engine);
}
- if ($this->encrypt->block_size) {
- $this->encrypt_block_size = $this->encrypt->block_size;
+ if ($this->encrypt->getBlockLengthInBytes()) {
+ $this->encrypt_block_size = $this->encrypt->getBlockLengthInBytes();
}
- $this->encrypt->enableContinuousBuffer();
$this->encrypt->disablePadding();
- if ($this->encrypt->getBlockLength()) {
- $this->encrypt_block_size = $this->encrypt->getBlockLength() >> 3;
+ if ($this->encrypt->usesIV()) {
+ $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'A' . $this->session_id);
+ while ($this->encrypt_block_size > strlen($iv)) {
+ $iv .= $kexHash->hash($keyBytes . $this->exchange_hash . $iv);
+ }
+ $this->encrypt->setIV(substr($iv, 0, $this->encrypt_block_size));
}
- $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'A' . $this->session_id);
- while ($this->encrypt_block_size > strlen($iv)) {
- $iv.= $kexHash->hash($keyBytes . $this->exchange_hash . $iv);
+ switch ($encrypt) {
+ case 'aes128-gcm@openssh.com':
+ case 'aes256-gcm@openssh.com':
+ $nonce = $kexHash->hash($keyBytes . $this->exchange_hash . 'A' . $this->session_id);
+ $this->encryptFixedPart = substr($nonce, 0, 4);
+ $this->encryptInvocationCounter = substr($nonce, 4, 8);
+ // fall-through
+ case 'chacha20-poly1305@openssh.com':
+ break;
+ default:
+ $this->encrypt->enableContinuousBuffer();
}
- $this->encrypt->setIV(substr($iv, 0, $this->encrypt_block_size));
$key = $kexHash->hash($keyBytes . $this->exchange_hash . 'C' . $this->session_id);
while ($encryptKeyLength > strlen($key)) {
- $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
+ $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
+ }
+ switch ($encrypt) {
+ case 'chacha20-poly1305@openssh.com':
+ $encryptKeyLength = 32;
+ $this->lengthEncrypt = self::encryption_algorithm_to_crypt_instance($encrypt);
+ $this->lengthEncrypt->setKey(substr($key, 32, 32));
}
$this->encrypt->setKey(substr($key, 0, $encryptKeyLength));
-
$this->encryptName = $encrypt;
}
- $this->decrypt = $this->_encryption_algorithm_to_crypt_instance($decrypt);
+ $this->decrypt = self::encryption_algorithm_to_crypt_instance($decrypt);
if ($this->decrypt) {
- if ($this->crypto_engine) {
- $this->decrypt->setPreferredEngine($this->crypto_engine);
+ if (self::$crypto_engine) {
+ $this->decrypt->setPreferredEngine(self::$crypto_engine);
}
- if ($this->decrypt->block_size) {
- $this->decrypt_block_size = $this->decrypt->block_size;
+ if ($this->decrypt->getBlockLengthInBytes()) {
+ $this->decrypt_block_size = $this->decrypt->getBlockLengthInBytes();
}
- $this->decrypt->enableContinuousBuffer();
$this->decrypt->disablePadding();
- if ($this->decrypt->getBlockLength()) {
- $this->decrypt_block_size = $this->decrypt->getBlockLength() >> 3;
+ if ($this->decrypt->usesIV()) {
+ $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'B' . $this->session_id);
+ while ($this->decrypt_block_size > strlen($iv)) {
+ $iv .= $kexHash->hash($keyBytes . $this->exchange_hash . $iv);
+ }
+ $this->decrypt->setIV(substr($iv, 0, $this->decrypt_block_size));
}
- $iv = $kexHash->hash($keyBytes . $this->exchange_hash . 'B' . $this->session_id);
- while ($this->decrypt_block_size > strlen($iv)) {
- $iv.= $kexHash->hash($keyBytes . $this->exchange_hash . $iv);
+ switch ($decrypt) {
+ case 'aes128-gcm@openssh.com':
+ case 'aes256-gcm@openssh.com':
+ // see https://tools.ietf.org/html/rfc5647#section-7.1
+ $nonce = $kexHash->hash($keyBytes . $this->exchange_hash . 'B' . $this->session_id);
+ $this->decryptFixedPart = substr($nonce, 0, 4);
+ $this->decryptInvocationCounter = substr($nonce, 4, 8);
+ // fall-through
+ case 'chacha20-poly1305@openssh.com':
+ break;
+ default:
+ $this->decrypt->enableContinuousBuffer();
}
- $this->decrypt->setIV(substr($iv, 0, $this->decrypt_block_size));
$key = $kexHash->hash($keyBytes . $this->exchange_hash . 'D' . $this->session_id);
while ($decryptKeyLength > strlen($key)) {
- $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
+ $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
+ }
+ switch ($decrypt) {
+ case 'chacha20-poly1305@openssh.com':
+ $decryptKeyLength = 32;
+ $this->lengthDecrypt = self::encryption_algorithm_to_crypt_instance($decrypt);
+ $this->lengthDecrypt->setKey(substr($key, 32, 32));
}
$this->decrypt->setKey(substr($key, 0, $decryptKeyLength));
-
$this->decryptName = $decrypt;
}
@@ -2155,77 +2064,45 @@ class SSH2
$this->decrypt->decrypt(str_repeat("\0", 1536));
}
- $mac_algorithm_out = $this->_array_intersect_first($c2s_mac_algorithms, $this->mac_algorithms_client_to_server);
- if ($mac_algorithm_out === false) {
- user_error('No compatible client to server message authentication algorithms found');
- return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
- }
-
- $createKeyLength = 0; // ie. $mac_algorithm == 'none'
- switch ($mac_algorithm_out) {
- case 'hmac-sha2-256':
- $this->hmac_create = new Hash('sha256');
- $createKeyLength = 32;
- break;
- case 'hmac-sha1':
- $this->hmac_create = new Hash('sha1');
- $createKeyLength = 20;
- break;
- case 'hmac-sha1-96':
- $this->hmac_create = new Hash('sha1-96');
- $createKeyLength = 20;
- break;
- case 'hmac-md5':
- $this->hmac_create = new Hash('md5');
- $createKeyLength = 16;
- break;
- case 'hmac-md5-96':
- $this->hmac_create = new Hash('md5-96');
- $createKeyLength = 16;
+ if (!$this->encrypt->usesNonce()) {
+ list($this->hmac_create, $createKeyLength) = self::mac_algorithm_to_hash_instance($mac_algorithm_out);
+ } else {
+ $this->hmac_create = new \stdClass();
+ $this->hmac_create_name = $mac_algorithm_out;
+ //$mac_algorithm_out = 'none';
+ $createKeyLength = 0;
}
- $this->hmac_create_name = $mac_algorithm_out;
- $checkKeyLength = 0;
- $this->hmac_size = 0;
- switch ($mac_algorithm_in) {
- case 'hmac-sha2-256':
- $this->hmac_check = new Hash('sha256');
- $checkKeyLength = 32;
- $this->hmac_size = 32;
- break;
- case 'hmac-sha1':
- $this->hmac_check = new Hash('sha1');
- $checkKeyLength = 20;
- $this->hmac_size = 20;
- break;
- case 'hmac-sha1-96':
- $this->hmac_check = new Hash('sha1-96');
- $checkKeyLength = 20;
- $this->hmac_size = 12;
- break;
- case 'hmac-md5':
- $this->hmac_check = new Hash('md5');
- $checkKeyLength = 16;
- $this->hmac_size = 16;
- break;
- case 'hmac-md5-96':
- $this->hmac_check = new Hash('md5-96');
- $checkKeyLength = 16;
- $this->hmac_size = 12;
+ if ($this->hmac_create instanceof Hash) {
+ $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'E' . $this->session_id);
+ while ($createKeyLength > strlen($key)) {
+ $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
+ }
+ $this->hmac_create->setKey(substr($key, 0, $createKeyLength));
+ $this->hmac_create_name = $mac_algorithm_out;
+ $this->hmac_create_etm = preg_match('#-etm@openssh\.com$#', $mac_algorithm_out);
}
- $this->hmac_check_name = $mac_algorithm_in;
- $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'E' . $this->session_id);
- while ($createKeyLength > strlen($key)) {
- $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
+ if (!$this->decrypt->usesNonce()) {
+ list($this->hmac_check, $checkKeyLength) = self::mac_algorithm_to_hash_instance($mac_algorithm_in);
+ $this->hmac_size = $this->hmac_check->getLengthInBytes();
+ } else {
+ $this->hmac_check = new \stdClass();
+ $this->hmac_check_name = $mac_algorithm_in;
+ //$mac_algorithm_in = 'none';
+ $checkKeyLength = 0;
+ $this->hmac_size = 0;
}
- $this->hmac_create->setKey(substr($key, 0, $createKeyLength));
- $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'F' . $this->session_id);
- while ($checkKeyLength > strlen($key)) {
- $key.= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
+ if ($this->hmac_check instanceof Hash) {
+ $key = $kexHash->hash($keyBytes . $this->exchange_hash . 'F' . $this->session_id);
+ while ($checkKeyLength > strlen($key)) {
+ $key .= $kexHash->hash($keyBytes . $this->exchange_hash . $key);
+ }
+ $this->hmac_check->setKey(substr($key, 0, $checkKeyLength));
+ $this->hmac_check_name = $mac_algorithm_in;
+ $this->hmac_check_etm = preg_match('#-etm@openssh\.com$#', $mac_algorithm_in);
}
- $this->hmac_check->setKey(substr($key, 0, $checkKeyLength));
$this->regenerate_compression_context = $this->regenerate_decompression_context = true;
@@ -2237,17 +2114,17 @@ class SSH2
*
* @param string $algorithm Name of the encryption algorithm
* @return int|null Number of bytes as an integer or null for unknown
- * @access private
*/
- function _encryption_algorithm_to_key_size($algorithm)
+ private function encryption_algorithm_to_key_size($algorithm)
{
- if ($this->bad_key_size_fix && $this->_bad_algorithm_candidate($algorithm)) {
+ if ($this->bad_key_size_fix && self::bad_algorithm_candidate($algorithm)) {
return 16;
}
switch ($algorithm) {
case 'none':
return 0;
+ case 'aes128-gcm@openssh.com':
case 'aes128-cbc':
case 'aes128-ctr':
case 'arcfour':
@@ -2264,6 +2141,7 @@ class SSH2
case 'twofish192-cbc':
case 'twofish192-ctr':
return 24;
+ case 'aes256-gcm@openssh.com':
case 'aes256-cbc':
case 'aes256-ctr':
case 'arcfour256':
@@ -2271,64 +2149,103 @@ class SSH2
case 'twofish256-cbc':
case 'twofish256-ctr':
return 32;
+ case 'chacha20-poly1305@openssh.com':
+ return 64;
}
return null;
}
/**
* Maps an encryption algorithm name to an instance of a subclass of
- * \phpseclib\Crypt\Base.
+ * \phpseclib3\Crypt\Common\SymmetricKey.
*
* @param string $algorithm Name of the encryption algorithm
- * @return mixed Instance of \phpseclib\Crypt\Base or null for unknown
- * @access private
+ * @return SymmetricKey|null
*/
- function _encryption_algorithm_to_crypt_instance($algorithm)
+ private static function encryption_algorithm_to_crypt_instance($algorithm)
{
switch ($algorithm) {
case '3des-cbc':
- return new TripleDES();
+ return new TripleDES('cbc');
case '3des-ctr':
- return new TripleDES(Base::MODE_CTR);
+ return new TripleDES('ctr');
case 'aes256-cbc':
case 'aes192-cbc':
case 'aes128-cbc':
- return new Rijndael();
+ return new Rijndael('cbc');
case 'aes256-ctr':
case 'aes192-ctr':
case 'aes128-ctr':
- return new Rijndael(Base::MODE_CTR);
+ return new Rijndael('ctr');
case 'blowfish-cbc':
- return new Blowfish();
+ return new Blowfish('cbc');
case 'blowfish-ctr':
- return new Blowfish(Base::MODE_CTR);
+ return new Blowfish('ctr');
case 'twofish128-cbc':
case 'twofish192-cbc':
case 'twofish256-cbc':
case 'twofish-cbc':
- return new Twofish();
+ return new Twofish('cbc');
case 'twofish128-ctr':
case 'twofish192-ctr':
case 'twofish256-ctr':
- return new Twofish(Base::MODE_CTR);
+ return new Twofish('ctr');
case 'arcfour':
case 'arcfour128':
case 'arcfour256':
return new RC4();
+ case 'aes128-gcm@openssh.com':
+ case 'aes256-gcm@openssh.com':
+ return new Rijndael('gcm');
+ case 'chacha20-poly1305@openssh.com':
+ return new ChaCha20();
}
return null;
}
/**
+ * Maps an encryption algorithm name to an instance of a subclass of
+ * \phpseclib3\Crypt\Hash.
+ *
+ * @param string $algorithm Name of the encryption algorithm
+ * @return array{Hash, int}|null
+ */
+ private static function mac_algorithm_to_hash_instance($algorithm)
+ {
+ switch ($algorithm) {
+ case 'umac-64@openssh.com':
+ case 'umac-64-etm@openssh.com':
+ return [new Hash('umac-64'), 16];
+ case 'umac-128@openssh.com':
+ case 'umac-128-etm@openssh.com':
+ return [new Hash('umac-128'), 16];
+ case 'hmac-sha2-512':
+ case 'hmac-sha2-512-etm@openssh.com':
+ return [new Hash('sha512'), 64];
+ case 'hmac-sha2-256':
+ case 'hmac-sha2-256-etm@openssh.com':
+ return [new Hash('sha256'), 32];
+ case 'hmac-sha1':
+ case 'hmac-sha1-etm@openssh.com':
+ return [new Hash('sha1'), 20];
+ case 'hmac-sha1-96':
+ return [new Hash('sha1-96'), 20];
+ case 'hmac-md5':
+ return [new Hash('md5'), 16];
+ case 'hmac-md5-96':
+ return [new Hash('md5-96'), 16];
+ }
+ }
+
+ /**
* Tests whether or not proposed algorithm has a potential for issues
*
* @link https://www.chiark.greenend.org.uk/~sgtatham/putty/wishlist/ssh2-aesctr-openssh.html
* @link https://bugzilla.mindrot.org/show_bug.cgi?id=1291
* @param string $algorithm Name of the encryption algorithm
* @return bool
- * @access private
*/
- function _bad_algorithm_candidate($algorithm)
+ private static function bad_algorithm_candidate($algorithm)
{
switch ($algorithm) {
case 'arcfour256':
@@ -2343,65 +2260,75 @@ class SSH2
/**
* Login
*
- * The $password parameter can be a plaintext password, a \phpseclib\Crypt\RSA object or an array
+ * The $password parameter can be a plaintext password, a \phpseclib3\Crypt\RSA|EC|DSA object, a \phpseclib3\System\SSH\Agent object or an array
*
* @param string $username
+ * @param string|PrivateKey|array[]|Agent|null ...$args
* @return bool
* @see self::_login()
- * @access public
*/
- function login($username)
+ public function login($username, ...$args)
{
- $args = func_get_args();
if (!$this->login_credentials_finalized) {
- $this->auth[] = $args;
+ $this->auth[] = func_get_args();
}
// try logging with 'none' as an authentication method first since that's what
// PuTTY does
if (substr($this->server_identifier, 0, 15) != 'SSH-2.0-CoreFTP' && $this->auth_methods_to_continue === null) {
- if ($this->_login($username)) {
+ if ($this->sublogin($username)) {
return true;
}
- if (count($args) == 1) {
+ if (!count($args)) {
return false;
}
}
- return call_user_func_array(array(&$this, '_login'), $args);
+ return $this->sublogin($username, ...$args);
}
/**
* Login Helper
*
* @param string $username
+ * @param string|PrivateKey|array[]|Agent|null ...$args
* @return bool
* @see self::_login_helper()
- * @access private
*/
- function _login($username)
+ protected function sublogin($username, ...$args)
{
if (!($this->bitmap & self::MASK_CONSTRUCTOR)) {
- if (!$this->_connect()) {
- return false;
- }
+ $this->connect();
}
- $args = array_slice(func_get_args(), 1);
if (empty($args)) {
- return $this->_login_helper($username);
+ return $this->login_helper($username);
+ }
+
+ foreach ($args as $arg) {
+ switch (true) {
+ case $arg instanceof PublicKey:
+ throw new \UnexpectedValueException('A PublicKey object was passed to the login method instead of a PrivateKey object');
+ case $arg instanceof PrivateKey:
+ case $arg instanceof Agent:
+ case is_array($arg):
+ case Strings::is_stringable($arg):
+ break;
+ default:
+ throw new \UnexpectedValueException('$password needs to either be an instance of \phpseclib3\Crypt\Common\PrivateKey, \System\SSH\Agent, an array or a string');
+ }
}
while (count($args)) {
if (!$this->auth_methods_to_continue || !$this->smartMFA) {
$newargs = $args;
- $args = array();
+ $args = [];
} else {
- $newargs = array();
+ $newargs = [];
foreach ($this->auth_methods_to_continue as $method) {
switch ($method) {
case 'publickey':
foreach ($args as $key => $arg) {
- if (is_object($arg)) {
+ if ($arg instanceof PrivateKey || $arg instanceof Agent) {
$newargs[] = $arg;
unset($args[$key]);
break;
@@ -2415,7 +2342,7 @@ class SSH2
$hasArray = true;
break;
}
- if ($hasString || is_string($arg)) {
+ if ($hasString || Strings::is_stringable($arg)) {
$hasString = true;
break;
}
@@ -2428,6 +2355,7 @@ class SSH2
}
}
}
+ // fall-through
case 'password':
foreach ($args as $key => $arg) {
$newargs[] = $arg;
@@ -2443,7 +2371,7 @@ class SSH2
}
foreach ($newargs as $arg) {
- if ($this->_login_helper($username, $arg)) {
+ if ($this->login_helper($username, $arg)) {
$this->login_credentials_finalized = true;
return true;
}
@@ -2455,102 +2383,63 @@ class SSH2
/**
* Login Helper
*
+ * {@internal It might be worthwhile, at some point, to protect against {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis}
+ * by sending dummy SSH_MSG_IGNORE messages.}
+ *
* @param string $username
- * @param string $password
+ * @param string|AsymmetricKey|array[]|Agent|null ...$args
* @return bool
- * @access private
- * @internal It might be worthwhile, at some point, to protect against {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis}
- * by sending dummy SSH_MSG_IGNORE messages.
+ * @throws \UnexpectedValueException on receipt of unexpected packets
+ * @throws \RuntimeException on other errors
*/
- function _login_helper($username, $password = null)
+ private function login_helper($username, $password = null)
{
if (!($this->bitmap & self::MASK_CONNECTED)) {
return false;
}
if (!($this->bitmap & self::MASK_LOGIN_REQ)) {
- $packet = pack(
- 'CNa*',
- NET_SSH2_MSG_SERVICE_REQUEST,
- strlen('ssh-userauth'),
- 'ssh-userauth'
- );
-
- if (!$this->_send_binary_packet($packet)) {
- return false;
- }
-
- $bad_key_size_fix = $this->bad_key_size_fix;
- $response = $this->_get_binary_packet();
- if ($response === false) {
- // bad_key_size_fix is only ever re-assigned to true
- // under certain conditions. when it's newly set we'll
- // retry the connection with that new setting but we'll
- // only try it once.
- if ($bad_key_size_fix != $this->bad_key_size_fix) {
- if (!$this->_connect()) {
- return false;
- }
- return $this->_login_helper($username, $password);
+ $packet = Strings::packSSH2('Cs', NET_SSH2_MSG_SERVICE_REQUEST, 'ssh-userauth');
+ $this->send_binary_packet($packet);
+
+ try {
+ $response = $this->get_binary_packet_or_close(NET_SSH2_MSG_SERVICE_ACCEPT);
+ } catch (InvalidPacketLengthException $e) {
+ // the first opportunity to encounter the "bad key size" error
+ if (!$this->bad_key_size_fix && $this->decryptName != null && self::bad_algorithm_candidate($this->decryptName)) {
+ // bad_key_size_fix is only ever re-assigned to true here
+ // retry the connection with that new setting but we'll
+ // only try it once.
+ $this->bad_key_size_fix = true;
+ return $this->reconnect();
}
- $this->bitmap = 0;
- user_error('Connection closed by server');
- return false;
+ throw $e;
}
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Ctype', $this->_string_shift($response, 1)));
+ list($type) = Strings::unpackSSH2('C', $response);
+ list($service) = Strings::unpackSSH2('s', $response);
- if ($type == NET_SSH2_MSG_EXT_INFO) {
- if (strlen($response) < 4) {
- return false;
- }
- $nr_extensions = unpack('Nlength', $this->_string_shift($response, 4));
- for ($i = 0; $i < $nr_extensions['length']; $i++) {
- if (strlen($response) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $this->_string_shift($response, 4));
- $extension_name = $this->_string_shift($response, $temp['length']);
- if ($extension_name == 'server-sig-algs') {
- if (strlen($response) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $this->_string_shift($response, 4));
- $this->supported_private_key_algorithms = explode(',', $this->_string_shift($response, $temp['length']));
- }
- }
-
- $response = $this->_get_binary_packet();
- if ($response === false) {
- $this->bitmap = 0;
- user_error('Connection closed by server');
- return false;
- }
- extract(unpack('Ctype', $this->_string_shift($response, 1)));
- }
-
- if ($type != NET_SSH2_MSG_SERVICE_ACCEPT) {
- user_error('Expected SSH_MSG_SERVICE_ACCEPT');
- return false;
+ if ($service != 'ssh-userauth') {
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
+ throw new \UnexpectedValueException('Expected SSH_MSG_SERVICE_ACCEPT');
}
$this->bitmap |= self::MASK_LOGIN_REQ;
}
if (strlen($this->last_interactive_response)) {
- return !is_string($password) && !is_array($password) ? false : $this->_keyboard_interactive_process($password);
+ return !Strings::is_stringable($password) && !is_array($password) ? false : $this->keyboard_interactive_process($password);
+ }
+
+ if ($password instanceof PrivateKey) {
+ return $this->privatekey_login($username, $password);
}
- if ($password instanceof RSA) {
- return $this->_privatekey_login($username, $password);
- } elseif ($password instanceof Agent) {
- return $this->_ssh_agent_login($username, $password);
+ if ($password instanceof Agent) {
+ return $this->ssh_agent_login($username, $password);
}
if (is_array($password)) {
- if ($this->_keyboard_interactive_login($username, $password)) {
+ if ($this->keyboard_interactive_login($username, $password)) {
$this->bitmap |= self::MASK_LOGIN;
return true;
}
@@ -2558,56 +2447,39 @@ class SSH2
}
if (!isset($password)) {
- $packet = pack(
- 'CNa*Na*Na*',
+ $packet = Strings::packSSH2(
+ 'Cs3',
NET_SSH2_MSG_USERAUTH_REQUEST,
- strlen($username),
$username,
- strlen('ssh-connection'),
'ssh-connection',
- strlen('none'),
'none'
);
- if (!$this->_send_binary_packet($packet)) {
- return false;
- }
-
- $response = $this->_get_binary_packet();
- if ($response === false) {
- $this->bitmap = 0;
- user_error('Connection closed by server');
- return false;
- }
+ $this->send_binary_packet($packet);
- if (!strlen($response)) {
- return false;
- }
- extract(unpack('Ctype', $this->_string_shift($response, 1)));
+ $response = $this->get_binary_packet_or_close();
+ list($type) = Strings::unpackSSH2('C', $response);
switch ($type) {
case NET_SSH2_MSG_USERAUTH_SUCCESS:
$this->bitmap |= self::MASK_LOGIN;
return true;
case NET_SSH2_MSG_USERAUTH_FAILURE:
- extract(unpack('Nmethodlistlen', $this->_string_shift($response, 4)));
- $this->auth_methods_to_continue = explode(',', $this->_string_shift($response, $methodlistlen));
+ list($auth_methods) = Strings::unpackSSH2('L', $response);
+ $this->auth_methods_to_continue = $auth_methods;
+ // fall-through
default:
return false;
}
}
- $packet = pack(
- 'CNa*Na*Na*CNa*',
+ $packet = Strings::packSSH2(
+ 'Cs3bs',
NET_SSH2_MSG_USERAUTH_REQUEST,
- strlen($username),
$username,
- strlen('ssh-connection'),
'ssh-connection',
- strlen('password'),
'password',
- 0,
- strlen($password),
+ false,
$password
);
@@ -2615,63 +2487,36 @@ class SSH2
if (!defined('NET_SSH2_LOGGING')) {
$logged = null;
} else {
- $logged = pack(
- 'CNa*Na*Na*CNa*',
+ $logged = Strings::packSSH2(
+ 'Cs3bs',
NET_SSH2_MSG_USERAUTH_REQUEST,
- strlen('username'),
- 'username',
- strlen('ssh-connection'),
+ $username,
'ssh-connection',
- strlen('password'),
'password',
- 0,
- strlen('password'),
+ false,
'password'
);
}
- if (!$this->_send_binary_packet($packet, $logged)) {
- return false;
- }
-
- $response = $this->_get_binary_packet();
- if ($response === false) {
- $this->bitmap = 0;
- user_error('Connection closed by server');
- return false;
- }
-
- if (!strlen($response)) {
- return false;
- }
- extract(unpack('Ctype', $this->_string_shift($response, 1)));
+ $this->send_binary_packet($packet, $logged);
+ $response = $this->get_binary_packet_or_close();
+ list($type) = Strings::unpackSSH2('C', $response);
switch ($type) {
case NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: // in theory, the password can be changed
- $this->_updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ');
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
- $this->errors[] = 'SSH_MSG_USERAUTH_PASSWD_CHANGEREQ: ' . $this->_string_shift($response, $length);
- return $this->_disconnect(NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
+ $this->updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ');
+
+ list($message) = Strings::unpackSSH2('s', $response);
+ $this->errors[] = 'SSH_MSG_USERAUTH_PASSWD_CHANGEREQ: ' . $message;
+
+ return $this->disconnect_helper(NET_SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
case NET_SSH2_MSG_USERAUTH_FAILURE:
// can we use keyboard-interactive authentication? if not then either the login is bad or the server employees
// multi-factor authentication
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
- $auth_methods = explode(',', $this->_string_shift($response, $length));
+ list($auth_methods, $partial_success) = Strings::unpackSSH2('Lb', $response);
$this->auth_methods_to_continue = $auth_methods;
- if (!strlen($response)) {
- return false;
- }
- extract(unpack('Cpartial_success', $this->_string_shift($response, 1)));
- $partial_success = $partial_success != 0;
-
if (!$partial_success && in_array('keyboard-interactive', $auth_methods)) {
- if ($this->_keyboard_interactive_login($username, $password)) {
+ if ($this->keyboard_interactive_login($username, $password)) {
$this->bitmap |= self::MASK_LOGIN;
return true;
}
@@ -2692,81 +2537,49 @@ class SSH2
* See {@link http://tools.ietf.org/html/rfc4256 RFC4256} for details. This is not a full-featured keyboard-interactive authenticator.
*
* @param string $username
- * @param string $password
+ * @param string|array $password
* @return bool
- * @access private
*/
- function _keyboard_interactive_login($username, $password)
+ private function keyboard_interactive_login($username, $password)
{
- $packet = pack(
- 'CNa*Na*Na*Na*Na*',
+ $packet = Strings::packSSH2(
+ 'Cs5',
NET_SSH2_MSG_USERAUTH_REQUEST,
- strlen($username),
$username,
- strlen('ssh-connection'),
'ssh-connection',
- strlen('keyboard-interactive'),
'keyboard-interactive',
- 0,
- '',
- 0,
- ''
+ '', // language tag
+ '' // submethods
);
+ $this->send_binary_packet($packet);
- if (!$this->_send_binary_packet($packet)) {
- return false;
- }
-
- return $this->_keyboard_interactive_process($password);
+ return $this->keyboard_interactive_process($password);
}
/**
* Handle the keyboard-interactive requests / responses.
*
+ * @param string|array ...$responses
* @return bool
- * @access private
+ * @throws \RuntimeException on connection error
*/
- function _keyboard_interactive_process()
+ private function keyboard_interactive_process(...$responses)
{
- $responses = func_get_args();
-
if (strlen($this->last_interactive_response)) {
$response = $this->last_interactive_response;
} else {
- $orig = $response = $this->_get_binary_packet();
- if ($response === false) {
- $this->bitmap = 0;
- user_error('Connection closed by server');
- return false;
- }
+ $orig = $response = $this->get_binary_packet_or_close();
}
- if (!strlen($response)) {
- return false;
- }
- extract(unpack('Ctype', $this->_string_shift($response, 1)));
-
+ list($type) = Strings::unpackSSH2('C', $response);
switch ($type) {
case NET_SSH2_MSG_USERAUTH_INFO_REQUEST:
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
- $this->_string_shift($response, $length); // name; may be empty
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
- $this->_string_shift($response, $length); // instruction; may be empty
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
- $this->_string_shift($response, $length); // language tag; may be empty
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nnum_prompts', $this->_string_shift($response, 4)));
+ list(
+ , // name; may be empty
+ , // instruction; may be empty
+ , // language tag; may be empty
+ $num_prompts
+ ) = Strings::unpackSSH2('s3N', $response);
for ($i = 0; $i < count($responses); $i++) {
if (is_array($responses[$i])) {
@@ -2780,13 +2593,10 @@ class SSH2
if (isset($this->keyboard_requests_responses)) {
for ($i = 0; $i < $num_prompts; $i++) {
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
- // prompt - ie. "Password: "; must not be empty
- $prompt = $this->_string_shift($response, $length);
- //$echo = $this->_string_shift($response) != chr(0);
+ list(
+ $prompt, // prompt - ie. "Password: "; must not be empty
+ // echo
+ ) = Strings::unpackSSH2('sC', $response);
foreach ($this->keyboard_requests_responses as $key => $value) {
if (substr($prompt, 0, strlen($key)) == $key) {
$responses[] = $value;
@@ -2800,7 +2610,7 @@ class SSH2
if (strlen($this->last_interactive_response)) {
$this->last_interactive_response = '';
} else {
- $this->_updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST');
+ $this->updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_INFO_REQUEST');
}
if (!count($responses) && $num_prompts) {
@@ -2815,15 +2625,13 @@ class SSH2
// see http://tools.ietf.org/html/rfc4256#section-3.4
$packet = $logged = pack('CN', NET_SSH2_MSG_USERAUTH_INFO_RESPONSE, count($responses));
for ($i = 0; $i < count($responses); $i++) {
- $packet.= pack('Na*', strlen($responses[$i]), $responses[$i]);
- $logged.= pack('Na*', strlen('dummy-answer'), 'dummy-answer');
+ $packet .= Strings::packSSH2('s', $responses[$i]);
+ $logged .= Strings::packSSH2('s', 'dummy-answer');
}
- if (!$this->_send_binary_packet($packet, $logged)) {
- return false;
- }
+ $this->send_binary_packet($packet, $logged);
- $this->_updateLogHistory('UNKNOWN (61)', 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE');
+ $this->updateLogHistory('UNKNOWN (61)', 'NET_SSH2_MSG_USERAUTH_INFO_RESPONSE');
/*
After receiving the response, the server MUST send either an
@@ -2832,12 +2640,12 @@ class SSH2
*/
// maybe phpseclib should force close the connection after x request / responses? unless something like that is done
// there could be an infinite loop of request / responses.
- return $this->_keyboard_interactive_process();
+ return $this->keyboard_interactive_process();
case NET_SSH2_MSG_USERAUTH_SUCCESS:
return true;
case NET_SSH2_MSG_USERAUTH_FAILURE:
- extract(unpack('Nmethodlistlen', $this->_string_shift($response, 4)));
- $this->auth_methods_to_continue = explode(',', $this->_string_shift($response, $methodlistlen));
+ list($auth_methods) = Strings::unpackSSH2('L', $response);
+ $this->auth_methods_to_continue = $auth_methods;
return false;
}
@@ -2848,17 +2656,16 @@ class SSH2
* Login with an ssh-agent provided key
*
* @param string $username
- * @param \phpseclib\System\SSH\Agent $agent
+ * @param Agent $agent
* @return bool
- * @access private
*/
- function _ssh_agent_login($username, $agent)
+ private function ssh_agent_login($username, Agent $agent)
{
$this->agent = $agent;
$keys = $agent->requestIdentities();
$orig_algorithms = $this->supported_private_key_algorithms;
foreach ($keys as $key) {
- if ($this->_privatekey_login($username, $key)) {
+ if ($this->privatekey_login($username, $key)) {
return true;
}
$this->supported_private_key_algorithms = $orig_algorithms;
@@ -2870,95 +2677,100 @@ class SSH2
/**
* Login with an RSA private key
*
+ * {@internal It might be worthwhile, at some point, to protect against {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis}
+ * by sending dummy SSH_MSG_IGNORE messages.}
+ *
* @param string $username
- * @param \phpseclib\Crypt\RSA $privatekey
+ * @param PrivateKey $privatekey
* @return bool
- * @access private
- * @internal It might be worthwhile, at some point, to protect against {@link http://tools.ietf.org/html/rfc4251#section-9.3.9 traffic analysis}
- * by sending dummy SSH_MSG_IGNORE messages.
+ * @throws \RuntimeException on connection error
*/
- function _privatekey_login($username, $privatekey)
+ private function privatekey_login($username, PrivateKey $privatekey)
{
- // see http://tools.ietf.org/html/rfc4253#page-15
- $publickey = $privatekey->getPublicKey(RSA::PUBLIC_FORMAT_RAW);
- if ($publickey === false) {
- return false;
- }
+ $publickey = $privatekey->getPublicKey();
- $publickey = array(
- 'e' => $publickey['e']->toBytes(true),
- 'n' => $publickey['n']->toBytes(true)
- );
- $publickey = pack(
- 'Na*Na*Na*',
- strlen('ssh-rsa'),
- 'ssh-rsa',
- strlen($publickey['e']),
- $publickey['e'],
- strlen($publickey['n']),
- $publickey['n']
- );
-
- $algos = array('rsa-sha2-256', 'rsa-sha2-512', 'ssh-rsa');
- if (isset($this->preferred['hostkey'])) {
- $algos = array_intersect($algos, $this->preferred['hostkey']);
+ if ($publickey instanceof RSA) {
+ $privatekey = $privatekey->withPadding(RSA::SIGNATURE_PKCS1);
+ $algos = ['rsa-sha2-256', 'rsa-sha2-512', 'ssh-rsa'];
+ if (isset($this->preferred['hostkey'])) {
+ $algos = array_intersect($algos, $this->preferred['hostkey']);
+ }
+ $algo = self::array_intersect_first($algos, $this->supported_private_key_algorithms);
+ switch ($algo) {
+ case 'rsa-sha2-512':
+ $hash = 'sha512';
+ $signatureType = 'rsa-sha2-512';
+ break;
+ case 'rsa-sha2-256':
+ $hash = 'sha256';
+ $signatureType = 'rsa-sha2-256';
+ break;
+ //case 'ssh-rsa':
+ default:
+ $hash = 'sha1';
+ $signatureType = 'ssh-rsa';
+ }
+ } elseif ($publickey instanceof EC) {
+ $privatekey = $privatekey->withSignatureFormat('SSH2');
+ $curveName = $privatekey->getCurve();
+ switch ($curveName) {
+ case 'Ed25519':
+ $hash = 'sha512';
+ $signatureType = 'ssh-ed25519';
+ break;
+ case 'secp256r1': // nistp256
+ $hash = 'sha256';
+ $signatureType = 'ecdsa-sha2-nistp256';
+ break;
+ case 'secp384r1': // nistp384
+ $hash = 'sha384';
+ $signatureType = 'ecdsa-sha2-nistp384';
+ break;
+ case 'secp521r1': // nistp521
+ $hash = 'sha512';
+ $signatureType = 'ecdsa-sha2-nistp521';
+ break;
+ default:
+ if (is_array($curveName)) {
+ throw new UnsupportedCurveException('Specified Curves are not supported by SSH2');
+ }
+ throw new UnsupportedCurveException('Named Curve of ' . $curveName . ' is not supported by phpseclib3\'s SSH2 implementation');
+ }
+ } elseif ($publickey instanceof DSA) {
+ $privatekey = $privatekey->withSignatureFormat('SSH2');
+ $hash = 'sha1';
+ $signatureType = 'ssh-dss';
+ } else {
+ throw new UnsupportedAlgorithmException('Please use either an RSA key, an EC one or a DSA key');
}
- $algo = $this->_array_intersect_first($algos, $this->supported_private_key_algorithms);
- switch ($algo) {
- case 'rsa-sha2-512':
- $hash = 'sha512';
- $signatureType = 'rsa-sha2-512';
- break;
- case 'rsa-sha2-256':
- $hash = 'sha256';
- $signatureType = 'rsa-sha2-256';
- break;
- //case 'ssh-rsa':
- default:
- $hash = 'sha1';
- $signatureType = 'ssh-rsa';
- }
+ $publickeyStr = $publickey->toString('OpenSSH', ['binary' => true]);
- $part1 = pack(
- 'CNa*Na*Na*',
+ $part1 = Strings::packSSH2(
+ 'Csss',
NET_SSH2_MSG_USERAUTH_REQUEST,
- strlen($username),
$username,
- strlen('ssh-connection'),
'ssh-connection',
- strlen('publickey'),
'publickey'
);
- $part2 = pack('Na*Na*', strlen($signatureType), $signatureType, strlen($publickey), $publickey);
+ $part2 = Strings::packSSH2('ss', $signatureType, $publickeyStr);
$packet = $part1 . chr(0) . $part2;
- if (!$this->_send_binary_packet($packet)) {
- return false;
- }
-
- $response = $this->_get_binary_packet();
- if ($response === false) {
- $this->bitmap = 0;
- user_error('Connection closed by server');
- return false;
- }
+ $this->send_binary_packet($packet);
- if (!strlen($response)) {
- return false;
- }
- extract(unpack('Ctype', $this->_string_shift($response, 1)));
+ $response = $this->get_binary_packet_or_close(
+ NET_SSH2_MSG_USERAUTH_SUCCESS,
+ NET_SSH2_MSG_USERAUTH_FAILURE,
+ NET_SSH2_MSG_USERAUTH_PK_OK
+ );
+ list($type) = Strings::unpackSSH2('C', $response);
switch ($type) {
case NET_SSH2_MSG_USERAUTH_FAILURE:
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nmethodlistlen', $this->_string_shift($response, 4)));
- $auth_methods = explode(',', $this->_string_shift($response, $methodlistlen));
+ list($auth_methods) = Strings::unpackSSH2('L', $response);
if (in_array('publickey', $auth_methods) && substr($signatureType, 0, 9) == 'rsa-sha2-') {
- $this->supported_private_key_algorithms = array_diff($this->supported_private_key_algorithms, array('rsa-sha2-256', 'rsa-sha2-512'));
- return $this->_privatekey_login($username, $privatekey);
+ $this->supported_private_key_algorithms = array_diff($this->supported_private_key_algorithms, ['rsa-sha2-256', 'rsa-sha2-512']);
+ return $this->privatekey_login($username, $privatekey);
}
$this->auth_methods_to_continue = $auth_methods;
$this->errors[] = 'SSH_MSG_USERAUTH_FAILURE';
@@ -2966,52 +2778,39 @@ class SSH2
case NET_SSH2_MSG_USERAUTH_PK_OK:
// we'll just take it on faith that the public key blob and the public key algorithm name are as
// they should be
- $this->_updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_PK_OK');
+ $this->updateLogHistory('UNKNOWN (60)', 'NET_SSH2_MSG_USERAUTH_PK_OK');
break;
case NET_SSH2_MSG_USERAUTH_SUCCESS:
$this->bitmap |= self::MASK_LOGIN;
return true;
- default:
- user_error('Unexpected response to publickey authentication pt 1');
- return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
}
$packet = $part1 . chr(1) . $part2;
- $privatekey->setSignatureMode(RSA::SIGNATURE_PKCS1);
- $privatekey->setHash($hash);
- $signature = $privatekey->sign(pack('Na*a*', strlen($this->session_id), $this->session_id, $packet));
- $signature = pack('Na*Na*', strlen($signatureType), $signatureType, strlen($signature), $signature);
- $packet.= pack('Na*', strlen($signature), $signature);
-
- if (!$this->_send_binary_packet($packet)) {
- return false;
+ $privatekey = $privatekey->withHash($hash);
+ $signature = $privatekey->sign(Strings::packSSH2('s', $this->session_id) . $packet);
+ if ($publickey instanceof RSA) {
+ $signature = Strings::packSSH2('ss', $signatureType, $signature);
}
+ $packet .= Strings::packSSH2('s', $signature);
- $response = $this->_get_binary_packet();
- if ($response === false) {
- $this->bitmap = 0;
- user_error('Connection closed by server');
- return false;
- }
+ $this->send_binary_packet($packet);
- if (!strlen($response)) {
- return false;
- }
- extract(unpack('Ctype', $this->_string_shift($response, 1)));
+ $response = $this->get_binary_packet_or_close(
+ NET_SSH2_MSG_USERAUTH_SUCCESS,
+ NET_SSH2_MSG_USERAUTH_FAILURE
+ );
+ list($type) = Strings::unpackSSH2('C', $response);
switch ($type) {
case NET_SSH2_MSG_USERAUTH_FAILURE:
// either the login is bad or the server employs multi-factor authentication
- extract(unpack('Nmethodlistlen', $this->_string_shift($response, 4)));
- $this->auth_methods_to_continue = explode(',', $this->_string_shift($response, $methodlistlen));
+ list($auth_methods) = Strings::unpackSSH2('L', $response);
+ $this->auth_methods_to_continue = $auth_methods;
return false;
case NET_SSH2_MSG_USERAUTH_SUCCESS:
$this->bitmap |= self::MASK_LOGIN;
return true;
}
-
- user_error('Unexpected response to publickey authentication pt 2');
- return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
}
/**
@@ -3019,7 +2818,7 @@ class SSH2
*
* @return int
*/
- function getTimeout()
+ public function getTimeout()
{
return $this->timeout;
}
@@ -3028,12 +2827,11 @@ class SSH2
* Set Timeout
*
* $ssh->exec('ping 127.0.0.1'); on a Linux host will never return and will run indefinitely. setTimeout() makes it so it'll timeout.
- * Setting $timeout to false or 0 will mean there is no timeout.
+ * Setting $timeout to false or 0 will revert to the default socket timeout.
*
* @param mixed $timeout
- * @access public
*/
- function setTimeout($timeout)
+ public function setTimeout($timeout)
{
$this->timeout = $this->curTimeout = $timeout;
}
@@ -3044,9 +2842,8 @@ class SSH2
* Sends an SSH2_MSG_IGNORE message every x seconds, if x is a positive non-zero number.
*
* @param int $interval
- * @access public
*/
- function setKeepAlive($interval)
+ public function setKeepAlive($interval)
{
$this->keepAlive = $interval;
}
@@ -3054,9 +2851,8 @@ class SSH2
/**
* Get the output from stdError
*
- * @access public
*/
- function getStdError()
+ public function getStdError()
{
return $this->stdErrorLog;
}
@@ -3064,15 +2860,16 @@ class SSH2
/**
* Execute Command
*
- * If $callback is set to false then \phpseclib\Net\SSH2::_get_channel_packet(self::CHANNEL_EXEC) will need to be called manually.
+ * If $callback is set to false then \phpseclib3\Net\SSH2::get_channel_packet(self::CHANNEL_EXEC) will need to be called manually.
* In all likelihood, this is not a feature you want to be taking advantage of.
*
* @param string $command
- * @param Callback $callback
- * @return string
- * @access public
+ * @param callable $callback
+ * @return string|bool
+ * @psalm-return ($callback is callable ? bool : string|bool)
+ * @throws \RuntimeException on connection error
*/
- function exec($command, $callback = null)
+ public function exec($command, $callback = null)
{
$this->curTimeout = $this->timeout;
$this->is_timeout = false;
@@ -3082,75 +2879,39 @@ class SSH2
return false;
}
- if ($this->in_request_pty_exec) {
- user_error('If you want to run multiple exec()\'s you will need to disable (and re-enable if appropriate) a PTY for each one.');
- return false;
- }
+ //if ($this->isPTYOpen()) {
+ // throw new \RuntimeException('If you want to run multiple exec()\'s you will need to disable (and re-enable if appropriate) a PTY for each one.');
+ //}
- // RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to
- // be adjusted". 0x7FFFFFFF is, at 2GB, the max size. technically, it should probably be decremented, but,
- // honestly, if you're transferring more than 2GB, you probably shouldn't be using phpseclib, anyway.
- // see http://tools.ietf.org/html/rfc4254#section-5.2 for more info
- $this->window_size_server_to_client[self::CHANNEL_EXEC] = $this->window_size;
- // 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy
- // uses 0x4000, that's what will be used here, as well.
- $packet_size = 0x4000;
-
- $packet = pack(
- 'CNa*N3',
- NET_SSH2_MSG_CHANNEL_OPEN,
- strlen('session'),
- 'session',
- self::CHANNEL_EXEC,
- $this->window_size_server_to_client[self::CHANNEL_EXEC],
- $packet_size
- );
-
- if (!$this->_send_binary_packet($packet)) {
- return false;
- }
-
- $this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_OPEN;
-
- $response = $this->_get_channel_packet(self::CHANNEL_EXEC);
- if ($response === false) {
- return false;
- }
+ $this->open_channel(self::CHANNEL_EXEC);
if ($this->request_pty === true) {
$terminal_modes = pack('C', NET_SSH2_TTY_OP_END);
- $packet = pack(
- 'CNNa*CNa*N5a*',
+ $packet = Strings::packSSH2(
+ 'CNsCsN4s',
NET_SSH2_MSG_CHANNEL_REQUEST,
$this->server_channels[self::CHANNEL_EXEC],
- strlen('pty-req'),
'pty-req',
1,
- strlen('vt100'),
- 'vt100',
+ $this->term,
$this->windowColumns,
$this->windowRows,
0,
0,
- strlen($terminal_modes),
$terminal_modes
);
- if (!$this->_send_binary_packet($packet)) {
- return false;
- }
+ $this->send_binary_packet($packet);
$this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_REQUEST;
- if (!$this->_get_channel_packet(self::CHANNEL_EXEC)) {
- user_error('Unable to request pseudo-terminal');
- return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
+ if (!$this->get_channel_packet(self::CHANNEL_EXEC)) {
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
+ throw new \RuntimeException('Unable to request pseudo-terminal');
}
-
- $this->in_request_pty_exec = true;
}
// sending a pty-req SSH_MSG_CHANNEL_REQUEST message is unnecessary and, in fact, in most cases, slows things
- // down. the one place where it might be desirable is if you're doing something like \phpseclib\Net\SSH2::exec('ping localhost &').
+ // down. the one place where it might be desirable is if you're doing something like \phpseclib3\Net\SSH2::exec('ping localhost &').
// with a pty-req SSH_MSG_CHANNEL_REQUEST, exec() will return immediately and the ping process will then
// then immediately terminate. without such a request exec() will loop indefinitely. the ping process won't end but
// neither will your script.
@@ -3158,36 +2919,32 @@ class SSH2
// although, in theory, the size of SSH_MSG_CHANNEL_REQUEST could exceed the maximum packet size established by
// SSH_MSG_CHANNEL_OPEN_CONFIRMATION, RFC4254#section-5.1 states that the "maximum packet size" refers to the
// "maximum size of an individual data packet". ie. SSH_MSG_CHANNEL_DATA. RFC4254#section-5.2 corroborates.
- $packet = pack(
- 'CNNa*CNa*',
+ $packet = Strings::packSSH2(
+ 'CNsCs',
NET_SSH2_MSG_CHANNEL_REQUEST,
$this->server_channels[self::CHANNEL_EXEC],
- strlen('exec'),
'exec',
1,
- strlen($command),
$command
);
- if (!$this->_send_binary_packet($packet)) {
- return false;
- }
+ $this->send_binary_packet($packet);
$this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_REQUEST;
- $response = $this->_get_channel_packet(self::CHANNEL_EXEC);
- if ($response === false) {
+ if (!$this->get_channel_packet(self::CHANNEL_EXEC)) {
return false;
}
$this->channel_status[self::CHANNEL_EXEC] = NET_SSH2_MSG_CHANNEL_DATA;
- if ($callback === false || $this->in_request_pty_exec) {
+ if ($this->request_pty === true) {
+ $this->channel_id_last_interactive = self::CHANNEL_EXEC;
return true;
}
$output = '';
while (true) {
- $temp = $this->_get_channel_packet(self::CHANNEL_EXEC);
+ $temp = $this->get_channel_packet(self::CHANNEL_EXEC);
switch (true) {
case $temp === true:
return is_callable($callback) ? true : $output;
@@ -3195,122 +2952,154 @@ class SSH2
return false;
default:
if (is_callable($callback)) {
- if (call_user_func($callback, $temp) === true) {
- $this->_close_channel(self::CHANNEL_EXEC);
+ if ($callback($temp) === true) {
+ $this->close_channel(self::CHANNEL_EXEC);
return true;
}
} else {
- $output.= $temp;
+ $output .= $temp;
}
}
}
}
/**
- * Creates an interactive shell
+ * How many channels are currently open?
*
- * @see self::read()
- * @see self::write()
+ * @return int
+ */
+ public function getOpenChannelCount()
+ {
+ return $this->channelCount;
+ }
+
+ /**
+ * Opens a channel
+ *
+ * @param string $channel
+ * @param bool $skip_extended
* @return bool
- * @access private
*/
- function _initShell()
+ protected function open_channel($channel, $skip_extended = false)
{
- if ($this->in_request_pty_exec === true) {
- return true;
+ if (isset($this->channel_status[$channel])) {
+ throw new \RuntimeException('Please close the channel (' . $channel . ') before trying to open it again');
}
- $this->window_size_server_to_client[self::CHANNEL_SHELL] = $this->window_size;
+ $this->channelCount++;
+
+ if ($this->channelCount > 1 && $this->errorOnMultipleChannels) {
+ throw new \RuntimeException("Ubuntu's OpenSSH from 5.8 to 6.9 doesn't work with multiple channels");
+ }
+
+ // RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to
+ // be adjusted". 0x7FFFFFFF is, at 2GB, the max size. technically, it should probably be decremented, but,
+ // honestly, if you're transferring more than 2GB, you probably shouldn't be using phpseclib, anyway.
+ // see http://tools.ietf.org/html/rfc4254#section-5.2 for more info
+ $this->window_size_server_to_client[$channel] = $this->window_size;
+ // 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy
+ // uses 0x4000, that's what will be used here, as well.
$packet_size = 0x4000;
- $packet = pack(
- 'CNa*N3',
+ $packet = Strings::packSSH2(
+ 'CsN3',
NET_SSH2_MSG_CHANNEL_OPEN,
- strlen('session'),
'session',
- self::CHANNEL_SHELL,
- $this->window_size_server_to_client[self::CHANNEL_SHELL],
+ $channel,
+ $this->window_size_server_to_client[$channel],
$packet_size
);
- if (!$this->_send_binary_packet($packet)) {
- return false;
- }
+ $this->send_binary_packet($packet);
- $this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_OPEN;
+ $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_OPEN;
- $response = $this->_get_channel_packet(self::CHANNEL_SHELL);
- if ($response === false) {
- return false;
+ return $this->get_channel_packet($channel, $skip_extended);
+ }
+
+ /**
+ * Creates an interactive shell
+ *
+ * Returns bool(true) if the shell was opened.
+ * Returns bool(false) if the shell was already open.
+ *
+ * @see self::isShellOpen()
+ * @see self::read()
+ * @see self::write()
+ * @return bool
+ * @throws InsufficientSetupException if not authenticated
+ * @throws \UnexpectedValueException on receipt of unexpected packets
+ * @throws \RuntimeException on other errors
+ */
+ public function openShell()
+ {
+ if (!$this->isAuthenticated()) {
+ throw new InsufficientSetupException('Operation disallowed prior to login()');
}
+ $this->open_channel(self::CHANNEL_SHELL);
+
$terminal_modes = pack('C', NET_SSH2_TTY_OP_END);
- $packet = pack(
- 'CNNa*CNa*N5a*',
+ $packet = Strings::packSSH2(
+ 'CNsbsN4s',
NET_SSH2_MSG_CHANNEL_REQUEST,
$this->server_channels[self::CHANNEL_SHELL],
- strlen('pty-req'),
'pty-req',
- 1,
- strlen('vt100'),
- 'vt100',
+ true, // want reply
+ $this->term,
$this->windowColumns,
$this->windowRows,
0,
0,
- strlen($terminal_modes),
$terminal_modes
);
- if (!$this->_send_binary_packet($packet)) {
- return false;
- }
+ $this->send_binary_packet($packet);
$this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_REQUEST;
- if (!$this->_get_channel_packet(self::CHANNEL_SHELL)) {
- user_error('Unable to request pseudo-terminal');
- return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
+ if (!$this->get_channel_packet(self::CHANNEL_SHELL)) {
+ throw new \RuntimeException('Unable to request pty');
}
- $packet = pack(
- 'CNNa*C',
+ $packet = Strings::packSSH2(
+ 'CNsb',
NET_SSH2_MSG_CHANNEL_REQUEST,
$this->server_channels[self::CHANNEL_SHELL],
- strlen('shell'),
'shell',
- 1
+ true // want reply
);
- if (!$this->_send_binary_packet($packet)) {
- return false;
- }
+ $this->send_binary_packet($packet);
- $response = $this->_get_channel_packet(self::CHANNEL_SHELL);
+ $response = $this->get_channel_packet(self::CHANNEL_SHELL);
if ($response === false) {
- return false;
+ throw new \RuntimeException('Unable to request shell');
}
$this->channel_status[self::CHANNEL_SHELL] = NET_SSH2_MSG_CHANNEL_DATA;
+ $this->channel_id_last_interactive = self::CHANNEL_SHELL;
+
$this->bitmap |= self::MASK_SHELL;
return true;
}
/**
- * Return the channel to be used with read() / write()
- *
+ * Return the channel to be used with read(), write(), and reset(), if none were specified
+ * @deprecated for lack of transparency in intended channel target, to be potentially replaced
+ * with method which guarantees open-ness of all yielded channels and throws
+ * error for multiple open channels
* @see self::read()
* @see self::write()
* @return int
- * @access public
*/
- function _get_interactive_channel()
+ private function get_interactive_channel()
{
switch (true) {
- case $this->in_subsystem:
+ case $this->is_channel_status_data(self::CHANNEL_SUBSYSTEM):
return self::CHANNEL_SUBSYSTEM;
- case $this->in_request_pty_exec:
+ case $this->is_channel_status_data(self::CHANNEL_EXEC):
return self::CHANNEL_EXEC;
default:
return self::CHANNEL_SHELL;
@@ -3318,12 +3107,22 @@ class SSH2
}
/**
+ * Indicates the DATA status on the given channel
+ *
+ * @param int $channel The channel number to evaluate
+ * @return bool
+ */
+ private function is_channel_status_data($channel)
+ {
+ return isset($this->channel_status[$channel]) && $this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_DATA;
+ }
+
+ /**
* Return an available open channel
*
* @return int
- * @access public
*/
- function _get_open_channel()
+ private function get_open_channel()
{
$channel = self::CHANNEL_EXEC;
do {
@@ -3336,36 +3135,82 @@ class SSH2
}
/**
+ * Request agent forwarding of remote server
+ *
+ * @return bool
+ */
+ public function requestAgentForwarding()
+ {
+ $request_channel = $this->get_open_channel();
+ if ($request_channel === false) {
+ return false;
+ }
+
+ $packet = Strings::packSSH2(
+ 'CNsC',
+ NET_SSH2_MSG_CHANNEL_REQUEST,
+ $this->server_channels[$request_channel],
+ 'auth-agent-req@openssh.com',
+ 1
+ );
+
+ $this->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_REQUEST;
+
+ $this->send_binary_packet($packet);
+
+ if (!$this->get_channel_packet($request_channel)) {
+ return false;
+ }
+
+ $this->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_OPEN;
+
+ return true;
+ }
+
+ /**
* Returns the output of an interactive shell
*
* Returns when there's a match for $expect, which can take the form of a string literal or,
* if $mode == self::READ_REGEX, a regular expression.
*
+ * If not specifying a channel, an open interactive channel will be selected, or, if there are
+ * no open channels, an interactive shell will be created. If there are multiple open
+ * interactive channels, a legacy behavior will apply in which channel selection prioritizes
+ * an active subsystem, the exec pty, and, lastly, the shell. If using multiple interactive
+ * channels, callers are discouraged from relying on this legacy behavior and should specify
+ * the intended channel.
+ *
* @see self::write()
* @param string $expect
- * @param int $mode
- * @return string|bool
- * @access public
+ * @param int $mode One of the self::READ_* constants
+ * @param int|null $channel Channel id returned by self::getInteractiveChannelId()
+ * @return string|bool|null
+ * @throws \RuntimeException on connection error
+ * @throws InsufficientSetupException on unexpected channel status, possibly due to closure
*/
- function read($expect = '', $mode = self::READ_SIMPLE)
+ public function read($expect = '', $mode = self::READ_SIMPLE, $channel = null)
{
+ if (!$this->isAuthenticated()) {
+ throw new InsufficientSetupException('Operation disallowed prior to login()');
+ }
+
$this->curTimeout = $this->timeout;
$this->is_timeout = false;
- if (!$this->isAuthenticated()) {
- user_error('Operation disallowed prior to login()');
- return false;
+ if ($channel === null) {
+ $channel = $this->get_interactive_channel();
}
- if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) {
- user_error('Unable to initiate an interactive shell session');
- return false;
+ if (!$this->is_channel_status_data($channel) && empty($this->channel_buffers[$channel])) {
+ if ($channel != self::CHANNEL_SHELL) {
+ throw new InsufficientSetupException('Data is not available on channel');
+ } elseif (!$this->openShell()) {
+ throw new \RuntimeException('Unable to initiate an interactive shell session');
+ }
}
- $channel = $this->_get_interactive_channel();
-
if ($mode == self::READ_NEXT) {
- return $this->_get_channel_packet($channel);
+ return $this->get_channel_packet($channel);
}
$match = $expect;
@@ -3376,39 +3221,56 @@ class SSH2
}
$pos = strlen($match) ? strpos($this->interactiveBuffer, $match) : false;
if ($pos !== false) {
- return $this->_string_shift($this->interactiveBuffer, $pos + strlen($match));
+ return Strings::shift($this->interactiveBuffer, $pos + strlen($match));
}
- $response = $this->_get_channel_packet($channel);
- if (is_bool($response)) {
- $this->in_request_pty_exec = false;
- return $response ? $this->_string_shift($this->interactiveBuffer, strlen($this->interactiveBuffer)) : false;
+ $response = $this->get_channel_packet($channel);
+ if ($response === true) {
+ return Strings::shift($this->interactiveBuffer, strlen($this->interactiveBuffer));
}
- $this->interactiveBuffer.= $response;
+ $this->interactiveBuffer .= $response;
}
}
/**
* Inputs a command into an interactive shell.
*
- * @see self::read()
+ * If not specifying a channel, an open interactive channel will be selected, or, if there are
+ * no open channels, an interactive shell will be created. If there are multiple open
+ * interactive channels, a legacy behavior will apply in which channel selection prioritizes
+ * an active subsystem, the exec pty, and, lastly, the shell. If using multiple interactive
+ * channels, callers are discouraged from relying on this legacy behavior and should specify
+ * the intended channel.
+ *
+ * @see SSH2::read()
* @param string $cmd
- * @return bool
- * @access public
+ * @param int|null $channel Channel id returned by self::getInteractiveChannelId()
+ * @return void
+ * @throws \RuntimeException on connection error
+ * @throws InsufficientSetupException on unexpected channel status, possibly due to closure
+ * @throws TimeoutException if the write could not be completed within the requested self::setTimeout()
*/
- function write($cmd)
+ public function write($cmd, $channel = null)
{
if (!$this->isAuthenticated()) {
- user_error('Operation disallowed prior to login()');
- return false;
+ throw new InsufficientSetupException('Operation disallowed prior to login()');
}
- if (!($this->bitmap & self::MASK_SHELL) && !$this->_initShell()) {
- user_error('Unable to initiate an interactive shell session');
- return false;
+ if ($channel === null) {
+ $channel = $this->get_interactive_channel();
+ }
+
+ if (!$this->is_channel_status_data($channel)) {
+ if ($channel != self::CHANNEL_SHELL) {
+ throw new InsufficientSetupException('Data is not available on channel');
+ } elseif (!$this->openShell()) {
+ throw new \RuntimeException('Unable to initiate an interactive shell session');
+ }
}
- return $this->_send_channel_packet($this->_get_interactive_channel(), $cmd);
+ $this->curTimeout = $this->timeout;
+ $this->is_timeout = false;
+ $this->send_channel_packet($channel, $cmd);
}
/**
@@ -3423,59 +3285,30 @@ class SSH2
* @see self::stopSubsystem()
* @param string $subsystem
* @return bool
- * @access public
*/
- function startSubsystem($subsystem)
+ public function startSubsystem($subsystem)
{
- $this->window_size_server_to_client[self::CHANNEL_SUBSYSTEM] = $this->window_size;
-
- $packet = pack(
- 'CNa*N3',
- NET_SSH2_MSG_CHANNEL_OPEN,
- strlen('session'),
- 'session',
- self::CHANNEL_SUBSYSTEM,
- $this->window_size,
- 0x4000
- );
+ $this->open_channel(self::CHANNEL_SUBSYSTEM);
- if (!$this->_send_binary_packet($packet)) {
- return false;
- }
-
- $this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_OPEN;
-
- $response = $this->_get_channel_packet(self::CHANNEL_SUBSYSTEM);
- if ($response === false) {
- return false;
- }
-
- $packet = pack(
- 'CNNa*CNa*',
+ $packet = Strings::packSSH2(
+ 'CNsCs',
NET_SSH2_MSG_CHANNEL_REQUEST,
$this->server_channels[self::CHANNEL_SUBSYSTEM],
- strlen('subsystem'),
'subsystem',
1,
- strlen($subsystem),
$subsystem
);
- if (!$this->_send_binary_packet($packet)) {
- return false;
- }
+ $this->send_binary_packet($packet);
$this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_REQUEST;
- $response = $this->_get_channel_packet(self::CHANNEL_SUBSYSTEM);
-
- if ($response === false) {
+ if (!$this->get_channel_packet(self::CHANNEL_SUBSYSTEM)) {
return false;
}
$this->channel_status[self::CHANNEL_SUBSYSTEM] = NET_SSH2_MSG_CHANNEL_DATA;
- $this->bitmap |= self::MASK_SHELL;
- $this->in_subsystem = true;
+ $this->channel_id_last_interactive = self::CHANNEL_SUBSYSTEM;
return true;
}
@@ -3485,12 +3318,12 @@ class SSH2
*
* @see self::startSubsystem()
* @return bool
- * @access public
*/
- function stopSubsystem()
+ public function stopSubsystem()
{
- $this->in_subsystem = false;
- $this->_close_channel(self::CHANNEL_SUBSYSTEM);
+ if ($this->isInteractiveChannelOpen(self::CHANNEL_SUBSYSTEM)) {
+ $this->close_channel(self::CHANNEL_SUBSYSTEM);
+ }
return true;
}
@@ -3499,11 +3332,44 @@ class SSH2
*
* If read() timed out you might want to just close the channel and have it auto-restart on the next read() call
*
- * @access public
+ * If not specifying a channel, an open interactive channel will be selected. If there are
+ * multiple open interactive channels, a legacy behavior will apply in which channel selection
+ * prioritizes an active subsystem, the exec pty, and, lastly, the shell. If using multiple
+ * interactive channels, callers are discouraged from relying on this legacy behavior and
+ * should specify the intended channel.
+ *
+ * @param int|null $channel Channel id returned by self::getInteractiveChannelId()
+ * @return void
*/
- function reset()
+ public function reset($channel = null)
{
- $this->_close_channel($this->_get_interactive_channel());
+ if ($channel === null) {
+ $channel = $this->get_interactive_channel();
+ }
+ if ($this->isInteractiveChannelOpen($channel)) {
+ $this->close_channel($channel);
+ }
+ }
+
+ /**
+ * Send EOF on a channel
+ *
+ * Sends an EOF to the stream; this is typically used to close standard
+ * input, while keeping output and error alive.
+ *
+ * @param int|null $channel Channel id returned by self::getInteractiveChannelId()
+ * @return void
+ */
+ public function sendEOF($channel = null)
+ {
+ if ($channel === null) {
+ $channel = $this->get_interactive_channel();
+ }
+
+ $excludeStatuses = [NET_SSH2_MSG_CHANNEL_EOF, NET_SSH2_MSG_CHANNEL_CLOSE];
+ if (isset($this->channel_status[$channel]) && !in_array($this->channel_status[$channel], $excludeStatuses)) {
+ $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$channel]));
+ }
}
/**
@@ -3511,9 +3377,8 @@ class SSH2
*
* Did exec() or read() return because they timed out or because they encountered the end?
*
- * @access public
*/
- function isTimeout()
+ public function isTimeout()
{
return $this->is_timeout;
}
@@ -3521,14 +3386,14 @@ class SSH2
/**
* Disconnect
*
- * @access public
*/
- function disconnect()
+ public function disconnect()
{
- $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
if (isset($this->realtime_log_file) && is_resource($this->realtime_log_file)) {
fclose($this->realtime_log_file);
}
+ unset(self::$connections[$this->getResourceId()]);
}
/**
@@ -3537,9 +3402,8 @@ class SSH2
* Will be called, automatically, if you're supporting just PHP5. If you're supporting PHP4, you'll need to call
* disconnect().
*
- * @access public
*/
- function __destruct()
+ public function __destruct()
{
$this->disconnect();
}
@@ -3547,102 +3411,201 @@ class SSH2
/**
* Is the connection still active?
*
+ * $level has 3x possible values:
+ * 0 (default): phpseclib takes a passive approach to see if the connection is still active by calling feof()
+ * on the socket
+ * 1: phpseclib takes an active approach to see if the connection is still active by sending an SSH_MSG_IGNORE
+ * packet that doesn't require a response
+ * 2: phpseclib takes an active approach to see if the connection is still active by sending an SSH_MSG_CHANNEL_OPEN
+ * packet and imediately trying to close that channel. some routers, in particular, however, will only let you
+ * open one channel, so this approach could yield false positives
+ *
+ * @param int $level
* @return bool
- * @access public
*/
- function isConnected()
+ public function isConnected($level = 0)
{
- return ($this->bitmap & self::MASK_CONNECTED) && is_resource($this->fsock) && !feof($this->fsock);
+ if (!is_int($level) || $level < 0 || $level > 2) {
+ throw new \InvalidArgumentException('$level must be 0, 1 or 2');
+ }
+
+ if ($level == 0) {
+ return ($this->bitmap & self::MASK_CONNECTED) && is_resource($this->fsock) && !feof($this->fsock);
+ }
+ try {
+ if ($level == 1) {
+ $this->send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0));
+ } else {
+ $this->open_channel(self::CHANNEL_KEEP_ALIVE);
+ $this->close_channel(self::CHANNEL_KEEP_ALIVE);
+ }
+ return true;
+ } catch (\Exception $e) {
+ return false;
+ }
}
/**
* Have you successfully been logged in?
*
* @return bool
- * @access public
*/
- function isAuthenticated()
+ public function isAuthenticated()
{
return (bool) ($this->bitmap & self::MASK_LOGIN);
}
/**
+ * Is the interactive shell active?
+ *
+ * @return bool
+ */
+ public function isShellOpen()
+ {
+ return $this->isInteractiveChannelOpen(self::CHANNEL_SHELL);
+ }
+
+ /**
+ * Is the exec pty active?
+ *
+ * @return bool
+ */
+ public function isPTYOpen()
+ {
+ return $this->isInteractiveChannelOpen(self::CHANNEL_EXEC);
+ }
+
+ /**
+ * Is the given interactive channel active?
+ *
+ * @param int $channel Channel id returned by self::getInteractiveChannelId()
+ * @return bool
+ */
+ public function isInteractiveChannelOpen($channel)
+ {
+ return $this->isAuthenticated() && $this->is_channel_status_data($channel);
+ }
+
+ /**
+ * Returns a channel identifier, presently of the last interactive channel opened, regardless of current status.
+ * Returns 0 if no interactive channel has been opened.
+ *
+ * @see self::isInteractiveChannelOpen()
+ * @return int
+ */
+ public function getInteractiveChannelId()
+ {
+ return $this->channel_id_last_interactive;
+ }
+
+ /**
* Pings a server connection, or tries to reconnect if the connection has gone down
*
* Inspired by http://php.net/manual/en/mysqli.ping.php
*
* @return bool
- * @access public
*/
- function ping()
+ public function ping()
{
if (!$this->isAuthenticated()) {
if (!empty($this->auth)) {
- return $this->_reconnect();
+ return $this->reconnect();
}
return false;
}
- $this->window_size_server_to_client[self::CHANNEL_KEEP_ALIVE] = $this->window_size;
- $packet_size = 0x4000;
- $packet = pack(
- 'CNa*N3',
- NET_SSH2_MSG_CHANNEL_OPEN,
- strlen('session'),
- 'session',
- self::CHANNEL_KEEP_ALIVE,
- $this->window_size_server_to_client[self::CHANNEL_KEEP_ALIVE],
- $packet_size
- );
-
- if (!@$this->_send_binary_packet($packet)) {
- return $this->_reconnect();
- }
-
- $this->channel_status[self::CHANNEL_KEEP_ALIVE] = NET_SSH2_MSG_CHANNEL_OPEN;
-
- $response = @$this->_get_channel_packet(self::CHANNEL_KEEP_ALIVE);
- if ($response !== false) {
- $this->_close_channel(self::CHANNEL_KEEP_ALIVE);
- return true;
+ try {
+ $this->open_channel(self::CHANNEL_KEEP_ALIVE);
+ } catch (\RuntimeException $e) {
+ return $this->reconnect();
}
- return $this->_reconnect();
+ $this->close_channel(self::CHANNEL_KEEP_ALIVE);
+ return true;
}
/**
* In situ reconnect method
*
* @return boolean
- * @access private
*/
- function _reconnect()
+ private function reconnect()
{
- $this->_reset_connection(NET_SSH2_DISCONNECT_CONNECTION_LOST);
- if (!$this->_connect()) {
- return false;
- }
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
+ $this->connect();
foreach ($this->auth as $auth) {
- $result = call_user_func_array(array(&$this, 'login'), $auth);
+ $result = $this->login(...$auth);
}
return $result;
}
/**
* Resets a connection for re-use
- *
- * @param int $reason
- * @access private
*/
- function _reset_connection($reason)
+ protected function reset_connection()
{
- $this->_disconnect($reason);
+ if (is_resource($this->fsock) && get_resource_type($this->fsock) === 'stream') {
+ fclose($this->fsock);
+ }
+ $this->fsock = null;
+ $this->bitmap = 0;
+ $this->binary_packet_buffer = null;
$this->decrypt = $this->encrypt = false;
$this->decrypt_block_size = $this->encrypt_block_size = 8;
$this->hmac_check = $this->hmac_create = false;
$this->hmac_size = false;
$this->session_id = false;
+ $this->last_packet = null;
$this->get_seq_no = $this->send_seq_no = 0;
+ $this->channel_status = [];
+ $this->channel_id_last_interactive = 0;
+ $this->channel_buffers = [];
+ $this->channel_buffers_write = [];
+ }
+
+ /**
+ * @return int[] second and microsecond stream timeout options based on user-requested timeout and keep-alive, or the default socket timeout by default, which mirrors PHP socket streams.
+ */
+ private function get_stream_timeout()
+ {
+ $sec = ini_get('default_socket_timeout');
+ $usec = 0;
+ if ($this->curTimeout > 0) {
+ $sec = (int) floor($this->curTimeout);
+ $usec = (int) (1000000 * ($this->curTimeout - $sec));
+ }
+ if ($this->keepAlive > 0) {
+ $elapsed = microtime(true) - $this->last_packet;
+ $timeout = max($this->keepAlive - $elapsed, 0);
+ if (!$this->curTimeout || $timeout < $this->curTimeout) {
+ $sec = (int) floor($timeout);
+ $usec = (int) (1000000 * ($timeout - $sec));
+ }
+ }
+ return [$sec, $usec];
+ }
+
+ /**
+ * Retrieves the next packet with added timeout and type handling
+ *
+ * @param string $message_types Message types to enforce in response, closing if not met
+ * @return string
+ * @throws ConnectionClosedException If an error has occurred preventing read of the next packet
+ */
+ private function get_binary_packet_or_close(...$message_types)
+ {
+ try {
+ $packet = $this->get_binary_packet();
+ if (count($message_types) > 0 && !in_array(ord($packet[0]), $message_types)) {
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
+ throw new ConnectionClosedException('Bad message type. Expected: #'
+ . implode(', #', $message_types) . '. Got: #' . ord($packet[0]));
+ }
+ return $packet;
+ } catch (TimeoutException $e) {
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
+ throw new ConnectionClosedException('Connection closed due to timeout');
+ }
}
/**
@@ -3652,145 +3615,151 @@ class SSH2
*
* @see self::_send_binary_packet()
* @return string
- * @access private
+ * @throws TimeoutException If user requested timeout was reached while waiting for next packet
+ * @throws ConnectionClosedException If an error has occurred preventing read of the next packet
*/
- function _get_binary_packet($skip_channel_filter = false)
+ private function get_binary_packet()
{
- if (!$this->keyExchangeInProgress && count($this->kex_buffer)) {
- return $this->_filter(array_shift($this->kex_buffer), $skip_channel_filter);
+ if (!is_resource($this->fsock)) {
+ throw new \InvalidArgumentException('fsock is not a resource.');
}
-
- if ($skip_channel_filter) {
- $read = array($this->fsock);
- $write = $except = null;
-
- if (!$this->curTimeout) {
- if ($this->keepAlive <= 0) {
- @stream_select($read, $write, $except, null);
- } else {
- if (!@stream_select($read, $write, $except, $this->keepAlive) && !count($read)) {
- $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0));
- return $this->_get_binary_packet(true);
- }
- }
- } else {
- if ($this->curTimeout < 0) {
- $this->is_timeout = true;
- return true;
- }
-
- $read = array($this->fsock);
- $write = $except = null;
-
- $start = microtime(true);
-
- if ($this->keepAlive > 0 && $this->keepAlive < $this->curTimeout) {
- if (!@stream_select($read, $write, $except, $this->keepAlive) && !count($read)) {
- $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0));
- $elapsed = microtime(true) - $start;
- $this->curTimeout-= $elapsed;
- return $this->_get_binary_packet(true);
- }
- $elapsed = microtime(true) - $start;
- $this->curTimeout-= $elapsed;
- }
-
- $sec = (int)floor($this->curTimeout);
- $usec = (int)(1000000 * ($this->curTimeout - $sec));
-
- // on windows this returns a "Warning: Invalid CRT parameters detected" error
- if (!@stream_select($read, $write, $except, $sec, $usec) && !count($read)) {
- $this->is_timeout = true;
- return true;
- }
- $elapsed = microtime(true) - $start;
- $this->curTimeout-= $elapsed;
+ if (!$this->keyExchangeInProgress && count($this->kex_buffer)) {
+ return $this->filter(array_shift($this->kex_buffer));
+ }
+ if ($this->binary_packet_buffer == null) {
+ // buffer the packet to permit continued reads across timeouts
+ $this->binary_packet_buffer = (object) [
+ 'read_time' => 0, // the time to read the packet from the socket
+ 'raw' => '', // the raw payload read from the socket
+ 'plain' => '', // the packet in plain text, excluding packet_length header
+ 'packet_length' => null, // the packet_length value pulled from the payload
+ 'size' => $this->decrypt_block_size, // the total size of this packet to be read from the socket
+ // initialize to read single block until packet_length is available
+ ];
+ }
+ $packet = $this->binary_packet_buffer;
+ while (strlen($packet->raw) < $packet->size) {
+ if (feof($this->fsock)) {
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
+ throw new ConnectionClosedException('Connection closed by server');
}
- }
-
- if (!is_resource($this->fsock) || feof($this->fsock)) {
- $this->bitmap = 0;
- $str = 'Connection closed (by server) prematurely';
- if (isset($elapsed)) {
- $str.= ' ' . $elapsed . 's';
+ if ($this->curTimeout < 0) {
+ $this->is_timeout = true;
+ throw new TimeoutException('Timed out waiting for server');
}
- user_error($str);
- return false;
- }
-
- $start = microtime(true);
- $sec = (int) floor($this->curTimeout);
- $usec = (int) (1000000 * ($this->curTimeout - $sec));
- stream_set_timeout($this->fsock, $sec, $usec);
- $raw = stream_get_contents($this->fsock, $this->decrypt_block_size);
-
- if (!strlen($raw)) {
- user_error('No data received from server');
- return false;
- }
+ $this->send_keep_alive();
- if ($this->decrypt !== false) {
- $raw = $this->decrypt->decrypt($raw);
- }
- if ($raw === false) {
- user_error('Unable to decrypt content');
- return false;
+ list($sec, $usec) = $this->get_stream_timeout();
+ stream_set_timeout($this->fsock, $sec, $usec);
+ $start = microtime(true);
+ $raw = stream_get_contents($this->fsock, $packet->size - strlen($packet->raw));
+ $elapsed = microtime(true) - $start;
+ $packet->read_time += $elapsed;
+ if ($this->curTimeout > 0) {
+ $this->curTimeout -= $elapsed;
+ }
+ if ($raw === false) {
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
+ throw new ConnectionClosedException('Connection closed by server');
+ } elseif (!strlen($raw)) {
+ continue;
+ }
+ $packet->raw .= $raw;
+ if (!$packet->packet_length) {
+ $this->get_binary_packet_size($packet);
+ }
}
- if (strlen($raw) < 5) {
- return false;
+ if (strlen($packet->raw) != $packet->size) {
+ throw new \RuntimeException('Size of packet was not expected length');
}
- extract(unpack('Npacket_length/Cpadding_length', $this->_string_shift($raw, 5)));
-
- $remaining_length = $packet_length + 4 - $this->decrypt_block_size;
-
- if (!$this->keyExchangeInProgress) {
- $this->bytesTransferredSinceLastKEX+= $packet_length + $padding_length + 5;
+ // destroy buffer as packet represents the entire payload and should be processed in full
+ $this->binary_packet_buffer = null;
+ // copy the raw payload, so as not to destroy original
+ $raw = $packet->raw;
+ if ($this->hmac_check instanceof Hash) {
+ $hmac = Strings::pop($raw, $this->hmac_size);
}
-
- // quoting <http://tools.ietf.org/html/rfc4253#section-6.1>,
- // "implementations SHOULD check that the packet length is reasonable"
- // PuTTY uses 0x9000 as the actual max packet size and so, too, shall we
- if ($remaining_length < -$this->decrypt_block_size || $remaining_length > 0x9000 || $remaining_length % $this->decrypt_block_size != 0) {
- if (!$this->bad_key_size_fix && $this->_bad_algorithm_candidate($this->decryptName) && !($this->bitmap & SSH2::MASK_LOGIN)) {
- $this->bad_key_size_fix = true;
- $this->_reset_connection(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
- return false;
+ $packet_length_header_size = 4;
+ if ($this->decrypt) {
+ switch ($this->decryptName) {
+ case 'aes128-gcm@openssh.com':
+ case 'aes256-gcm@openssh.com':
+ $this->decrypt->setNonce(
+ $this->decryptFixedPart .
+ $this->decryptInvocationCounter
+ );
+ Strings::increment_str($this->decryptInvocationCounter);
+ $this->decrypt->setAAD(Strings::shift($raw, $packet_length_header_size));
+ $this->decrypt->setTag(Strings::pop($raw, $this->decrypt_block_size));
+ $packet->plain = $this->decrypt->decrypt($raw);
+ break;
+ case 'chacha20-poly1305@openssh.com':
+ // This should be impossible, but we are checking anyway to narrow the type for Psalm.
+ if (!($this->decrypt instanceof ChaCha20)) {
+ throw new \LogicException('$this->decrypt is not a ' . ChaCha20::class);
+ }
+ $this->decrypt->setNonce(pack('N2', 0, $this->get_seq_no));
+ $this->decrypt->setCounter(0);
+ // this is the same approach that's implemented in Salsa20::createPoly1305Key()
+ // but we don't want to use the same AEAD construction that RFC8439 describes
+ // for ChaCha20-Poly1305 so we won't rely on it (see Salsa20::poly1305())
+ $this->decrypt->setPoly1305Key(
+ $this->decrypt->encrypt(str_repeat("\0", 32))
+ );
+ $this->decrypt->setAAD(Strings::shift($raw, $packet_length_header_size));
+ $this->decrypt->setCounter(1);
+ $this->decrypt->setTag(Strings::pop($raw, 16));
+ $packet->plain = $this->decrypt->decrypt($raw);
+ break;
+ default:
+ if (!$this->hmac_check instanceof Hash || !$this->hmac_check_etm) {
+ // first block was already decrypted for contained packet_length header
+ Strings::shift($raw, $this->decrypt_block_size);
+ if (strlen($raw) > 0) {
+ $packet->plain .= $this->decrypt->decrypt($raw);
+ }
+ } else {
+ Strings::shift($raw, $packet_length_header_size);
+ $packet->plain = $this->decrypt->decrypt($raw);
+ }
+ break;
}
- user_error('Invalid size');
- return false;
- }
-
- $buffer = '';
- while ($remaining_length > 0) {
- $temp = stream_get_contents($this->fsock, $remaining_length);
- if ($temp === false || feof($this->fsock)) {
- $this->bitmap = 0;
- user_error('Error reading from socket');
- return false;
+ } else {
+ Strings::shift($raw, $packet_length_header_size);
+ $packet->plain = $raw;
+ }
+ if ($this->hmac_check instanceof Hash) {
+ $reconstructed = !$this->hmac_check_etm ?
+ pack('Na*', $packet->packet_length, $packet->plain) :
+ substr($packet->raw, 0, -$this->hmac_size);
+ if (($this->hmac_check->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') {
+ $this->hmac_check->setNonce("\0\0\0\0" . pack('N', $this->get_seq_no));
+ if ($hmac != $this->hmac_check->hash($reconstructed)) {
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_MAC_ERROR);
+ throw new ConnectionClosedException('Invalid UMAC');
+ }
+ } else {
+ if ($hmac != $this->hmac_check->hash(pack('Na*', $this->get_seq_no, $reconstructed))) {
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_MAC_ERROR);
+ throw new ConnectionClosedException('Invalid HMAC');
+ }
}
- $buffer.= $temp;
- $remaining_length-= strlen($temp);
}
-
- $stop = microtime(true);
- if (strlen($buffer)) {
- $raw.= $this->decrypt !== false ? $this->decrypt->decrypt($buffer) : $buffer;
+ $padding_length = 0;
+ $payload = $packet->plain;
+ $padding_length = unpack('Cpadding_length', Strings::shift($payload, 1))['padding_length'];
+ if ($padding_length > 0) {
+ Strings::pop($payload, $padding_length);
}
- $payload = $this->_string_shift($raw, $packet_length - $padding_length - 1);
- $padding = $this->_string_shift($raw, $padding_length); // should leave $raw empty
+ if (!$this->keyExchangeInProgress) {
+ $this->bytesTransferredSinceLastKEX += $packet->packet_length + $padding_length + 5;
+ }
- if ($this->hmac_check !== false) {
- $hmac = stream_get_contents($this->fsock, $this->hmac_size);
- if ($hmac === false || strlen($hmac) != $this->hmac_size) {
- $this->bitmap = 0;
- user_error('Error reading socket');
- return false;
- } elseif ($hmac != $this->hmac_check->hash(pack('NNCa*', $this->get_seq_no, $packet_length, $padding_length, $payload . $padding))) {
- user_error('Invalid HMAC');
- return false;
- }
+ if (empty($payload)) {
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
+ throw new ConnectionClosedException('Plaintext is too short');
}
switch ($this->decompress) {
@@ -3798,6 +3767,7 @@ class SSH2
if (!$this->isAuthenticated()) {
break;
}
+ // fall-through
case self::NET_SSH2_COMPRESSION_ZLIB:
if ($this->regenerate_decompression_context) {
$this->regenerate_decompression_context = false;
@@ -3805,23 +3775,23 @@ class SSH2
$cmf = ord($payload[0]);
$cm = $cmf & 0x0F;
if ($cm != 8) { // deflate
- user_error("Only CM = 8 ('deflate') is supported ($cm)");
+ throw new UnsupportedAlgorithmException("Only CM = 8 ('deflate') is supported ($cm)");
}
$cinfo = ($cmf & 0xF0) >> 4;
if ($cinfo > 7) {
- user_error("CINFO above 7 is not allowed ($cinfo)");
+ throw new \RuntimeException("CINFO above 7 is not allowed ($cinfo)");
}
$windowSize = 1 << ($cinfo + 8);
$flg = ord($payload[1]);
//$fcheck = $flg && 0x0F;
if ((($cmf << 8) | $flg) % 31) {
- user_error('fcheck failed');
+ throw new \RuntimeException('fcheck failed');
}
$fdict = boolval($flg & 0x20);
$flevel = ($flg & 0xC0) >> 6;
- $this->decompress_context = inflate_init(ZLIB_ENCODING_RAW, array('window' => $cinfo + 8));
+ $this->decompress_context = inflate_init(ZLIB_ENCODING_RAW, ['window' => $cinfo + 8]);
$payload = substr($payload, 2);
}
if ($this->decompress_context) {
@@ -3833,19 +3803,80 @@ class SSH2
if (defined('NET_SSH2_LOGGING')) {
$current = microtime(true);
- $message_number = isset($this->message_numbers[ord($payload[0])]) ? $this->message_numbers[ord($payload[0])] : 'UNKNOWN (' . ord($payload[0]) . ')';
+ $message_number = isset(self::$message_numbers[ord($payload[0])]) ? self::$message_numbers[ord($payload[0])] : 'UNKNOWN (' . ord($payload[0]) . ')';
$message_number = '<- ' . $message_number .
- ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)';
- $this->_append_log($message_number, $payload);
- $this->last_packet = $current;
+ ' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($packet->read_time, 4) . 's)';
+ $this->append_log($message_number, $payload);
}
+ $this->last_packet = microtime(true);
if ($this->bytesTransferredSinceLastKEX > $this->doKeyReexchangeAfterXBytes) {
- $this->_key_exchange();
+ $this->key_exchange();
}
- // don't filter if we're in the middle of a key exchange (since _filter might send out packets)
- return $this->keyExchangeInProgress ? $payload : $this->_filter($payload, $skip_channel_filter);
+ return $this->filter($payload);
+ }
+
+ /**
+ * @param object $packet The packet object being constructed, passed by reference
+ * The size, packet_length, and plain properties of this object may be modified in processing
+ * @throws InvalidPacketLengthException if the packet length header is invalid
+ */
+ private function get_binary_packet_size(&$packet)
+ {
+ $packet_length_header_size = 4;
+ if (strlen($packet->raw) < $packet_length_header_size) {
+ return;
+ }
+ $packet_length = 0;
+ $added_validation_length = 0; // indicates when the packet length header is included when validating packet length against block size
+ if ($this->decrypt) {
+ switch ($this->decryptName) {
+ case 'aes128-gcm@openssh.com':
+ case 'aes256-gcm@openssh.com':
+ $packet_length = unpack('Npacket_length', substr($packet->raw, 0, $packet_length_header_size))['packet_length'];
+ $packet->size = $packet_length_header_size + $packet_length + $this->decrypt_block_size; // expect tag
+ break;
+ case 'chacha20-poly1305@openssh.com':
+ $this->lengthDecrypt->setNonce(pack('N2', 0, $this->get_seq_no));
+ $packet_length_header = $this->lengthDecrypt->decrypt(substr($packet->raw, 0, $packet_length_header_size));
+ $packet_length = unpack('Npacket_length', $packet_length_header)['packet_length'];
+ $packet->size = $packet_length_header_size + $packet_length + 16; // expect tag
+ break;
+ default:
+ if (!$this->hmac_check instanceof Hash || !$this->hmac_check_etm) {
+ if (strlen($packet->raw) < $this->decrypt_block_size) {
+ return;
+ }
+ $packet->plain = $this->decrypt->decrypt(substr($packet->raw, 0, $this->decrypt_block_size));
+ $packet_length = unpack('Npacket_length', Strings::shift($packet->plain, $packet_length_header_size))['packet_length'];
+ $packet->size = $packet_length_header_size + $packet_length;
+ $added_validation_length = $packet_length_header_size;
+ } else {
+ $packet_length = unpack('Npacket_length', substr($packet->raw, 0, $packet_length_header_size))['packet_length'];
+ $packet->size = $packet_length_header_size + $packet_length;
+ }
+ break;
+ }
+ } else {
+ $packet_length = unpack('Npacket_length', substr($packet->raw, 0, $packet_length_header_size))['packet_length'];
+ $packet->size = $packet_length_header_size + $packet_length;
+ $added_validation_length = $packet_length_header_size;
+ }
+ // quoting <http://tools.ietf.org/html/rfc4253#section-6.1>,
+ // "implementations SHOULD check that the packet length is reasonable"
+ // PuTTY uses 0x9000 as the actual max packet size and so to shall we
+ if (
+ $packet_length <= 0 || $packet_length > 0x9000
+ || ($packet_length + $added_validation_length) % $this->decrypt_block_size != 0
+ ) {
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_PROTOCOL_ERROR);
+ throw new InvalidPacketLengthException('Invalid packet length');
+ }
+ if ($this->hmac_check instanceof Hash) {
+ $packet->size += $this->hmac_size;
+ }
+ $packet->packet_length = $packet_length;
}
/**
@@ -3853,21 +3884,18 @@ class SSH2
*
* Because some binary packets need to be ignored...
*
- * @see self::_filter()
- * @see self::_key_exchange
+ * @see self::filter()
+ * @see self::key_exchange()
* @return boolean
* @access private
*/
- function _handleDisconnect($payload)
+ private function handleDisconnect($payload)
{
- $this->_string_shift($payload, 1);
- if (strlen($payload) < 8) {
- return false;
- }
- extract(unpack('Nreason_code/Nlength', $this->_string_shift($payload, 8)));
- $this->errors[] = 'SSH_MSG_DISCONNECT: ' . $this->disconnect_reasons[$reason_code] . "\r\n" . $this->_string_shift($payload, $length);
- $this->bitmap = 0;
- return false;
+ Strings::shift($payload, 1);
+ list($reason_code, $message) = Strings::unpackSSH2('Ns', $payload);
+ $this->errors[] = 'SSH_MSG_DISCONNECT: ' . self::$disconnect_reasons[$reason_code] . "\r\n$message";
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
+ throw new ConnectionClosedException('Connection closed by server');
}
/**
@@ -3876,136 +3904,117 @@ class SSH2
* Because some binary packets need to be ignored...
*
* @see self::_get_binary_packet()
+ * @param string $payload
* @return string
- * @access private
*/
- function _filter($payload, $skip_channel_filter)
+ private function filter($payload)
{
+ if (ord($payload[0]) == NET_SSH2_MSG_DISCONNECT) {
+ return $this->handleDisconnect($payload);
+ }
+
+ if ($this->session_id === false && $this->keyExchangeInProgress) {
+ return $payload;
+ }
+
switch (ord($payload[0])) {
- case NET_SSH2_MSG_DISCONNECT:
- return $this->_handleDisconnect($payload);
case NET_SSH2_MSG_IGNORE:
- $payload = $this->_get_binary_packet($skip_channel_filter);
+ $payload = $this->get_binary_packet();
break;
case NET_SSH2_MSG_DEBUG:
- $this->_string_shift($payload, 2);
- if (strlen($payload) < 4) {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($payload, 4)));
- $this->errors[] = 'SSH_MSG_DEBUG: ' . $this->_string_shift($payload, $length);
- $payload = $this->_get_binary_packet($skip_channel_filter);
+ Strings::shift($payload, 2); // second byte is "always_display"
+ list($message) = Strings::unpackSSH2('s', $payload);
+ $this->errors[] = "SSH_MSG_DEBUG: $message";
+ $payload = $this->get_binary_packet();
break;
case NET_SSH2_MSG_UNIMPLEMENTED:
- return false;
+ break; // return payload
case NET_SSH2_MSG_KEXINIT:
// this is here for server initiated key re-exchanges after the initial key exchange
- if ($this->session_id !== false) {
- $this->send_kex_first = false;
- if (!$this->_key_exchange($payload)) {
- $this->bitmap = 0;
- return false;
+ if (!$this->keyExchangeInProgress && $this->session_id !== false) {
+ if (!$this->key_exchange($payload)) {
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
+ throw new ConnectionClosedException('Key exchange failed');
}
- $payload = $this->_get_binary_packet($skip_channel_filter);
+ $payload = $this->get_binary_packet();
}
break;
case NET_SSH2_MSG_EXT_INFO:
- $this->_string_shift($payload, 1);
- if (strlen($payload) < 4) {
- return false;
- }
- $nr_extensions = unpack('Nlength', $this->_string_shift($payload, 4));
- for ($i = 0; $i < $nr_extensions['length']; $i++) {
- if (strlen($payload) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $this->_string_shift($payload, 4));
- $extension_name = $this->_string_shift($payload, $temp['length']);
+ Strings::shift($payload, 1);
+ list($nr_extensions) = Strings::unpackSSH2('N', $payload);
+ for ($i = 0; $i < $nr_extensions; $i++) {
+ list($extension_name, $extension_value) = Strings::unpackSSH2('ss', $payload);
if ($extension_name == 'server-sig-algs') {
- if (strlen($payload) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $this->_string_shift($payload, 4));
- $this->supported_private_key_algorithms = explode(',', $this->_string_shift($payload, $temp['length']));
+ $this->supported_private_key_algorithms = explode(',', $extension_value);
}
}
- $payload = $this->_get_binary_packet($skip_channel_filter);
+ $payload = $this->get_binary_packet();
+ }
+
+ /*
+ Once a party has sent a SSH_MSG_KEXINIT message for key exchange or
+ re-exchange, until it has sent a SSH_MSG_NEWKEYS message (Section
+ 7.3), it MUST NOT send any messages other than:
+
+ o Transport layer generic messages (1 to 19) (but
+ SSH_MSG_SERVICE_REQUEST and SSH_MSG_SERVICE_ACCEPT MUST NOT be
+ sent);
+
+ o Algorithm negotiation messages (20 to 29) (but further
+ SSH_MSG_KEXINIT messages MUST NOT be sent);
+
+ o Specific key exchange method messages (30 to 49).
+
+ -- https://www.rfc-editor.org/rfc/rfc4253#section-7.1
+ */
+ if ($this->keyExchangeInProgress) {
+ return $payload;
}
// see http://tools.ietf.org/html/rfc4252#section-5.4; only called when the encryption has been activated and when we haven't already logged in
if (($this->bitmap & self::MASK_CONNECTED) && !$this->isAuthenticated() && ord($payload[0]) == NET_SSH2_MSG_USERAUTH_BANNER) {
- $this->_string_shift($payload, 1);
- if (strlen($payload) < 4) {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($payload, 4)));
- $this->banner_message = $this->_string_shift($payload, $length);
- $payload = $this->_get_binary_packet();
+ Strings::shift($payload, 1);
+ list($this->banner_message) = Strings::unpackSSH2('s', $payload);
+ $payload = $this->get_binary_packet();
}
// only called when we've already logged in
if (($this->bitmap & self::MASK_CONNECTED) && $this->isAuthenticated()) {
- if (is_bool($payload)) {
- return $payload;
- }
-
switch (ord($payload[0])) {
case NET_SSH2_MSG_CHANNEL_REQUEST:
if (strlen($payload) == 31) {
- extract(unpack('cpacket_type/Nchannel/Nlength', $payload));
+ $unpacked = unpack('cpacket_type/Nchannel/Nlength', $payload);
+ $packet_type = $unpacked['packet_type'];
+ $channel = $unpacked['channel'];
+ $length = $unpacked['length'];
if (substr($payload, 9, $length) == 'keepalive@openssh.com' && isset($this->server_channels[$channel])) {
if (ord(substr($payload, 9 + $length))) { // want reply
- $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_SUCCESS, $this->server_channels[$channel]));
+ $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_SUCCESS, $this->server_channels[$channel]));
}
- $payload = $this->_get_binary_packet($skip_channel_filter);
+ $payload = $this->get_binary_packet();
}
}
break;
- case NET_SSH2_MSG_CHANNEL_DATA:
- case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA:
- case NET_SSH2_MSG_CHANNEL_CLOSE:
- case NET_SSH2_MSG_CHANNEL_EOF:
- if (!$skip_channel_filter && !empty($this->server_channels)) {
- $this->binary_packet_buffer = $payload;
- $this->_get_channel_packet(true);
- $payload = $this->_get_binary_packet();
- }
- break;
case NET_SSH2_MSG_GLOBAL_REQUEST: // see http://tools.ietf.org/html/rfc4254#section-4
- if (strlen($payload) < 4) {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($payload, 4)));
- $this->errors[] = 'SSH_MSG_GLOBAL_REQUEST: ' . $this->_string_shift($payload, $length);
-
- if (!$this->_send_binary_packet(pack('C', NET_SSH2_MSG_REQUEST_FAILURE))) {
- return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
- }
-
- $payload = $this->_get_binary_packet($skip_channel_filter);
+ Strings::shift($payload, 1);
+ list($request_name) = Strings::unpackSSH2('s', $payload);
+ $this->errors[] = "SSH_MSG_GLOBAL_REQUEST: $request_name";
+ $this->send_binary_packet(pack('C', NET_SSH2_MSG_REQUEST_FAILURE));
+ $payload = $this->get_binary_packet();
break;
case NET_SSH2_MSG_CHANNEL_OPEN: // see http://tools.ietf.org/html/rfc4254#section-5.1
- $this->_string_shift($payload, 1);
- if (strlen($payload) < 4) {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($payload, 4)));
- $data = $this->_string_shift($payload, $length);
- if (strlen($payload) < 4) {
- return false;
- }
- extract(unpack('Nserver_channel', $this->_string_shift($payload, 4)));
+ Strings::shift($payload, 1);
+ list($data, $server_channel) = Strings::unpackSSH2('sN', $payload);
switch ($data) {
case 'auth-agent':
case 'auth-agent@openssh.com':
if (isset($this->agent)) {
$new_channel = self::CHANNEL_AGENT_FORWARD;
- if (strlen($payload) < 8) {
- return false;
- }
- extract(unpack('Nremote_window_size', $this->_string_shift($payload, 4)));
- extract(unpack('Nremote_maximum_packet_size', $this->_string_shift($payload, 4)));
+ list(
+ $remote_window_size,
+ $remote_maximum_packet_size
+ ) = Strings::unpackSSH2('NN', $payload);
$this->packet_size_client_to_server[$new_channel] = $remote_window_size;
$this->window_size_server_to_client[$new_channel] = $remote_maximum_packet_size;
@@ -4024,39 +4033,23 @@ class SSH2
$this->server_channels[$new_channel] = $server_channel;
$this->channel_status[$new_channel] = NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION;
- if (!$this->_send_binary_packet($packet)) {
- return false;
- }
+ $this->send_binary_packet($packet);
}
break;
default:
- $packet = pack(
- 'CN3a*Na*',
- NET_SSH2_MSG_REQUEST_FAILURE,
+ $packet = Strings::packSSH2(
+ 'CN2ss',
+ NET_SSH2_MSG_CHANNEL_OPEN_FAILURE,
$server_channel,
NET_SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED,
- 0,
- '',
- 0,
- ''
+ '', // description
+ '' // language tag
);
-
- if (!$this->_send_binary_packet($packet)) {
- return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
- }
- }
- $payload = $this->_get_binary_packet($skip_channel_filter);
- break;
- case NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST:
- $this->_string_shift($payload, 1);
- if (strlen($payload) < 8) {
- return false;
+ $this->send_binary_packet($packet);
}
- extract(unpack('Nchannel', $this->_string_shift($payload, 4)));
- extract(unpack('Nwindow_size', $this->_string_shift($payload, 4)));
- $this->window_size_client_to_server[$channel]+= $window_size;
- $payload = ($this->bitmap & self::MASK_WINDOW_ADJUST) ? true : $this->_get_binary_packet($skip_channel_filter);
+ $payload = $this->get_binary_packet();
+ break;
}
}
@@ -4068,9 +4061,8 @@ class SSH2
*
* Suppress stderr from output
*
- * @access public
*/
- function enableQuietMode()
+ public function enableQuietMode()
{
$this->quiet_mode = true;
}
@@ -4080,9 +4072,8 @@ class SSH2
*
* Show stderr in output
*
- * @access public
*/
- function disableQuietMode()
+ public function disableQuietMode()
{
$this->quiet_mode = false;
}
@@ -4092,10 +4083,9 @@ class SSH2
*
* @see self::enableQuietMode()
* @see self::disableQuietMode()
- * @access public
* @return bool
*/
- function isQuietModeEnabled()
+ public function isQuietModeEnabled()
{
return $this->quiet_mode;
}
@@ -4103,9 +4093,8 @@ class SSH2
/**
* Enable request-pty when using exec()
*
- * @access public
*/
- function enablePTY()
+ public function enablePTY()
{
$this->request_pty = true;
}
@@ -4113,13 +4102,11 @@ class SSH2
/**
* Disable request-pty when using exec()
*
- * @access public
*/
- function disablePTY()
+ public function disablePTY()
{
- if ($this->in_request_pty_exec) {
- $this->_close_channel(self::CHANNEL_EXEC);
- $this->in_request_pty_exec = false;
+ if ($this->isPTYOpen()) {
+ $this->close_channel(self::CHANNEL_EXEC);
}
$this->request_pty = false;
}
@@ -4129,10 +4116,9 @@ class SSH2
*
* @see self::enablePTY()
* @see self::disablePTY()
- * @access public
* @return bool
*/
- function isPTYEnabled()
+ public function isPTYEnabled()
{
return $this->request_pty;
}
@@ -4140,17 +4126,32 @@ class SSH2
/**
* Gets channel data
*
- * Returns the data as a string if it's available and false if not.
+ * Returns the data as a string. bool(true) is returned if:
*
- * @param int $client_channel
+ * - the server closes the channel
+ * - if the connection times out
+ * - if a window adjust packet is received on the given negated client channel
+ * - if the channel status is CHANNEL_OPEN and the response was CHANNEL_OPEN_CONFIRMATION
+ * - if the channel status is CHANNEL_REQUEST and the response was CHANNEL_SUCCESS
+ * - if the channel status is CHANNEL_CLOSE and the response was CHANNEL_CLOSE
+ *
+ * bool(false) is returned if:
+ *
+ * - if the channel status is CHANNEL_REQUEST and the response was CHANNEL_FAILURE
+ *
+ * @param int $client_channel Specifies the channel to return data for, and data received
+ * on other channels is buffered. The respective negative value of a channel is
+ * also supported for the case that the caller is awaiting adjustment of the data
+ * window, and where data received on that respective channel is also buffered.
* @param bool $skip_extended
- * @return mixed|bool
- * @access private
+ * @return mixed
+ * @throws \RuntimeException on connection error
*/
- function _get_channel_packet($client_channel, $skip_extended = false)
+ protected function get_channel_packet($client_channel, $skip_extended = false)
{
if (!empty($this->channel_buffers[$client_channel])) {
- switch ($this->channel_status[$client_channel]) {
+ // in phpseclib 4.0 this should be changed to $this->channel_status[$client_channel] ?? null
+ switch (isset($this->channel_status[$client_channel]) ? $this->channel_status[$client_channel] : null) {
case NET_SSH2_MSG_CHANNEL_REQUEST:
foreach ($this->channel_buffers[$client_channel] as $i => $packet) {
switch (ord($packet[0])) {
@@ -4167,67 +4168,47 @@ class SSH2
}
while (true) {
- if ($this->binary_packet_buffer !== false) {
- $response = $this->binary_packet_buffer;
- $this->binary_packet_buffer = false;
- } else {
- $response = $this->_get_binary_packet(true);
- if ($response === true && $this->is_timeout) {
- return true;
- }
- if ($response === false) {
- $this->bitmap = 0;
- user_error('Connection closed by server');
- return false;
- }
- }
-
- if ($client_channel == -1 && $response === true) {
+ try {
+ $response = $this->get_binary_packet();
+ } catch (TimeoutException $e) {
return true;
}
- if (!strlen($response)) {
- return false;
- }
- extract(unpack('Ctype', $this->_string_shift($response, 1)));
-
- if (strlen($response) < 4) {
- return false;
- }
- if ($type == NET_SSH2_MSG_CHANNEL_OPEN) {
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
- } else {
- extract(unpack('Nchannel', $this->_string_shift($response, 4)));
+ list($type) = Strings::unpackSSH2('C', $response);
+ if (strlen($response) >= 4) {
+ list($channel) = Strings::unpackSSH2('N', $response);
}
// will not be setup yet on incoming channel open request
if (isset($channel) && isset($this->channel_status[$channel]) && isset($this->window_size_server_to_client[$channel])) {
- $this->window_size_server_to_client[$channel]-= strlen($response);
+ $this->window_size_server_to_client[$channel] -= strlen($response);
// resize the window, if appropriate
if ($this->window_size_server_to_client[$channel] < 0) {
// PuTTY does something more analogous to the following:
//if ($this->window_size_server_to_client[$channel] < 0x3FFFFFFF) {
$packet = pack('CNN', NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST, $this->server_channels[$channel], $this->window_resize);
- if (!$this->_send_binary_packet($packet)) {
- return false;
- }
- $this->window_size_server_to_client[$channel]+= $this->window_resize;
+ $this->send_binary_packet($packet);
+ $this->window_size_server_to_client[$channel] += $this->window_resize;
}
switch ($type) {
+ case NET_SSH2_MSG_CHANNEL_WINDOW_ADJUST:
+ list($window_size) = Strings::unpackSSH2('N', $response);
+ $this->window_size_client_to_server[$channel] += $window_size;
+ if ($channel == -$client_channel) {
+ return true;
+ }
+
+ continue 2;
case NET_SSH2_MSG_CHANNEL_EXTENDED_DATA:
/*
if ($client_channel == self::CHANNEL_EXEC) {
- $this->_send_channel_packet($client_channel, chr(0));
+ $this->send_channel_packet($client_channel, chr(0));
}
*/
// currently, there's only one possible value for $data_type_code: NET_SSH2_EXTENDED_DATA_STDERR
- if (strlen($response) < 8) {
- return false;
- }
- extract(unpack('Ndata_type_code/Nlength', $this->_string_shift($response, 8)));
- $data = $this->_string_shift($response, $length);
- $this->stdErrorLog.= $data;
+ list($data_type_code, $data) = Strings::unpackSSH2('Ns', $response);
+ $this->stdErrorLog .= $data;
if ($skip_extended || $this->quiet_mode) {
continue 2;
}
@@ -4238,43 +4219,36 @@ class SSH2
continue 2;
case NET_SSH2_MSG_CHANNEL_REQUEST:
- if ($this->channel_status[$channel] == NET_SSH2_MSG_CHANNEL_CLOSE) {
+ if (!isset($this->channel_status[$channel])) {
continue 2;
}
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
- $value = $this->_string_shift($response, $length);
+ list($value) = Strings::unpackSSH2('s', $response);
switch ($value) {
case 'exit-signal':
- $this->_string_shift($response, 1);
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
- $this->errors[] = 'SSH_MSG_CHANNEL_REQUEST (exit-signal): ' . $this->_string_shift($response, $length);
- $this->_string_shift($response, 1);
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
- if ($length) {
- $this->errors[count($this->errors)].= "\r\n" . $this->_string_shift($response, $length);
+ list(
+ , // FALSE
+ $signal_name,
+ , // core dumped
+ $error_message
+ ) = Strings::unpackSSH2('bsbs', $response);
+
+ $this->errors[] = "SSH_MSG_CHANNEL_REQUEST (exit-signal): $signal_name";
+ if (strlen($error_message)) {
+ $this->errors[count($this->errors) - 1] .= "\r\n$error_message";
}
- $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel]));
- $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel]));
+ if (isset($this->channel_status[$channel]) && $this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_CLOSE) {
+ if ($this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_EOF) {
+ $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$channel]));
+ }
+ $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel]));
- $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_EOF;
+ $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_CLOSE;
+ }
continue 3;
case 'exit-status':
- if (strlen($response) < 5) {
- return false;
- }
- extract(unpack('Cfalse/Nexit_status', $this->_string_shift($response, 5)));
- $this->exit_status = $exit_status;
+ list(, $this->exit_status) = Strings::unpackSSH2('CN', $response);
// "The client MAY ignore these messages."
// -- http://tools.ietf.org/html/rfc4254#section-6.10
@@ -4291,37 +4265,29 @@ class SSH2
case NET_SSH2_MSG_CHANNEL_OPEN:
switch ($type) {
case NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nserver_channel', $this->_string_shift($response, 4)));
- $this->server_channels[$channel] = $server_channel;
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nwindow_size', $this->_string_shift($response, 4)));
+ list(
+ $this->server_channels[$channel],
+ $window_size,
+ $this->packet_size_client_to_server[$channel]
+ ) = Strings::unpackSSH2('NNN', $response);
+
if ($window_size < 0) {
- $window_size&= 0x7FFFFFFF;
- $window_size+= 0x80000000;
+ $window_size &= 0x7FFFFFFF;
+ $window_size += 0x80000000;
}
$this->window_size_client_to_server[$channel] = $window_size;
- if (strlen($response) < 4) {
- return false;
- }
- $temp = unpack('Npacket_size_client_to_server', $this->_string_shift($response, 4));
- $this->packet_size_client_to_server[$channel] = $temp['packet_size_client_to_server'];
- $result = $client_channel == $channel ? true : $this->_get_channel_packet($client_channel, $skip_extended);
- $this->_on_channel_open();
+ $result = $client_channel == $channel ? true : $this->get_channel_packet($client_channel, $skip_extended);
+ $this->on_channel_open();
return $result;
case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE:
- user_error('Unable to open channel');
- return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
+ throw new \RuntimeException('Unable to open channel');
default:
if ($client_channel == $channel) {
- user_error('Unexpected response to open request');
- return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
+ throw new \RuntimeException('Unexpected response to open request');
}
- return $this->_get_channel_packet($client_channel, $skip_extended);
+ return $this->get_channel_packet($client_channel, $skip_extended);
}
break;
case NET_SSH2_MSG_CHANNEL_REQUEST:
@@ -4331,19 +4297,18 @@ class SSH2
case NET_SSH2_MSG_CHANNEL_FAILURE:
return false;
case NET_SSH2_MSG_CHANNEL_DATA:
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
- $data = $this->_string_shift($response, $length);
+ list($data) = Strings::unpackSSH2('s', $response);
$this->channel_buffers[$channel][] = chr($type) . $data;
- return $this->_get_channel_packet($client_channel, $skip_extended);
+ return $this->get_channel_packet($client_channel, $skip_extended);
default:
- user_error('Unable to fulfill channel request');
- return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
+ throw new \RuntimeException('Unable to fulfill channel request');
}
case NET_SSH2_MSG_CHANNEL_CLOSE:
- return $type == NET_SSH2_MSG_CHANNEL_CLOSE ? true : $this->_get_channel_packet($client_channel, $skip_extended);
+ if ($client_channel == $channel && $type == NET_SSH2_MSG_CHANNEL_CLOSE) {
+ return true;
+ }
+ return $this->get_channel_packet($client_channel, $skip_extended);
}
}
@@ -4357,19 +4322,15 @@ class SSH2
// this actually seems to make things twice as fast. more to the point, the message right after
// SSH_MSG_CHANNEL_DATA (usually SSH_MSG_IGNORE) won't block for as long as it would have otherwise.
// in OpenSSH it slows things down but only by a couple thousandths of a second.
- $this->_send_channel_packet($channel, chr(0));
+ $this->send_channel_packet($channel, chr(0));
}
*/
- if (strlen($response) < 4) {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($response, 4)));
- $data = $this->_string_shift($response, $length);
+ list($data) = Strings::unpackSSH2('s', $response);
if ($channel == self::CHANNEL_AGENT_FORWARD) {
- $agent_response = $this->agent->_forward_data($data);
+ $agent_response = $this->agent->forwardData($data);
if (!is_bool($agent_response)) {
- $this->_send_channel_packet($channel, $agent_response);
+ $this->send_channel_packet($channel, $agent_response);
}
break;
}
@@ -4382,22 +4343,24 @@ class SSH2
case NET_SSH2_MSG_CHANNEL_CLOSE:
$this->curTimeout = 5;
- if ($this->bitmap & self::MASK_SHELL) {
- $this->bitmap&= ~self::MASK_SHELL;
- }
- if ($this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_EOF) {
- $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel]));
+ $this->close_channel_bitmap($channel);
+
+ if ($this->channel_status[$channel] != NET_SSH2_MSG_CHANNEL_CLOSE) {
+ $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$channel]));
}
- $this->channel_status[$channel] = NET_SSH2_MSG_CHANNEL_CLOSE;
+ unset($this->channel_status[$channel]);
+ $this->channelCount--;
+
if ($client_channel == $channel) {
return true;
}
+ // fall-through
case NET_SSH2_MSG_CHANNEL_EOF:
break;
default:
- user_error("Error reading channel data ($type)");
- return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
+ throw new \RuntimeException("Error reading channel data ($type)");
}
}
}
@@ -4410,15 +4373,13 @@ class SSH2
* @param string $data
* @param string $logged
* @see self::_get_binary_packet()
- * @return bool
- * @access private
+ * @return void
*/
- function _send_binary_packet($data, $logged = null)
+ protected function send_binary_packet($data, $logged = null)
{
if (!is_resource($this->fsock) || feof($this->fsock)) {
- $this->bitmap = 0;
- user_error('Connection closed prematurely');
- return false;
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_CONNECTION_LOST);
+ throw new ConnectionClosedException('Connection closed prematurely');
}
if (!isset($logged)) {
@@ -4430,12 +4391,13 @@ class SSH2
if (!$this->isAuthenticated()) {
break;
}
+ // fall-through
case self::NET_SSH2_COMPRESSION_ZLIB:
if (!$this->regenerate_compression_context) {
$header = '';
} else {
$this->regenerate_compression_context = false;
- $this->compress_context = deflate_init(ZLIB_ENCODING_RAW, array('window' => 15));
+ $this->compress_context = deflate_init(ZLIB_ENCODING_RAW, ['window' => 15]);
$header = "\x78\x9C";
}
if ($this->compress_context) {
@@ -4445,46 +4407,132 @@ class SSH2
// 4 (packet length) + 1 (padding length) + 4 (minimal padding amount) == 9
$packet_length = strlen($data) + 9;
+ if ($this->encrypt && $this->encrypt->usesNonce()) {
+ $packet_length -= 4;
+ }
// round up to the nearest $this->encrypt_block_size
- $packet_length+= (($this->encrypt_block_size - 1) * $packet_length) % $this->encrypt_block_size;
+ $packet_length += (($this->encrypt_block_size - 1) * $packet_length) % $this->encrypt_block_size;
// subtracting strlen($data) is obvious - subtracting 5 is necessary because of packet_length and padding_length
$padding_length = $packet_length - strlen($data) - 5;
+ switch (true) {
+ case $this->encrypt && $this->encrypt->usesNonce():
+ case $this->hmac_create instanceof Hash && $this->hmac_create_etm:
+ $padding_length += 4;
+ $packet_length += 4;
+ }
+
$padding = Random::string($padding_length);
// we subtract 4 from packet_length because the packet_length field isn't supposed to include itself
$packet = pack('NCa*', $packet_length - 4, $padding_length, $data . $padding);
- $hmac = $this->hmac_create !== false ? $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet)) : '';
- $this->send_seq_no++;
+ $hmac = '';
+ if ($this->hmac_create instanceof Hash && !$this->hmac_create_etm) {
+ if (($this->hmac_create->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') {
+ $this->hmac_create->setNonce("\0\0\0\0" . pack('N', $this->send_seq_no));
+ $hmac = $this->hmac_create->hash($packet);
+ } else {
+ $hmac = $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet));
+ }
+ }
+
+ if ($this->encrypt) {
+ switch ($this->encryptName) {
+ case 'aes128-gcm@openssh.com':
+ case 'aes256-gcm@openssh.com':
+ $this->encrypt->setNonce(
+ $this->encryptFixedPart .
+ $this->encryptInvocationCounter
+ );
+ Strings::increment_str($this->encryptInvocationCounter);
+ $this->encrypt->setAAD($temp = ($packet & "\xFF\xFF\xFF\xFF"));
+ $packet = $temp . $this->encrypt->encrypt(substr($packet, 4));
+ break;
+ case 'chacha20-poly1305@openssh.com':
+ // This should be impossible, but we are checking anyway to narrow the type for Psalm.
+ if (!($this->encrypt instanceof ChaCha20)) {
+ throw new \LogicException('$this->encrypt is not a ' . ChaCha20::class);
+ }
- if ($this->encrypt !== false) {
- $packet = $this->encrypt->encrypt($packet);
+ $nonce = pack('N2', 0, $this->send_seq_no);
+
+ $this->encrypt->setNonce($nonce);
+ $this->lengthEncrypt->setNonce($nonce);
+
+ $length = $this->lengthEncrypt->encrypt($packet & "\xFF\xFF\xFF\xFF");
+
+ $this->encrypt->setCounter(0);
+ // this is the same approach that's implemented in Salsa20::createPoly1305Key()
+ // but we don't want to use the same AEAD construction that RFC8439 describes
+ // for ChaCha20-Poly1305 so we won't rely on it (see Salsa20::poly1305())
+ $this->encrypt->setPoly1305Key(
+ $this->encrypt->encrypt(str_repeat("\0", 32))
+ );
+ $this->encrypt->setAAD($length);
+ $this->encrypt->setCounter(1);
+ $packet = $length . $this->encrypt->encrypt(substr($packet, 4));
+ break;
+ default:
+ $packet = $this->hmac_create instanceof Hash && $this->hmac_create_etm ?
+ ($packet & "\xFF\xFF\xFF\xFF") . $this->encrypt->encrypt(substr($packet, 4)) :
+ $this->encrypt->encrypt($packet);
+ }
}
- $packet.= $hmac;
+ if ($this->hmac_create instanceof Hash && $this->hmac_create_etm) {
+ if (($this->hmac_create->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') {
+ $this->hmac_create->setNonce("\0\0\0\0" . pack('N', $this->send_seq_no));
+ $hmac = $this->hmac_create->hash($packet);
+ } else {
+ $hmac = $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet));
+ }
+ }
+
+ $this->send_seq_no++;
+
+ $packet .= $this->encrypt && $this->encrypt->usesNonce() ? $this->encrypt->getTag() : $hmac;
if (!$this->keyExchangeInProgress) {
- $this->bytesTransferredSinceLastKEX+= strlen($packet);
+ $this->bytesTransferredSinceLastKEX += strlen($packet);
}
-
+
$start = microtime(true);
- $result = strlen($packet) == @fputs($this->fsock, $packet);
+ $sent = @fputs($this->fsock, $packet);
$stop = microtime(true);
if (defined('NET_SSH2_LOGGING')) {
$current = microtime(true);
- $message_number = isset($this->message_numbers[ord($logged[0])]) ? $this->message_numbers[ord($logged[0])] : 'UNKNOWN (' . ord($logged[0]) . ')';
+ $message_number = isset(self::$message_numbers[ord($logged[0])]) ? self::$message_numbers[ord($logged[0])] : 'UNKNOWN (' . ord($logged[0]) . ')';
$message_number = '-> ' . $message_number .
' (since last: ' . round($current - $this->last_packet, 4) . ', network: ' . round($stop - $start, 4) . 's)';
- $this->_append_log($message_number, $logged);
- $this->last_packet = $current;
+ $this->append_log($message_number, $logged);
+ }
+ $this->last_packet = microtime(true);
+
+ if (strlen($packet) != $sent) {
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_BY_APPLICATION);
+ $message = $sent === false ?
+ 'Unable to write ' . strlen($packet) . ' bytes' :
+ "Only $sent of " . strlen($packet) . " bytes were sent";
+ throw new \RuntimeException($message);
}
if ($this->bytesTransferredSinceLastKEX > $this->doKeyReexchangeAfterXBytes) {
- $this->_key_exchange();
+ $this->key_exchange();
}
+ }
- return $result;
+ /**
+ * Sends a keep-alive message, if keep-alive is enabled and interval is met
+ */
+ private function send_keep_alive()
+ {
+ if ($this->bitmap & self::MASK_CONNECTED) {
+ $elapsed = microtime(true) - $this->last_packet;
+ if ($this->keepAlive > 0 && $elapsed >= $this->keepAlive) {
+ $this->send_binary_packet(pack('CN', NET_SSH2_MSG_IGNORE, 0));
+ }
+ }
}
/**
@@ -4494,28 +4542,61 @@ class SSH2
*
* @param string $message_number
* @param string $message
- * @access private
*/
- function _append_log($message_number, $message)
+ private function append_log($message_number, $message)
+ {
+ $this->append_log_helper(
+ NET_SSH2_LOGGING,
+ $message_number,
+ $message,
+ $this->message_number_log,
+ $this->message_log,
+ $this->log_size,
+ $this->realtime_log_file,
+ $this->realtime_log_wrap,
+ $this->realtime_log_size
+ );
+ }
+
+ /**
+ * Logs data packet helper
+ *
+ * @param int $constant
+ * @param string $message_number
+ * @param string $message
+ * @param array &$message_number_log
+ * @param array &$message_log
+ * @param int &$log_size
+ * @param resource &$realtime_log_file
+ * @param bool &$realtime_log_wrap
+ * @param int &$realtime_log_size
+ */
+ protected function append_log_helper($constant, $message_number, $message, array &$message_number_log, array &$message_log, &$log_size, &$realtime_log_file, &$realtime_log_wrap, &$realtime_log_size)
{
// remove the byte identifying the message type from all but the first two messages (ie. the identification strings)
- if (strlen($message_number) > 2) {
- $this->_string_shift($message);
+ if (!in_array(substr($message_number, 0, 4), ['<- (', '-> (']) && strlen($message_number) > 2) {
+ Strings::shift($message);
}
- switch (NET_SSH2_LOGGING) {
+ switch ($constant) {
// useful for benchmarks
case self::LOG_SIMPLE:
- $this->message_number_log[] = $message_number;
+ $message_number_log[] = $message_number;
+ break;
+ case self::LOG_SIMPLE_REALTIME:
+ echo $message_number;
+ echo PHP_SAPI == 'cli' ? "\r\n" : '<br>';
+ @flush();
+ @ob_flush();
break;
// the most useful log for SSH2
case self::LOG_COMPLEX:
- $this->message_number_log[] = $message_number;
- $this->log_size+= strlen($message);
- $this->message_log[] = $message;
- while ($this->log_size > self::LOG_MAX_SIZE) {
- $this->log_size-= strlen(array_shift($this->message_log));
- array_shift($this->message_number_log);
+ $message_number_log[] = $message_number;
+ $log_size += strlen($message);
+ $message_log[] = $message;
+ while ($log_size > self::LOG_MAX_SIZE) {
+ $log_size -= strlen(array_shift($message_log));
+ array_shift($message_number_log);
}
break;
// dump the output out realtime; packets may be interspersed with non packets,
@@ -4530,39 +4611,39 @@ class SSH2
$start = '<pre>';
$stop = '</pre>';
}
- echo $start . $this->_format_log(array($message), array($message_number)) . $stop;
+ echo $start . $this->format_log([$message], [$message_number]) . $stop;
@flush();
@ob_flush();
break;
- // basically the same thing as self::LOG_REALTIME with the caveat that self::LOG_REALTIME_FILE
+ // basically the same thing as self::LOG_REALTIME with the caveat that NET_SSH2_LOG_REALTIME_FILENAME
// needs to be defined and that the resultant log file will be capped out at self::LOG_MAX_SIZE.
// the earliest part of the log file is denoted by the first <<< START >>> and is not going to necessarily
// at the beginning of the file
case self::LOG_REALTIME_FILE:
- if (!isset($this->realtime_log_file)) {
+ if (!isset($realtime_log_file)) {
// PHP doesn't seem to like using constants in fopen()
- $filename = self::LOG_REALTIME_FILENAME;
+ $filename = NET_SSH2_LOG_REALTIME_FILENAME;
$fp = fopen($filename, 'w');
- $this->realtime_log_file = $fp;
+ $realtime_log_file = $fp;
}
- if (!is_resource($this->realtime_log_file)) {
+ if (!is_resource($realtime_log_file)) {
break;
}
- $entry = $this->_format_log(array($message), array($message_number));
- if ($this->realtime_log_wrap) {
+ $entry = $this->format_log([$message], [$message_number]);
+ if ($realtime_log_wrap) {
$temp = "<<< START >>>\r\n";
- $entry.= $temp;
- fseek($this->realtime_log_file, ftell($this->realtime_log_file) - strlen($temp));
+ $entry .= $temp;
+ fseek($realtime_log_file, ftell($realtime_log_file) - strlen($temp));
}
- $this->realtime_log_size+= strlen($entry);
- if ($this->realtime_log_size > self::LOG_MAX_SIZE) {
- fseek($this->realtime_log_file, 0);
- $this->realtime_log_size = strlen($entry);
- $this->realtime_log_wrap = true;
+ $realtime_log_size += strlen($entry);
+ if ($realtime_log_size > self::LOG_MAX_SIZE) {
+ fseek($realtime_log_file, 0);
+ $realtime_log_size = strlen($entry);
+ $realtime_log_wrap = true;
}
- fputs($this->realtime_log_file, $entry);
+ fputs($realtime_log_file, $entry);
break;
- case NET_SSH2_LOG_REALTIME_SIMPLE:
+ case self::LOG_REALTIME_SIMPLE:
echo $message_number;
echo PHP_SAPI == 'cli' ? "\r\n" : '<br>';
}
@@ -4575,17 +4656,28 @@ class SSH2
*
* @param int $client_channel
* @param string $data
- * @return bool
- * @access private
+ * @return void
*/
- function _send_channel_packet($client_channel, $data)
+ protected function send_channel_packet($client_channel, $data)
{
+ if (
+ isset($this->channel_buffers_write[$client_channel])
+ && strpos($data, $this->channel_buffers_write[$client_channel]) === 0
+ ) {
+ // if buffer holds identical initial data content, resume send from the unmatched data portion
+ $data = substr($data, strlen($this->channel_buffers_write[$client_channel]));
+ } else {
+ $this->channel_buffers_write[$client_channel] = '';
+ }
while (strlen($data)) {
if (!$this->window_size_client_to_server[$client_channel]) {
- $this->bitmap^= self::MASK_WINDOW_ADJUST;
// using an invalid channel will let the buffers be built up for the valid channels
- $this->_get_channel_packet(-1);
- $this->bitmap^= self::MASK_WINDOW_ADJUST;
+ $this->get_channel_packet(-$client_channel);
+ if ($this->isTimeout()) {
+ throw new TimeoutException('Timed out waiting for server');
+ } elseif (!$this->window_size_client_to_server[$client_channel]) {
+ throw new \RuntimeException('Data window was not adjusted');
+ }
}
/* The maximum amount of data allowed is determined by the maximum
@@ -4597,62 +4689,69 @@ class SSH2
$this->window_size_client_to_server[$client_channel]
);
- $temp = $this->_string_shift($data, $max_size);
- $packet = pack(
- 'CN2a*',
+ $temp = Strings::shift($data, $max_size);
+ $packet = Strings::packSSH2(
+ 'CNs',
NET_SSH2_MSG_CHANNEL_DATA,
$this->server_channels[$client_channel],
- strlen($temp),
$temp
);
- $this->window_size_client_to_server[$client_channel]-= strlen($temp);
- if (!$this->_send_binary_packet($packet)) {
- return false;
- }
+ $this->window_size_client_to_server[$client_channel] -= strlen($temp);
+ $this->send_binary_packet($packet);
+ $this->channel_buffers_write[$client_channel] .= $temp;
}
-
- return true;
+ unset($this->channel_buffers_write[$client_channel]);
}
/**
* Closes and flushes a channel
*
- * \phpseclib\Net\SSH2 doesn't properly close most channels. For exec() channels are normally closed by the server
+ * \phpseclib3\Net\SSH2 doesn't properly close most channels. For exec() channels are normally closed by the server
* and for SFTP channels are presumably closed when the client disconnects. This functions is intended
* for SCP more than anything.
*
* @param int $client_channel
* @param bool $want_reply
- * @return bool
- * @access private
+ * @return void
*/
- function _close_channel($client_channel, $want_reply = false)
+ private function close_channel($client_channel)
{
// see http://tools.ietf.org/html/rfc4254#section-5.3
- $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel]));
-
- if (!$want_reply) {
- $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel]));
+ if ($this->channel_status[$client_channel] != NET_SSH2_MSG_CHANNEL_EOF) {
+ $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_EOF, $this->server_channels[$client_channel]));
}
+ $this->send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel]));
$this->channel_status[$client_channel] = NET_SSH2_MSG_CHANNEL_CLOSE;
- $this->curTimeout = 5;
+ $this->channelCount--;
- while (!is_bool($this->_get_channel_packet($client_channel))) {
+ $this->curTimeout = 5;
+ while (!is_bool($this->get_channel_packet($client_channel))) {
}
- if ($this->is_timeout) {
- $this->disconnect();
- }
+ unset($this->channel_status[$client_channel]);
- if ($want_reply) {
- $this->_send_binary_packet(pack('CN', NET_SSH2_MSG_CHANNEL_CLOSE, $this->server_channels[$client_channel]));
- }
+ $this->close_channel_bitmap($client_channel);
+ }
- if ($this->bitmap & self::MASK_SHELL) {
- $this->bitmap&= ~self::MASK_SHELL;
+ /**
+ * Maintains execution state bitmap in response to channel closure
+ *
+ * @param int $client_channel The channel number to maintain closure status of
+ * @return void
+ */
+ private function close_channel_bitmap($client_channel)
+ {
+ switch ($client_channel) {
+ case self::CHANNEL_SHELL:
+ // Shell status has been maintained in the bitmap for backwards
+ // compatibility sake, but can be removed going forward
+ if ($this->bitmap & self::MASK_SHELL) {
+ $this->bitmap &= ~self::MASK_SHELL;
+ }
+ break;
}
}
@@ -4660,39 +4759,26 @@ class SSH2
* Disconnect
*
* @param int $reason
- * @return bool
- * @access private
+ * @return false
*/
- function _disconnect($reason)
+ protected function disconnect_helper($reason)
{
- if ($this->bitmap & self::MASK_CONNECTED) {
- $data = pack('CNNa*Na*', NET_SSH2_MSG_DISCONNECT, $reason, 0, '', 0, '');
- $this->_send_binary_packet($data);
+ if ($this->bitmap & self::MASK_DISCONNECT) {
+ // Disregard subsequent disconnect requests
+ return false;
}
-
- $this->bitmap = 0;
- if (is_resource($this->fsock) && get_resource_type($this->fsock) == 'stream') {
- fclose($this->fsock);
+ $this->bitmap |= self::MASK_DISCONNECT;
+ if ($this->isConnected()) {
+ $data = Strings::packSSH2('CNss', NET_SSH2_MSG_DISCONNECT, $reason, '', '');
+ try {
+ $this->send_binary_packet($data);
+ } catch (\Exception $e) {
+ }
}
- return false;
- }
+ $this->reset_connection();
- /**
- * String Shift
- *
- * Inspired by array_shift
- *
- * @param string $string
- * @param int $index
- * @return string
- * @access private
- */
- function _string_shift(&$string, $index = 1)
- {
- $substr = substr($string, 0, $index);
- $string = substr($string, $index);
- return $substr;
+ return false;
}
/**
@@ -4702,11 +4788,11 @@ class SSH2
* named constants from it, using the value as the name of the constant and the index as the value of the constant.
* If any of the constants that would be defined already exists, none of the constants will be defined.
*
- * @access private
+ * @param mixed[] ...$args
+ * @access protected
*/
- function _define_array()
+ protected static function define_array(...$args)
{
- $args = func_get_args();
foreach ($args as $arg) {
foreach ($arg as $key => $value) {
if (!defined($value)) {
@@ -4723,10 +4809,9 @@ class SSH2
*
* Returns a string if NET_SSH2_LOGGING == self::LOG_COMPLEX, an array if NET_SSH2_LOGGING == self::LOG_SIMPLE and false if !defined('NET_SSH2_LOGGING')
*
- * @access public
* @return array|false|string
*/
- function getLog()
+ public function getLog()
{
if (!defined('NET_SSH2_LOGGING')) {
return false;
@@ -4736,7 +4821,7 @@ class SSH2
case self::LOG_SIMPLE:
return $this->message_number_log;
case self::LOG_COMPLEX:
- $log = $this->_format_log($this->message_log, $this->message_number_log);
+ $log = $this->format_log($this->message_log, $this->message_number_log);
return PHP_SAPI == 'cli' ? $log : '<pre>' . $log . '</pre>';
default:
return false;
@@ -4748,62 +4833,51 @@ class SSH2
*
* @param array $message_log
* @param array $message_number_log
- * @access private
* @return string
*/
- function _format_log($message_log, $message_number_log)
+ protected function format_log(array $message_log, array $message_number_log)
{
$output = '';
for ($i = 0; $i < count($message_log); $i++) {
- $output.= $message_number_log[$i] . "\r\n";
+ $output .= $message_number_log[$i];
$current_log = $message_log[$i];
$j = 0;
+ if (strlen($current_log)) {
+ $output .= "\r\n";
+ }
do {
if (strlen($current_log)) {
- $output.= str_pad(dechex($j), 7, '0', STR_PAD_LEFT) . '0 ';
+ $output .= str_pad(dechex($j), 7, '0', STR_PAD_LEFT) . '0 ';
}
- $fragment = $this->_string_shift($current_log, $this->log_short_width);
- $hex = substr(preg_replace_callback('#.#s', array($this, '_format_log_helper'), $fragment), strlen($this->log_boundary));
+ $fragment = Strings::shift($current_log, $this->log_short_width);
+ $hex = substr(preg_replace_callback('#.#s', function ($matches) {
+ return $this->log_boundary . str_pad(dechex(ord($matches[0])), 2, '0', STR_PAD_LEFT);
+ }, $fragment), strlen($this->log_boundary));
// replace non ASCII printable characters with dots
// http://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters
// also replace < with a . since < messes up the output on web browsers
$raw = preg_replace('#[^\x20-\x7E]|<#', '.', $fragment);
- $output.= str_pad($hex, $this->log_long_width - $this->log_short_width, ' ') . $raw . "\r\n";
+ $output .= str_pad($hex, $this->log_long_width - $this->log_short_width, ' ') . $raw . "\r\n";
$j++;
} while (strlen($current_log));
- $output.= "\r\n";
+ $output .= "\r\n";
}
return $output;
}
/**
- * Helper function for _format_log
- *
- * For use with preg_replace_callback()
- *
- * @param array $matches
- * @access private
- * @return string
- */
- function _format_log_helper($matches)
- {
- return $this->log_boundary . str_pad(dechex(ord($matches[0])), 2, '0', STR_PAD_LEFT);
- }
-
- /**
- * Helper function for agent->_on_channel_open()
+ * Helper function for agent->on_channel_open()
*
* Used when channels are created to inform agent
* of said channel opening. Must be called after
* channel open confirmation received
*
- * @access private
*/
- function _on_channel_open()
+ private function on_channel_open()
{
if (isset($this->agent)) {
- $this->agent->_on_channel_open($this);
+ $this->agent->registerChannelOpen($this);
}
}
@@ -4814,9 +4888,8 @@ class SSH2
* @param array $array1
* @param array $array2
* @return mixed False if intersection is empty, else intersected value.
- * @access private
*/
- function _array_intersect_first($array1, $array2)
+ private static function array_intersect_first(array $array1, array $array2)
{
foreach ($array1 as $value) {
if (in_array($value, $array2)) {
@@ -4832,9 +4905,8 @@ class SSH2
* If you are looking for messages from the SFTP layer, please see SFTP::getSFTPErrors()
*
* @return string[]
- * @access public
*/
- function getErrors()
+ public function getErrors()
{
return $this->errors;
}
@@ -4845,9 +4917,8 @@ class SSH2
* If you are looking for messages from the SFTP layer, please see SFTP::getLastSFTPError()
*
* @return string
- * @access public
*/
- function getLastError()
+ public function getLastError()
{
$count = count($this->errors);
@@ -4859,203 +4930,74 @@ class SSH2
/**
* Return the server identification.
*
- * @return string
- * @access public
+ * @return string|false
*/
- function getServerIdentification()
+ public function getServerIdentification()
{
- $this->_connect();
+ $this->connect();
return $this->server_identifier;
}
/**
- * Return a list of the key exchange algorithms the server supports.
- *
- * @return array
- * @access public
- */
- function getKexAlgorithms()
- {
- $this->_connect();
-
- return $this->kex_algorithms;
- }
-
- /**
- * Return a list of the host key (public key) algorithms the server supports.
- *
- * @return array
- * @access public
- */
- function getServerHostKeyAlgorithms()
- {
- $this->_connect();
-
- return $this->server_host_key_algorithms;
- }
-
- /**
- * Return a list of the (symmetric key) encryption algorithms the server supports, when receiving stuff from the client.
- *
- * @return array
- * @access public
- */
- function getEncryptionAlgorithmsClient2Server()
- {
- $this->_connect();
-
- return $this->encryption_algorithms_client_to_server;
- }
-
- /**
- * Return a list of the (symmetric key) encryption algorithms the server supports, when sending stuff to the client.
- *
- * @return array
- * @access public
- */
- function getEncryptionAlgorithmsServer2Client()
- {
- $this->_connect();
-
- return $this->encryption_algorithms_server_to_client;
- }
-
- /**
- * Return a list of the MAC algorithms the server supports, when receiving stuff from the client.
- *
- * @return array
- * @access public
- */
- function getMACAlgorithmsClient2Server()
- {
- $this->_connect();
-
- return $this->mac_algorithms_client_to_server;
- }
-
- /**
- * Return a list of the MAC algorithms the server supports, when sending stuff to the client.
- *
- * @return array
- * @access public
- */
- function getMACAlgorithmsServer2Client()
- {
- $this->_connect();
-
- return $this->mac_algorithms_server_to_client;
- }
-
- /**
- * Return a list of the compression algorithms the server supports, when receiving stuff from the client.
- *
- * @return array
- * @access public
- */
- function getCompressionAlgorithmsClient2Server()
- {
- $this->_connect();
-
- return $this->compression_algorithms_client_to_server;
- }
-
- /**
- * Return a list of the compression algorithms the server supports, when sending stuff to the client.
- *
- * @return array
- * @access public
- */
- function getCompressionAlgorithmsServer2Client()
- {
- $this->_connect();
-
- return $this->compression_algorithms_server_to_client;
- }
-
- /**
- * Return a list of the languages the server supports, when sending stuff to the client.
- *
- * @return array
- * @access public
- */
- function getLanguagesServer2Client()
- {
- $this->_connect();
-
- return $this->languages_server_to_client;
- }
-
- /**
- * Return a list of the languages the server supports, when receiving stuff from the client.
- *
- * @return array
- * @access public
- */
- function getLanguagesClient2Server()
- {
- $this->_connect();
-
- return $this->languages_client_to_server;
- }
-
- /**
* Returns a list of algorithms the server supports
*
* @return array
- * @access public
*/
- function getServerAlgorithms()
+ public function getServerAlgorithms()
{
- $this->_connect();
+ $this->connect();
- return array(
+ return [
'kex' => $this->kex_algorithms,
'hostkey' => $this->server_host_key_algorithms,
- 'client_to_server' => array(
+ 'client_to_server' => [
'crypt' => $this->encryption_algorithms_client_to_server,
'mac' => $this->mac_algorithms_client_to_server,
'comp' => $this->compression_algorithms_client_to_server,
'lang' => $this->languages_client_to_server
- ),
- 'server_to_client' => array(
+ ],
+ 'server_to_client' => [
'crypt' => $this->encryption_algorithms_server_to_client,
'mac' => $this->mac_algorithms_server_to_client,
'comp' => $this->compression_algorithms_server_to_client,
'lang' => $this->languages_server_to_client
- )
- );
+ ]
+ ];
}
/**
* Returns a list of KEX algorithms that phpseclib supports
*
* @return array
- * @access public
*/
- function getSupportedKEXAlgorithms()
+ public static function getSupportedKEXAlgorithms()
{
- $kex_algorithms = array(
+ $kex_algorithms = [
// Elliptic Curve Diffie-Hellman Key Agreement (ECDH) using
// Curve25519. See doc/curve25519-sha256@libssh.org.txt in the
// libssh repository for more information.
+ 'curve25519-sha256',
'curve25519-sha256@libssh.org',
+ 'ecdh-sha2-nistp256', // RFC 5656
+ 'ecdh-sha2-nistp384', // RFC 5656
+ 'ecdh-sha2-nistp521', // RFC 5656
+
'diffie-hellman-group-exchange-sha256',// RFC 4419
'diffie-hellman-group-exchange-sha1', // RFC 4419
// Diffie-Hellman Key Agreement (DH) using integer modulo prime
// groups.
+ 'diffie-hellman-group14-sha256',
'diffie-hellman-group14-sha1', // REQUIRED
- 'diffie-hellman-group1-sha1', // REQUIRED
- );
+ 'diffie-hellman-group15-sha512',
+ 'diffie-hellman-group16-sha512',
+ 'diffie-hellman-group17-sha512',
+ 'diffie-hellman-group18-sha512',
- if (!function_exists('sodium_crypto_box_publickey_from_secretkey')) {
- $kex_algorithms = array_diff(
- $kex_algorithms,
- array('curve25519-sha256@libssh.org')
- );
- }
+ 'diffie-hellman-group1-sha1', // REQUIRED
+ ];
return $kex_algorithms;
}
@@ -5064,27 +5006,33 @@ class SSH2
* Returns a list of host key algorithms that phpseclib supports
*
* @return array
- * @access public
*/
- function getSupportedHostKeyAlgorithms()
+ public static function getSupportedHostKeyAlgorithms()
{
- return array(
+ return [
+ 'ssh-ed25519', // https://tools.ietf.org/html/draft-ietf-curdle-ssh-ed25519-02
+ 'ecdsa-sha2-nistp256', // RFC 5656
+ 'ecdsa-sha2-nistp384', // RFC 5656
+ 'ecdsa-sha2-nistp521', // RFC 5656
'rsa-sha2-256', // RFC 8332
'rsa-sha2-512', // RFC 8332
'ssh-rsa', // RECOMMENDED sign Raw RSA Key
'ssh-dss' // REQUIRED sign Raw DSS Key
- );
+ ];
}
/**
* Returns a list of symmetric key algorithms that phpseclib supports
*
* @return array
- * @access public
*/
- function getSupportedEncryptionAlgorithms()
+ public static function getSupportedEncryptionAlgorithms()
{
- $algos = array(
+ $algos = [
+ // from <https://tools.ietf.org/html/rfc5647>:
+ 'aes128-gcm@openssh.com',
+ 'aes256-gcm@openssh.com',
+
// from <http://tools.ietf.org/html/rfc4345#section-4>:
'arcfour256',
'arcfour128',
@@ -5096,6 +5044,16 @@ class SSH2
'aes192-ctr', // RECOMMENDED AES with 192-bit key
'aes256-ctr', // RECOMMENDED AES with 256-bit key
+ // from <https://github.com/openssh/openssh-portable/blob/001aa55/PROTOCOL.chacha20poly1305>:
+ // one of the big benefits of chacha20-poly1305 is speed. the problem is...
+ // libsodium doesn't generate the poly1305 keys in the way ssh does and openssl's PHP bindings don't even
+ // seem to support poly1305 currently. so even if libsodium or openssl are being used for the chacha20
+ // part, pure-PHP has to be used for the poly1305 part and that's gonna cause a big slow down.
+ // speed-wise it winds up being faster to use AES (when openssl or mcrypt are available) and some HMAC
+ // (which is always gonna be super fast to compute thanks to the hash extension, which
+ // "is bundled and compiled into PHP by default")
+ 'chacha20-poly1305@openssh.com',
+
'twofish128-ctr', // OPTIONAL Twofish in SDCTR mode, with 128-bit key
'twofish192-ctr', // OPTIONAL Twofish with 192-bit key
'twofish256-ctr', // OPTIONAL Twofish with 256-bit key
@@ -5119,34 +5077,66 @@ class SSH2
'3des-cbc', // REQUIRED three-key 3DES in CBC mode
//'none' // OPTIONAL no encryption; NOT RECOMMENDED
- );
+ ];
- if ($this->crypto_engine) {
- $engines = array($this->crypto_engine);
+ if (self::$crypto_engine) {
+ $engines = [self::$crypto_engine];
} else {
- $engines = array(
- Base::ENGINE_OPENSSL,
- Base::ENGINE_MCRYPT,
- Base::ENGINE_INTERNAL
- );
+ $engines = [
+ 'libsodium',
+ 'OpenSSL (GCM)',
+ 'OpenSSL',
+ 'mcrypt',
+ 'Eval',
+ 'PHP'
+ ];
}
- $ciphers = array();
+ $ciphers = [];
+
foreach ($engines as $engine) {
foreach ($algos as $algo) {
- $obj = $this->_encryption_algorithm_to_crypt_instance($algo);
+ $obj = self::encryption_algorithm_to_crypt_instance($algo);
if ($obj instanceof Rijndael) {
$obj->setKeyLength(preg_replace('#[^\d]#', '', $algo));
}
switch ($algo) {
+ // Eval engines do not exist for ChaCha20 or RC4 because they would not benefit from one.
+ // to benefit from an Eval engine they'd need to loop a variable amount of times, they'd
+ // need to do table lookups (eg. sbox subsitutions). ChaCha20 doesn't do either because
+ // it's a so-called ARX cipher, meaning that the only operations it does are add (A), rotate (R)
+ // and XOR (X). RC4 does do table lookups but being a stream cipher it works differently than
+ // block ciphers. with RC4 you XOR the plaintext against a keystream and the keystream changes
+ // as you encrypt stuff. the only table lookups are made against this keystream and thus table
+ // lookups are kinda unavoidable. with AES and DES, however, the table lookups that are done
+ // are done against substitution boxes (sboxes), which are invariant.
+
+ // OpenSSL can't be used as an engine, either, because OpenSSL doesn't support continuous buffers
+ // as SSH2 uses and altho you can emulate a continuous buffer with block ciphers you can't do so
+ // with stream ciphers. As for ChaCha20... for the ChaCha20 part OpenSSL could prob be used but
+ // the big slow down isn't with ChaCha20 - it's with Poly1305. SSH constructs the key for that
+ // differently than how OpenSSL does it (OpenSSL does it as the RFC describes, SSH doesn't).
+
+ // libsodium can't be used because it doesn't support RC4 and it doesn't construct the Poly1305
+ // keys in the same way that SSH does
+
+ // mcrypt could prob be used for RC4 but mcrypt hasn't been included in PHP core for yearss
+ case 'chacha20-poly1305@openssh.com':
case 'arcfour128':
case 'arcfour256':
- if ($engine != Base::ENGINE_INTERNAL) {
+ if ($engine != 'PHP') {
+ continue 2;
+ }
+ break;
+ case 'aes128-gcm@openssh.com':
+ case 'aes256-gcm@openssh.com':
+ if ($engine == 'OpenSSL') {
continue 2;
}
+ $obj->setNonce('dummydummydu');
}
if ($obj->isValidEngine($engine)) {
- $algos = array_diff($algos, array($algo));
+ $algos = array_diff($algos, [$algo]);
$ciphers[] = $algo;
}
}
@@ -5159,31 +5149,42 @@ class SSH2
* Returns a list of MAC algorithms that phpseclib supports
*
* @return array
- * @access public
*/
- function getSupportedMACAlgorithms()
+ public static function getSupportedMACAlgorithms()
{
- return array(
+ return [
+ 'hmac-sha2-256-etm@openssh.com',
+ 'hmac-sha2-512-etm@openssh.com',
+ 'hmac-sha1-etm@openssh.com',
+
// from <http://www.ietf.org/rfc/rfc6668.txt>:
'hmac-sha2-256',// RECOMMENDED HMAC-SHA256 (digest length = key length = 32)
+ 'hmac-sha2-512',// OPTIONAL HMAC-SHA512 (digest length = key length = 64)
'hmac-sha1-96', // RECOMMENDED first 96 bits of HMAC-SHA1 (digest length = 12, key length = 20)
'hmac-sha1', // REQUIRED HMAC-SHA1 (digest length = key length = 20)
'hmac-md5-96', // OPTIONAL first 96 bits of HMAC-MD5 (digest length = 12, key length = 16)
'hmac-md5', // OPTIONAL HMAC-MD5 (digest length = key length = 16)
+
+ 'umac-64-etm@openssh.com',
+ 'umac-128-etm@openssh.com',
+
+ // from <https://tools.ietf.org/html/draft-miller-secsh-umac-01>:
+ 'umac-64@openssh.com',
+ 'umac-128@openssh.com',
+
//'none' // OPTIONAL no MAC; NOT RECOMMENDED
- );
+ ];
}
/**
* Returns a list of compression algorithms that phpseclib supports
*
* @return array
- * @access public
*/
- function getSupportedCompressionAlgorithms()
+ public static function getSupportedCompressionAlgorithms()
{
- $algos = array('none'); // REQUIRED no compression
+ $algos = ['none']; // REQUIRED no compression
if (function_exists('deflate_init')) {
$algos[] = 'zlib@openssh.com'; // https://datatracker.ietf.org/doc/html/draft-miller-secsh-compression-delayed
$algos[] = 'zlib';
@@ -5197,32 +5198,49 @@ class SSH2
* Uses the same format as https://www.php.net/ssh2-methods-negotiated
*
* @return array
- * @access public
*/
- function getAlgorithmsNegotiated()
+ public function getAlgorithmsNegotiated()
{
- $this->_connect();
+ $this->connect();
- $compression_map = array(
+ $compression_map = [
self::NET_SSH2_COMPRESSION_NONE => 'none',
self::NET_SSH2_COMPRESSION_ZLIB => 'zlib',
self::NET_SSH2_COMPRESSION_ZLIB_AT_OPENSSH => 'zlib@openssh.com'
- );
+ ];
- return array(
+ return [
'kex' => $this->kex_algorithm,
'hostkey' => $this->signature_format,
- 'client_to_server' => array(
+ 'client_to_server' => [
'crypt' => $this->encryptName,
'mac' => $this->hmac_create_name,
'comp' => $compression_map[$this->compress],
- ),
- 'server_to_client' => array(
+ ],
+ 'server_to_client' => [
'crypt' => $this->decryptName,
'mac' => $this->hmac_check_name,
'comp' => $compression_map[$this->decompress],
- )
- );
+ ]
+ ];
+ }
+
+ /**
+ * Force multiple channels (even if phpseclib has decided to disable them)
+ */
+ public function forceMultipleChannels()
+ {
+ $this->errorOnMultipleChannels = false;
+ }
+
+ /**
+ * Allows you to set the terminal
+ *
+ * @param string $term
+ */
+ public function setTerminal($term)
+ {
+ $this->term = $term;
}
/**
@@ -5230,11 +5248,10 @@ class SSH2
* <https://www.php.net/manual/en/function.ssh2-connect.php>
*
* @param array $methods
- * @access public
*/
- function setPreferredAlgorithms($methods)
+ public function setPreferredAlgorithms(array $methods)
{
- $keys = array('client_to_server', 'server_to_client');
+ $keys = ['client_to_server', 'server_to_client'];
if (isset($methods['kex']) && is_string($methods['kex'])) {
$methods['kex'] = explode(',', $methods['kex']);
@@ -5299,7 +5316,7 @@ class SSH2
}
}
- $keys = array(
+ $keys = [
'kex',
'hostkey',
'client_to_server/crypt',
@@ -5308,7 +5325,7 @@ class SSH2
'server_to_client/crypt',
'server_to_client/comp',
'server_to_client/mac',
- );
+ ];
foreach ($keys as $key) {
$p = $preferred;
$m = $methods;
@@ -5327,8 +5344,7 @@ class SSH2
$msg = count($diff) == 1 ?
' is not a supported algorithm' :
' are not supported algorithms';
- user_error(implode(', ', $diff) . $msg);
- return false;
+ throw new UnsupportedAlgorithmException(implode(', ', $diff) . $msg);
}
}
@@ -5342,9 +5358,8 @@ class SSH2
* authentication may be relevant for getting legal protection."
*
* @return string
- * @access public
*/
- function getBannerMessage()
+ public function getBannerMessage()
{
return $this->banner_message;
}
@@ -5355,170 +5370,64 @@ class SSH2
* Caching this the first time you connect to a server and checking the result on subsequent connections
* is recommended. Returns false if the server signature is not signed correctly with the public host key.
*
- * @return mixed
- * @access public
+ * @return string|false
+ * @throws \RuntimeException on badly formatted keys
+ * @throws NoSupportedAlgorithmsException when the key isn't in a supported format
*/
- function getServerPublicHostKey()
+ public function getServerPublicHostKey()
{
if (!($this->bitmap & self::MASK_CONSTRUCTOR)) {
- if (!$this->_connect()) {
- return false;
- }
+ $this->connect();
}
$signature = $this->signature;
- $server_public_host_key = $this->server_public_host_key;
-
- if (strlen($server_public_host_key) < 4) {
- return false;
- }
- extract(unpack('Nlength', $this->_string_shift($server_public_host_key, 4)));
- $this->_string_shift($server_public_host_key, $length);
+ $server_public_host_key = base64_encode($this->server_public_host_key);
if ($this->signature_validated) {
return $this->bitmap ?
- $this->signature_format . ' ' . base64_encode($this->server_public_host_key) :
+ $this->signature_format . ' ' . $server_public_host_key :
false;
}
$this->signature_validated = true;
switch ($this->signature_format) {
- case 'ssh-dss':
- $zero = new BigInteger();
-
- if (strlen($server_public_host_key) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
- $p = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);
-
- if (strlen($server_public_host_key) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
- $q = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);
-
- if (strlen($server_public_host_key) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
- $g = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);
-
- if (strlen($server_public_host_key) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
- $y = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);
-
- /* The value for 'dss_signature_blob' is encoded as a string containing
- r, followed by s (which are 160-bit integers, without lengths or
- padding, unsigned, and in network byte order). */
- $temp = unpack('Nlength', $this->_string_shift($signature, 4));
- if ($temp['length'] != 40) {
- user_error('Invalid signature');
- return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
- }
-
- $r = new BigInteger($this->_string_shift($signature, 20), 256);
- $s = new BigInteger($this->_string_shift($signature, 20), 256);
-
- switch (true) {
- case $r->equals($zero):
- case $r->compare($q) >= 0:
- case $s->equals($zero):
- case $s->compare($q) >= 0:
- user_error('Invalid signature');
- return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
- }
-
- $w = $s->modInverse($q);
-
- $u1 = $w->multiply(new BigInteger(sha1($this->exchange_hash), 16));
- list(, $u1) = $u1->divide($q);
-
- $u2 = $w->multiply($r);
- list(, $u2) = $u2->divide($q);
-
- $g = $g->modPow($u1, $p);
- $y = $y->modPow($u2, $p);
-
- $v = $g->multiply($y);
- list(, $v) = $v->divide($p);
- list(, $v) = $v->divide($q);
-
- if (!$v->equals($r)) {
- user_error('Bad server signature');
- return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
- }
-
- break;
- case 'ssh-rsa':
- case 'rsa-sha2-256':
- case 'rsa-sha2-512':
- if (strlen($server_public_host_key) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
- $e = new BigInteger($this->_string_shift($server_public_host_key, $temp['length']), -256);
-
- if (strlen($server_public_host_key) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $this->_string_shift($server_public_host_key, 4));
- $rawN = $this->_string_shift($server_public_host_key, $temp['length']);
- $n = new BigInteger($rawN, -256);
- $nLength = strlen(ltrim($rawN, "\0"));
-
- /*
- if (strlen($signature) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $this->_string_shift($signature, 4));
- $signature = $this->_string_shift($signature, $temp['length']);
-
- $rsa = new RSA();
+ case 'ssh-ed25519':
+ case 'ecdsa-sha2-nistp256':
+ case 'ecdsa-sha2-nistp384':
+ case 'ecdsa-sha2-nistp521':
+ $key = EC::loadFormat('OpenSSH', $server_public_host_key)
+ ->withSignatureFormat('SSH2');
switch ($this->signature_format) {
- case 'rsa-sha2-512':
+ case 'ssh-ed25519':
$hash = 'sha512';
break;
- case 'rsa-sha2-256':
+ case 'ecdsa-sha2-nistp256':
$hash = 'sha256';
break;
- //case 'ssh-rsa':
- default:
- $hash = 'sha1';
- }
- $rsa->setHash($hash);
- $rsa->setSignatureMode(RSA::SIGNATURE_PKCS1);
- $rsa->loadKey(array('e' => $e, 'n' => $n), RSA::PUBLIC_FORMAT_RAW);
-
- if (!$rsa->verify($this->exchange_hash, $signature)) {
- user_error('Bad server signature');
- return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
- }
- */
-
- if (strlen($signature) < 4) {
- return false;
- }
- $temp = unpack('Nlength', $this->_string_shift($signature, 4));
- $s = new BigInteger($this->_string_shift($signature, $temp['length']), 256);
-
- // validate an RSA signature per "8.2 RSASSA-PKCS1-v1_5", "5.2.2 RSAVP1", and "9.1 EMSA-PSS" in the
- // following URL:
- // ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1.pdf
-
- // also, see SSHRSA.c (rsa2_verifysig) in PuTTy's source.
-
- if ($s->compare(new BigInteger()) < 0 || $s->compare($n->subtract(new BigInteger(1))) > 0) {
- user_error('Invalid signature');
- return $this->_disconnect(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
+ case 'ecdsa-sha2-nistp384':
+ $hash = 'sha384';
+ break;
+ case 'ecdsa-sha2-nistp521':
+ $hash = 'sha512';
}
+ $key = $key->withHash($hash);
+ break;
+ case 'ssh-dss':
+ $key = DSA::loadFormat('OpenSSH', $server_public_host_key)
+ ->withSignatureFormat('SSH2')
+ ->withHash('sha1');
+ break;
+ case 'ssh-rsa':
+ case 'rsa-sha2-256':
+ case 'rsa-sha2-512':
+ // could be ssh-rsa, rsa-sha2-256, rsa-sha2-512
+ // we don't check here because we already checked in key_exchange
+ // some signatures have the type embedded within the message and some don't
+ list(, $signature) = Strings::unpackSSH2('ss', $signature);
- $s = $s->modPow($e, $n);
- $s = $s->toBytes();
-
+ $key = RSA::loadFormat('OpenSSH', $server_public_host_key)
+ ->withPadding(RSA::SIGNATURE_PKCS1);
switch ($this->signature_format) {
case 'rsa-sha2-512':
$hash = 'sha512';
@@ -5530,41 +5439,26 @@ class SSH2
default:
$hash = 'sha1';
}
- $hashObj = new Hash($hash);
- switch ($this->signature_format) {
- case 'rsa-sha2-512':
- $h = pack('N5a*', 0x00305130, 0x0D060960, 0x86480165, 0x03040203, 0x05000440, $hashObj->hash($this->exchange_hash));
- break;
- case 'rsa-sha2-256':
- $h = pack('N5a*', 0x00303130, 0x0D060960, 0x86480165, 0x03040201, 0x05000420, $hashObj->hash($this->exchange_hash));
- break;
- //case 'ssh-rsa':
- default:
- $hash = 'sha1';
- $h = pack('N4a*', 0x00302130, 0x0906052B, 0x0E03021A, 0x05000414, $hashObj->hash($this->exchange_hash));
- }
- $h = chr(0x01) . str_repeat(chr(0xFF), $nLength - 2 - strlen($h)) . $h;
-
- if ($s != $h) {
- user_error('Bad server signature');
- return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
- }
+ $key = $key->withHash($hash);
break;
default:
- user_error('Unsupported signature format');
- return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
+ $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
+ throw new NoSupportedAlgorithmsException('Unsupported signature format');
}
- return $this->signature_format . ' ' . base64_encode($this->server_public_host_key);
+ if (!$key->verify($this->exchange_hash, $signature)) {
+ return $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
+ };
+
+ return $this->signature_format . ' ' . $server_public_host_key;
}
/**
* Returns the exit status of an SSH command or false.
*
* @return false|int
- * @access public
*/
- function getExitStatus()
+ public function getExitStatus()
{
if (is_null($this->exit_status)) {
return false;
@@ -5576,9 +5470,8 @@ class SSH2
* Returns the number of columns for the terminal window size.
*
* @return int
- * @access public
*/
- function getWindowColumns()
+ public function getWindowColumns()
{
return $this->windowColumns;
}
@@ -5587,9 +5480,8 @@ class SSH2
* Returns the number of rows for the terminal window size.
*
* @return int
- * @access public
*/
- function getWindowRows()
+ public function getWindowRows()
{
return $this->windowRows;
}
@@ -5598,9 +5490,8 @@ class SSH2
* Sets the number of columns for the terminal window size.
*
* @param int $value
- * @access public
*/
- function setWindowColumns($value)
+ public function setWindowColumns($value)
{
$this->windowColumns = $value;
}
@@ -5609,9 +5500,8 @@ class SSH2
* Sets the number of rows for the terminal window size.
*
* @param int $value
- * @access public
*/
- function setWindowRows($value)
+ public function setWindowRows($value)
{
$this->windowRows = $value;
}
@@ -5621,22 +5511,79 @@ class SSH2
*
* @param int $columns
* @param int $rows
- * @access public
*/
- function setWindowSize($columns = 80, $rows = 24)
+ public function setWindowSize($columns = 80, $rows = 24)
{
$this->windowColumns = $columns;
$this->windowRows = $rows;
}
/**
+ * To String Magic Method
+ *
+ * @return string
+ */
+ #[\ReturnTypeWillChange]
+ public function __toString()
+ {
+ return $this->getResourceId();
+ }
+
+ /**
+ * Get Resource ID
+ *
+ * We use {} because that symbols should not be in URL according to
+ * {@link http://tools.ietf.org/html/rfc3986#section-2 RFC}.
+ * It will safe us from any conflicts, because otherwise regexp will
+ * match all alphanumeric domains.
+ *
+ * @return string
+ */
+ public function getResourceId()
+ {
+ return '{' . spl_object_hash($this) . '}';
+ }
+
+ /**
+ * Return existing connection
+ *
+ * @param string $id
+ *
+ * @return bool|SSH2 will return false if no such connection
+ */
+ public static function getConnectionByResourceId($id)
+ {
+ if (isset(self::$connections[$id])) {
+ return self::$connections[$id] instanceof \WeakReference ? self::$connections[$id]->get() : self::$connections[$id];
+ }
+ return false;
+ }
+
+ /**
+ * Return all excising connections
+ *
+ * @return array<string, SSH2>
+ */
+ public static function getConnections()
+ {
+ if (!class_exists('WeakReference')) {
+ /** @var array<string, SSH2> */
+ return self::$connections;
+ }
+ $temp = [];
+ foreach (self::$connections as $key => $ref) {
+ $temp[$key] = $ref->get();
+ }
+ return $temp;
+ }
+
+ /*
* Update packet types in log history
*
* @param string $old
* @param string $new
- * @access private
*/
- function _updateLogHistory($old, $new)
+ private function updateLogHistory($old, $new)
{
if (defined('NET_SSH2_LOGGING') && NET_SSH2_LOGGING == self::LOG_COMPLEX) {
$this->message_number_log[count($this->message_number_log) - 1] = str_replace(
@@ -5653,7 +5600,7 @@ class SSH2
* @see https://tools.ietf.org/html/rfc4252#section-5.1
* @return array|null
*/
- function getAuthMethodsToContinue()
+ public function getAuthMethodsToContinue()
{
return $this->auth_methods_to_continue;
}
@@ -5661,7 +5608,7 @@ class SSH2
/**
* Enables "smart" multi-factor authentication (MFA)
*/
- function enableSmartMFA()
+ public function enableSmartMFA()
{
$this->smartMFA = true;
}
@@ -5669,15 +5616,17 @@ class SSH2
/**
* Disables "smart" multi-factor authentication (MFA)
*/
- function disableSmartMFA()
+ public function disableSmartMFA()
{
$this->smartMFA = false;
}
/**
* How many bytes until the next key re-exchange?
+ *
+ * @param int $bytes
*/
- function bytesUntilKeyReexchange($bytes)
+ public function bytesUntilKeyReexchange($bytes)
{
$this->doKeyReexchangeAfterXBytes = $bytes;
}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/System/SSH/Agent.php b/vendor/phpseclib/phpseclib/phpseclib/System/SSH/Agent.php
index ec1d9773e..376d77bfe 100644
--- a/vendor/phpseclib/phpseclib/phpseclib/System/SSH/Agent.php
+++ b/vendor/phpseclib/phpseclib/phpseclib/System/SSH/Agent.php
@@ -3,6 +3,8 @@
/**
* Pure-PHP ssh-agent client.
*
+ * {@internal See http://api.libssh.org/rfc/PROTOCOL.agent}
+ *
* PHP version 5
*
* Here are some examples of how to use this library:
@@ -10,9 +12,9 @@
* <?php
* include 'vendor/autoload.php';
*
- * $agent = new \phpseclib\System\SSH\Agent();
+ * $agent = new \phpseclib3\System\SSH\Agent();
*
- * $ssh = new \phpseclib\Net\SSH2('www.domain.tld');
+ * $ssh = new \phpseclib3\Net\SSH2('www.domain.tld');
* if (!$ssh->login('username', $agent)) {
* exit('Login Failed');
* }
@@ -22,36 +24,35 @@
* ?>
* </code>
*
- * @category System
- * @package SSH\Agent
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2014 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
- * @internal See http://api.libssh.org/rfc/PROTOCOL.agent
*/
-namespace phpseclib\System\SSH;
+namespace phpseclib3\System\SSH;
-use phpseclib\Crypt\RSA;
-use phpseclib\System\SSH\Agent\Identity;
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\PublicKey;
+use phpseclib3\Crypt\PublicKeyLoader;
+use phpseclib3\Crypt\RSA;
+use phpseclib3\Exception\BadConfigurationException;
+use phpseclib3\Net\SSH2;
+use phpseclib3\System\SSH\Agent\Identity;
/**
* Pure-PHP ssh-agent client identity factory
*
- * requestIdentities() method pumps out \phpseclib\System\SSH\Agent\Identity objects
+ * requestIdentities() method pumps out \phpseclib3\System\SSH\Agent\Identity objects
*
- * @package SSH\Agent
* @author Jim Wigginton <terrafrost@php.net>
- * @access public
*/
class Agent
{
- /**#@+
- * Message numbers
- *
- * @access private
- */
+ use Common\Traits\ReadBytes;
+
+ // Message numbers
+
// to request SSH1 keys you have to use SSH_AGENTC_REQUEST_RSA_IDENTITIES (1)
const SSH_AGENTC_REQUEST_IDENTITIES = 11;
// this is the SSH2 response; the SSH1 response is SSH_AGENT_RSA_IDENTITIES_ANSWER (2).
@@ -60,20 +61,15 @@ class Agent
const SSH_AGENTC_SIGN_REQUEST = 13;
// the SSH1 response is SSH_AGENT_RSA_RESPONSE (4)
const SSH_AGENT_SIGN_RESPONSE = 14;
- /**#@-*/
- /**@+
- * Agent forwarding status
- *
- * @access private
- */
+ // Agent forwarding status
+
// no forwarding requested and not active
const FORWARD_NONE = 0;
// request agent forwarding when opportune
const FORWARD_REQUEST = 1;
// forwarding has been request and is active
const FORWARD_ACTIVE = 2;
- /**#@-*/
/**
* Unused
@@ -84,40 +80,42 @@ class Agent
* Socket Resource
*
* @var resource
- * @access private
*/
- var $fsock;
+ private $fsock;
/**
* Agent forwarding status
*
- * @access private
+ * @var int
*/
- var $forward_status = self::FORWARD_NONE;
+ private $forward_status = self::FORWARD_NONE;
/**
* Buffer for accumulating forwarded authentication
* agent data arriving on SSH data channel destined
* for agent unix socket
*
- * @access private
+ * @var string
*/
- var $socket_buffer = '';
+ private $socket_buffer = '';
/**
* Tracking the number of bytes we are expecting
* to arrive for the agent socket on the SSH data
* channel
+ *
+ * @var int
*/
- var $expected_bytes = 0;
+ private $expected_bytes = 0;
/**
* Default Constructor
*
- * @return \phpseclib\System\SSH\Agent
- * @access public
+ * @return Agent
+ * @throws BadConfigurationException if SSH_AUTH_SOCK cannot be found
+ * @throws \RuntimeException on connection errors
*/
- function __construct($address = null)
+ public function __construct($address = null)
{
if (!$address) {
switch (true) {
@@ -128,24 +126,23 @@ class Agent
$address = $_ENV['SSH_AUTH_SOCK'];
break;
default:
- user_error('SSH_AUTH_SOCK not found');
- return false;
+ throw new BadConfigurationException('SSH_AUTH_SOCK not found');
}
}
if (in_array('unix', stream_get_transports())) {
$this->fsock = fsockopen('unix://' . $address, 0, $errno, $errstr);
if (!$this->fsock) {
- user_error("Unable to connect to ssh-agent (Error $errno: $errstr)");
+ throw new \RuntimeException("Unable to connect to ssh-agent (Error $errno: $errstr)");
}
} else {
if (substr($address, 0, 9) != '\\\\.\\pipe\\' || strpos(substr($address, 9), '\\') !== false) {
- user_error('Address is not formatted as a named pipe should be');
- } else {
- $this->fsock = fopen($address, 'r+b');
- if (!$this->fsock) {
- user_error('Unable to open address');
- }
+ throw new \RuntimeException('Address is not formatted as a named pipe should be');
+ }
+
+ $this->fsock = fopen($address, 'r+b');
+ if (!$this->fsock) {
+ throw new \RuntimeException('Unable to open address');
}
}
}
@@ -154,85 +151,50 @@ class Agent
* Request Identities
*
* See "2.5.2 Requesting a list of protocol 2 keys"
- * Returns an array containing zero or more \phpseclib\System\SSH\Agent\Identity objects
+ * Returns an array containing zero or more \phpseclib3\System\SSH\Agent\Identity objects
*
* @return array
- * @access public
+ * @throws \RuntimeException on receipt of unexpected packets
*/
- function requestIdentities()
+ public function requestIdentities()
{
if (!$this->fsock) {
- return array();
+ return [];
}
$packet = pack('NC', 1, self::SSH_AGENTC_REQUEST_IDENTITIES);
if (strlen($packet) != fputs($this->fsock, $packet)) {
- user_error('Connection closed while requesting identities');
- return array();
+ throw new \RuntimeException('Connection closed while requesting identities');
}
- $temp = fread($this->fsock, 4);
- if (strlen($temp) != 4) {
- user_error('Connection closed while requesting identities');
- return array();
- }
- $length = current(unpack('N', $temp));
- $type = ord(fread($this->fsock, 1));
+ $length = current(unpack('N', $this->readBytes(4)));
+ $packet = $this->readBytes($length);
+
+ list($type, $keyCount) = Strings::unpackSSH2('CN', $packet);
if ($type != self::SSH_AGENT_IDENTITIES_ANSWER) {
- user_error('Unable to request identities');
- return array();
+ throw new \RuntimeException('Unable to request identities');
}
- $identities = array();
- $temp = fread($this->fsock, 4);
- if (strlen($temp) != 4) {
- user_error('Connection closed while requesting identities');
- return array();
- }
- $keyCount = current(unpack('N', $temp));
+ $identities = [];
for ($i = 0; $i < $keyCount; $i++) {
- $temp = fread($this->fsock, 4);
- if (strlen($temp) != 4) {
- user_error('Connection closed while requesting identities');
- return array();
- }
- $length = current(unpack('N', $temp));
- $key_blob = fread($this->fsock, $length);
- if (strlen($key_blob) != $length) {
- user_error('Connection closed while requesting identities');
- return array();
- }
- $key_str = 'ssh-rsa ' . base64_encode($key_blob);
- $temp = fread($this->fsock, 4);
- if (strlen($temp) != 4) {
- user_error('Connection closed while requesting identities');
- return array();
- }
- $length = current(unpack('N', $temp));
- if ($length) {
- $temp = fread($this->fsock, $length);
- if (strlen($temp) != $length) {
- user_error('Connection closed while requesting identities');
- return array();
- }
- $key_str.= ' ' . $temp;
- }
- $length = current(unpack('N', substr($key_blob, 0, 4)));
- $key_type = substr($key_blob, 4, $length);
+ list($key_blob, $comment) = Strings::unpackSSH2('ss', $packet);
+ $temp = $key_blob;
+ list($key_type) = Strings::unpackSSH2('s', $temp);
switch ($key_type) {
case 'ssh-rsa':
- $key = new RSA();
- $key->loadKey($key_str);
- break;
case 'ssh-dss':
- // not currently supported
- break;
+ case 'ssh-ed25519':
+ case 'ecdsa-sha2-nistp256':
+ case 'ecdsa-sha2-nistp384':
+ case 'ecdsa-sha2-nistp521':
+ $key = PublicKeyLoader::load($key_type . ' ' . base64_encode($key_blob));
}
// resources are passed by reference by default
if (isset($key)) {
- $identity = new Identity($this->fsock);
- $identity->setPublicKey($key);
- $identity->setPublicKeyBlob($key_blob);
+ $identity = (new Identity($this->fsock))
+ ->withPublicKey($key)
+ ->withPublicKeyBlob($key_blob)
+ ->withComment($comment);
$identities[] = $identity;
unset($key);
}
@@ -242,13 +204,30 @@ class Agent
}
/**
+ * Returns the SSH Agent identity matching a given public key or null if no identity is found
+ *
+ * @return ?Identity
+ */
+ public function findIdentityByPublicKey(PublicKey $key)
+ {
+ $identities = $this->requestIdentities();
+ $key = (string) $key;
+ foreach ($identities as $identity) {
+ if (((string) $identity->getPublicKey()) == $key) {
+ return $identity;
+ }
+ }
+
+ return null;
+ }
+
+ /**
* Signal that agent forwarding should
* be requested when a channel is opened
*
- * @return bool
- * @access public
+ * @return void
*/
- function startSSHForwarding()
+ public function startSSHForwarding()
{
if ($this->forward_status == self::FORWARD_NONE) {
$this->forward_status = self::FORWARD_REQUEST;
@@ -258,38 +237,15 @@ class Agent
/**
* Request agent forwarding of remote server
*
- * @param Net_SSH2 $ssh
+ * @param SSH2 $ssh
* @return bool
- * @access private
*/
- function _request_forwarding($ssh)
+ private function request_forwarding(SSH2 $ssh)
{
- $request_channel = $ssh->_get_open_channel();
- if ($request_channel === false) {
- return false;
- }
-
- $packet = pack(
- 'CNNa*C',
- NET_SSH2_MSG_CHANNEL_REQUEST,
- $ssh->server_channels[$request_channel],
- strlen('auth-agent-req@openssh.com'),
- 'auth-agent-req@openssh.com',
- 1
- );
-
- $ssh->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_REQUEST;
-
- if (!$ssh->_send_binary_packet($packet)) {
- return false;
- }
-
- $response = $ssh->_get_channel_packet($request_channel);
- if ($response === false) {
+ if (!$ssh->requestAgentForwarding()) {
return false;
}
- $ssh->channel_status[$request_channel] = NET_SSH2_MSG_CHANNEL_OPEN;
$this->forward_status = self::FORWARD_ACTIVE;
return true;
@@ -302,13 +258,12 @@ class Agent
* open to give the SSH Agent an opportunity
* to take further action. i.e. request agent forwarding
*
- * @param Net_SSH2 $ssh
- * @access private
+ * @param SSH2 $ssh
*/
- function _on_channel_open($ssh)
+ public function registerChannelOpen(SSH2 $ssh)
{
if ($this->forward_status == self::FORWARD_REQUEST) {
- $this->_request_forwarding($ssh);
+ $this->request_forwarding($ssh);
}
}
@@ -316,13 +271,13 @@ class Agent
* Forward data to SSH Agent and return data reply
*
* @param string $data
- * @return data from SSH Agent
- * @access private
+ * @return string Data from SSH Agent
+ * @throws \RuntimeException on connection errors
*/
- function _forward_data($data)
+ public function forwardData($data)
{
if ($this->expected_bytes > 0) {
- $this->socket_buffer.= $data;
+ $this->socket_buffer .= $data;
$this->expected_bytes -= strlen($data);
} else {
$agent_data_bytes = current(unpack('N', $data));
@@ -335,25 +290,15 @@ class Agent
}
if (strlen($this->socket_buffer) != fwrite($this->fsock, $this->socket_buffer)) {
- user_error('Connection closed attempting to forward data to SSH agent');
- return false;
+ throw new \RuntimeException('Connection closed attempting to forward data to SSH agent');
}
$this->socket_buffer = '';
$this->expected_bytes = 0;
- $temp = fread($this->fsock, 4);
- if (strlen($temp) != 4) {
- user_error('Connection closed while reading data response');
- return false;
- }
- $agent_reply_bytes = current(unpack('N', $temp));
+ $agent_reply_bytes = current(unpack('N', $this->readBytes(4)));
- $agent_reply_data = fread($this->fsock, $agent_reply_bytes);
- if (strlen($agent_reply_data) != $agent_reply_bytes) {
- user_error('Connection closed while reading data response');
- return false;
- }
+ $agent_reply_data = $this->readBytes($agent_reply_bytes);
$agent_reply_data = current(unpack('a*', $agent_reply_data));
return pack('Na*', $agent_reply_bytes, $agent_reply_data);
diff --git a/vendor/phpseclib/phpseclib/phpseclib/System/SSH/Agent/Identity.php b/vendor/phpseclib/phpseclib/phpseclib/System/SSH/Agent/Identity.php
index 68b6bfdfa..06a4bafd1 100644
--- a/vendor/phpseclib/phpseclib/phpseclib/System/SSH/Agent/Identity.php
+++ b/vendor/phpseclib/phpseclib/phpseclib/System/SSH/Agent/Identity.php
@@ -1,93 +1,109 @@
<?php
+
/**
* Pure-PHP ssh-agent client.
*
+ * {@internal See http://api.libssh.org/rfc/PROTOCOL.agent}
+ *
* PHP version 5
*
- * @category System
- * @package SSH\Agent
* @author Jim Wigginton <terrafrost@php.net>
* @copyright 2009 Jim Wigginton
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @link http://phpseclib.sourceforge.net
- * @internal See http://api.libssh.org/rfc/PROTOCOL.agent
*/
-namespace phpseclib\System\SSH\Agent;
+namespace phpseclib3\System\SSH\Agent;
-use phpseclib\System\SSH\Agent;
+use phpseclib3\Common\Functions\Strings;
+use phpseclib3\Crypt\Common\PrivateKey;
+use phpseclib3\Crypt\Common\PublicKey;
+use phpseclib3\Crypt\DSA;
+use phpseclib3\Crypt\EC;
+use phpseclib3\Crypt\RSA;
+use phpseclib3\Exception\UnsupportedAlgorithmException;
+use phpseclib3\System\SSH\Agent;
+use phpseclib3\System\SSH\Common\Traits\ReadBytes;
/**
* Pure-PHP ssh-agent client identity object
*
- * Instantiation should only be performed by \phpseclib\System\SSH\Agent class.
- * This could be thought of as implementing an interface that phpseclib\Crypt\RSA
+ * Instantiation should only be performed by \phpseclib3\System\SSH\Agent class.
+ * This could be thought of as implementing an interface that phpseclib3\Crypt\RSA
* implements. ie. maybe a Net_SSH_Auth_PublicKey interface or something.
* The methods in this interface would be getPublicKey and sign since those are the
* methods phpseclib looks for to perform public key authentication.
*
- * @package SSH\Agent
* @author Jim Wigginton <terrafrost@php.net>
- * @access internal
+ * @internal
*/
-class Identity
+class Identity implements PrivateKey
{
- /**@+
- * Signature Flags
- *
- * See https://tools.ietf.org/html/draft-miller-ssh-agent-00#section-5.3
- *
- * @access private
- */
+ use ReadBytes;
+
+ // Signature Flags
+ // See https://tools.ietf.org/html/draft-miller-ssh-agent-00#section-5.3
const SSH_AGENT_RSA2_256 = 2;
const SSH_AGENT_RSA2_512 = 4;
- /**#@-*/
/**
* Key Object
*
- * @var \phpseclib\Crypt\RSA
- * @access private
+ * @var PublicKey
* @see self::getPublicKey()
*/
- var $key;
+ private $key;
/**
* Key Blob
*
* @var string
- * @access private
* @see self::sign()
*/
- var $key_blob;
+ private $key_blob;
/**
* Socket Resource
*
* @var resource
- * @access private
* @see self::sign()
*/
- var $fsock;
+ private $fsock;
/**
* Signature flags
*
* @var int
- * @access private
* @see self::sign()
* @see self::setHash()
*/
- var $flags = 0;
+ private $flags = 0;
+
+ /**
+ * Comment
+ *
+ * @var null|string
+ */
+ private $comment;
+
+ /**
+ * Curve Aliases
+ *
+ * @var array
+ */
+ private static $curveAliases = [
+ 'secp256r1' => 'nistp256',
+ 'secp384r1' => 'nistp384',
+ 'secp521r1' => 'nistp521',
+ 'Ed25519' => 'Ed25519'
+ ];
/**
* Default Constructor.
*
* @param resource $fsock
- * @return \phpseclib\System\SSH\Agent\Identity
- * @access private
*/
- function __construct($fsock)
+ public function __construct($fsock)
{
$this->fsock = $fsock;
}
@@ -95,29 +111,36 @@ class Identity
/**
* Set Public Key
*
- * Called by \phpseclib\System\SSH\Agent::requestIdentities()
+ * Called by \phpseclib3\System\SSH\Agent::requestIdentities()
*
- * @param \phpseclib\Crypt\RSA $key
- * @access private
+ * @param PublicKey $key
*/
- function setPublicKey($key)
+ public function withPublicKey(PublicKey $key)
{
- $this->key = $key;
- $this->key->setPublicKey();
+ if ($key instanceof EC) {
+ if (is_array($key->getCurve()) || !isset(self::$curveAliases[$key->getCurve()])) {
+ throw new UnsupportedAlgorithmException('The only supported curves are nistp256, nistp384, nistp512 and Ed25519');
+ }
+ }
+
+ $new = clone $this;
+ $new->key = $key;
+ return $new;
}
/**
* Set Public Key
*
- * Called by \phpseclib\System\SSH\Agent::requestIdentities(). The key blob could be extracted from $this->key
+ * Called by \phpseclib3\System\SSH\Agent::requestIdentities(). The key blob could be extracted from $this->key
* but this saves a small amount of computation.
*
* @param string $key_blob
- * @access private
*/
- function setPublicKeyBlob($key_blob)
+ public function withPublicKeyBlob($key_blob)
{
- $this->key_blob = $key_blob;
+ $new = clone $this;
+ $new->key_blob = $key_blob;
+ return $new;
}
/**
@@ -125,51 +148,115 @@ class Identity
*
* Wrapper for $this->key->getPublicKey()
*
- * @param int $format optional
* @return mixed
- * @access public
*/
- function getPublicKey($format = null)
+ public function getPublicKey()
{
- return !isset($format) ? $this->key->getPublicKey() : $this->key->getPublicKey($format);
+ return $this->key;
}
/**
- * Set Signature Mode
+ * Sets the hash
*
- * Doesn't do anything as ssh-agent doesn't let you pick and choose the signature mode. ie.
- * ssh-agent's only supported mode is \phpseclib\Crypt\RSA::SIGNATURE_PKCS1
+ * @param string $hash
+ */
+ public function withHash($hash)
+ {
+ $new = clone $this;
+
+ $hash = strtolower($hash);
+
+ if ($this->key instanceof RSA) {
+ $new->flags = 0;
+ switch ($hash) {
+ case 'sha1':
+ break;
+ case 'sha256':
+ $new->flags = self::SSH_AGENT_RSA2_256;
+ break;
+ case 'sha512':
+ $new->flags = self::SSH_AGENT_RSA2_512;
+ break;
+ default:
+ throw new UnsupportedAlgorithmException('The only supported hashes for RSA are sha1, sha256 and sha512');
+ }
+ }
+ if ($this->key instanceof EC) {
+ switch ($this->key->getCurve()) {
+ case 'secp256r1':
+ $expectedHash = 'sha256';
+ break;
+ case 'secp384r1':
+ $expectedHash = 'sha384';
+ break;
+ //case 'secp521r1':
+ //case 'Ed25519':
+ default:
+ $expectedHash = 'sha512';
+ }
+ if ($hash != $expectedHash) {
+ throw new UnsupportedAlgorithmException('The only supported hash for ' . self::$curveAliases[$this->key->getCurve()] . ' is ' . $expectedHash);
+ }
+ }
+ if ($this->key instanceof DSA) {
+ if ($hash != 'sha1') {
+ throw new UnsupportedAlgorithmException('The only supported hash for DSA is sha1');
+ }
+ }
+ return $new;
+ }
+
+ /**
+ * Sets the padding
*
- * @param int $mode
- * @access public
+ * Only PKCS1 padding is supported
+ *
+ * @param string $padding
*/
- function setSignatureMode($mode)
+ public function withPadding($padding)
{
+ if (!$this->key instanceof RSA) {
+ throw new UnsupportedAlgorithmException('Only RSA keys support padding');
+ }
+ if ($padding != RSA::SIGNATURE_PKCS1 && $padding != RSA::SIGNATURE_RELAXED_PKCS1) {
+ throw new UnsupportedAlgorithmException('ssh-agent can only create PKCS1 signatures');
+ }
+ return $this;
}
/**
- * Set Hash
+ * Determines the signature padding mode
*
- * ssh-agent doesn't support using hashes for RSA other than SHA1
+ * Valid values are: ASN1, SSH2, Raw
*
- * @param string $hash
- * @access public
+ * @param string $format
*/
- function setHash($hash)
+ public function withSignatureFormat($format)
{
- $this->flags = 0;
- switch ($hash) {
- case 'sha1':
- break;
- case 'sha256':
- $this->flags = self::SSH_AGENT_RSA2_256;
- break;
- case 'sha512':
- $this->flags = self::SSH_AGENT_RSA2_512;
- break;
- default:
- user_error('The only supported hashes for RSA are sha1, sha256 and sha512');
+ if ($this->key instanceof RSA) {
+ throw new UnsupportedAlgorithmException('Only DSA and EC keys support signature format setting');
}
+ if ($format != 'SSH2') {
+ throw new UnsupportedAlgorithmException('Only SSH2-formatted signatures are currently supported');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Returns the curve
+ *
+ * Returns a string if it's a named curve, an array if not
+ *
+ * @return string|array
+ */
+ public function getCurve()
+ {
+ if (!$this->key instanceof EC) {
+ throw new UnsupportedAlgorithmException('Only EC keys have curves');
+ }
+
+ return $this->key->getCurve();
}
/**
@@ -179,63 +266,81 @@ class Identity
*
* @param string $message
* @return string
- * @access public
+ * @throws \RuntimeException on connection errors
+ * @throws UnsupportedAlgorithmException if the algorithm is unsupported
*/
- function sign($message)
+ public function sign($message)
{
// the last parameter (currently 0) is for flags and ssh-agent only defines one flag (for ssh-dss): SSH_AGENT_OLD_SIGNATURE
- $packet = pack('CNa*Na*N', Agent::SSH_AGENTC_SIGN_REQUEST, strlen($this->key_blob), $this->key_blob, strlen($message), $message, $this->flags);
- $packet = pack('Na*', strlen($packet), $packet);
+ $packet = Strings::packSSH2(
+ 'CssN',
+ Agent::SSH_AGENTC_SIGN_REQUEST,
+ $this->key_blob,
+ $message,
+ $this->flags
+ );
+ $packet = Strings::packSSH2('s', $packet);
if (strlen($packet) != fputs($this->fsock, $packet)) {
- user_error('Connection closed during signing');
- return false;
+ throw new \RuntimeException('Connection closed during signing');
}
- $temp = fread($this->fsock, 4);
- if (strlen($temp) != 4) {
- user_error('Connection closed during signing');
- return false;
- }
- $length = current(unpack('N', $temp));
- $type = ord(fread($this->fsock, 1));
+ $length = current(unpack('N', $this->readBytes(4)));
+ $packet = $this->readBytes($length);
+
+ list($type, $signature_blob) = Strings::unpackSSH2('Cs', $packet);
if ($type != Agent::SSH_AGENT_SIGN_RESPONSE) {
- user_error('Unable to retrieve signature');
- return false;
+ throw new \RuntimeException('Unable to retrieve signature');
}
- $signature_blob = fread($this->fsock, $length - 1);
- if (strlen($signature_blob) != $length - 1) {
- user_error('Connection closed during signing');
- return false;
- }
- $length = current(unpack('N', $this->_string_shift($signature_blob, 4)));
- if ($length != strlen($signature_blob)) {
- user_error('Malformed signature blob');
+ if (!$this->key instanceof RSA) {
+ return $signature_blob;
}
- $length = current(unpack('N', $this->_string_shift($signature_blob, 4)));
- if ($length > strlen($signature_blob) + 4) {
- user_error('Malformed signature blob');
- }
- $type = $this->_string_shift($signature_blob, $length);
- $this->_string_shift($signature_blob, 4);
+
+ list($type, $signature_blob) = Strings::unpackSSH2('ss', $signature_blob);
return $signature_blob;
}
/**
- * String Shift
+ * Returns the private key
*
- * Inspired by array_shift
- *
- * @param string $string
- * @param int $index
+ * @param string $type
+ * @param array $options optional
* @return string
- * @access private
*/
- function _string_shift(&$string, $index = 1)
+ public function toString($type, array $options = [])
+ {
+ throw new \RuntimeException('ssh-agent does not provide a mechanism to get the private key');
+ }
+
+ /**
+ * Sets the password
+ *
+ * @param string|bool $password
+ * @return never
+ */
+ public function withPassword($password = false)
+ {
+ throw new \RuntimeException('ssh-agent does not provide a mechanism to get the private key');
+ }
+
+ /**
+ * Sets the comment
+ */
+ public function withComment($comment = null)
+ {
+ $new = clone $this;
+ $new->comment = $comment;
+ return $new;
+ }
+
+ /**
+ * Returns the comment
+ *
+ * @return null|string
+ */
+ public function getComment()
{
- $substr = substr($string, 0, $index);
- $string = substr($string, $index);
- return $substr;
+ return $this->comment;
}
}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/System/SSH/Common/Traits/ReadBytes.php b/vendor/phpseclib/phpseclib/phpseclib/System/SSH/Common/Traits/ReadBytes.php
new file mode 100644
index 000000000..6fd032bd4
--- /dev/null
+++ b/vendor/phpseclib/phpseclib/phpseclib/System/SSH/Common/Traits/ReadBytes.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * ReadBytes trait
+ *
+ * PHP version 5
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2015 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib3\System\SSH\Common\Traits;
+
+/**
+ * ReadBytes trait
+ *
+ * @author Jim Wigginton <terrafrost@php.net>
+ */
+trait ReadBytes
+{
+ /**
+ * Read data
+ *
+ * @param int $length
+ * @throws \RuntimeException on connection errors
+ */
+ public function readBytes($length)
+ {
+ $temp = fread($this->fsock, $length);
+ if (strlen($temp) != $length) {
+ throw new \RuntimeException("Expected $length bytes; got " . strlen($temp));
+ }
+ return $temp;
+ }
+}
diff --git a/vendor/phpseclib/phpseclib/phpseclib/bootstrap.php b/vendor/phpseclib/phpseclib/phpseclib/bootstrap.php
index 547688f9f..517106c3a 100644
--- a/vendor/phpseclib/phpseclib/phpseclib/bootstrap.php
+++ b/vendor/phpseclib/phpseclib/phpseclib/bootstrap.php
@@ -1,7 +1,12 @@
<?php
+
/**
* Bootstrapping File for phpseclib
*
+ * composer isn't a requirement for phpseclib 2.0 but this file isn't really required
+ * either. it's a bonus for those using composer but if you're not phpseclib will
+ * still work
+ *
* @license http://www.opensource.org/licenses/mit-license.html MIT License
*/
@@ -9,7 +14,7 @@ if (extension_loaded('mbstring')) {
// 2 - MB_OVERLOAD_STRING
// mbstring.func_overload is deprecated in php 7.2 and removed in php 8.0.
if (version_compare(PHP_VERSION, '8.0.0') < 0 && ini_get('mbstring.func_overload') & 2) {
- throw new \UnexpectedValueException(
+ throw new UnexpectedValueException(
'Overloading of string functions using mbstring.func_overload ' .
'is not supported by phpseclib.'
);
diff --git a/vendor/phpseclib/phpseclib2_compat/.github/workflows/ci.yml b/vendor/phpseclib/phpseclib2_compat/.github/workflows/ci.yml
new file mode 100644
index 000000000..4d19f96c9
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/.github/workflows/ci.yml
@@ -0,0 +1,61 @@
+name: CI
+on: [push, pull_request]
+
+permissions:
+ contents: read # to fetch code (actions/checkout)
+
+jobs:
+ tests:
+ name: Tests
+ timeout-minutes: 10
+ runs-on: ${{ matrix.os }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php-version }}
+ - name: Clone phpseclib v2 and update tests
+ run: |
+ git clone -b 2.0 --single-branch https://github.com/phpseclib/phpseclib.git temp
+ mv temp/tests tests
+ rm tests/Functional/Net/SCPSSH2UserStoryTest.php
+ rm tests/Unit/Net/SSH1Test.php
+ rm tests/Unit/Net/SSH2UnitTest.php
+ rm tests/Unit/Math/BigInteger/InternalTest.php
+ php .github/workflows/update.php
+ - name: Composer Install
+ run: composer install --no-interaction --no-cache
+ - name: Make Tests Compatiable With PHPUnit 9+
+ if: matrix.php-version != '5.6' && matrix.php-version != '7.0' && matrix.php-version != '7.1' && matrix.php-version != '7.2'
+ run: php tests/make_compatible_with_phpunit9.php
+ - name: Setup Secure Shell Functional Tests
+ if: matrix.os == 'ubuntu-latest'
+ run: |
+ PHPSECLIB_SSH_USERNAME='phpseclib'
+ PHPSECLIB_SSH_PASSWORD='EePoov8po1aethu2kied1ne0'
+
+ sudo useradd --create-home --base-dir /home "$PHPSECLIB_SSH_USERNAME"
+ echo "$PHPSECLIB_SSH_USERNAME:$PHPSECLIB_SSH_PASSWORD" | sudo chpasswd
+ ssh-keygen -t rsa -b 1024 -f "$HOME/.ssh/id_rsa" -q -N ""
+ eval `ssh-agent -s`
+ ssh-add "$HOME/.ssh/id_rsa"
+ sudo mkdir -p "/home/$PHPSECLIB_SSH_USERNAME/.ssh/"
+ sudo cp "$HOME/.ssh/id_rsa.pub" "/home/$PHPSECLIB_SSH_USERNAME/.ssh/authorized_keys"
+ sudo ssh-keyscan -t rsa localhost > "/tmp/known_hosts"
+ sudo cp "/tmp/known_hosts" "/home/$PHPSECLIB_SSH_USERNAME/.ssh/known_hosts"
+ sudo chown "$PHPSECLIB_SSH_USERNAME:$PHPSECLIB_SSH_USERNAME" "/home/$PHPSECLIB_SSH_USERNAME/.ssh/" -R
+
+ echo "PHPSECLIB_SSH_HOSTNAME=localhost" >> $GITHUB_ENV
+ echo "PHPSECLIB_SSH_USERNAME=$PHPSECLIB_SSH_USERNAME" >> $GITHUB_ENV
+ echo "PHPSECLIB_SSH_PASSWORD=$PHPSECLIB_SSH_PASSWORD" >> $GITHUB_ENV
+ echo "PHPSECLIB_SSH_HOME=/home/phpseclib" >> $GITHUB_ENV
+ echo "SSH_AUTH_SOCK=$SSH_AUTH_SOCK" >> $GITHUB_ENV
+ - name: PHPUnit
+ run: vendor/bin/phpunit
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ php-version: ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2']
diff --git a/vendor/phpseclib/phpseclib2_compat/.github/workflows/update.php b/vendor/phpseclib/phpseclib2_compat/.github/workflows/update.php
new file mode 100644
index 000000000..61cd00641
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/.github/workflows/update.php
@@ -0,0 +1,12 @@
+<?php
+
+function replaceFileContents($fileName, $search, $replace)
+{
+ $fileContents = file_get_contents($fileName);
+ $updatedFileContents = preg_replace($search, $replace, $fileContents);
+ file_put_contents($fileName, $updatedFileContents);
+}
+
+replaceFileContents('tests/PhpseclibTestCase.php', '~%s/../phpseclib/%s~', '%s/../src/%s');
+replaceFileContents('tests/Unit/Crypt/RSA/LoadKeyTest.php', '~ public function testSetPrivate\(\)~', ' private function skiptestSetPrivate()');
+replaceFileContents('tests/Unit/Crypt/RSA/ModeTest.php', '~ public function testOAEPWithLabel\(\)~', ' private function skiptestOAEPWithLabel()'); \ No newline at end of file
diff --git a/vendor/phpseclib/phpseclib2_compat/LICENSE b/vendor/phpseclib/phpseclib2_compat/LICENSE
new file mode 100644
index 000000000..39a347e41
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 phpseclib
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/phpseclib/phpseclib2_compat/README.md b/vendor/phpseclib/phpseclib2_compat/README.md
new file mode 100644
index 000000000..df4739d34
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/README.md
@@ -0,0 +1,92 @@
+# phpseclib2_compat
+
+[![CI Status](https://github.com/phpseclib/phpseclib2_compat/actions/workflows/ci.yml/badge.svg?branch=1.0&event=push "CI Status")](https://github.com/phpseclib/phpseclib2_compat/actions/workflows/ci.yml?query=branch%3A1.0)
+
+phpseclib 2.0 polyfill built with phpseclib 3.0
+
+## Overview
+
+phpseclib 3.0 breaks backwards compatability with phpseclib 2.0. Most notably, public keys work completely differently. So let's say you wanted to use phpseclib 3.0 whilst some of your other dependencies still use phpseclib 2.0. What would you do in that instance?
+
+That's where phpseclib2_compat comes into play. Require phpseclib/phpseclib:~3.0 and phpseclib/phpseclib2_compat:~1.0 and your dependencies will magically start using phpseclib 3.0 even if they don't know it.
+
+Using phpseclib2_compat will actually bring a few enhancements to your dependencies. For example, while phpseclib 2.0 only supports RSA keys phpseclib2_compat sports support for ECDSA / DSA / Ed25519 / Ed449 keys.
+
+Consider this code sample:
+
+```php
+use phpseclib\Crypt\RSA;
+
+$rsa = new RSA;
+$rsa->loadKey('ecdsa private key');
+
+$ssh = new SSH2('website.com');
+$ssh->login('username', $rsa);
+```
+That'll work with phpseclib2_compat, even with an ECDSA private key, whereas in phpseclib 2.0 it would not work.
+
+SSH1 and SCP are not supported but those were likely never frequently used anyway.
+
+## Using the old cipher suite
+
+phpseclib 3.0 uses a different cipher suite (an expanded one) than 2.0. If this causes you issues you can use the 2.0 ciphersuite by doing this prior to calling `$ssh->login()`:
+
+```php
+$methods = [
+ 'crypt' => array_intersect([
+ 'arcfour256',
+ 'arcfour128',
+ 'aes128-ctr',
+ 'aes192-ctr',
+ 'aes256-ctr',
+ 'twofish128-ctr',
+ 'twofish192-ctr',
+ 'twofish256-ctr',
+ 'aes128-cbc',
+ 'aes192-cbc',
+ 'aes256-cbc',
+ 'twofish128-cbc',
+ 'twofish192-cbc',
+ 'twofish256-cbc',
+ 'twofish-cbc',
+ 'blowfish-ctr',
+ 'blowfish-cbc',
+ '3des-ctr',
+ '3des-cbc'
+ ], $ssh->getSupportedEncryptionAlgorithms()),
+ 'mac' => [
+ 'hmac-sha2-256',
+ 'hmac-sha1-96',
+ 'hmac-sha1',
+ 'hmac-md5-96',
+ 'hmac-md5'
+ ],
+ 'comp' => ['none']
+];
+
+$ssh->setPreferredAlgorithms([
+ 'kex' => [
+ 'curve25519-sha256@libssh.org',
+ 'diffie-hellman-group-exchange-sha256',
+ 'diffie-hellman-group-exchange-sha1',
+ 'diffie-hellman-group14-sha1',
+ 'diffie-hellman-group14-sha256'
+ ],
+ 'hostkey' => [
+ 'rsa-sha2-256',
+ 'rsa-sha2-512',
+ 'ssh-rsa',
+ 'ssh-dss'
+ ],
+ 'client_to_server' => $methods,
+ 'server_to_client' => $methods
+]);
+```
+
+## Installation
+
+With [Composer](https://getcomposer.org/):
+
+```
+composer require phpseclib/phpseclib2_compat:~1.0
+```
diff --git a/vendor/phpseclib/phpseclib2_compat/composer.json b/vendor/phpseclib/phpseclib2_compat/composer.json
new file mode 100644
index 000000000..f32ef85e8
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/composer.json
@@ -0,0 +1,30 @@
+{
+ "name": "phpseclib/phpseclib2_compat",
+ "description": "phpseclib 2.0 polyfill built with phpseclib 3.0",
+ "type": "library",
+ "homepage": "https://github.com/phpseclib/phpseclib2_compat",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Jim Wigginton",
+ "email": "terrafrost@php.net",
+ "role": "Lead Developer"
+ }
+ ],
+ "support": {
+ "issues": "https://github.com/phpseclib/phpseclib2_compat/issues",
+ "source": "https://github.com/phpseclib/phpseclib2_compat"
+ },
+ "provide": {
+ "phpseclib/phpseclib": "2.0.47"
+ },
+ "require": {
+ "phpseclib/phpseclib": "^3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5.7|^6.0|^9.4"
+ },
+ "autoload": {
+ "psr-4": {"phpseclib\\": "src/"}
+ }
+}
diff --git a/vendor/phpseclib/phpseclib2_compat/src/Crypt/AES.php b/vendor/phpseclib/phpseclib2_compat/src/Crypt/AES.php
new file mode 100644
index 000000000..0e4da3543
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/src/Crypt/AES.php
@@ -0,0 +1,93 @@
+<?php
+
+/**
+ * Pure-PHP implementation of AES.
+ *
+ * Uses mcrypt, if available/possible, and an internal implementation, otherwise.
+ *
+ * PHP version 5
+ *
+ * NOTE: Since AES.php is (for compatibility and phpseclib-historical reasons) virtually
+ * just a wrapper to Rijndael.php you may consider using Rijndael.php instead of
+ * to save one include_once().
+ *
+ * If {@link self::setKeyLength() setKeyLength()} isn't called, it'll be calculated from
+ * {@link self::setKey() setKey()}. ie. if the key is 128-bits, the key length will be 128-bits. If it's 136-bits
+ * it'll be null-padded to 192-bits and 192 bits will be the key length until {@link self::setKey() setKey()}
+ * is called, again, at which point, it'll be recalculated.
+ *
+ * Since \phpseclib\Crypt\AES extends \phpseclib\Crypt\Rijndael, some functions are available to be called that, in the context of AES, don't
+ * make a whole lot of sense. {@link self::setBlockLength() setBlockLength()}, for instance. Calling that function,
+ * however possible, won't do anything (AES has a fixed block length whereas Rijndael has a variable one).
+ *
+ * Here's a short example of how to use this library:
+ * <code>
+ * <?php
+ * include 'vendor/autoload.php';
+ *
+ * $aes = new \phpseclib\Crypt\AES();
+ *
+ * $aes->setKey('abcdefghijklmnop');
+ *
+ * $size = 10 * 1024;
+ * $plaintext = '';
+ * for ($i = 0; $i < $size; $i++) {
+ * $plaintext.= 'a';
+ * }
+ *
+ * echo $aes->decrypt($aes->encrypt($plaintext));
+ * ?>
+ * </code>
+ *
+ * @category Crypt
+ * @package AES
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2008 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Crypt;
+
+/**
+ * Pure-PHP implementation of AES.
+ *
+ * @package AES
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class AES extends Rijndael
+{
+ /**
+ * Dummy function
+ *
+ * Since \phpseclib\Crypt\AES extends \phpseclib\Crypt\Rijndael, this function is, technically, available, but it doesn't do anything.
+ *
+ * @see \phpseclib\Crypt\Rijndael::setBlockLength()
+ * @access public
+ * @param int $length
+ */
+ function setBlockLength($length)
+ {
+ return;
+ }
+
+ /**
+ * Turns key lengths, be they valid or invalid, to valid key lengths
+ *
+ * @param int $length
+ * @access private
+ * @return int
+ */
+ protected function calculateNewKeyLength($length)
+ {
+ switch (true) {
+ case $length <= 128:
+ return 128;
+ case $length <= 192:
+ return 192;
+ default:
+ return 256;
+ }
+ }
+} \ No newline at end of file
diff --git a/vendor/phpseclib/phpseclib2_compat/src/Crypt/Base.php b/vendor/phpseclib/phpseclib2_compat/src/Crypt/Base.php
new file mode 100644
index 000000000..fad69c023
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/src/Crypt/Base.php
@@ -0,0 +1,585 @@
+<?php
+
+/**
+ * Base Class for all \phpseclib\Crypt\* cipher classes
+ *
+ * PHP version 5
+ *
+ * @category Crypt
+ * @package Base
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @author Hans-Juergen Petrich <petrich@tronic-media.com>
+ * @copyright 2007 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Crypt;
+
+use phpseclib3\Exception\BadDecryptionException;
+use phpseclib3\Exception\InconsistentSetupException;
+
+/**
+ * Base Class for all \phpseclib\Crypt\* cipher classes
+ *
+ * @package Base
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @author Hans-Juergen Petrich <petrich@tronic-media.com>
+ */
+abstract class Base
+{
+ /**#@+
+ * @access public
+ * @see \phpseclib\Crypt\Base::encrypt()
+ * @see \phpseclib\Crypt\Base::decrypt()
+ */
+ /**
+ * Encrypt / decrypt using the Counter mode.
+ *
+ * Set to -1 since that's what Crypt/Random.php uses to index the CTR mode.
+ *
+ * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Counter_.28CTR.29
+ */
+ const MODE_CTR = -1;
+ /**
+ * Encrypt / decrypt using the Electronic Code Book mode.
+ *
+ * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Electronic_codebook_.28ECB.29
+ */
+ const MODE_ECB = 1;
+ /**
+ * Encrypt / decrypt using the Code Book Chaining mode.
+ *
+ * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher-block_chaining_.28CBC.29
+ */
+ const MODE_CBC = 2;
+ /**
+ * Encrypt / decrypt using the Cipher Feedback mode.
+ *
+ * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Cipher_feedback_.28CFB.29
+ */
+ const MODE_CFB = 3;
+ /**
+ * Encrypt / decrypt using the Cipher Feedback mode (8bit)
+ */
+ const MODE_CFB8 = 38;
+ /**
+ * Encrypt / decrypt using the Output Feedback mode (8bit)
+ */
+ const MODE_OFB8 = 7;
+ /**
+ * Encrypt / decrypt using the Output Feedback mode.
+ *
+ * @link http://en.wikipedia.org/wiki/Block_cipher_modes_of_operation#Output_feedback_.28OFB.29
+ */
+ const MODE_OFB = 4;
+ /**
+ * Encrypt / decrypt using streaming mode.
+ */
+ const MODE_STREAM = 5;
+ /**
+ * Encrypt / decrypt using Galois/Counter mode.
+ *
+ * @link https://en.wikipedia.org/wiki/Galois/Counter_Mode
+ */
+ const MODE_GCM = 6;
+ /**#@-*/
+
+ /**#@+
+ * @access private
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::__construct()
+ */
+ /**
+ * Base value for the internal implementation $engine switch
+ */
+ const ENGINE_INTERNAL = 1;
+ /**
+ * Base value for the eval() implementation $engine switch
+ */
+ const ENGINE_EVAL = 4;
+ /**
+ * Base value for the mcrypt implementation $engine switch
+ */
+ const ENGINE_MCRYPT = 2;
+ /**
+ * Base value for the openssl implementation $engine switch
+ */
+ const ENGINE_OPENSSL = 3;
+ /**
+ * Base value for the libsodium implementation $engine switch
+ */
+ const ENGINE_LIBSODIUM = 5;
+ /**
+ * Base value for the openssl / gcm implementation $engine switch
+ */
+ const ENGINE_OPENSSL_GCM = 6;
+ /**#@-*/
+
+ /**
+ * Engine Map
+ *
+ * @access private
+ * @see \phpseclib3\Crypt\Common\SymmetricKey::getEngine()
+ */
+ const ENGINE_MAP = [
+ self::ENGINE_INTERNAL => 'PHP',
+ self::ENGINE_EVAL => 'Eval',
+ self::ENGINE_MCRYPT => 'mcrypt',
+ self::ENGINE_OPENSSL => 'OpenSSL',
+ self::ENGINE_LIBSODIUM => 'libsodium',
+ self::ENGINE_OPENSSL_GCM => 'OpenSSL (GCM)'
+ ];
+
+ /**
+ * The Cipher
+ *
+ * @var \phpseclib3\Crypt\Common\SymmetricKey
+ * @access private
+ */
+ protected $cipher;
+
+ /**
+ * The Key
+ *
+ * @see self::setKey()
+ * @var string
+ * @access private
+ */
+ protected $key = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
+
+ /**
+ * Password Parameters
+ *
+ * @see self::setPassword()
+ * @var array
+ * @access private
+ */
+ protected $password = [];
+
+ /**
+ * The Key Length (in bytes)
+ *
+ * @see self::setKeyLength()
+ * @var int
+ * @access private
+ */
+ protected $key_length = 128;
+
+ /**
+ * Does internal cipher state need to be (re)initialized?
+ *
+ * @see self::setKey()
+ * @var bool
+ * @access private
+ */
+ private $changed = true;
+
+ /**
+ * Has the IV been set?
+ *
+ * @var bool
+ * @access private
+ */
+ protected $ivSet = false;
+
+ /**
+ * Has the key length been explictly set?
+ *
+ * @var bool
+ * @access private
+ */
+ protected $explicit_key_length = false;
+
+ /**
+ * Default Constructor.
+ *
+ * Determines whether or not the mcrypt extension should be used.
+ *
+ * $mode could be:
+ *
+ * - self::MODE_ECB
+ *
+ * - self::MODE_CBC
+ *
+ * - self::MODE_CTR
+ *
+ * - self::MODE_CFB
+ *
+ * - self::MODE_OFB
+ *
+ * If not explicitly set, self::MODE_CBC will be used.
+ *
+ * @param int $mode
+ * @access public
+ */
+ public function __construct($mode = self::MODE_CBC)
+ {
+ $map = [
+ self::MODE_CTR => 'ctr',
+ self::MODE_ECB => 'ecb',
+ self::MODE_CBC => 'cbc',
+ self::MODE_CFB => 'cfb',
+ self::MODE_CFB8 => 'cfb8',
+ self::MODE_OFB => 'ofb',
+ self::MODE_OFB8 => 'ofb8',
+ self::MODE_GCM => 'gcm',
+ self::MODE_STREAM => 'stream'
+ ];
+ if (!isset($map[$mode])) {
+ $mode = self::MODE_CBC;
+ }
+ $class = new \ReflectionClass(static::class);
+ $class = "phpseclib3\\Crypt\\" . $class->getShortName();
+ $this->cipher = new $class($map[$mode]);
+ $this->key_length = $this->cipher->getKeyLength();
+ }
+
+ /**
+ * Sets the initialization vector. (optional)
+ *
+ * SetIV is not required when self::MODE_ECB (or ie for AES: \phpseclib\Crypt\AES::MODE_ECB) is being used. If not explicitly set, it'll be assumed
+ * to be all zero's.
+ *
+ * @access public
+ * @param string $iv
+ * @internal Can be overwritten by a sub class, but does not have to be
+ */
+ public function setIV($iv)
+ {
+ $this->ivSet = true;
+
+ if (!$this->cipher->usesIV()) {
+ return;
+ }
+
+ $length = $this->cipher->getBlockLengthInBytes();
+ $iv = str_pad(substr($iv, 0, $length), $length, "\0");
+
+ try {
+ $this->cipher->setIV($iv);
+ } catch (\Exception $e) {}
+ }
+
+ /**
+ * Sets the key length.
+ *
+ * Keys with explicitly set lengths need to be treated accordingly
+ *
+ * @access public
+ * @param int $length
+ */
+ public function setKeyLength($length)
+ {
+ // algorithms that have a fixed key length should override this with a method that does nothing
+ $this->changed = true;
+ $this->key_length = static::calculateNewKeyLength($length);
+ $this->explicit_key_length = true;
+ }
+
+ /**
+ * Returns the current key length in bits
+ *
+ * @access public
+ * @return int
+ */
+ public function getKeyLength()
+ {
+ return $this->key_length;
+ }
+
+ /**
+ * Returns the current block length in bits
+ *
+ * @access public
+ * @return int
+ */
+ public function getBlockLength()
+ {
+ return $this->cipher->getBlockLength();
+ }
+
+ /**
+ * Sets the key.
+ *
+ * The min/max length(s) of the key depends on the cipher which is used.
+ * If the key not fits the length(s) of the cipher it will paded with null bytes
+ * up to the closest valid key length. If the key is more than max length,
+ * we trim the excess bits.
+ *
+ * If the key is not explicitly set, it'll be assumed to be all null bytes.
+ *
+ * @access public
+ * @param string $key
+ * @internal Could, but not must, extend by the child Crypt_* class
+ */
+ public function setKey($key)
+ {
+ $this->key = $key;
+ $this->password = [];
+ if (!$this->explicit_key_length) {
+ $this->key_length = static::calculateNewKeyLength(strlen($key) << 3);
+ }
+ $this->changed = true;
+ }
+
+ /**
+ * Sets the password.
+ *
+ * Depending on what $method is set to, setPassword()'s (optional) parameters are as follows:
+ * {@link http://en.wikipedia.org/wiki/PBKDF2 pbkdf2} or pbkdf1:
+ * $hash, $salt, $count, $dkLen
+ *
+ * Where $hash (default = sha1) currently supports the following hashes: see: Crypt/Hash.php
+ *
+ * @see Crypt/Hash.php
+ * @param string $password
+ * @param string $method
+ * @return bool
+ * @access public
+ * @internal Could, but not must, extend by the child Crypt_* class
+ */
+ public function setPassword($password, $method = 'pbkdf2')
+ {
+ $this->password = func_get_args();
+ $this->cipher->setKeyLength($this->key_length);
+ $this->cipher->setPassword(...func_get_args());
+ }
+
+ /**
+ * Encrypts a message.
+ *
+ * $plaintext will be padded with additional bytes such that it's length is a multiple of the block size. Other cipher
+ * implementations may or may not pad in the same manner. Other common approaches to padding and the reasons why it's
+ * necessary are discussed in the following
+ * URL:
+ *
+ * {@link http://www.di-mgt.com.au/cryptopad.html http://www.di-mgt.com.au/cryptopad.html}
+ *
+ * An alternative to padding is to, separately, send the length of the file. This is what SSH, in fact, does.
+ * strlen($plaintext) will still need to be a multiple of the block size, however, arbitrary values can be added to make it that
+ * length.
+ *
+ * @see self::decrypt()
+ * @access public
+ * @param string $plaintext
+ * @return string $ciphertext
+ * @internal Could, but not must, extend by the child Crypt_* class
+ */
+ public function encrypt($plaintext)
+ {
+ if ($this->changed) {
+ $this->setup();
+ }
+
+ try {
+ return $this->cipher->encrypt($plaintext);
+ } catch (\LengthException $e) {
+ user_error($e->getMessage());
+ $this->cipher->enablePadding();
+ return $this->cipher->encrypt($plaintext);
+ }
+ }
+
+ /**
+ * Decrypts a message.
+ *
+ * If strlen($ciphertext) is not a multiple of the block size, null bytes will be added to the end of the string until
+ * it is.
+ *
+ * @see self::encrypt()
+ * @access public
+ * @param string $ciphertext
+ * @return string $plaintext
+ * @internal Could, but not must, extend by the child Crypt_* class
+ */
+ public function decrypt($ciphertext)
+ {
+ if ($this->changed) {
+ $this->setup();
+ }
+
+ try {
+ return $this->cipher->decrypt($ciphertext);
+ } catch (\LengthException $e) {
+ $len = strlen($ciphertext);
+ $block_size = $this->cipher->getBlockLengthInBytes();
+ $ciphertext = str_pad($ciphertext, $len + ($block_size - $len % $block_size) % $block_size, chr(0));
+ try {
+ return $this->cipher->decrypt($ciphertext);
+ } catch (BadDecryptionException $e) {
+ return false;
+ }
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * Setup IV and key
+ */
+ protected function setup()
+ {
+ // we set this just in case it was already set to anything via setPassword()
+ $temp = $this->explicit_key_length;
+ $this->setKeyLength($this->key_length);
+ $this->explicit_key_length = $temp;
+ if ($this->explicit_key_length) {
+ $this->cipher->setKeyLength($this->key_length);
+ }
+ if (empty($this->password)) {
+ $key_length = $this->key_length >> 3;
+ $key = str_pad(substr($this->key, 0, $key_length), $key_length, "\0");
+ $this->cipher->setKey($key);
+ } else {
+ $this->cipher->setPassword(...$this->password);
+ }
+ if (!$this->ivSet) {
+ $this->setIV('');
+ }
+ $this->changed = false;
+ }
+
+ /**
+ * Pad "packets".
+ *
+ * Block ciphers working by encrypting between their specified [$this->]block_size at a time
+ * If you ever need to encrypt or decrypt something that isn't of the proper length, it becomes necessary to
+ * pad the input so that it is of the proper length.
+ *
+ * Padding is enabled by default. Sometimes, however, it is undesirable to pad strings. Such is the case in SSH,
+ * where "packets" are padded with random bytes before being encrypted. Unpad these packets and you risk stripping
+ * away characters that shouldn't be stripped away. (SSH knows how many bytes are added because the length is
+ * transmitted separately)
+ *
+ * @see self::disablePadding()
+ * @access public
+ */
+ public function enablePadding()
+ {
+ $this->cipher->enablePadding();
+ }
+
+ /**
+ * Do not pad packets.
+ *
+ * @see self::enablePadding()
+ * @access public
+ */
+ public function disablePadding()
+ {
+ $this->cipher->disablePadding();
+ }
+
+ /**
+ * Treat consecutive "packets" as if they are a continuous buffer.
+ *
+ * Say you have a 32-byte plaintext $plaintext. Using the default behavior, the two following code snippets
+ * will yield different outputs:
+ *
+ * <code>
+ * echo $rijndael->encrypt(substr($plaintext, 0, 16));
+ * echo $rijndael->encrypt(substr($plaintext, 16, 16));
+ * </code>
+ * <code>
+ * echo $rijndael->encrypt($plaintext);
+ * </code>
+ *
+ * The solution is to enable the continuous buffer. Although this will resolve the above discrepancy, it creates
+ * another, as demonstrated with the following:
+ *
+ * <code>
+ * $rijndael->encrypt(substr($plaintext, 0, 16));
+ * echo $rijndael->decrypt($rijndael->encrypt(substr($plaintext, 16, 16)));
+ * </code>
+ * <code>
+ * echo $rijndael->decrypt($rijndael->encrypt(substr($plaintext, 16, 16)));
+ * </code>
+ *
+ * With the continuous buffer disabled, these would yield the same output. With it enabled, they yield different
+ * outputs. The reason is due to the fact that the initialization vector's change after every encryption /
+ * decryption round when the continuous buffer is enabled. When it's disabled, they remain constant.
+ *
+ * Put another way, when the continuous buffer is enabled, the state of the \phpseclib\Crypt\*() object changes after each
+ * encryption / decryption round, whereas otherwise, it'd remain constant. For this reason, it's recommended that
+ * continuous buffers not be used. They do offer better security and are, in fact, sometimes required (SSH uses them),
+ * however, they are also less intuitive and more likely to cause you problems.
+ *
+ * @see self::disableContinuousBuffer()
+ * @access public
+ * @internal Could, but not must, extend by the child Crypt_* class
+ */
+ public function enableContinuousBuffer()
+ {
+ try {
+ $this->cipher->enableContinuousBuffer();
+ } catch (\BadMethodCallException $e) {
+ user_error($e->getMessage());
+ }
+ }
+
+ /**
+ * Treat consecutive packets as if they are a discontinuous buffer.
+ *
+ * The default behavior.
+ *
+ * @see self::enableContinuousBuffer()
+ * @access public
+ * @internal Could, but not must, extend by the child Crypt_* class
+ */
+ public function disableContinuousBuffer()
+ {
+ $this->cipher->disableContinuousBuffer();
+ }
+
+ /**
+ * Test for engine validity
+ *
+ * @see self::__construct()
+ * @param int $engine
+ * @access public
+ * @return bool
+ */
+ public function isValidEngine($engine)
+ {
+ $map = self::ENGINE_MAP;
+ return $this->cipher->isValidEngine($map[$engine]);
+ }
+
+ /**
+ * Sets the preferred crypt engine
+ *
+ * Currently, $engine could be:
+ *
+ * - \phpseclib\Crypt\Base::ENGINE_OPENSSL [very fast]
+ *
+ * - \phpseclib\Crypt\Base::ENGINE_MCRYPT [fast]
+ *
+ * - \phpseclib\Crypt\Base::ENGINE_INTERNAL [slow]
+ *
+ * If the preferred crypt engine is not available the fastest available one will be used
+ *
+ * @see self::__construct()
+ * @param int $engine
+ * @access public
+ */
+ public function setPreferredEngine($engine)
+ {
+ $map = self::ENGINE_MAP;
+ $this->cipher->setPreferredEngine($map[$engine]);
+ }
+
+ /**
+ * Returns the engine currently being utilized
+ *
+ * @see self::_setEngine()
+ * @access public
+ */
+ public function getEngine()
+ {
+ static $reverseMap;
+ if (!isset($reverseMap)) {
+ $reverseMap = array_flip(self::ENGINE_MAP);
+ }
+ return $reverseMap[$this->cipher->getEngine()];
+ }
+} \ No newline at end of file
diff --git a/vendor/phpseclib/phpseclib2_compat/src/Crypt/Blowfish.php b/vendor/phpseclib/phpseclib2_compat/src/Crypt/Blowfish.php
new file mode 100644
index 000000000..f2cb68ee8
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/src/Crypt/Blowfish.php
@@ -0,0 +1,66 @@
+<?php
+
+/**
+ * Pure-PHP implementation of Blowfish.
+ *
+ * Uses mcrypt, if available, and an internal implementation, otherwise.
+ *
+ * PHP version 5
+ *
+ * Useful resources are as follows:
+ *
+ * - {@link http://en.wikipedia.org/wiki/Blowfish_(cipher) Wikipedia description of Blowfish}
+ *
+ * Here's a short example of how to use this library:
+ * <code>
+ * <?php
+ * include 'vendor/autoload.php';
+ *
+ * $blowfish = new \phpseclib\Crypt\Blowfish();
+ *
+ * $blowfish->setKey('12345678901234567890123456789012');
+ *
+ * $plaintext = str_repeat('a', 1024);
+ *
+ * echo $blowfish->decrypt($blowfish->encrypt($plaintext));
+ * ?>
+ * </code>
+ *
+ * @category Crypt
+ * @package Blowfish
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @author Hans-Juergen Petrich <petrich@tronic-media.com>
+ * @copyright 2007 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Crypt;
+
+/**
+ * Pure-PHP implementation of Blowfish.
+ *
+ * @package Blowfish
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class Blowfish extends Base
+{
+ /**
+ * Turns key lengths, be they valid or invalid, to valid key lengths
+ *
+ * @param int $length
+ * @access private
+ * @return int
+ */
+ protected function calculateNewKeyLength($length)
+ {
+ switch (true) {
+ case $length < 32:
+ return 32;
+ case $length > 448:
+ return 448;
+ }
+ return $length;
+ }
+} \ No newline at end of file
diff --git a/vendor/phpseclib/phpseclib2_compat/src/Crypt/DES.php b/vendor/phpseclib/phpseclib2_compat/src/Crypt/DES.php
new file mode 100644
index 000000000..512c59a27
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/src/Crypt/DES.php
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * Pure-PHP implementation of DES.
+ *
+ * Uses mcrypt, if available, and an internal implementation, otherwise.
+ *
+ * PHP version 5
+ *
+ * Useful resources are as follows:
+ *
+ * - {@link http://en.wikipedia.org/wiki/DES_supplementary_material Wikipedia: DES supplementary material}
+ * - {@link http://www.itl.nist.gov/fipspubs/fip46-2.htm FIPS 46-2 - (DES), Data Encryption Standard}
+ * - {@link http://www.cs.eku.edu/faculty/styer/460/Encrypt/JS-DES.html JavaScript DES Example}
+ *
+ * Here's a short example of how to use this library:
+ * <code>
+ * <?php
+ * include 'vendor/autoload.php';
+ *
+ * $des = new \phpseclib\Crypt\DES();
+ *
+ * $des->setKey('abcdefgh');
+ *
+ * $size = 10 * 1024;
+ * $plaintext = '';
+ * for ($i = 0; $i < $size; $i++) {
+ * $plaintext.= 'a';
+ * }
+ *
+ * echo $des->decrypt($des->encrypt($plaintext));
+ * ?>
+ * </code>
+ *
+ * @category Crypt
+ * @package DES
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2007 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Crypt;
+
+/**
+ * Pure-PHP implementation of DES.
+ *
+ * @package DES
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class DES extends Base
+{
+ /**
+ * Turns key lengths, be they valid or invalid, to valid key lengths
+ *
+ * @param int $length
+ * @access private
+ * @return int
+ */
+ protected function calculateNewKeyLength($length)
+ {
+ return 64;
+ }
+} \ No newline at end of file
diff --git a/vendor/phpseclib/phpseclib2_compat/src/Crypt/Hash.php b/vendor/phpseclib/phpseclib2_compat/src/Crypt/Hash.php
new file mode 100644
index 000000000..de7942705
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/src/Crypt/Hash.php
@@ -0,0 +1,145 @@
+<?php
+
+/**
+ * Pure-PHP implementations of keyed-hash message authentication codes (HMACs) and various cryptographic hashing functions.
+ *
+ * Uses hash() or mhash() if available and an internal implementation, otherwise. Currently supports the following:
+ *
+ * md2, md5, md5-96, sha1, sha1-96, sha256, sha256-96, sha384, and sha512, sha512-96
+ *
+ * If {@link self::setKey() setKey()} is called, {@link self::hash() hash()} will return the HMAC as opposed to
+ * the hash. If no valid algorithm is provided, sha1 will be used.
+ *
+ * PHP version 5
+ *
+ * {@internal The variable names are the same as those in
+ * {@link http://tools.ietf.org/html/rfc2104#section-2 RFC2104}.}}
+ *
+ * Here's a short example of how to use this library:
+ * <code>
+ * <?php
+ * include 'vendor/autoload.php';
+ *
+ * $hash = new \phpseclib\Crypt\Hash('sha1');
+ *
+ * $hash->setKey('abcdefg');
+ *
+ * echo base64_encode($hash->hash('abcdefg'));
+ * ?>
+ * </code>
+ *
+ * @category Crypt
+ * @package Hash
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2007 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Crypt;
+
+/**
+ * Pure-PHP implementations of keyed-hash message authentication codes (HMACs) and various cryptographic hashing functions.
+ *
+ * @package Hash
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class Hash
+{
+ /**#@+
+ * @access private
+ * @see \phpseclib\Crypt\Hash::__construct()
+ */
+ /**
+ * Toggles the internal implementation
+ */
+ const MODE_INTERNAL = 1;
+ /**#@-*/
+
+ /**
+ * Hash Object
+ *
+ * @see self::setHash()
+ * @var null|\phpseclib3\Crypt\Hash
+ * @access private
+ */
+ private $hash;
+
+ /**
+ * Default Constructor.
+ *
+ * @param string $hash
+ * @return \phpseclib\Crypt\Hash
+ * @access public
+ */
+ public function __construct($hash = 'sha1')
+ {
+ $this->setHash($hash);
+ }
+
+ /**
+ * Sets the key for HMACs
+ *
+ * Keys can be of any length.
+ *
+ * @access public
+ * @param string $key
+ */
+ public function setKey($key = false)
+ {
+ $this->hash->setKey($key);
+ }
+
+ /**
+ * Gets the hash function.
+ *
+ * As set by the constructor or by the setHash() method.
+ *
+ * @access public
+ * @return string
+ */
+ public function getHash()
+ {
+ return $this->hash->getHash();
+ }
+
+ /**
+ * Sets the hash function.
+ *
+ * @access public
+ * @param string $hash
+ */
+ public function setHash($hash)
+ {
+ $this->hash = new \phpseclib3\Crypt\Hash;
+ try {
+ $this->hash->setHash($hash);
+ } catch (\phpseclib3\Exception\UnsupportedAlgorithmException $e) {
+ $this->hash->setHash('sha1');
+ }
+ }
+
+ /**
+ * Compute the HMAC.
+ *
+ * @access public
+ * @param string $text
+ * @return string
+ */
+ public function hash($text)
+ {
+ return $this->hash->hash($text);
+ }
+
+ /**
+ * Returns the hash length (in bytes)
+ *
+ * @access public
+ * @return int
+ */
+ public function getLength()
+ {
+ return $this->hash->getLengthInBytes();
+ }
+} \ No newline at end of file
diff --git a/vendor/phpseclib/phpseclib2_compat/src/Crypt/RC2.php b/vendor/phpseclib/phpseclib2_compat/src/Crypt/RC2.php
new file mode 100644
index 000000000..b23f73bca
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/src/Crypt/RC2.php
@@ -0,0 +1,132 @@
+<?php
+
+/**
+ * Pure-PHP implementation of RC2.
+ *
+ * Uses mcrypt, if available, and an internal implementation, otherwise.
+ *
+ * PHP version 5
+ *
+ * Useful resources are as follows:
+ *
+ * - {@link http://tools.ietf.org/html/rfc2268}
+ *
+ * Here's a short example of how to use this library:
+ * <code>
+ * <?php
+ * include 'vendor/autoload.php';
+ *
+ * $rc2 = new \phpseclib\Crypt\RC2();
+ *
+ * $rc2->setKey('abcdefgh');
+ *
+ * $plaintext = str_repeat('a', 1024);
+ *
+ * echo $rc2->decrypt($rc2->encrypt($plaintext));
+ * ?>
+ * </code>
+ *
+ * @category Crypt
+ * @package RC2
+ * @author Patrick Monnerat <pm@datasphere.ch>
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Crypt;
+
+/**
+ * Pure-PHP implementation of RC2.
+ *
+ * @package RC2
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class RC2 extends Base
+{
+ /**
+ * Default Constructor.
+ *
+ * Determines whether or not the mcrypt extension should be used.
+ *
+ * $mode could be:
+ *
+ * - self::MODE_ECB
+ *
+ * - self::MODE_CBC
+ *
+ * - self::MODE_CTR
+ *
+ * - self::MODE_CFB
+ *
+ * - self::MODE_OFB
+ *
+ * If not explicitly set, self::MODE_CBC will be used.
+ *
+ * @param int $mode
+ * @access public
+ */
+ public function __construct($mode = self::MODE_CBC)
+ {
+ parent::__construct($mode);
+ $this->key_length = 8;
+ }
+
+ /**
+ * Turns key lengths, be they valid or invalid, to valid key lengths
+ *
+ * @param int $length
+ * @access private
+ * @return int
+ */
+ protected function calculateNewKeyLength($length)
+ {
+ switch (true) {
+ case $length < 8:
+ return 8;
+ case $length > 1024:
+ return 1024;
+ }
+
+ return $length;
+ }
+
+ /**
+ * Setup IV and key
+ */
+ protected function setup()
+ {
+ if ($this->explicit_key_length) {
+ $this->cipher->setKeyLength($this->key_length);
+ }
+ $this->cipher->setKey($this->key);
+ if (!$this->ivSet) {
+ $this->setIV('');
+ }
+ $this->changed = false;
+ }
+
+ /**
+ * Sets the key.
+ *
+ * Keys can be of any length. RC2, itself, uses 8 to 1024 bit keys (eg.
+ * strlen($key) <= 128), however, we only use the first 128 bytes if $key
+ * has more then 128 bytes in it, and set $key to a single null byte if
+ * it is empty.
+ *
+ * If the key is not explicitly set, it'll be assumed to be a single
+ * null byte.
+ *
+ * @see \phpseclib\Crypt\Base::setKey()
+ * @access public
+ * @param string $key
+ * @param int $t1 optional Effective key length in bits.
+ */
+ function setKey($key, $t1 = 0)
+ {
+ parent::setKey($key);
+ if ($t1) {
+ $this->setKeyLength($t1);
+ }
+ }
+} \ No newline at end of file
diff --git a/vendor/phpseclib/phpseclib2_compat/src/Crypt/RC4.php b/vendor/phpseclib/phpseclib2_compat/src/Crypt/RC4.php
new file mode 100644
index 000000000..78781f553
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/src/Crypt/RC4.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * Pure-PHP implementation of RC4.
+ *
+ * Uses mcrypt, if available, and an internal implementation, otherwise.
+ *
+ * PHP version 5
+ *
+ * Useful resources are as follows:
+ *
+ * - {@link http://www.mozilla.org/projects/security/pki/nss/draft-kaukonen-cipher-arcfour-03.txt ARCFOUR Algorithm}
+ * - {@link http://en.wikipedia.org/wiki/RC4 - Wikipedia: RC4}
+ *
+ * RC4 is also known as ARCFOUR or ARC4. The reason is elaborated upon at Wikipedia. This class is named RC4 and not
+ * ARCFOUR or ARC4 because RC4 is how it is referred to in the SSH1 specification.
+ *
+ * Here's a short example of how to use this library:
+ * <code>
+ * <?php
+ * include 'vendor/autoload.php';
+ *
+ * $rc4 = new \phpseclib\Crypt\RC4();
+ *
+ * $rc4->setKey('abcdefgh');
+ *
+ * $size = 10 * 1024;
+ * $plaintext = '';
+ * for ($i = 0; $i < $size; $i++) {
+ * $plaintext.= 'a';
+ * }
+ *
+ * echo $rc4->decrypt($rc4->encrypt($plaintext));
+ * ?>
+ * </code>
+ *
+ * @category Crypt
+ * @package RC4
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2007 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Crypt;
+
+/**
+ * Pure-PHP implementation of RC4.
+ *
+ * @package RC4
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class RC4 extends Base
+{
+ /**
+ * Turns key lengths, be they valid or invalid, to valid key lengths
+ *
+ * @param int $length
+ * @access private
+ * @return int
+ */
+ protected function calculateNewKeyLength($length)
+ {
+ switch (true) {
+ case $length < 8:
+ return 8;
+ case $length > 2048:
+ return 2048;
+ }
+
+ return $length;
+ }
+} \ No newline at end of file
diff --git a/vendor/phpseclib/phpseclib2_compat/src/Crypt/RSA.php b/vendor/phpseclib/phpseclib2_compat/src/Crypt/RSA.php
new file mode 100644
index 000000000..7a45a0bb5
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/src/Crypt/RSA.php
@@ -0,0 +1,948 @@
+<?php
+
+/**
+ * Pure-PHP PKCS#1 (v2.1) compliant implementation of RSA.
+ *
+ * PHP version 5
+ *
+ * Here's an example of how to encrypt and decrypt text with this library:
+ * <code>
+ * <?php
+ * include 'vendor/autoload.php';
+ *
+ * $rsa = new \phpseclib\Crypt\RSA();
+ * extract($rsa->createKey());
+ *
+ * $plaintext = 'terrafrost';
+ *
+ * $rsa->loadKey($privatekey);
+ * $ciphertext = $rsa->encrypt($plaintext);
+ *
+ * $rsa->loadKey($publickey);
+ * echo $rsa->decrypt($ciphertext);
+ * ?>
+ * </code>
+ *
+ * Here's an example of how to create signatures and verify signatures with this library:
+ * <code>
+ * <?php
+ * include 'vendor/autoload.php';
+ *
+ * $rsa = new \phpseclib\Crypt\RSA();
+ * extract($rsa->createKey());
+ *
+ * $plaintext = 'terrafrost';
+ *
+ * $rsa->loadKey($privatekey);
+ * $signature = $rsa->sign($plaintext);
+ *
+ * $rsa->loadKey($publickey);
+ * echo $rsa->verify($plaintext, $signature) ? 'verified' : 'unverified';
+ * ?>
+ * </code>
+ *
+ * @category Crypt
+ * @package RSA
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2009 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Crypt;
+
+use phpseclib3\Crypt\RSA as RSA2;
+use phpseclib3\Crypt\PublicKeyLoader;
+use phpseclib3\Crypt\Common\AsymmetricKey;
+use phpseclib3\Crypt\Common\PublicKey;
+use phpseclib3\Crypt\Common\PrivateKey;
+use phpseclib3\Exception\UnsupportedAlgorithmException;
+use phpseclib3\Exception\UnsupportedFormatException;
+use phpseclib3\Exception\NoKeyLoadedException;
+use phpseclib3\Crypt\Common\Formats\Keys\PuTTY;
+use phpseclib3\Crypt\Common\Formats\Keys\OpenSSH;
+use phpseclib3\Math\BigInteger;
+use phpseclib\Math\BigInteger as BigInteger2;
+
+/**
+ * Pure-PHP PKCS#1 compliant implementation of RSA.
+ *
+ * @package RSA
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class RSA
+{
+ /**#@+
+ * @access public
+ * @see self::encrypt()
+ * @see self::decrypt()
+ */
+ /**
+ * Use {@link http://en.wikipedia.org/wiki/Optimal_Asymmetric_Encryption_Padding Optimal Asymmetric Encryption Padding}
+ * (OAEP) for encryption / decryption.
+ *
+ * Uses sha1 by default.
+ *
+ * @see self::setHash()
+ * @see self::setMGFHash()
+ */
+ const ENCRYPTION_OAEP = 1;
+ /**
+ * Use PKCS#1 padding.
+ *
+ * Although self::ENCRYPTION_OAEP offers more security, including PKCS#1 padding is necessary for purposes of backwards
+ * compatibility with protocols (like SSH-1) written before OAEP's introduction.
+ */
+ const ENCRYPTION_PKCS1 = 2;
+ /**
+ * Do not use any padding
+ *
+ * Although this method is not recommended it can none-the-less sometimes be useful if you're trying to decrypt some legacy
+ * stuff, if you're trying to diagnose why an encrypted message isn't decrypting, etc.
+ */
+ const ENCRYPTION_NONE = 3;
+ /**#@-*/
+
+ /**#@+
+ * @access public
+ * @see self::sign()
+ * @see self::verify()
+ * @see self::setHash()
+ */
+ /**
+ * Use the Probabilistic Signature Scheme for signing
+ *
+ * Uses sha1 by default.
+ *
+ * @see self::setSaltLength()
+ * @see self::setMGFHash()
+ */
+ const SIGNATURE_PSS = 1;
+ /**
+ * Use the PKCS#1 scheme by default.
+ *
+ * Although self::SIGNATURE_PSS offers more security, including PKCS#1 signing is necessary for purposes of backwards
+ * compatibility with protocols (like SSH-2) written before PSS's introduction.
+ */
+ const SIGNATURE_PKCS1 = 2;
+ /**#@-*/
+
+ /**#@+
+ * @access public
+ * @see \phpseclib\Crypt\RSA::createKey()
+ * @see \phpseclib\Crypt\RSA::setPrivateKeyFormat()
+ */
+ /**
+ * PKCS#1 formatted private key
+ *
+ * Used by OpenSSH
+ */
+ const PRIVATE_FORMAT_PKCS1 = 0;
+ /**
+ * PuTTY formatted private key
+ */
+ const PRIVATE_FORMAT_PUTTY = 1;
+ /**
+ * XML formatted private key
+ */
+ const PRIVATE_FORMAT_XML = 2;
+ /**
+ * PKCS#8 formatted private key
+ */
+ const PRIVATE_FORMAT_PKCS8 = 8;
+ /**
+ * OpenSSH formatted private key
+ */
+ const PRIVATE_FORMAT_OPENSSH = 9;
+ /**#@-*/
+
+ /**#@+
+ * @access public
+ * @see \phpseclib\Crypt\RSA::createKey()
+ * @see \phpseclib\Crypt\RSA::setPublicKeyFormat()
+ */
+ /**
+ * Raw public key
+ *
+ * An array containing two \phpseclib\Math\BigInteger objects.
+ *
+ * The exponent can be indexed with any of the following:
+ *
+ * 0, e, exponent, publicExponent
+ *
+ * The modulus can be indexed with any of the following:
+ *
+ * 1, n, modulo, modulus
+ */
+ const PUBLIC_FORMAT_RAW = 3;
+ /**
+ * PKCS#1 formatted public key (raw)
+ *
+ * Used by File/X509.php
+ *
+ * Has the following header:
+ *
+ * -----BEGIN RSA PUBLIC KEY-----
+ *
+ * Analogous to ssh-keygen's pem format (as specified by -m)
+ */
+ const PUBLIC_FORMAT_PKCS1 = 4;
+ const PUBLIC_FORMAT_PKCS1_RAW = 4;
+ /**
+ * XML formatted public key
+ */
+ const PUBLIC_FORMAT_XML = 5;
+ /**
+ * OpenSSH formatted public key
+ *
+ * Place in $HOME/.ssh/authorized_keys
+ */
+ const PUBLIC_FORMAT_OPENSSH = 6;
+ /**
+ * PKCS#1 formatted public key (encapsulated)
+ *
+ * Used by PHP's openssl_public_encrypt() and openssl's rsautl (when -pubin is set)
+ *
+ * Has the following header:
+ *
+ * -----BEGIN PUBLIC KEY-----
+ *
+ * Analogous to ssh-keygen's pkcs8 format (as specified by -m). Although PKCS8
+ * is specific to private keys it's basically creating a DER-encoded wrapper
+ * for keys. This just extends that same concept to public keys (much like ssh-keygen)
+ */
+ const PUBLIC_FORMAT_PKCS8 = 7;
+ /**#@-*/
+
+ /**
+ * The Original Key
+ *
+ * @see self::getComment()
+ * @var string
+ * @access private
+ */
+ private $origKey = null;
+
+ /**
+ * The Key
+ *
+ * @var \phpseclib3\Crypt\Common\AsymmetricKey
+ * @access private
+ */
+ private $key = null;
+
+ /**
+ * Password
+ *
+ * @var string
+ * @access private
+ */
+ private $password = false;
+
+ /**
+ * Private Key Format
+ *
+ * @var int
+ * @access private
+ */
+ private $privateKeyFormat = self::PRIVATE_FORMAT_PKCS1;
+
+ /**
+ * Public Key Format
+ *
+ * @var int
+ * @access public
+ */
+ private $publicKeyFormat = self::PUBLIC_FORMAT_PKCS1;
+
+ /**
+ * Public key comment field.
+ *
+ * @var string
+ * @access private
+ */
+ private $comment = 'phpseclib-generated-key';
+
+ /**
+ * Encryption mode
+ *
+ * @var int
+ * @access private
+ */
+ private $encryptionMode = self::ENCRYPTION_OAEP;
+
+ /**
+ * Signature mode
+ *
+ * @var int
+ * @access private
+ */
+ private $signatureMode = self::SIGNATURE_PSS;
+
+ /**
+ * Hash name
+ *
+ * @var string
+ * @access private
+ */
+ private $hash = 'sha1';
+
+ /**
+ * Hash function for the Mask Generation Function
+ *
+ * @var string
+ * @access private
+ */
+ private $mgfHash = 'sha1';
+
+ /**
+ * Length of salt
+ *
+ * @var int
+ * @access private
+ */
+ private $sLen;
+
+ /**
+ * The constructor
+ *
+ * @return \phpseclib\Crypt\RSA
+ * @access public
+ */
+ public function __construct()
+ {
+ // don't do anything
+ }
+
+ /**
+ * Create public / private key pair
+ *
+ * Returns an array with the following three elements:
+ * - 'privatekey': The private key.
+ * - 'publickey': The public key.
+ * - 'partialkey': A partially computed key (if the execution time exceeded $timeout).
+ * Will need to be passed back to \phpseclib\Crypt\RSA::createKey() as the third parameter for further processing.
+ *
+ * @access public
+ * @param int $bits
+ */
+ public function createKey($bits = 1024)
+ {
+ $privatekey = RSA2::createKey($bits);
+
+ return [
+ 'privatekey' => $privatekey,
+ 'publickey' => $privatekey->getPublicKey(),
+ 'partialkey' => false
+ ];
+ }
+
+ /**
+ * Returns the key size
+ *
+ * More specifically, this returns the size of the modulo in bits.
+ *
+ * @access public
+ * @return int
+ */
+ public function getSize()
+ {
+ // for EC and RSA keys this'll return an integer
+ // for DSA keys this'll return an array (L + N)
+ return isset($this->key) ? $this->key->getLength() : 0;
+ }
+
+ /**
+ * Sets the password
+ *
+ * Private keys can be encrypted with a password. To unset the password, pass in the empty string or false.
+ * Or rather, pass in $password such that empty($password) && !is_string($password) is true.
+ *
+ * @see self::createKey()
+ * @see self::loadKey()
+ * @access public
+ * @param string $password
+ */
+ public function setPassword($password = false)
+ {
+ $this->password = $password;
+ }
+
+ /**
+ * Loads a public or private key
+ *
+ * Returns true on success and false on failure (ie. an incorrect password was provided or the key was malformed)
+ *
+ * @access public
+ * @param string|RSA|array $key
+ * @param bool|int $type optional
+ * @return bool
+ */
+ public function loadKey($key)
+ {
+ if ($key instanceof AsymmetricKey) {
+ $this->key = $key;
+ } else if ($key instanceof RSA) {
+ $this->key = $key->key;
+ } else {
+ try {
+ if (is_array($key)) {
+ foreach ($key as &$value) {
+ if ($value instanceof BigInteger2) {
+ $value = new BigInteger($value->toBytes(true), -256);
+ }
+ }
+ }
+ $this->key = PublicKeyLoader::load($key, $this->password);
+ } catch (NoKeyLoadedException $e) {
+ $this->key = $this->origKey = null;
+ return false;
+ }
+ $this->origKey = $key;
+ }
+
+ // with phpseclib 2.0 loading a key does not reset any of the following
+ // so we'll need to preserve the old settings whenever a new key is loaded
+ // with this shim
+ $this->setEncryptionMode($this->encryptionMode);
+ //$this->setSignatureMode($this->signatureMode);
+ $this->setHash($this->hash);
+ $this->setMGFHash($this->mgfHash);
+ $this->setSaltLength($this->sLen);
+
+ return true;
+ }
+
+ /**
+ * __toString() magic method
+ *
+ * @access public
+ * @return string
+ */
+ public function __toString()
+ {
+ PuTTY::setComment($this->comment);
+ OpenSSH::setComment($this->comment);
+
+ if ($this->key instanceof PublicKey) {
+ return $this->key->toString(self::const2str($this->publicKeyFormat));
+ }
+
+ if ($this->key instanceof PrivateKey) {
+ try {
+ return $this->key->toString(self::const2str($this->privateKeyFormat));
+ } catch (UnsupportedFormatException $e) {
+ if ($this->password) {
+ return $this->key->withPassword()->toString(self::const2str($this->privateKeyFormat));
+ }
+ }
+ }
+
+ return '';
+ }
+
+ /**
+ * Defines the public key
+ *
+ * Some private key formats define the public exponent and some don't. Those that don't define it are problematic when
+ * used in certain contexts. For example, in SSH-2, RSA authentication works by sending the public key along with a
+ * message signed by the private key to the server. The SSH-2 server looks the public key up in an index of public keys
+ * and if it's present then proceeds to verify the signature. Problem is, if your private key doesn't include the public
+ * exponent this won't work unless you manually add the public exponent. phpseclib tries to guess if the key being used
+ * is the public key but in the event that it guesses incorrectly you might still want to explicitly set the key as being
+ * public.
+ *
+ * Do note that when a new key is loaded the index will be cleared.
+ *
+ * Returns true on success, false on failure
+ *
+ * @see self::getPublicKey()
+ * @access public
+ * @param string $key optional
+ * @param int $type optional
+ * @return bool
+ */
+ public function setPublicKey()
+ {
+ return false;
+ }
+
+ /**
+ * Defines the private key
+ *
+ * If phpseclib guessed a private key was a public key and loaded it as such it might be desirable to force
+ * phpseclib to treat the key as a private key. This function will do that.
+ *
+ * Do note that when a new key is loaded the index will be cleared.
+ *
+ * Returns true on success, false on failure
+ *
+ * @see self::getPublicKey()
+ * @access public
+ * @param string $key optional
+ * @param int $type optional
+ * @return bool
+ */
+ public function setPrivateKey($key = false)
+ {
+ if ($key === false && $this->key instanceof RSA2) {
+ $this->key = $this->key->asPrivateKey();
+ }
+
+ try {
+ $key = PublicKeyLoader::load($key);
+ } catch (NoKeyLoadedException $e) {
+ return false;
+ }
+ if ($key instanceof RSA2) {
+ $this->key = $key instanceof PublicKey ? $key->asPrivateKey() : $key;
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the public key
+ *
+ * The public key is only returned under two circumstances - if the private key had the public key embedded within it
+ * or if the public key was set via setPublicKey(). If the currently loaded key is supposed to be the public key this
+ * function won't return it since this library, for the most part, doesn't distinguish between public and private keys.
+ *
+ * @see self::getPublicKey()
+ * @access public
+ * @param string $key
+ * @param int $type optional
+ */
+ public function getPublicKey($type = self::PUBLIC_FORMAT_PKCS8)
+ {
+ PuTTY::setComment($this->comment);
+ OpenSSH::setComment($this->comment);
+
+ if ($this->key instanceof PrivateKey) {
+ return $this->key->getPublicKey()->toString(self::const2str($type));
+ }
+
+ if ($this->key instanceof PublicKey) {
+ return $this->key->toString(self::const2str($type));
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the public key's fingerprint
+ *
+ * The public key's fingerprint is returned, which is equivalent to running `ssh-keygen -lf rsa.pub`. If there is
+ * no public key currently loaded, false is returned.
+ * Example output (md5): "c1:b1:30:29:d7:b8:de:6c:97:77:10:d7:46:41:63:87" (as specified by RFC 4716)
+ *
+ * @access public
+ * @param string $algorithm The hashing algorithm to be used. Valid options are 'md5' and 'sha256'. False is returned
+ * for invalid values.
+ * @return mixed
+ */
+ public function getPublicKeyFingerprint($algorithm = 'md5')
+ {
+ if ($this->key instanceof PublicKey) {
+ return $this->key->getFingerprint($algorithm);
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the private key
+ *
+ * The private key is only returned if the currently loaded key contains the constituent prime numbers.
+ *
+ * @see self::getPublicKey()
+ * @access public
+ * @param string $key
+ * @param int $type optional
+ * @return mixed
+ */
+ public function getPrivateKey($type = self::PUBLIC_FORMAT_PKCS1)
+ {
+ PuTTY::setComment($this->comment);
+ OpenSSH::setComment($this->comment);
+
+ if ($this->key instanceof PrivateKey) {
+ try {
+ return $this->key->toString(self::const2str($this->privateKeyFormat));
+ } catch (UnsupportedFormatException $e) {
+ if ($this->password) {
+ return $this->key->withPassword()->toString(self::const2str($this->privateKeyFormat));
+ }
+ }
+
+ }
+
+ return false;
+ }
+
+ /**
+ * __clone() magic method
+ *
+ * @access public
+ * @return Crypt_RSA
+ */
+ public function __clone()
+ {
+ $key = new RSA();
+ $key->loadKey($this);
+ return $key;
+ }
+
+ /**
+ * Convert phpseclib 2.0 style constants to phpseclib 3.0 style strings
+ *
+ * @param int $const
+ * @access private
+ * @return string
+ */
+ private static function const2str($const)
+ {
+ switch ($const) {
+ case self::PRIVATE_FORMAT_PKCS1:
+ case self::PUBLIC_FORMAT_PKCS1:
+ return 'PKCS1';
+ case self::PRIVATE_FORMAT_PUTTY:
+ return 'PuTTY';
+ case self::PRIVATE_FORMAT_XML:
+ case self::PUBLIC_FORMAT_XML:
+ return 'XML';
+ case self::PRIVATE_FORMAT_PKCS8:
+ case self::PUBLIC_FORMAT_PKCS8:
+ return 'PKCS8';
+ case self::PRIVATE_FORMAT_OPENSSH:
+ case self::PUBLIC_FORMAT_OPENSSH:
+ return 'OpenSSH';
+ }
+ }
+
+ /**
+ * Determines the private key format
+ *
+ * @see self::createKey()
+ * @access public
+ * @param int $format
+ */
+ public function setPrivateKeyFormat($format)
+ {
+ $this->privateKeyFormat = $format;
+ }
+
+ /**
+ * Determines the public key format
+ *
+ * @see self::createKey()
+ * @access public
+ * @param int $format
+ */
+ public function setPublicKeyFormat($format)
+ {
+ $this->publicKeyFormat = $format;
+ }
+
+ /**
+ * Determines which hashing function should be used
+ *
+ * Used with signature production / verification and (if the encryption mode is self::ENCRYPTION_OAEP) encryption and
+ * decryption. If $hash isn't supported, sha1 is used.
+ *
+ * @access public
+ * @param string $hash
+ */
+ public function setHash($hash)
+ {
+ $this->hash = $hash;
+ if ($this->key instanceof AsymmetricKey) {
+ try {
+ $this->key = $this->key->withHash($hash);
+ } catch (UnsupportedAlgorithmException $e) {
+ $this->key = $this->key->withHash('sha1');
+ }
+ }
+ }
+
+ /**
+ * Determines which hashing function should be used for the mask generation function
+ *
+ * The mask generation function is used by self::ENCRYPTION_OAEP and self::SIGNATURE_PSS and although it's
+ * best if Hash and MGFHash are set to the same thing this is not a requirement.
+ *
+ * @access public
+ * @param string $hash
+ */
+ public function setMGFHash($hash)
+ {
+ $this->mgfHash = $hash;
+ if ($this->key instanceof RSA2) {
+ try {
+ $this->key = $this->key->withMGFHash($hash);
+ } catch (UnsupportedAlgorithmException $e) {
+ $this->key = $this->key->withMGFHash('sha1');
+ }
+ }
+ }
+
+ /**
+ * Determines the salt length
+ *
+ * To quote from {@link http://tools.ietf.org/html/rfc3447#page-38 RFC3447#page-38}:
+ *
+ * Typical salt lengths in octets are hLen (the length of the output
+ * of the hash function Hash) and 0.
+ *
+ * @access public
+ * @param int $format
+ */
+ public function setSaltLength($sLen)
+ {
+ $this->sLen = $sLen;
+ if ($this->key instanceof RSA2) {
+ $this->key = $this->key->withSaltLength($sLen);
+ }
+ }
+
+ /**
+ * Set Encryption Mode
+ *
+ * Valid values include self::ENCRYPTION_OAEP and self::ENCRYPTION_PKCS1.
+ *
+ * @access public
+ * @param int $mode
+ */
+ public function setEncryptionMode($mode)
+ {
+ $this->encryptionMode = $mode;
+ if ($this->key instanceof RSA2) {
+ $this->key = $this->key->withPadding(
+ self::enc2pad($this->encryptionMode) |
+ self::sig2pad($this->signatureMode)
+ );
+ }
+ }
+
+ /**
+ * Set Signature Mode
+ *
+ * Valid values include self::SIGNATURE_PSS and self::SIGNATURE_PKCS1
+ *
+ * @access public
+ * @param int $mode
+ */
+ public function setSignatureMode($mode)
+ {
+ $this->signatureMode = $mode;
+ if ($this->key instanceof RSA2) {
+ $this->key = $this->key->withPadding(
+ self::enc2pad($this->encryptionMode) |
+ self::sig2pad($this->signatureMode)
+ );
+ }
+ }
+
+ /**
+ * Convert phpseclib 2.0 style constants to phpseclib 3.0 style constants
+ *
+ * @param int $mode
+ * @access private
+ * @return int
+ */
+ private function enc2pad($mode)
+ {
+ switch ($mode) {
+ case self::ENCRYPTION_PKCS1:
+ return RSA2::ENCRYPTION_PKCS1;
+ case self::ENCRYPTION_NONE:
+ return RSA2::ENCRYPTION_NONE;
+ //case self::ENCRYPTION_OAEP:
+ default:
+ return RSA2::ENCRYPTION_OAEP;
+ }
+ }
+
+ /**
+ * Convert phpseclib 2.0 style constants to phpseclib 3.0 style constants
+ *
+ * @param int $mode
+ * @access private
+ * @return int
+ */
+ private function sig2pad($mode)
+ {
+ switch ($mode) {
+ case self::SIGNATURE_PKCS1:
+ return RSA2::SIGNATURE_PKCS1;
+ //case self::SIGNATURE_PSS:
+ default:
+ return RSA2::SIGNATURE_PSS;
+ }
+ }
+
+ /**
+ * Set public key comment.
+ *
+ * @access public
+ * @param string $comment
+ */
+ public function setComment($comment)
+ {
+ $this->comment = $comment;
+ }
+
+ /**
+ * Get public key comment.
+ *
+ * @access public
+ * @return string
+ */
+ public function getComment()
+ {
+ // we'd need to make the load method in the parent PuTTY and OpenSSH classes public instead of protected
+ // for this to work
+ try {
+ $key = PuTTY::load($this->origKey);
+ return $key['comment'];
+ } catch (\Exception $e) {}
+
+ try {
+ $key = OpenSSH::load($this->origKey);
+ return $key['comment'];
+ } catch (\Exception $e) {}
+
+ return '';
+ }
+
+ /**
+ * Encryption
+ *
+ * Both self::ENCRYPTION_OAEP and self::ENCRYPTION_PKCS1 both place limits on how long $plaintext can be.
+ * If $plaintext exceeds those limits it will be broken up so that it does and the resultant ciphertext's will
+ * be concatenated together.
+ *
+ * @see self::decrypt()
+ * @access public
+ * @param string $plaintext
+ * @return string
+ */
+ public function encrypt($plaintext)
+ {
+ if (!$this->key instanceof RSA2) {
+ return false;
+ }
+ $key = $this->key;
+ if ($key instanceof PrivateKey) {
+ $key = $key->toString('Raw');
+ $temp = new static();
+ $temp->loadKey(['e' => $key['d'], 'n' => $key['n']]);
+ $key = $temp->key;
+ }
+ if ($key instanceof PublicKey) {
+ switch ($this->encryptionMode) {
+ case self::ENCRYPTION_PKCS1:
+ $len = ($key->getLength() - 88) >> 3;
+ break;
+ case self::ENCRYPTION_NONE:
+ $len = $key->getLength() >> 3;
+ break;
+ //case self::ENCRYPTION_OAEP:
+ default:
+ $len = ($key->getLength() - 2 * $key->getHash()->getLength() - 16) >> 3;
+ }
+ $plaintext = str_split($plaintext, $len);
+ $ciphertext = '';
+ foreach ($plaintext as $m) {
+ $ciphertext.= $key->encrypt($m);
+ }
+ return $ciphertext;
+ }
+
+ return false;
+ }
+
+ /**
+ * Decryption
+ *
+ * @see self::encrypt()
+ * @access public
+ * @param string $plaintext
+ * @return string
+ */
+ public function decrypt($ciphertext)
+ {
+ if (!$this->key instanceof RSA2) {
+ return false;
+ }
+ $key = $this->key;
+ if ($key instanceof PublicKey) {
+ $key = $key->asPrivateKey();
+ }
+ if ($key instanceof PrivateKey) {
+ $len = $key->getLength() >> 3;
+ $ciphertext = str_split($ciphertext, $len);
+ $ciphertext[count($ciphertext) - 1] = str_pad($ciphertext[count($ciphertext) - 1], $len, chr(0), STR_PAD_LEFT);
+
+ $plaintext = '';
+ foreach ($ciphertext as $c) {
+ try {
+ $plaintext.= $key->decrypt($c);
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+ return $plaintext;
+ }
+
+ return false;
+ }
+
+ /**
+ * Create a signature
+ *
+ * @see self::verify()
+ * @access public
+ * @param string $message
+ * @return string
+ */
+ public function sign($message)
+ {
+ if ($this->key instanceof PrivateKey) {
+ return $this->key->sign($message);
+ }
+
+ return false;
+ }
+
+ /**
+ * Verifies a signature
+ *
+ * @see self::sign()
+ * @access public
+ * @param string $message
+ * @param string $signature
+ * @return bool
+ */
+ public function verify($message, $signature)
+ {
+ if ($this->key instanceof PublicKey) {
+ return $this->key->verify($message, $signature);
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns a public key object
+ *
+ * @access public
+ * @return AsymmetricKey|false
+ */
+ public function getKeyObject()
+ {
+ return $this->key;
+ }
+} \ No newline at end of file
diff --git a/vendor/phpseclib/phpseclib2_compat/src/Crypt/Random.php b/vendor/phpseclib/phpseclib2_compat/src/Crypt/Random.php
new file mode 100644
index 000000000..49a281b3d
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/src/Crypt/Random.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * Random Number Generator
+ *
+ * PHP version 5
+ *
+ * Here's a short example of how to use this library:
+ * <code>
+ * <?php
+ * include 'vendor/autoload.php';
+ *
+ * echo bin2hex(\phpseclib\Crypt\Random::string(8));
+ * ?>
+ * </code>
+ *
+ * @category Crypt
+ * @package Random
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2007 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Crypt;
+
+/**
+ * Pure-PHP Random Number Generator
+ *
+ * @package Random
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class Random extends \phpseclib3\Crypt\Random
+{
+} \ No newline at end of file
diff --git a/vendor/phpseclib/phpseclib2_compat/src/Crypt/Rijndael.php b/vendor/phpseclib/phpseclib2_compat/src/Crypt/Rijndael.php
new file mode 100644
index 000000000..f3b9a7df8
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/src/Crypt/Rijndael.php
@@ -0,0 +1,137 @@
+<?php
+
+/**
+ * Pure-PHP implementation of Rijndael.
+ *
+ * Uses mcrypt, if available/possible, and an internal implementation, otherwise.
+ *
+ * PHP version 5
+ *
+ * If {@link self::setBlockLength() setBlockLength()} isn't called, it'll be assumed to be 128 bits. If
+ * {@link self::setKeyLength() setKeyLength()} isn't called, it'll be calculated from
+ * {@link self::setKey() setKey()}. ie. if the key is 128-bits, the key length will be 128-bits. If it's
+ * 136-bits it'll be null-padded to 192-bits and 192 bits will be the key length until
+ * {@link self::setKey() setKey()} is called, again, at which point, it'll be recalculated.
+ *
+ * Not all Rijndael implementations may support 160-bits or 224-bits as the block length / key length. mcrypt, for example,
+ * does not. AES, itself, only supports block lengths of 128 and key lengths of 128, 192, and 256.
+ * {@link http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=10 Rijndael-ammended.pdf#page=10} defines the
+ * algorithm for block lengths of 192 and 256 but not for block lengths / key lengths of 160 and 224. Indeed, 160 and 224
+ * are first defined as valid key / block lengths in
+ * {@link http://csrc.nist.gov/archive/aes/rijndael/Rijndael-ammended.pdf#page=44 Rijndael-ammended.pdf#page=44}:
+ * Extensions: Other block and Cipher Key lengths.
+ * Note: Use of 160/224-bit Keys must be explicitly set by setKeyLength(160) respectively setKeyLength(224).
+ *
+ * {@internal The variable names are the same as those in
+ * {@link http://www.csrc.nist.gov/publications/fips/fips197/fips-197.pdf#page=10 fips-197.pdf#page=10}.}}
+ *
+ * Here's a short example of how to use this library:
+ * <code>
+ * <?php
+ * include 'vendor/autoload.php';
+ *
+ * $rijndael = new \phpseclib\Crypt\Rijndael();
+ *
+ * $rijndael->setKey('abcdefghijklmnop');
+ *
+ * $size = 10 * 1024;
+ * $plaintext = '';
+ * for ($i = 0; $i < $size; $i++) {
+ * $plaintext.= 'a';
+ * }
+ *
+ * echo $rijndael->decrypt($rijndael->encrypt($plaintext));
+ * ?>
+ * </code>
+ *
+ * @category Crypt
+ * @package Rijndael
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2008 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Crypt;
+
+/**
+ * Pure-PHP implementation of Rijndael.
+ *
+ * @package Rijndael
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class Rijndael extends Base
+{
+ /**
+ * Sets the block length
+ *
+ * Valid block lengths are 128, 160, 192, 224, and 256. If the length is less than 128, it will be rounded up to
+ * 128. If the length is greater than 128 and invalid, it will be rounded down to the closest valid amount.
+ *
+ * @access public
+ * @param int $length
+ */
+ public function setBlockLength($length)
+ {
+ $length >>= 5;
+ if ($length > 8) {
+ $length = 8;
+ } elseif ($length < 4) {
+ $length = 4;
+ }
+ $this->cipher->setBlockLength($length);
+ }
+
+ /**
+ * Turns key lengths, be they valid or invalid, to valid key lengths
+ *
+ * @param int $length
+ * @access private
+ * @return int
+ */
+ protected function calculateNewKeyLength($length)
+ {
+ switch (true) {
+ case $length <= 128:
+ return 128;
+ case $length <= 160:
+ return 160;
+ case $length <= 192:
+ return 192;
+ case $length <= 224:
+ return 224;
+ default:
+ return 256;
+ }
+ }
+
+ /**
+ * Sets the password.
+ *
+ * Depending on what $method is set to, setPassword()'s (optional) parameters are as follows:
+ * {@link http://en.wikipedia.org/wiki/PBKDF2 pbkdf2} or pbkdf1:
+ * $hash, $salt, $count, $dkLen
+ *
+ * Where $hash (default = sha1) currently supports the following hashes: see: Crypt/Hash.php
+ *
+ * @see Crypt/Hash.php
+ * @param string $password
+ * @param string $method
+ * @return bool
+ * @access public
+ * @internal Could, but not must, extend by the child Crypt_* class
+ */
+ public function setPassword($password, $method = 'pbkdf2')
+ {
+ $this->cipher->setKeyLength($this->key_length);
+ $args = func_get_args();
+ if (in_array($method, ['pbkdf1', 'pbkdf2']) && !isset($args[3])) {
+ $args[1] = $method;
+ $args[2] = isset($args[2]) ? $args[2] : 'sha1';
+ $args[3] = 'phpseclib';
+ }
+ $this->password = $args;
+ $this->cipher->setPassword(...$args);
+ }
+} \ No newline at end of file
diff --git a/vendor/phpseclib/phpseclib2_compat/src/Crypt/TripleDES.php b/vendor/phpseclib/phpseclib2_compat/src/Crypt/TripleDES.php
new file mode 100644
index 000000000..d66dc74d6
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/src/Crypt/TripleDES.php
@@ -0,0 +1,116 @@
+<?php
+
+/**
+ * Pure-PHP implementation of Triple DES.
+ *
+ * Uses mcrypt, if available, and an internal implementation, otherwise. Operates in the EDE3 mode (encrypt-decrypt-encrypt).
+ *
+ * PHP version 5
+ *
+ * Here's a short example of how to use this library:
+ * <code>
+ * <?php
+ * include 'vendor/autoload.php';
+ *
+ * $des = new \phpseclib\Crypt\TripleDES();
+ *
+ * $des->setKey('abcdefghijklmnopqrstuvwx');
+ *
+ * $size = 10 * 1024;
+ * $plaintext = '';
+ * for ($i = 0; $i < $size; $i++) {
+ * $plaintext.= 'a';
+ * }
+ *
+ * echo $des->decrypt($des->encrypt($plaintext));
+ * ?>
+ * </code>
+ *
+ * @category Crypt
+ * @package TripleDES
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2007 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Crypt;
+
+/**
+ * Pure-PHP implementation of Triple DES.
+ *
+ * @package TripleDES
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class TripleDES extends Base
+{
+ /**
+ * Encrypt / decrypt using inner chaining
+ *
+ * Inner chaining is used by SSH-1 and is generally considered to be less secure then outer chaining (self::MODE_CBC3).
+ */
+ const MODE_3CBC = -2;
+
+ /**
+ * Encrypt / decrypt using outer chaining
+ *
+ * Outer chaining is used by SSH-2 and when the mode is set to \phpseclib\Crypt\Base::MODE_CBC.
+ */
+ const MODE_CBC3 = Base::MODE_CBC;
+
+ /**
+ * Default Constructor.
+ *
+ * Determines whether or not the mcrypt extension should be used.
+ *
+ * $mode could be:
+ *
+ * - \phpseclib\Crypt\Base::MODE_ECB
+ *
+ * - \phpseclib\Crypt\Base::MODE_CBC
+ *
+ * - \phpseclib\Crypt\Base::MODE_CTR
+ *
+ * - \phpseclib\Crypt\Base::MODE_CFB
+ *
+ * - \phpseclib\Crypt\Base::MODE_OFB
+ *
+ * - \phpseclib\Crypt\TripleDES::MODE_3CBC
+ *
+ * If not explicitly set, \phpseclib\Crypt\Base::MODE_CBC will be used.
+ *
+ * @see \phpseclib\Crypt\DES::__construct()
+ * @see \phpseclib\Crypt\Base::__construct()
+ * @param int $mode
+ * @access public
+ */
+ public function __construct($mode = self::MODE_CBC)
+ {
+ if ($mode == self::MODE_3CBC) {
+ $this->cipher = new \phpseclib3\Crypt\TripleDES('3cbc');
+ $this->key_length = $this->cipher->getKeyLength();
+ return;
+ }
+ parent::__construct($mode);
+ }
+
+ /**
+ * Turns key lengths, be they valid or invalid, to valid key lengths
+ *
+ * @param int $length
+ * @access private
+ * @return int
+ */
+ protected function calculateNewKeyLength($length)
+ {
+ switch (true) {
+ case $length <= 64:
+ return 64;
+ case $length <= 128:
+ return 128;
+ default:
+ return 192;
+ }
+ }
+} \ No newline at end of file
diff --git a/vendor/phpseclib/phpseclib2_compat/src/Crypt/Twofish.php b/vendor/phpseclib/phpseclib2_compat/src/Crypt/Twofish.php
new file mode 100644
index 000000000..f57c54c9d
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/src/Crypt/Twofish.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * Pure-PHP implementation of Twofish.
+ *
+ * Uses mcrypt, if available, and an internal implementation, otherwise.
+ *
+ * PHP version 5
+ *
+ * Useful resources are as follows:
+ *
+ * - {@link http://en.wikipedia.org/wiki/Twofish Wikipedia description of Twofish}
+ *
+ * Here's a short example of how to use this library:
+ * <code>
+ * <?php
+ * include 'vendor/autoload.php';
+ *
+ * $twofish = new \phpseclib\Crypt\Twofish();
+ *
+ * $twofish->setKey('12345678901234567890123456789012');
+ *
+ * $plaintext = str_repeat('a', 1024);
+ *
+ * echo $twofish->decrypt($twofish->encrypt($plaintext));
+ * ?>
+ * </code>
+ *
+ * @category Crypt
+ * @package Twofish
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @author Hans-Juergen Petrich <petrich@tronic-media.com>
+ * @copyright 2007 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Crypt;
+
+/**
+ * Pure-PHP implementation of Twofish.
+ *
+ * @package Twofish
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class Twofish extends Base
+{
+ /**
+ * Turns key lengths, be they valid or invalid, to valid key lengths
+ *
+ * @param int $length
+ * @access private
+ * @return int
+ */
+ protected function calculateNewKeyLength($length)
+ {
+ switch (true) {
+ case $length <= 128:
+ return 128;
+ case $length <= 192:
+ return 192;
+ default:
+ return 256;
+ }
+ }
+} \ No newline at end of file
diff --git a/vendor/phpseclib/phpseclib2_compat/src/File/ANSI.php b/vendor/phpseclib/phpseclib2_compat/src/File/ANSI.php
new file mode 100644
index 000000000..73bbb8a24
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/src/File/ANSI.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * Pure-PHP ANSI Decoder
+ *
+ * PHP version 5
+ *
+ * If you call read() in \phpseclib\Net\SSH2 you may get {@link http://en.wikipedia.org/wiki/ANSI_escape_code ANSI escape codes} back.
+ * They'd look like chr(0x1B) . '[00m' or whatever (0x1B = ESC). They tell a
+ * {@link http://en.wikipedia.org/wiki/Terminal_emulator terminal emulator} how to format the characters, what
+ * color to display them in, etc. \phpseclib\File\ANSI is a {@link http://en.wikipedia.org/wiki/VT100 VT100} terminal emulator.
+ *
+ * @category File
+ * @package ANSI
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2012 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\File;
+
+/**
+ * Pure-PHP ANSI Decoder
+ *
+ * @package ANSI
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class ANSI extends \phpseclib3\File\ANSI
+{
+} \ No newline at end of file
diff --git a/vendor/phpseclib/phpseclib2_compat/src/File/ASN1.php b/vendor/phpseclib/phpseclib2_compat/src/File/ASN1.php
new file mode 100644
index 000000000..ceecf7c0e
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/src/File/ASN1.php
@@ -0,0 +1,95 @@
+<?php
+
+/**
+ * Pure-PHP ASN.1 Parser
+ *
+ * PHP version 5
+ *
+ * ASN.1 provides the semantics for data encoded using various schemes. The most commonly
+ * utilized scheme is DER or the "Distinguished Encoding Rules". PEM's are base64 encoded
+ * DER blobs.
+ *
+ * \phpseclib\File\ASN1 decodes and encodes DER formatted messages and places them in a semantic context.
+ *
+ * Uses the 1988 ASN.1 syntax.
+ *
+ * @category File
+ * @package ASN1
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2012 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\File;
+
+/**
+ * Pure-PHP ASN.1 Parser
+ *
+ * @package ASN1
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class ASN1 extends \phpseclib3\File\ASN1
+{
+ /**
+ * Parse BER-encoding
+ *
+ * Serves a similar purpose to openssl's asn1parse
+ *
+ * @param string $encoded
+ * @return array
+ * @access public
+ */
+ public static function decodeBER($encoded)
+ {
+ $decoded = parent::decodeBER($encoded);
+ if ($decoded === null) {
+ return [false];
+ }
+ return $decoded;
+ }
+
+ /**
+ * BER-decode the OID
+ *
+ * Called by _decode_ber()
+ *
+ * @access private
+ * @param string $content
+ * @return string
+ */
+ public function _decodeOID($content)
+ {
+ return $this->decodeOID($content);
+ }
+
+ /**
+ * DER-encode the length
+ *
+ * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4. See
+ * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
+ *
+ * @access private
+ * @param int $length
+ * @return string
+ */
+ public function _encodeLength($length)
+ {
+ return $this->encodeLength($length);
+ }
+
+ /**
+ * DER-encode the OID
+ *
+ * Called by _encode_der()
+ *
+ * @access private
+ * @param string $content
+ * @return string
+ */
+ public function _encodeOID($source)
+ {
+ return $this->encodeOID($source);
+ }
+} \ No newline at end of file
diff --git a/vendor/phpseclib/phpseclib2_compat/src/File/ASN1/Element.php b/vendor/phpseclib/phpseclib2_compat/src/File/ASN1/Element.php
new file mode 100644
index 000000000..fe1871eb3
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/src/File/ASN1/Element.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * Pure-PHP ASN.1 Parser
+ *
+ * PHP version 5
+ *
+ * @category File
+ * @package ASN1
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2012 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\File\ASN1;
+
+/**
+ * ASN.1 Element
+ *
+ * Bypass normal encoding rules in phpseclib\File\ASN1::encodeDER()
+ *
+ * @package ASN1
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class Element extends \phpseclib3\File\ASN1\Element
+{
+} \ No newline at end of file
diff --git a/vendor/phpseclib/phpseclib2_compat/src/File/X509.php b/vendor/phpseclib/phpseclib2_compat/src/File/X509.php
new file mode 100644
index 000000000..e285ab0b9
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/src/File/X509.php
@@ -0,0 +1,407 @@
+<?php
+
+/**
+ * Pure-PHP X.509 Parser
+ *
+ * PHP version 5
+ *
+ * Encode and decode X.509 certificates.
+ *
+ * The extensions are from {@link http://tools.ietf.org/html/rfc5280 RFC5280} and
+ * {@link http://web.archive.org/web/19961027104704/http://www3.netscape.com/eng/security/cert-exts.html Netscape Certificate Extensions}.
+ *
+ * Note that loading an X.509 certificate and resaving it may invalidate the signature. The reason being that the signature is based on a
+ * portion of the certificate that contains optional parameters with default values. ie. if the parameter isn't there the default value is
+ * used. Problem is, if the parameter is there and it just so happens to have the default value there are two ways that that parameter can
+ * be encoded. It can be encoded explicitly or left out all together. This would effect the signature value and thus may invalidate the
+ * the certificate all together unless the certificate is re-signed.
+ *
+ * @category File
+ * @package X509
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2012 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\File;
+
+use phpseclib\Crypt\RSA;
+use phpseclib3\File\ASN1\Element as NewElement;
+use phpseclib\File\ASN1\Element as OldElement;
+
+/**
+ * Pure-PHP X.509 Parser
+ *
+ * @package X509
+ * @method bool|mixed[] loadX509(string $cert, int $mode = X509::FORMAT_AUTO_DETECT)
+ * @method string|false saveX509(mixed[] $cert, int $format = X509::FORMAT_PEM)
+ * @method bool loadCA(string $cert)
+ * @method bool validateURL(string $url)
+ * @method bool validateDate(\DateTimeInterface|string $date = null)
+ * @method ?bool validateSignature(bool $caonly = true)
+ * @method static void setRecurLimit(int $count)
+ * @method static void disableURLFetch()
+ * @method static void enableURLFetch()
+ * @method static string decodeIP(string $ip)
+ * @method static array{bool|string, bool|string} decodeNameConstraintIP(string $ip)
+ * @method static string encodeIP(string|array{string, string} $ip)
+ * @method bool setDNProp(string $propName, mixed $propValue, string $type = 'utf8String')
+ * @method void removeDNProp(string $propName)
+ * @method mixed[] getDNProp(string $propName, mixed[] $dn, bool $withType = false)
+ * @method bool setDN(mixed $dn, bool $merge = false, string $type = 'utf8String')
+ * @method mixed getDN(int $format = X509::DN_ARRAY, mixed[] $dn = null)
+ * @method mixed getIssuerDN(int $format = X509::DN_ARRAY)
+ * @method mixed getSubjectDN(int $format = X509::DN_ARRAY)
+ * @method mixed getIssuerDNProp(string $propName, bool $withType = false)
+ * @method mixed getSubjectDNProp(string $propName, bool $withType = false)
+ * @method mixed[] getChain()
+ * @method bool|mixed[] getCurrentCert()
+ * @method void setChallenge(string $challenge)
+ * @method PublicKey|false getPublicKey()
+ * @method bool|mixed[] loadCSR(string $csr, int $mode = X509::FORMAT_AUTO_DETECT)
+ * @method string|false saveCSR(array $csr, int $format = X509::FORMAT_PEM)
+ * @method bool|mixed[] loadSPKAC(string $spkac)
+ * @method string|false saveSPKAC(array $spkac, int $format = X509::FORMAT_PEM)
+ * @method bool|mixed[] loadCRL(string $crl, int $mode = X509::FORMAT_AUTO_DETECT)
+ * @method string|false saveCRL(array $crl, int $format = X509::FORMAT_PEM)
+ * @method bool|mixed[] sign(X509 $issuer, X509 $subject)
+ * @method bool|mixed[] signCSR()
+ * @method bool|mixed[] signSPKAC()
+ * @method bool|mixed[] signCRL(X509 $issuer, X509 $crl)
+ * @method void setStartDate(\DateTimeInterface|string $date)
+ * @method void setEndDate(\DateTimeInterface|string $date)
+ * @method void setSerialNumber(string $serial, int $base = -256)
+ * @method void makeCA()
+ * @method bool removeExtension(string $id)
+ * @method mixed getExtension(string $id, mixed[] $cert = null, string $path = null)
+ * @method mixed[] getExtension(mixed[] $cert = null, string $path = null)
+ * @method bool setExtension(mixed[] $cert = null, mixed $value, string $path = null)
+ * @method bool removeAttribute(string $id, int $disposition = X509::ATTR_ALL)
+ * @method mixed getAttribute(string $id, int $disposition = X509::ATTR_ALL, array $csr = null)
+ * @method mixed[] getAttributes(mixed[] $csr = null)
+ * @method void setKeyIdentifier(string $value)
+ * @method mixed computeKeyIdentifier(mixed $key = null, int $method = 1)
+ * @method void setDomain(string ...$domains)
+ * @method void setIPAddress(mixed ...$ipAddresses)
+ * @method bool revoke(string $serial, string $date = null)
+ * @method bool unrevoke(string $serial)
+ * @method mixed getRevoked(string $serial)
+ * @method mixed[] listRevoked(mixed[] $crl = null)
+ * @method bool removeRevokedCertificateExtension(string $serial, string $id)
+ * @method mixed getRevokedCertificateExtension(string $serial, string $id, mixed[] $crl = null)
+ * @method bool|mixed[] getRevokedCertificateExtensions(string $serial, mixed[] $crl = null)
+ * @method bool setRevokedCertificateExtension(string $serial, string $id, $value, bool $critical = false, bool $replace = true)
+ * @method static void registerExtension(string $id, mixed[] $mapping)
+ * @method static ?mixed[] getRegisteredExtension(string $id)
+ * @method static void setExtensionValue(string $id, $value, bool $critical = false, bool $replace = false)
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class X509
+{
+ /**
+ * Flag to only accept signatures signed by certificate authorities
+ *
+ * Not really used anymore but retained all the same to suppress E_NOTICEs from old installs
+ *
+ * @access public
+ */
+ const VALIDATE_SIGNATURE_BY_CA = 1;
+
+ /**#@+
+ * @access public
+ * @see \phpseclib3\File\X509::getDN()
+ */
+ /**
+ * Return internal array representation
+ */
+ const DN_ARRAY = 0;
+ /**
+ * Return string
+ */
+ const DN_STRING = 1;
+ /**
+ * Return ASN.1 name string
+ */
+ const DN_ASN1 = 2;
+ /**
+ * Return OpenSSL compatible array
+ */
+ const DN_OPENSSL = 3;
+ /**
+ * Return canonical ASN.1 RDNs string
+ */
+ const DN_CANON = 4;
+ /**
+ * Return name hash for file indexing
+ */
+ const DN_HASH = 5;
+ /**#@-*/
+
+ /**#@+
+ * @access public
+ * @see \phpseclib3\File\X509::saveX509()
+ * @see \phpseclib3\File\X509::saveCSR()
+ * @see \phpseclib3\File\X509::saveCRL()
+ */
+ /**
+ * Save as PEM
+ *
+ * ie. a base64-encoded PEM with a header and a footer
+ */
+ const FORMAT_PEM = 0;
+ /**
+ * Save as DER
+ */
+ const FORMAT_DER = 1;
+ /**
+ * Save as a SPKAC
+ *
+ * Only works on CSRs. Not currently supported.
+ */
+ const FORMAT_SPKAC = 2;
+ /**
+ * Auto-detect the format
+ *
+ * Used only by the load*() functions
+ */
+ const FORMAT_AUTO_DETECT = 3;
+ /**#@-*/
+
+ /**
+ * Attribute value disposition.
+ * If disposition is >= 0, this is the index of the target value.
+ */
+ const ATTR_ALL = -1; // All attribute values (array).
+ const ATTR_APPEND = -2; // Add a value.
+ const ATTR_REPLACE = -3; // Clear first, then add a value.
+
+ /**
+ * The X509 object
+ *
+ * @var \phpseclib3\File\X509
+ * @access private
+ */
+ private $x509;
+
+ /**
+ * Default Constructor.
+ *
+ * @return \phpseclib\File\X509
+ * @access public
+ */
+ public function __construct()
+ {
+ // we don't extend phpseclib3\File\X509 because the setPublicKey() and setPrivateKey() methods
+ // have different method signatures
+ $this->x509 = new \phpseclib3\File\X509();
+ }
+
+ /**
+ * __call() magic method
+ *
+ * @access public
+ */
+ public function __call($name, $args)
+ {
+ foreach ($args as &$arg) {
+ if ($arg instanceof \phpseclib\File\X509) {
+ $arg = $arg->x509;
+ }
+ }
+
+ switch ($name) {
+ case 'loadX509':
+ case 'saveX509':
+ case 'sign':
+ $part1 = 'tbsCertificate';
+ $part2 = 'extensions';
+ break;
+ case 'loadCRL':
+ case 'saveCRL':
+ case 'signCRL':
+ $part1 = 'tbsCertList';
+ $part2 = 'crlExtensions';
+ break;
+ case 'loadCSR':
+ case 'saveCSR':
+ case 'signCSR':
+ $part1 = 'certificationRequestInfo';
+ $part2 = 'attributes';
+ }
+
+ if (isset($args[0])) {
+ switch ($name) {
+ case 'saveX509':
+ case 'saveCRL':
+ case 'saveCSR':
+ if (isset($args[0][$part1][$part2])) {
+ $arr = &$args[0][$part1][$part2];
+ if ($part2 == 'attributes') {
+ foreach ($arr as &$attr) {
+ if (isset($attr['type']) && $attr['type'] == 'pkcs-9-at-extensionRequest') {
+ $arr = $attr['value'][0];
+ break;
+ }
+ }
+ }
+ foreach ($arr as &$extension) {
+ if ($extension instanceof NewElement || !is_array($extension)) {
+ continue;
+ }
+ if (is_string($extension['extnValue'])) {
+ $extension['extnValue'] = base64_decode($extension['extnValue']);
+ }
+ }
+ }
+
+ if (isset($args[0]['signature'])) {
+ $args[0]['signature'] = base64_decode($args[0]['signature']);
+ }
+ }
+ }
+
+ $result = $this->x509->$name(...$args);
+ if ($result instanceof \phpseclib3\File\X509) {
+ $temp = new static;
+ $temp->x509 = $result;
+ return $temp;
+ }
+
+ if (!is_array($result)) {
+ return $result;
+ }
+
+ $result = self::replaceNewElements($result);
+
+ if (!isset($part1)) {
+ return $result;
+ }
+
+ if (isset($result[$part1][$part2])) {
+ $arr = &$result[$part1][$part2];
+ if ($part2 == 'attributes') {
+ foreach ($arr as &$attr) {
+ if (isset($attr['type']) && $attr['type'] == 'pkcs-9-at-extensionRequest') {
+ $arr = $attr['value'][0];
+ break;
+ }
+ }
+ }
+ foreach ($arr as &$extension) {
+ if ($extension instanceof NewElement || !is_array($extension)) {
+ continue;
+ }
+ if (is_string($extension['extnValue'])) {
+ $extension['extnValue'] = base64_encode($extension['extnValue']);
+ }
+ }
+ }
+
+ if (isset($result['signature'])) {
+ $result['signature'] = base64_encode($result['signature']);
+ }
+
+ return $result;
+ }
+
+ /**
+ * __callStatic() magic method
+ *
+ * @access public
+ */
+ public static function __callStatic($name, $args)
+ {
+ return \phpseclib3\File\X509::$name(...$args);
+ }
+
+ /**
+ * Set public key
+ *
+ * Key needs to be a \phpseclib\Crypt\RSA object
+ *
+ * @param object $key
+ * @access public
+ * @return bool
+ */
+ public function setPublicKey($key)
+ {
+ if (!$key instanceof RSA) {
+ return;
+ }
+ $key = $key->getKeyObject();
+ if ($key instanceof \phpseclib3\Crypt\Common\PublicKey) {
+ if ($key instanceof \phpseclib3\Crypt\RSA) {
+ $key = $key->withPadding(\phpseclib3\Crypt\RSA::SIGNATURE_PKCS1);
+ }
+ $this->x509->setPublicKey($key);
+ }
+ }
+
+ /**
+ * Set private key
+ *
+ * Key needs to be a \phpseclib\Crypt\RSA object
+ *
+ * @param object $key
+ * @access public
+ */
+ public function setPrivateKey($key)
+ {
+ if (!$key instanceof RSA) {
+ return;
+ }
+ $key = $key->getKeyObject();
+ if ($key instanceof \phpseclib3\Crypt\Common\PrivateKey) {
+ if ($key instanceof \phpseclib3\Crypt\RSA) {
+ $key = $key->withPadding(\phpseclib3\Crypt\RSA::SIGNATURE_PKCS1);
+ }
+ $this->x509->setPrivateKey($key);
+ }
+ }
+
+ /**
+ * Returns the OID corresponding to a name
+ *
+ * What's returned in the associative array returned by loadX509() (or load*()) is either a name or an OID if
+ * no OID to name mapping is available. The problem with this is that what may be an unmapped OID in one version
+ * of phpseclib may not be unmapped in the next version, so apps that are looking at this OID may not be able
+ * to work from version to version.
+ *
+ * This method will return the OID if a name is passed to it and if no mapping is avialable it'll assume that
+ * what's being passed to it already is an OID and return that instead. A few examples.
+ *
+ * getOID('2.16.840.1.101.3.4.2.1') == '2.16.840.1.101.3.4.2.1'
+ * getOID('id-sha256') == '2.16.840.1.101.3.4.2.1'
+ * getOID('zzz') == 'zzz'
+ *
+ * @access public
+ * @return string
+ */
+ public function getOID($name)
+ {
+ return \phpseclib3\File\ASN1::getOID($name);
+ }
+
+ /**
+ * Replaces \phpseclib3\File\ASN1\Element with \phpseclib\File\ASN1\Element
+ *
+ * @return array
+ */
+ private static function replaceNewElements($el)
+ {
+ switch (true) {
+ case $el instanceof NewElement:
+ return new OldElement($el->element);
+ case !is_array($el):
+ return $el;
+ }
+
+ foreach ($el as &$val) {
+ $val = self::replaceNewElements($val);
+ }
+
+ return $el;
+ }
+} \ No newline at end of file
diff --git a/vendor/phpseclib/phpseclib2_compat/src/Math/BigInteger.php b/vendor/phpseclib/phpseclib2_compat/src/Math/BigInteger.php
new file mode 100644
index 000000000..905d8fe65
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/src/Math/BigInteger.php
@@ -0,0 +1,308 @@
+<?php
+
+/**
+ * Pure-PHP arbitrary precision integer arithmetic library.
+ *
+ * Supports base-2, base-10, base-16, and base-256 numbers. Uses the GMP or BCMath extensions, if available,
+ * and an internal implementation, otherwise.
+ *
+ * PHP version 5
+ *
+ * {@internal (all DocBlock comments regarding implementation - such as the one that follows - refer to the
+ * {@link self::MODE_INTERNAL self::MODE_INTERNAL} mode)
+ *
+ * BigInteger uses base-2**26 to perform operations such as multiplication and division and
+ * base-2**52 (ie. two base 2**26 digits) to perform addition and subtraction. Because the largest possible
+ * value when multiplying two base-2**26 numbers together is a base-2**52 number, double precision floating
+ * point numbers - numbers that should be supported on most hardware and whose significand is 53 bits - are
+ * used. As a consequence, bitwise operators such as >> and << cannot be used, nor can the modulo operator %,
+ * which only supports integers. Although this fact will slow this library down, the fact that such a high
+ * base is being used should more than compensate.
+ *
+ * Numbers are stored in {@link http://en.wikipedia.org/wiki/Endianness little endian} format. ie.
+ * (new \phpseclib\Math\BigInteger(pow(2, 26)))->value = array(0, 1)
+ *
+ * Useful resources are as follows:
+ *
+ * - {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf Handbook of Applied Cryptography (HAC)}
+ * - {@link http://math.libtomcrypt.com/files/tommath.pdf Multi-Precision Math (MPM)}
+ * - Java's BigInteger classes. See /j2se/src/share/classes/java/math in jdk-1_5_0-src-jrl.zip
+ *
+ * Here's an example of how to use this library:
+ * <code>
+ * <?php
+ * $a = new \phpseclib\Math\BigInteger(2);
+ * $b = new \phpseclib\Math\BigInteger(3);
+ *
+ * $c = $a->add($b);
+ *
+ * echo $c->toString(); // outputs 5
+ * ?>
+ * </code>
+ *
+ * @category Math
+ * @package BigInteger
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2006 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ */
+
+namespace phpseclib\Math;
+
+use phpseclib3\Math\BigInteger as BigInteger2;
+
+/**
+ * Pure-PHP arbitrary precision integer arithmetic library. Supports base-2, base-10, base-16, and base-256
+ * numbers.
+ *
+ * @package BigInteger
+ * @method string toString()
+ * @method string toBytes()
+ * @method string toHex()
+ * @method string toBits()
+ * @method BigInteger add(BigInteger $y)
+ * @method BigInteger subtract(BigInteger $y)
+ * @method BigInteger multiply(BigInteger $x)
+ * @method array{BigInteger, BigInteger} divide(BigInteger $x)
+ * @method BigInteger modInverse(BigInteger $n)
+ * @method {'gcd': BigInteger, 'x': BigInteger, 'y': BigInteger} extendedGCD(BigInteger $n)
+ * @method BigInteger gcd(BigInteger $n)
+ * @method BigInteger abs()
+ * @method void setPrecision(int $bits)
+ * @method int|bool getPrecision()
+ * @method BigInteger powMod(BigInteger $e, BigInteger $n)
+ * @method BigInteger modPow(BigInteger $e, BigInteger $n)
+ * @method int compare(BigInteger $y)
+ * @method bool equals(BigInteger $x)
+ * @method BigInteger bitwise_not()
+ * @method BigInteger bitwise_and(BigInteger $x)
+ * @method BigInteger bitwise_or(BigInteger $x)
+ * @method BigInteger bitwise_rightShift($shift)
+ * @method BigInteger bitwise_leftShift($shift)
+ * @method BigInteger bitwise_leftRotate($shift)
+ * @method BigInteger bitwise_rightRotate($shift)
+ * @method {'min': BigInteger, 'max': BigInteger} minMaxBits($bits)
+ * @method int getLength()
+ * @method int getLengthInBytes()
+ * @method bool isPrime(int|bool $t = false)
+ * @method BigInteger root(int $n = 2)
+ * @method BigInteger pow(BigInteger $n)
+ * @method static BigInteger min(BigInteger ...$nums)
+ * @method static BigInteger max(BigInteger ...$nums)
+ * @method bool between(BigInteger $min, BigInteger $max)
+ * @method bool isOdd()
+ * @method bool testBit(int $x)
+ * @method bool isNegative()
+ * @method BigInteger negate()
+ * @method callable createRecurringModuloFunction()
+ * @method BigInteger[] bitwise_split(int $split)
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class BigInteger
+{
+ /**#@+
+ * Array constants
+ *
+ * Rather than create a thousands and thousands of new BigInteger objects in repeated function calls to add() and
+ * multiply() or whatever, we'll just work directly on arrays, taking them in as parameters and returning them.
+ *
+ * @access private
+ */
+ /**
+ * $result[self::VALUE] contains the value.
+ */
+ const VALUE = 0;
+ /**
+ * $result[self::SIGN] contains the sign.
+ */
+ const SIGN = 1;
+ /**#@-*/
+
+ /**#@+
+ * @access private
+ * @see BigInteger::_montgomery()
+ * @see BigInteger::_barrett()
+ */
+ /**
+ * Cache constants
+ *
+ * $cache[self::VARIABLE] tells us whether or not the cached data is still valid.
+ */
+ const VARIABLE = 0;
+ /**
+ * $cache[self::DATA] contains the cached data.
+ */
+ const DATA = 1;
+ /**#@-*/
+
+ /**#@+
+ * Mode constants.
+ *
+ * @access private
+ * @see BigInteger::__construct()
+ */
+ /**
+ * To use the pure-PHP implementation
+ */
+ const MODE_INTERNAL = 1;
+ /**
+ * To use the BCMath library
+ *
+ * (if enabled; otherwise, the internal implementation will be used)
+ */
+ const MODE_BCMATH = 2;
+ /**
+ * To use the GMP library
+ *
+ * (if present; otherwise, either the BCMath or the internal implementation will be used)
+ */
+ const MODE_GMP = 3;
+ /**#@-*/
+
+ /**
+ * The BigInteger object
+ *
+ * @var \phpseclib3\Math\BigInteger
+ * @access private
+ */
+ private $bigint;
+
+ /**
+ * Converts base-2, base-10, base-16, and binary strings (base-256) to BigIntegers.
+ *
+ * If the second parameter - $base - is negative, then it will be assumed that the number's are encoded using
+ * two's compliment. The sole exception to this is -10, which is treated the same as 10 is.
+ *
+ * Here's an example:
+ * <code>
+ * <?php
+ * $a = new \phpseclib\Math\BigInteger('0x32', 16); // 50 in base-16
+ *
+ * echo $a->toString(); // outputs 50
+ * ?>
+ * </code>
+ *
+ * @param $x base-10 number or base-$base number if $base set.
+ * @param int $base
+ * @return \phpseclib\Math\BigInteger
+ * @access public
+ */
+ public function __construct($x = 0, $base = 10)
+ {
+ $this->bigint = new BigInteger2($x, $base);
+ }
+
+ /**
+ * __call() magic method
+ *
+ * @access public
+ */
+ public function __call($name, $args)
+ {
+ foreach ($args as &$arg) {
+ if ($arg instanceof BigInteger) {
+ $arg = $arg->bigint;
+ }
+ }
+ $result = $this->bigint->$name(...$args);
+ if (!$result instanceof BigInteger2) {
+ return $result;
+ }
+
+ $temp = new static;
+ $temp->bigint = $result;
+
+ return $temp;
+ }
+
+ /**
+ * __toString() magic method
+ *
+ * Will be called, automatically, if you're supporting just PHP5. If you're supporting PHP4, you'll need to call
+ * toString().
+ *
+ * @access public
+ * @internal Implemented per a suggestion by Techie-Michael - thanks!
+ */
+ public function __toString()
+ {
+ return $this->bigint->__toString();
+ }
+
+ /**
+ * __debugInfo() magic method
+ *
+ * Will be called, automatically, when print_r() or var_dump() are called
+ *
+ * @access public
+ */
+ public function __debugInfo()
+ {
+ return $this->bigint->__debugInfo();
+ }
+
+ /**
+ * Generate a random number
+ *
+ * Returns a random number between $min and $max where $min and $max
+ * can be defined using one of the two methods:
+ *
+ * $min->random($max)
+ * $max->random($min)
+ *
+ * @param \phpseclib\Math\BigInteger $arg1
+ * @param \phpseclib\Math\BigInteger $arg2
+ * @return \phpseclib\Math\BigInteger
+ * @access public
+ * @internal The API for creating random numbers used to be $a->random($min, $max), where $a was a BigInteger object.
+ * That method is still supported for BC purposes.
+ */
+ public function random($arg1, $arg2 = false)
+ {
+ $temp = new static;
+ $temp->bigint = BigInteger2::randomRange(
+ $arg1->bigint,
+ $arg2 instanceof BigInteger ? $arg2->bigint : $this->bigint
+ );
+ return $temp;
+ }
+
+ /**
+ * Generate a random prime number.
+ *
+ * If there's not a prime within the given range, false will be returned.
+ * If more than $timeout seconds have elapsed, give up and return false.
+ *
+ * @param \phpseclib\Math\BigInteger $arg1
+ * @param \phpseclib\Math\BigInteger $arg2
+ * @return Math_BigInteger|false
+ * @access public
+ * @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=15 HAC 4.44}.
+ */
+ public function randomPrime($arg1, $arg2 = false)
+ {
+ $temp = new static;
+ $temp->bigint = BigInteger2::randomRange(
+ $arg1->bigint,
+ $arg2 instanceof BigInteger ? $arg2->bigint : $this->bigint
+ );
+ return $temp;
+ }
+
+ /**
+ * Logical Exclusive-Or
+ *
+ * See https://github.com/phpseclib/phpseclib/issues/1245 for more context
+ *
+ * @param \phpseclib\Math\BigInteger $x
+ * @access public
+ * @return \phpseclib\Math\BigInteger
+ */
+ public function bitwise_xor($x)
+ {
+ $temp = new static;
+ $temp->bigint = $this->bigint->abs()->bitwise_xor($x->bigint->abs());
+ return $temp;
+ }
+} \ No newline at end of file
diff --git a/vendor/phpseclib/phpseclib2_compat/src/Net/SFTP.php b/vendor/phpseclib/phpseclib2_compat/src/Net/SFTP.php
new file mode 100644
index 000000000..fab1f7219
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/src/Net/SFTP.php
@@ -0,0 +1,276 @@
+<?php
+
+/**
+ * Pure-PHP implementation of SFTP.
+ *
+ * PHP version 5
+ *
+ * Currently only supports SFTPv2 and v3, which, according to wikipedia.org, "is the most widely used version,
+ * implemented by the popular OpenSSH SFTP server". If you want SFTPv4/5/6 support, provide me with access
+ * to an SFTPv4/5/6 server.
+ *
+ * The API for this library is modeled after the API from PHP's {@link http://php.net/book.ftp FTP extension}.
+ *
+ * Here's a short example of how to use this library:
+ * <code>
+ * <?php
+ * include 'vendor/autoload.php';
+ *
+ * $sftp = new \phpseclib\Net\SFTP('www.domain.tld');
+ * if (!$sftp->login('username', 'password')) {
+ * exit('Login Failed');
+ * }
+ *
+ * echo $sftp->pwd() . "\r\n";
+ * $sftp->put('filename.ext', 'hello, world!');
+ * print_r($sftp->nlist());
+ * ?>
+ * </code>
+ *
+ * @category Net
+ * @package SFTP
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2009 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Net;
+
+use phpseclib\Crypt\RSA;
+
+/**
+ * Pure-PHP implementation of SFTP.
+ *
+ * @package SFTP
+ * @method void disableStatCache()
+ * @method void enableStatCache()
+ * @method void clearStatCache()
+ * @method void enablePathCanonicalization()
+ * @method void disablePathCanonicalization()
+ * @method void enableArbitraryLengthPackets()
+ * @method void disableArbitraryLengthPackets()
+ * @method string|false pwd()
+ * @method string|false realpath(string $path)
+ * @method bool chdir(string $dir)
+ * @method string[]|false nlist(string $dir = '.', bool $recursive = false)
+ * @method mixed[]|false rawlist(string $dir = '.', bool $recursive = false)
+ * @method void setListOrder(mixed ...$args)
+ * @method mixed[]|false stat(string $filename)
+ * @method mixed[]|false lstat(string $filename)
+ * @method bool truncate(string $filename, int $new_size)
+ * @method bool touch(string $filename, int $time = null, int $atime = null)
+ * @method bool chown(string $filename, int|string $uid, bool $recursive = false)
+ * @method bool chgrp(string $filename, int|string $gid, bool $recursive = false)
+ * @method bool chmod(int $mode, string $filename, bool $recursive = false)
+ * @method mixed readlink(string $link)
+ * @method bool symlink(string $target, string $link)
+ * @method bool mkdir(string $dir, int $mode = -1, bool $recursive = false)
+ * @method bool rmdir(string $dir)
+ * @method bool put(string $remote_file, string $data, int $mode = SFTP::SOURCE_STRING, int $start = -1, int $local_start = -1, ?callable $progressCallback = null)
+ * @method string|bool get(string $remote_file, string $local_file = false, int $offset = 0, int $length = -1, ?callable $progressCallback = null)
+ * @method bool delete(string $path, bool $recursive = true)
+ * @method bool file_exists(string $path)
+ * @method bool is_dir(string $path)
+ * @method bool is_file(string $path)
+ * @method bool is_link(string $path)
+ * @method bool is_readable(string $path)
+ * @method bool is_writable(string $path)
+ * @method bool is_writeable(string $path)
+ * @method int|float|false fileatime(string $path)
+ * @method int|float|false filemtime(string $path)
+ * @method int|false fileperms(string $path)
+ * @method int|false fileowner(string $path)
+ * @method int|false filegroup(string $path)
+ * @method int|float|false filesize(string $path)
+ * @method string|false filetype(string $path)
+ * @method bool rename(string $oldname, string $newname)
+ * @method string[]|string getSFTPLog()
+ * @method string[] getSFTPErrors()
+ * @method string getLastSFTPError()
+ * @method mixed[]|false getSupportedVersions()
+ * @method int|false getNegotiatedVersion()
+ * @method void setPreferredVersion(int $version)
+ * @method void enableDatePreservation()
+ * @method void disableDatePreservation()
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class SFTP
+{
+ /**#@+
+ * @access public
+ * @see \phpseclib\Net\SFTP::put()
+ */
+ /**
+ * Reads data from a local file.
+ */
+ const SOURCE_LOCAL_FILE = 1;
+ /**
+ * Reads data from a string.
+ */
+ // this value isn't really used anymore but i'm keeping it reserved for historical reasons
+ const SOURCE_STRING = 2;
+ /**
+ * Reads data from callback:
+ * function callback($length) returns string to proceed, null for EOF
+ */
+ const SOURCE_CALLBACK = 16;
+ /**
+ * Resumes an upload
+ */
+ const RESUME = 4;
+ /**
+ * Append a local file to an already existing remote file
+ */
+ const RESUME_START = 8;
+ /**#@-*/
+
+ /**
+ * The SFTP object
+ *
+ * @var \phpseclib3\File\SFTP
+ * @access private
+ */
+ private $sftp = null;
+
+ /**
+ * Default Constructor.
+ *
+ * Connects to an SFTP server
+ *
+ * @param string $host
+ * @param int $port
+ * @param int $timeout
+ * @return \phpseclib\Net\SFTP
+ * @access public
+ */
+ function __construct($host, $port = 22, $timeout = 10)
+ {
+ $this->sftp = new \phpseclib3\Net\SFTP($host, $port, $timeout);
+ }
+
+ /**
+ * Login
+ *
+ * The $password parameter can be a plaintext password, a \phpseclib3\Crypt\RSA object or an array
+ *
+ * @param string $username
+ * @param $args[] param mixed $password
+ * @return bool
+ * @see self::_login()
+ * @access public
+ */
+ public function login($username, ...$args)
+ {
+ foreach ($args as &$arg) {
+ if ($arg instanceof RSA) {
+ $arg = $arg->getKeyObject();
+ if (!$arg instanceof \phpseclib3\Crypt\Common\PrivateKey) {
+ return false;
+ }
+ }
+ }
+
+ try {
+ return $this->sftp->login($username, ...$args);
+ } catch (\Exception $e) {
+ user_error($e->getMessage());
+ return false;
+ }
+ }
+
+ /**
+ * Parse Attributes
+ *
+ * See '7. File Attributes' of draft-ietf-secsh-filexfer-13 for more info.
+ *
+ * @param string $response
+ * @return array
+ * @access private
+ */
+ protected function parseAttributes(&$response)
+ {
+ $r = $this->sftp->parseAttributes($response);
+ if (isset($r['mode'])) {
+ $r['permissions'] = $r['mode'];
+ }
+ return $r;
+ }
+
+ /**
+ * Defines how nlist() and rawlist() will be sorted - if at all.
+ *
+ * If sorting is enabled directories and files will be sorted independently with
+ * directories appearing before files in the resultant array that is returned.
+ *
+ * Any parameter returned by stat is a valid sort parameter for this function.
+ * Filename comparisons are case insensitive.
+ *
+ * Examples:
+ *
+ * $sftp->setListOrder('filename', SORT_ASC);
+ * $sftp->setListOrder('size', SORT_DESC, 'filename', SORT_ASC);
+ * $sftp->setListOrder(true);
+ * Separates directories from files but doesn't do any sorting beyond that
+ * $sftp->setListOrder();
+ * Don't do any sort of sorting
+ *
+ * @param $args[]
+ * @access public
+ */
+ public function setListOrder(...$args)
+ {
+ $sortOptions = [];
+ if (empty($args)) {
+ return;
+ }
+ $len = count($args) & 0x7FFFFFFE;
+ for ($i = 0; $i < $len; $i+=2) {
+ if ($args[$i] == 'permissions') {
+ $args[$i] = 'mode';
+ }
+ $sortOptions[$args[$i]] = $args[$i + 1];
+ }
+ $this->sftp->setListOrder(...$args);
+ }
+
+ /**
+ * Returns the file size, in bytes, or false, on failure
+ *
+ * Files larger than 4GB will show up as being exactly 4GB.
+ *
+ * @param string $filename
+ * @return mixed
+ * @access public
+ */
+ public function size($filename)
+ {
+ return $this->sftp->filesize($filename);
+ }
+
+ /**
+ * Returns a public key object
+ *
+ * @access public
+ * @return SFTP|false
+ */
+ public function getSFTPObject()
+ {
+ return $this->sftp;
+ }
+
+ /**
+ * __call() magic method
+ *
+ * @access public
+ */
+ public function __call($name, $args)
+ {
+ try {
+ return $this->sftp->$name(...$args);
+ } catch (\Exception $e) {
+ user_error($e->getMessage());
+ }
+ }
+} \ No newline at end of file
diff --git a/vendor/phpseclib/phpseclib2_compat/src/Net/SFTP/Stream.php b/vendor/phpseclib/phpseclib2_compat/src/Net/SFTP/Stream.php
new file mode 100644
index 000000000..c456990a7
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/src/Net/SFTP/Stream.php
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * SFTP Stream Wrapper
+ *
+ * Creates an sftp:// protocol handler that can be used with, for example, fopen(), dir(), etc.
+ *
+ * PHP version 5
+ *
+ * @category Net
+ * @package SFTP
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2013 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+namespace phpseclib\Net\SFTP;
+
+use phpseclib\Crypt\RSA;
+use phpseclib\Net\SFTP;
+
+/**
+ * SFTP Stream Wrapper
+ *
+ * @package SFTP
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class Stream extends \phpseclib3\Net\SFTP\Stream
+{
+ /**
+ * Path Parser
+ *
+ * Extract a path from a URI and actually connect to an SSH server if appropriate
+ *
+ * If "notification" is set as a context parameter the message code for successful login is
+ * NET_SSH2_MSG_USERAUTH_SUCCESS. For a failed login it's NET_SSH2_MSG_USERAUTH_FAILURE.
+ *
+ * @param string $path
+ * @return string
+ * @access private
+ */
+ protected function parse_path($path)
+ {
+ $scheme = parse_url($path, PHP_URL_SCHEME);
+ if (isset($this->context)) {
+ $options = stream_context_get_options($this->context);
+ }
+ if (isset($options[$scheme]['privkey']) && $options[$scheme]['privkey'] instanceof RSA) {
+ stream_context_set_option($this->context, $scheme, 'privKey', $options[$scheme]['privkey']->getKeyObject());
+ }
+ if (isset($options[$scheme]['session']) && $options[$scheme]['session'] instanceof SFTP) {
+ stream_context_set_option($this->context, $scheme, 'session', $options[$scheme]['session']->getSFTPObject());
+ }
+ if (isset($options[$scheme]['sftp']) && $options[$scheme]['sftp'] instanceof SFTP) {
+ stream_context_set_option($this->context, $scheme, 'sftp', $options[$scheme]['sftp']->getSFTPObject());
+ }
+ return parent::parse_path($path);
+ }
+
+ /**
+ * __call() magic method
+ *
+ * @access public
+ */
+ public function __call($name, array $args)
+ {
+ try {
+ return parent::__call($name, $args);
+ } catch (\Exception $e) {
+ user_error($e->getMessage());
+ }
+ }
+} \ No newline at end of file
diff --git a/vendor/phpseclib/phpseclib2_compat/src/Net/SSH2.php b/vendor/phpseclib/phpseclib2_compat/src/Net/SSH2.php
new file mode 100644
index 000000000..a78e7e87d
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/src/Net/SSH2.php
@@ -0,0 +1,233 @@
+<?php
+
+/**
+ * Pure-PHP implementation of SSHv2.
+ *
+ * PHP version 5
+ *
+ * Here are some examples of how to use this library:
+ * <code>
+ * <?php
+ * include 'vendor/autoload.php';
+ *
+ * $ssh = new \phpseclib\Net\SSH2('www.domain.tld');
+ * if (!$ssh->login('username', 'password')) {
+ * exit('Login Failed');
+ * }
+ *
+ * echo $ssh->exec('pwd');
+ * echo $ssh->exec('ls -la');
+ * ?>
+ * </code>
+ *
+ * <code>
+ * <?php
+ * include 'vendor/autoload.php';
+ *
+ * $key = new \phpseclib\Crypt\RSA();
+ * //$key->setPassword('whatever');
+ * $key->loadKey(file_get_contents('privatekey'));
+ *
+ * $ssh = new \phpseclib\Net\SSH2('www.domain.tld');
+ * if (!$ssh->login('username', $key)) {
+ * exit('Login Failed');
+ * }
+ *
+ * echo $ssh->read('username@username:~$');
+ * $ssh->write("ls -la\n");
+ * echo $ssh->read('username@username:~$');
+ * ?>
+ * </code>
+ *
+ * @category Net
+ * @package SSH2
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2007 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ */
+
+
+namespace phpseclib\Net;
+
+use phpseclib\Crypt\RSA;
+
+/**
+ * Pure-PHP implementation of SSHv2.
+ *
+ * @package SSHv2
+ * @method static void setCryptoEngine(string $engine)
+ * @method void sendIdentificationStringFirst()
+ * @method void sendIdentificationStringLast()
+ * @method void sendKEXINITFirst()
+ * @method void sendKEXINITLast()
+ * @method int|float getTimeout()
+ * @method void setTimeout(int|float $timeout)
+ * @method void setKeepAlive(int|float $interval)
+ * @method string getStdError()
+ * @method string|bool exec(string $command, callable ?$callback = null)
+ * @method bool requestAgentForwarding()
+ * @method string|bool|null read(string $expect = '', int $mode = SSH2::READ_SIMPLE)
+ * @method void write(string $cmd)
+ * @method bool startSubsystem(string $subsystem)
+ * @method bool stopSubsystem()
+ * @method void reset()
+ * @method bool isTimeout()
+ * @method void disconnect()
+ * @method bool isConnected()
+ * @method bool isAuthenticated()
+ * @method bool ping()
+ * @method void enableQuietMode()
+ * @method void disableQuietMode()
+ * @method bool isQuietModeEnabled()
+ * @method void enablePTY()
+ * @method void disablePTY()
+ * @method bool isPTYEnabled()
+ * @method array|false|string getLog()
+ * @method string[] getErrors()
+ * @method ?string getLastError()
+ * @method string|false getServerIdentification()
+ * @method mixed[] getServerAlgorithms()
+ * @method static string[] getSupportedKEXAlgorithms()
+ * @method static string[] getSupportedHostKeyAlgorithms()
+ * @method static string[] getSupportedEncryptionAlgorithms()
+ * @method static string[] getSupportedMACAlgorithms()
+ * @method static string[] getSupportedCompressionAlgorithms()
+ * @method mixed[] getAlgorithmsNegotiated()
+ * @method void setTerminal(string $term)
+ * @method void setPreferredAlgorithms(mixed[] $methods)
+ * @method string getBannerMessage()
+ * @method string|false getServerPublicHostKey()
+ * @method false|int getExitStatus()
+ * @method int getWindowColumns()
+ * @method int getWindowRows()
+ * @method setWindowColumns(int $value)
+ * @method setWindowRows(int $value)
+ * @method setWindowSize(int $columns = 80, int $rows = 24)
+ * @method string getResourceId()
+ * @method static bool|SSH2 getConnectionByResourceId(string $id)
+ * @method static array<string, SSH2> getConnections()
+ * @method ?mixed[] getAuthMethodsToContinue()
+ * @method void enableSmartMFA()
+ * @method void disableSmartMFA()
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class SSH2
+{
+ /**#@+
+ * @access public
+ * @see \phpseclib\Net\SSH2::getLog()
+ */
+ /**
+ * Returns the message numbers
+ */
+ const LOG_SIMPLE = 1;
+ /**
+ * Returns the message content
+ */
+ const LOG_COMPLEX = 2;
+ /**
+ * Outputs the content real-time
+ */
+ const LOG_REALTIME = 3;
+ /**
+ * Dumps the content real-time to a file
+ */
+ const LOG_REALTIME_FILE = 4;
+ /**
+ * Make sure that the log never gets larger than this
+ */
+ const LOG_MAX_SIZE = 1048576; // 1024 * 1024
+ /**#@-*/
+
+ /**#@+
+ * @access public
+ * @see \phpseclib\Net\SSH2::read()
+ */
+ /**
+ * Returns when a string matching $expect exactly is found
+ */
+ const READ_SIMPLE = 1;
+ /**
+ * Returns when a string matching the regular expression $expect is found
+ */
+ const READ_REGEX = 2;
+ /**
+ * Returns whenever a data packet is received.
+ *
+ * Some data packets may only contain a single character so it may be necessary
+ * to call read() multiple times when using this option
+ */
+ const READ_NEXT = 3;
+ /**#@-*/
+
+ /**
+ * The SSH2 object
+ *
+ * @var \phpseclib3\File\SSH2
+ * @access private
+ */
+ private $ssh;
+
+ /**
+ * Default Constructor.
+ *
+ * $host can either be a string, representing the host, or a stream resource.
+ *
+ * @param mixed $host
+ * @param int $port
+ * @param int $timeout
+ * @see self::login()
+ * @return \phpseclib\Net\SSH2
+ * @access public
+ */
+ function __construct($host, $port = 22, $timeout = 10)
+ {
+ $this->ssh = new \phpseclib3\Net\SSH2($host, $port, $timeout);
+ }
+
+ /**
+ * Login
+ *
+ * The $password parameter can be a plaintext password, a \phpseclib3\Crypt\RSA object or an array
+ *
+ * @param string $username
+ * @param $args[] param mixed $password
+ * @return bool
+ * @see self::_login()
+ * @access public
+ */
+ public function login($username, ...$args)
+ {
+ foreach ($args as &$arg) {
+ if ($arg instanceof RSA) {
+ $arg = $arg->getKeyObject();
+ if (!$arg) {
+ return false;
+ }
+ }
+ }
+
+ try {
+ return $this->ssh->login($username, ...$args);
+ } catch (\Exception $e) {
+ user_error($e->getMessage());
+ return false;
+ }
+ }
+
+ /**
+ * __call() magic method
+ *
+ * @access public
+ */
+ public function __call($name, $args)
+ {
+ try {
+ return $this->ssh->$name(...$args);
+ } catch (\Exception $e) {
+ user_error($e->getMessage());
+ }
+ }
+} \ No newline at end of file
diff --git a/vendor/phpseclib/phpseclib2_compat/src/System/SSH/Agent.php b/vendor/phpseclib/phpseclib2_compat/src/System/SSH/Agent.php
new file mode 100644
index 000000000..365353f67
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/src/System/SSH/Agent.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * Pure-PHP ssh-agent client.
+ *
+ * PHP version 5
+ *
+ * Here are some examples of how to use this library:
+ * <code>
+ * <?php
+ * include 'vendor/autoload.php';
+ *
+ * $agent = new \phpseclib3\System\SSH\Agent();
+ *
+ * $ssh = new \phpseclib3\Net\SSH2('www.domain.tld');
+ * if (!$ssh->login('username', $agent)) {
+ * exit('Login Failed');
+ * }
+ *
+ * echo $ssh->exec('pwd');
+ * echo $ssh->exec('ls -la');
+ * ?>
+ * </code>
+ *
+ * @category System
+ * @package SSH\Agent
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2014 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ * @internal See http://api.libssh.org/rfc/PROTOCOL.agent
+ */
+
+namespace phpseclib\System\SSH;
+
+/**
+ * Pure-PHP ssh-agent client identity factory
+ *
+ * requestIdentities() method pumps out \phpseclib3\System\SSH\Agent\Identity objects
+ *
+ * @package SSH\Agent
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access public
+ */
+class Agent extends \phpseclib3\System\SSH\Agent
+{
+} \ No newline at end of file
diff --git a/vendor/phpseclib/phpseclib2_compat/src/System/SSH/Agent/Identity.php b/vendor/phpseclib/phpseclib2_compat/src/System/SSH/Agent/Identity.php
new file mode 100644
index 000000000..c85f8330f
--- /dev/null
+++ b/vendor/phpseclib/phpseclib2_compat/src/System/SSH/Agent/Identity.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * Pure-PHP ssh-agent client.
+ *
+ * PHP version 5
+ *
+ * @category System
+ * @package SSH\Agent
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @copyright 2009 Jim Wigginton
+ * @license http://www.opensource.org/licenses/mit-license.html MIT License
+ * @link http://phpseclib.sourceforge.net
+ * @internal See http://api.libssh.org/rfc/PROTOCOL.agent
+ */
+
+namespace phpseclib\System\SSH\Agent;
+
+/**
+ * Pure-PHP ssh-agent client identity object
+ *
+ * Instantiation should only be performed by \phpseclib3\System\SSH\Agent class.
+ * This could be thought of as implementing an interface that phpseclib3\Crypt\RSA
+ * implements. ie. maybe a Net_SSH_Auth_PublicKey interface or something.
+ * The methods in this interface would be getPublicKey and sign since those are the
+ * methods phpseclib looks for to perform public key authentication.
+ *
+ * @package SSH\Agent
+ * @author Jim Wigginton <terrafrost@php.net>
+ * @access internal
+ */
+class Identity extends \phpseclib3\System\SSH\Agent\Identity
+{
+} \ No newline at end of file
diff --git a/vendor/ralouphie/getallheaders/LICENSE b/vendor/ralouphie/getallheaders/LICENSE
new file mode 100644
index 000000000..be5540c2a
--- /dev/null
+++ b/vendor/ralouphie/getallheaders/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Ralph Khattar
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/ralouphie/getallheaders/README.md b/vendor/ralouphie/getallheaders/README.md
new file mode 100644
index 000000000..9430d76bb
--- /dev/null
+++ b/vendor/ralouphie/getallheaders/README.md
@@ -0,0 +1,27 @@
+getallheaders
+=============
+
+PHP `getallheaders()` polyfill. Compatible with PHP >= 5.3.
+
+[![Build Status](https://travis-ci.org/ralouphie/getallheaders.svg?branch=master)](https://travis-ci.org/ralouphie/getallheaders)
+[![Coverage Status](https://coveralls.io/repos/ralouphie/getallheaders/badge.png?branch=master)](https://coveralls.io/r/ralouphie/getallheaders?branch=master)
+[![Latest Stable Version](https://poser.pugx.org/ralouphie/getallheaders/v/stable.png)](https://packagist.org/packages/ralouphie/getallheaders)
+[![Latest Unstable Version](https://poser.pugx.org/ralouphie/getallheaders/v/unstable.png)](https://packagist.org/packages/ralouphie/getallheaders)
+[![License](https://poser.pugx.org/ralouphie/getallheaders/license.png)](https://packagist.org/packages/ralouphie/getallheaders)
+
+
+This is a simple polyfill for [`getallheaders()`](http://www.php.net/manual/en/function.getallheaders.php).
+
+## Install
+
+For PHP version **`>= 5.6`**:
+
+```
+composer require ralouphie/getallheaders
+```
+
+For PHP version **`< 5.6`**:
+
+```
+composer require ralouphie/getallheaders "^2"
+```
diff --git a/vendor/ralouphie/getallheaders/composer.json b/vendor/ralouphie/getallheaders/composer.json
new file mode 100644
index 000000000..de8ce62e4
--- /dev/null
+++ b/vendor/ralouphie/getallheaders/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "ralouphie/getallheaders",
+ "description": "A polyfill for getallheaders.",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Ralph Khattar",
+ "email": "ralph.khattar@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^5 || ^6.5",
+ "php-coveralls/php-coveralls": "^2.1"
+ },
+ "autoload": {
+ "files": ["src/getallheaders.php"]
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "getallheaders\\Tests\\": "tests/"
+ }
+ }
+}
diff --git a/vendor/ralouphie/getallheaders/src/getallheaders.php b/vendor/ralouphie/getallheaders/src/getallheaders.php
new file mode 100644
index 000000000..c7285a5ba
--- /dev/null
+++ b/vendor/ralouphie/getallheaders/src/getallheaders.php
@@ -0,0 +1,46 @@
+<?php
+
+if (!function_exists('getallheaders')) {
+
+ /**
+ * Get all HTTP header key/values as an associative array for the current request.
+ *
+ * @return string[string] The HTTP header key/value pairs.
+ */
+ function getallheaders()
+ {
+ $headers = array();
+
+ $copy_server = array(
+ 'CONTENT_TYPE' => 'Content-Type',
+ 'CONTENT_LENGTH' => 'Content-Length',
+ 'CONTENT_MD5' => 'Content-Md5',
+ );
+
+ foreach ($_SERVER as $key => $value) {
+ if (substr($key, 0, 5) === 'HTTP_') {
+ $key = substr($key, 5);
+ if (!isset($copy_server[$key]) || !isset($_SERVER[$key])) {
+ $key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key))));
+ $headers[$key] = $value;
+ }
+ } elseif (isset($copy_server[$key])) {
+ $headers[$copy_server[$key]] = $value;
+ }
+ }
+
+ if (!isset($headers['Authorization'])) {
+ if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {
+ $headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
+ } elseif (isset($_SERVER['PHP_AUTH_USER'])) {
+ $basic_pass = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : '';
+ $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $basic_pass);
+ } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) {
+ $headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST'];
+ }
+ }
+
+ return $headers;
+ }
+
+}
diff --git a/vendor/stephenhill/base58/src/Base58.php b/vendor/stephenhill/base58/src/Base58.php
index 75a2e0de4..2b44b22b2 100644
--- a/vendor/stephenhill/base58/src/Base58.php
+++ b/vendor/stephenhill/base58/src/Base58.php
@@ -29,8 +29,8 @@ class Base58
* @since v1.1.0 Added the optional $service argument.
*/
public function __construct(
- $alphabet = null,
- ServiceInterface $service = null
+ string|null $alphabet = null,
+ ServiceInterface|null $service = null
) {
// Handle null alphabet
if (is_null($alphabet) === true) {
diff --git a/view/css/conversation.css b/view/css/conversation.css
index fb26c7e3f..38970bb45 100644
--- a/view/css/conversation.css
+++ b/view/css/conversation.css
@@ -193,8 +193,118 @@ a.wall-item-name-link {
color: var(--bs-primary);
}
-.item-highlight {
- border-left: 0.2rem solid var(--bs-primary);
+.item-highlight,
+.item-indent,
+.wall-item-comment,
+.thread-wrapper {
+ position: relative;
+}
+
+.item-fade-in {
+ opacity: 0;
+ transform: scale(.7) translateY(-20px);
+ transition: opacity 0.3s ease-out, transform 0.3s ease-out;
+}
+.item-fade-in.show {
+ opacity: 1;
+ transform: scale(1) translateY(0);
+}
+
+.item-highlight::after {
+ content: '';
+ position: absolute;
+ pointer-events: none;
+ box-shadow: inset .15rem 0 0 0 var(--hz-item-highlight);
+ width: 100%;
+ top: 0;
+ bottom: 0;
+/* margin: var(--bs-border-radius) 0 var(--bs-border-radius) 0; */
+}
+
+.item-highlight-fade {
+ background-color: var(--bs-primary-bg-subtle);
+ animation: fadeBgOut .35s .7s backwards;
+}
+
+@keyframes fadeBgOut {
+ from { background-color: var(--bs-primary-bg-subtle); }
+ to { background-color: none; }
+}
+
+.item-indent {
+ /* This must be equal to .item.indent:before width */
+ padding-left: .75rem;
+}
+
+.item-indent::before {
+ content: '';
+ position: absolute;
+ height: 100%;
+ width: .75rem;
+ top: 0;
+ left: 0;
+ border-color: var(--hz-item-indent, var(--bs-border-color));
+ border-radius: 0 1.5rem 0 0;
+ border-style: solid;
+ border-width: 1px 1px 0 0;
+}
+
+.wall-item-comment.expanded {
+ opacity: 0.5;
+}
+
+.wall-item-comment.collapsed::before {
+ content: '\2212';
+ position: absolute;
+ left: .85rem;
+ top: .45rem;
+}
+
+.wall-item-backdrop::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ backdrop-filter: saturate(0%) brightness(90%);
+ z-index: 1;
+}
+
+.wall-item-expanded {
+ position: relative;
+ border-radius: var(--bs-border-radius);
+ background-color: var(--bs-body-bg);
+ z-index: 2;
+}
+
+.wall-item-expanded::after {
+ content: '';
+ position: absolute;
+ pointer-events: none;
+ box-shadow: 0 0 0 1px var(--bs-border-color);
+ border-radius: var(--bs-border-radius);
+ width: 100%;
+ top: 0;
+ bottom: 0;
+}
+
+.wall-item-expanded::before {
+ content: var(--hz-wall-item-expanded-before-content, "");;
+ position: absolute;
+ top: 0;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ background: var(--bs-info-bg-subtle);
+ color: var(--bs-info-text-emphasis);
+ border-radius: var(--bs-border-radius-pill);
+ padding: 0.35em 0.65em;
+ font-size: 0.6em;
+ font-weight: 700;
+ z-index: 3;
+ text-transform: uppercase;
+}
+
+.load-more:hover .load-more-dots {
+ background: var(--bs-light-bg-subtle);
+ padding: 0 0.35em;
}
/* comment_item */
diff --git a/view/css/mod_help.css b/view/css/mod_help.css
index d596048c9..b77b65e0e 100644
--- a/view/css/mod_help.css
+++ b/view/css/mod_help.css
@@ -12,7 +12,7 @@
}
#doco-content img {
- width: 100%;
+ max-width: 100%;
}
#region_1 .widget ul ul {
diff --git a/view/js/autocomplete.js b/view/js/autocomplete.js
index 7d6ddb1c4..ad8e04050 100644
--- a/view/js/autocomplete.js
+++ b/view/js/autocomplete.js
@@ -38,7 +38,7 @@ function contact_format(item) {
var desc = ((item.label) ? item.nick + ' ' + item.label : item.nick);
if(typeof desc === 'undefined') desc = '';
if(desc) desc = ' ('+desc+')';
- return "<div class='dropdown-item dropdown-notification lh-sm text-truncate' title='{4}'><img class='menu-img-2' src='{1}' loading='lazy'><strong>{2}</strong><br><small class='opacity-75'>{4}</small></div>".format(item.taggable, item.photo, item.name, desc, typeof(item.link) !== 'undefined' ? item.link : desc.replace('(','').replace(')',''));
+ return "<div class='dropdown-item dropdown-notification lh-sm text-truncate' title='{4}'><img class='menu-img-2' src='{1}' loading='lazy'><strong>{2}</strong><br><small class='text-body-secondary'>{4}</small></div>".format(item.taggable, item.photo, item.name, desc, typeof(item.link) !== 'undefined' ? item.link : desc.replace('(','').replace(')',''));
}
else
return "";
diff --git a/view/js/main.js b/view/js/main.js
index 43f0333ed..5bf7234aa 100644
--- a/view/js/main.js
+++ b/view/js/main.js
@@ -26,6 +26,10 @@ var followUpPageLoad = false;
var window_needs_alert = true;
var expanded_items = [];
var updateTimeout = [];
+const singlethread_modules = ['display', 'hq'];
+const redirect_modules = ['display', 'notify'];
+let b64mids = [];
+
var page_cache = {};
@@ -85,6 +89,146 @@ $(document).ready(function() {
}
});
+ document.addEventListener('click', function(event) {
+ // Only handle clicks on .wall-item-reaction or its children
+ const target = event.target.closest('.wall-item-reaction');
+ if (!target) return;
+
+ let doRequest = true;
+ const isUserClick = event.isTrusted;
+
+ // Destructure relevant data attributes
+ const { itemId: id, itemMid: mid, itemParent: parentId, itemUuid: uuid, itemVerb: verb } = target.dataset;
+ const isCommentBtn = target.classList.contains('wall-item-comment');
+
+ if (isCommentBtn) {
+ if (id === parentId) {
+ // Handle blog mode
+ target.classList.add('disabled');
+ document.getElementById(`load-more-progress-wrapper-${id}`).classList.remove('d-none');
+ document.getElementById(`load-more-${id}`).classList.remove('d-none')
+ request(id, mid, 'load', parentId, uuid, isUserClick);
+ return;
+ }
+
+ // Get relevant DOM elements
+ const threadWrapper = document.getElementById(`thread-wrapper-${id}`);
+ const parentWrapper = document.getElementById(`thread-wrapper-${parentId}`);
+ const subThreadWrapper = document.getElementById(`wall-item-sub-thread-wrapper-${id}`);
+ const parentSubThreadWrapper = document.getElementById(`wall-item-sub-thread-wrapper-${parentId}`);
+
+ // Query related sub-thread and highlight elements
+ const parentIndentedThreads = document.querySelectorAll(`#wall-item-sub-thread-wrapper-${parentId} .wall-item-sub-thread-wrapper.item-indent`);
+
+ let ancestorIds = [];
+
+ doRequest = !subThreadWrapper.children.length;
+
+ // Set visual styles using UUID
+ subThreadWrapper.style.setProperty('--hz-item-indent', stringToHslColor(uuid));
+ threadWrapper.style.setProperty('--hz-item-highlight', stringToHslColor(uuid));
+ threadWrapper.style.setProperty('--hz-wall-item-expanded-before-content', '"' + aStr.dblclick_to_exit_zoom + '"');
+
+ // Clear previous highlights
+ parentSubThreadWrapper.querySelectorAll('.thread-wrapper.item-highlight').forEach(el => el.classList.remove('item-highlight'));
+
+ if (isUserClick && parentIndentedThreads.length === 0 && !subThreadWrapper.children.length) {
+ // Handle first-time expansion and highlighting but not for toplevels (blog mode)
+ threadWrapper.classList.add('item-highlight');
+ } else {
+ // Handle indentation and zooming
+ let ancestor = subThreadWrapper.parentElement;
+ ancestorIds.push(ancestor.id.slice(15)); // thread-wrapper-1234
+
+ while (ancestor) {
+ if (ancestor.classList.contains('item-indent') && ancestor.classList.contains('wall-item-sub-thread-wrapper')) {
+ ancestorIds.push(ancestor.parentElement.id.slice(15));
+ }
+ ancestor = ancestor.parentElement;
+ }
+
+ ancestorIds.reverse();
+
+ if (ancestorIds.length > 3) {
+ // Handle zooming in
+ let firstWrapper = document.getElementById('thread-wrapper-' + ancestorIds[0]);
+ let firstSubWrapper = document.getElementById('wall-item-sub-thread-wrapper-' + ancestorIds[0]);
+
+ firstWrapper.querySelector('.wall-item-comment').classList.remove('indented');
+ firstWrapper.classList.remove('wall-item-expanded', 'shadow');
+ firstSubWrapper.classList.remove('item-indent');
+
+ let newFirstWrapper = document.getElementById('thread-wrapper-' + ancestorIds[1])
+ let newFirstSubWrapper = document.getElementById('wall-item-sub-thread-wrapper-' + ancestorIds[1])
+
+ newFirstWrapper.classList.add('wall-item-expanded', 'shadow');
+ parentWrapper.classList.add('wall-item-backdrop');
+
+ // Exit zoom on double-click
+ newFirstWrapper.addEventListener('dblclick', function() {
+ parentWrapper.querySelectorAll('.wall-item-comment.indented').forEach(el => el.classList.remove('indented'));
+ parentWrapper.querySelectorAll('.wall-item-comment.collapsed').forEach(el => el.classList.remove('collapsed'));
+ parentWrapper.classList.remove('wall-item-backdrop');
+ parentWrapper.querySelectorAll('.wall-item-sub-thread-wrapper.item-indent').forEach(el => el.classList.remove('item-indent'));
+ parentWrapper.querySelectorAll('.wall-item-sub-thread-wrapper.d-none').forEach(el => el.classList.remove('d-none'));
+ parentWrapper.querySelectorAll('.thread-wrapper.wall-item-expanded').forEach(el => el.classList.remove('wall-item-expanded', 'shadow'));
+ }, { once: true });
+ }
+
+ // Toggle sub-thread visibility if indented
+ if (isUserClick && target.classList.contains('indented')) {
+ doRequest = false;
+ subThreadWrapper.classList.toggle('d-none');
+ target.classList.toggle('collapsed');
+ }
+
+ // Indenting of already expanded but flattened items
+ if (isUserClick && subThreadWrapper.classList.contains('item-expanded') && !subThreadWrapper.classList.contains('item-indent')) {
+ doRequest = false;
+
+ threadWrapper.querySelectorAll('.wall-item-sub-thread-wrapper.item-expanded').forEach(function (el, i) {
+ el.classList.add('item-indent');
+
+ el.querySelectorAll('.wall-item-comment.expanded').forEach(function (el, i) {
+ el.classList.add('collapsed', 'indented');
+ });
+
+ // Collapse everything below the first level
+ if (i > 0) {
+ el.classList.add('d-none');
+ }
+ });
+ }
+
+ // Indent the subthread
+ subThreadWrapper.classList.add('item-indent', 'item-expanded');
+
+ // Mark as indented after visibility toggle
+ target.classList.add('indented');
+ }
+
+ // Mark as expanded
+ target.classList.add('expanded');
+ }
+
+ if (doRequest) {
+ request(id, mid, verb, parentId, uuid, isUserClick);
+ }
+ });
+
+ document.addEventListener('click', function(event) {
+ const targetElement = event.target.closest('.dropdown-item-expand');
+ if (!targetElement) return;
+
+ event.preventDefault();
+
+ const id = targetElement.dataset.itemId;
+ const subWrapper = document.getElementById(`wall-item-sub-thread-wrapper-${id}`);
+
+ subWrapper.innerHTML = '';
+ autoExpand(id);
+ });
+
// @hilmar |->
if ( typeof(window.tao) == 'undefined' ) {
window.tao = {};
@@ -149,8 +293,6 @@ $(document).ready(function() {
let notify_id = this.dataset.notify_id;
let path = $(this)[0].pathname.split('/')[1];
let stateObj = { b64mid: b64mid };
- let singlethread_modules = ['display', 'hq'];
- let redirect_modules = ['display', 'notify'];
if (!b64mid && !notify_id) {
return;
@@ -329,6 +471,13 @@ function handle_comment_form(e) {
var commentSaveTimer = null;
var emptyCommentElm = form.find('.comment-edit-text').attr('id');
var convId = emptyCommentElm.replace('comment-edit-text-','');
+
+ // in case parent input is set use it as convId
+ const parentInputVal = form.find(':input[name=parent]').val();
+ if (parentInputVal) {
+ convId = parentInputVal;
+ }
+
$('#' + emptyCommentElm).on('focusout',function(e){
if(commentSaveTimer)
clearTimeout(commentSaveTimer);
@@ -502,8 +651,11 @@ function showHideComments(id) {
let isCollapsed = collapsedComments.style.display === 'none';
collapsedComments.style.display = isCollapsed ? '' : 'none';
- hideCommentsLabel.textContent = isCollapsed ? aStr.showfewer : aStr.showmore;
- hideCommentsTotal.style.display = isCollapsed ? 'none' : '';
+ hideCommentsLabel.textContent = isCollapsed ? hideCommentsLabel.dataset.expanded : hideCommentsLabel.dataset.collapsed;
+
+ if (hideCommentsTotal) {
+ hideCommentsTotal.style.display = isCollapsed ? 'none' : '';
+ }
let oldClass = isCollapsed ? 'bi-chevron-down' : 'bi-chevron-up';
let newClass = isCollapsed ? 'bi-chevron-up' : 'bi-chevron-down';
@@ -740,7 +892,27 @@ function updateConvItems(mode, data) {
}
}
- b64mids.push(...JSON.parse(elem.dataset.b64mids));
+ let data_json = JSON.parse(elem.dataset.b64mids);
+
+ if (elem.parentNode.classList.contains('wall-item-sub-thread-wrapper') && elem.parentNode.children.length) {
+ // Set the highlight state
+ if (data_json.includes(bParam_mid) && !elem.parentNode.parentNode.classList.contains('toplevel_item')) {
+ elem.parentNode.parentNode.classList.add('item-highlight');
+ document.documentElement.style.setProperty('--hz-item-highlight', stringToHslColor(JSON.parse(elem.parentNode.parentNode.dataset.b64mids)[0]));
+ }
+
+ let elemSubThreadWrapper = elem.querySelector('.wall-item-sub-thread-wrapper');
+ let elemCommentButton = elem.querySelector('.wall-item-comment');
+
+ // Set the button and sub-thread-wrapper state
+ if (elemCommentButton && elemSubThreadWrapper.children.length) {
+ elemCommentButton.classList.add('expanded');
+ }
+
+ elem.parentNode.classList.add('item-expanded');
+ }
+
+ b64mids.push(...data_json);
});
document.dispatchEvent(new CustomEvent('hz:sse_setNotificationsStatus', { detail: b64mids }));
@@ -771,10 +943,12 @@ function updateConvItems(mode, data) {
}
imagesLoaded(document.querySelectorAll('.wall-item-body img, .wall-photo-item img'), function () {
- collapseHeight();
if (bParam_mid && mode === 'replace') {
scrollToItem();
}
+ else {
+ collapseHeight();
+ }
});
// reset rotators and cursors we may have set before reaching this place
@@ -789,6 +963,7 @@ function updateConvItems(mode, data) {
followUpPageLoad = true;
+
updateRelativeTime('.autotime');
}
@@ -859,19 +1034,25 @@ function imagesLoaded(elements, callback) {
// Iterate through images to add load and error event listeners
images.forEach((img) => {
- img.loading = 'eager'; // Preload the image
+ let new_img = new Image();
+ new_img.src = img.src;
- if (img.complete && img.naturalHeight > 0) {
+ if (new_img.complete && new_img.naturalHeight > 0) {
// Image is already loaded, handle immediately
- checkComplete(img.src);
+ // console.log(`Image cached: ${new_img.src}`);
+ checkComplete(new_img.src);
} else {
// Add event listeners for load and error events
- img.addEventListener('load', () => checkComplete(img.src));
- img.addEventListener('error', () => {
- console.log(`Image failed to load: ${img.src}`);
- checkComplete(img.src);
+ new_img.addEventListener('load', () => {
+ // console.log(`Image loaded: ${new_img.src}`);
+ checkComplete(new_img.src)
+ });
+ new_img.addEventListener('error', () => {
+ console.log(`Image failed to load: ${new_img.src}`);
+ checkComplete(new_img.src);
});
}
+
});
}
@@ -927,45 +1108,52 @@ function updateRelativeTime(selector) {
}
function scrollToItem() {
- // auto-scroll to a particular comment in a thread (designated by mid) when in single-thread mode
+ // auto-scroll to a particular comment in a thread (designated by mid) when in single-thread mode
- if (justifiedGalleryActive) return;
+ if (justifiedGalleryActive) return;
- let submid = ((bParam_mid.length) ? bParam_mid : 'abcdefg');
+ let submid = ((bParam_mid.length) ? bParam_mid : 'abcdefg');
- // Select all thread wrappers
- let threadWrappers = document.querySelectorAll('.thread-wrapper');
+ // Select all thread wrappers
+ let threadWrappers = document.querySelectorAll('.thread-wrapper');
- threadWrappers.forEach(thread => {
- // Get the 'data-b64mids' attribute and check if it contains submid
- let b64mids = thread.dataset.b64mids;
+ threadWrappers.forEach(thread => {
+ // Get the 'data-b64mids' attribute and check if it contains submid
+ let b64mids = thread.dataset.b64mids;
- if (b64mids && b64mids.includes(submid) && !thread.classList.contains('toplevel_item')) {
+ if (b64mids && b64mids.includes(submid)) {
+ // Handle collapsed comments if any
+ let collapsedComments = document.querySelectorAll('.collapsed-comments');
+ if (collapsedComments.length) {
+ let scrollToId = collapsedComments[0].id.substring(19);
+ showHideComments(scrollToId);
+ }
- // Handle collapsed comments if any
- let collapsedComments = document.querySelectorAll('.collapsed-comments');
- if (collapsedComments.length) {
- let scrolltoid = collapsedComments[0].id.substring(19);
- let collapsedComment = document.getElementById('collapsed-comments-' + scrolltoid);
- let hideCommentsLabel = document.getElementById('hide-comments-label-' + scrolltoid);
- let hideCommentsTotal = document.getElementById('hide-comments-total-' + scrolltoid);
+ collapseHeight();
- if (collapsedComment) collapsedComment.style.display = 'block';
- if (hideCommentsLabel) hideCommentsLabel.innerHTML = aStr.showfewer;
- if (hideCommentsTotal) hideCommentsTotal.style.display = 'none';
- }
+ if (!thread.classList.contains('toplevel_item')) {
+ // Scroll to the target element
+ let navHeight = document.getElementById('navbar-top') ? document.getElementById('navbar-top').offsetHeight : 0;
+ window.scrollTo({
+ top: getOffsetTopRelativeToBody(thread) - navHeight,
+ behavior: 'smooth'
+ });
+ }
- // Scroll to the target element
- let navHeight = document.querySelector('nav') ? document.querySelector('nav').offsetHeight : 0;
- window.scrollTo({
- top: thread.offsetTop - navHeight,
- behavior: 'smooth'
- });
+ let id = thread.id.replace('thread-wrapper-', '');
+ let content = document.getElementById('wall-item-content-wrapper-' + id);
+ content.classList.add('item-highlight-fade');
+ }
+ });
+}
- // Add highlight class
- thread.classList.add('item-highlight');
- }
- });
+function getOffsetTopRelativeToBody(element) {
+ let offsetTop = 0;
+ while (element) {
+ offsetTop += element.offsetTop;
+ element = element.offsetParent;
+ }
+ return offsetTop;
}
function collapseHeight() {
@@ -1153,26 +1341,16 @@ function liveUpdate(notify_id) {
var dready = new Date();
console.log('DATA ready in: ' + (dready - dstart)/1000 + ' seconds.');
- if(update_mode === 'update' || preloadImages) {
- console.log('LOADING images...');
- imagesLoaded(data, function () {
- var iready = new Date();
- console.log('IMAGES ready in: ' + (iready - dready)/1000 + ' seconds.');
-
- page_load = false;
- scroll_next = false;
- updateConvItems(update_mode,data);
+ console.log('LOADING images...');
+ imagesLoaded(data, function () {
+ var iready = new Date();
+ console.log('IMAGES ready in: ' + (iready - dready)/1000 + ' seconds.');
- in_progress = false;
- });
- }
- else {
page_load = false;
scroll_next = false;
updateConvItems(update_mode,data);
in_progress = false;
- }
-
+ });
});
}
@@ -1266,6 +1444,277 @@ function justifyPhotosAjax(id) {
$('#' + id).justifiedGallery('norewind').on('jg.complete', function(e){ justifiedGalleryActive = false; });
}
+function request(id, mid, verb, parent, uuid, userClick) {
+
+ if (verb === 'load') {
+ const dots = document.getElementById('load-more-dots-' + parent);
+ dots.classList.add('jumping-dots');
+
+ const parent_sub = document.getElementById('wall-item-sub-thread-wrapper-' + parent);
+ const offset = parent_sub.children.length;
+
+ fetch('/request?offset=' + offset + '&verb=' + verb + '&mid=' + mid + '&parent=' + parent + '&module=' + module)
+ .then(response => response.json())
+ .then(obj => {
+ let parser = new DOMParser();
+ let doc = parser.parseFromString(obj.html, 'text/html');
+ let b64mids = [];
+
+ doc.querySelectorAll('.thread-wrapper').forEach(function (e) {
+ let data = JSON.parse(e.dataset.b64mids);
+ b64mids.push(...data);
+ });
+
+ imagesLoaded(doc.querySelectorAll('.wall-item-body img'), function () {
+ injectWithAnimation('wall-item-sub-thread-wrapper-' + parent, doc);
+ dots.classList.remove('jumping-dots');
+
+ const loadmore_progress = document.getElementById('load-more-progress-' + parent);
+ loadmore_progress.style.width = Math.round(100 * parent_sub.children.length / loadmore_progress.dataset.commentsTotal) + '%';
+
+ if (Number(parent_sub.children.length) === Number(loadmore_progress.dataset.commentsTotal)) {
+ const loadmore = document.getElementById('load-more-' + parent);
+ loadmore.remove();
+ }
+
+ updateRelativeTime('.autotime');
+ collapseHeight();
+
+ document.dispatchEvent(new CustomEvent('hz:sse_setNotificationsStatus', { detail: b64mids }));
+ document.dispatchEvent(new Event('hz:sse_bs_counts'));
+ });
+
+ })
+ .catch(error => {
+ console.error('Error fetching data:', error);
+ });
+
+ return;
+ }
+
+ const loading = document.getElementById('like-rotator-' + id);
+
+ if (userClick) {
+ loading.style.display = 'block';
+ }
+
+ if (verb === 'comment') {
+ if (userClick && singlethread_modules.indexOf(module) !== -1) {
+ let stateObj = { b64mid: uuid };
+ history.pushState(stateObj, '', module + '/' + uuid);
+ }
+
+ fetch('/request?verb=' + verb + '&mid=' + mid + '&parent=' + parent + '&module=' + module)
+ .then(response => response.json())
+ .then(obj => {
+ let parser = new DOMParser();
+ let doc = parser.parseFromString(obj.html, 'text/html');
+
+ doc.querySelectorAll('.thread-wrapper').forEach(function (e) {
+ let data = JSON.parse(e.dataset.b64mids);
+ b64mids.push(...data);
+ });
+
+ imagesLoaded(doc.querySelectorAll('.wall-item-body img'), function () {
+ injectWithAnimation('wall-item-sub-thread-wrapper-' + id, doc, true);
+ updateRelativeTime('.autotime');
+ collapseHeight();
+
+ if (userClick) {
+ loading.style.display = 'none';
+ document.dispatchEvent(new CustomEvent('hz:sse_setNotificationsStatus', { detail: b64mids }));
+ document.dispatchEvent(new Event('hz:sse_bs_counts'));
+ }
+ });
+
+ })
+ .catch(error => {
+ console.error('Error fetching data:', error);
+ });
+ }
+ else {
+ fetch('/request?verb=' + verb + '&mid=' + mid + '&parent=' + parent)
+ .then(response => response.json())
+ .then(obj => {
+ const modal = new bootstrap.Modal('#reactions');
+ const modal_content = document.getElementById('reactions_body');
+ const modal_title = document.getElementById('reactions_title');
+ const modal_action = document.getElementById('reactions_action');
+ modal_action.style.display = 'none';
+ modal_title.innerHTML = obj.title;
+ modal_content.innerHTML = '';
+ if (obj.action) {
+ modal_action.innerHTML = '<a href="#" onclick="' + obj.action + '(' + id + ',\'' + verb + '\'); return false;">' + obj.action_label + '</a>';
+ modal_action.style.display = 'block';
+ }
+ console.log(obj)
+ obj.result.forEach(e => {
+ let mod = '';
+ if (e.item_blocked === 4) {
+ mod = '<span onclick="moderate_approve(' + e.id + '); return false;" class="text-success pe-4 d-inline-block"><i class="bi bi-check-lg" ></i></span><span onclick="moderate_drop(' + e.id + '); return false;" class="text-danger pe-4 d-inline-block"><i class="bi bi-trash" ></i></span>';
+ }
+ modal_content.innerHTML += '<a href="' + e.url + '" class="list-group-item list-group-item-action border-0">' + mod + '<img src="' + e.photo + '" class="menu-img-1" loading="lazy">&nbsp;' + e.name + '</a>';
+
+ });
+
+ modal.show();
+ loading.style.display = 'none';
+ })
+ .catch(error => {
+ console.error('Error fetching data:', error);
+ });
+ }
+
+}
+
+function injectWithAnimation(containerId, parsedDoc, overwrite = false) {
+ const container = document.getElementById(containerId);
+ if (!container) return;
+ if (overwrite) container.innerHTML = '';
+
+ const newElements = Array.from(parsedDoc.body.children);
+
+ for (let i = newElements.length - 1; i >= 0; i--) {
+ const el = newElements[i].cloneNode(true);
+ el.classList.add('item-fade-in');
+ container.insertBefore(el, container.firstChild);
+
+ // Remove classes after transition ends
+ const onTransitionEnd = (event) => {
+ el.classList.remove('item-fade-in', 'show');
+ el.removeEventListener('transitionend', onTransitionEnd);
+ };
+ el.addEventListener('transitionend', onTransitionEnd);
+
+ setTimeout(() => {
+ el.classList.add('show');
+ }, (newElements.length - 1 - i) * 30);
+ }
+}
+
+const autoExpand = (function () {
+ const clickedElements = new Set(); // Stores clicked button references
+
+ // We wait 10 seconds for images. Set the timeout here slightly higher.
+ function waitForElement(selector, timeout = 11000) {
+ return new Promise((resolve, reject) => {
+ // Check if the element already exists
+ const element = document.querySelector(selector);
+
+ if (element) {
+ resolve(element);
+ return;
+ }
+
+ // Set up a timeout to reject the promise if the element doesn't appear in time
+ const timer = setTimeout(() => {
+ observer.disconnect();
+ reject(new Error(`Element "${selector}" not found within ${timeout}ms`));
+ }, timeout);
+
+ // Create a MutationObserver to watch for DOM changes
+ const observer = new MutationObserver(() => {
+ const el = document.querySelector(selector);
+ if (el) {
+ clearTimeout(timer);
+ observer.disconnect();
+ resolve(el);
+ }
+ });
+
+ // Start observing the document for changes
+ observer.observe(document.documentElement, {
+ childList: true,
+ subtree: true
+ });
+ });
+ }
+
+ async function autoExpand(id) {
+ const loading = document.getElementById('like-rotator-' + id);
+ let iteration = 0;
+ const maxIterations = 3;
+ clickedElements.clear();
+
+ try {
+ // Step 1: Ensure initial button is clicked
+ const initBtnSelector = '#wall-item-comment-' + id;
+ const initBtn = await waitForElement(initBtnSelector);
+
+ if (!clickedElements.has(initBtn)) {
+ initBtn.click();
+ clickedElements.add(initBtn);
+ iteration++;
+ }
+
+ // Step 2: Loop until no new buttons are found
+ let newButtonsFound;
+
+ const commentSelector = `#wall-item-sub-thread-wrapper-${id} .thread-wrapper`;
+ const commentBtnSelector = `#wall-item-sub-thread-wrapper-${id} .wall-item-comment`;
+
+ do {
+ newButtonsFound = false;
+
+ // Wait for any comment to appear
+ await waitForElement(commentSelector);
+
+ const expandButtons = document.querySelectorAll(commentBtnSelector);
+
+ for (const btn of expandButtons) {
+ if (!clickedElements.has(btn)) {
+ btn.click();
+ clickedElements.add(btn);
+ newButtonsFound = true;
+ // Optional: await waitForElement(...) to wait for new content
+ }
+ }
+
+ // Wait between iterations to allow UI to update
+ if (newButtonsFound) {
+ iteration++;
+ await new Promise(res => setTimeout(res, 700));
+ }
+
+ } while (newButtonsFound && iteration < maxIterations);
+
+ console.log('Replies loaded!');
+
+ loading.style.display = 'none';
+
+ document.dispatchEvent(new CustomEvent('hz:sse_setNotificationsStatus', { detail: b64mids }));
+ document.dispatchEvent(new Event('hz:sse_bs_counts'));
+
+ } catch (error) {
+ loading.style.display = 'none';
+ console.error("autoExpand failed:", error.message);
+ }
+ }
+
+ return autoExpand;
+})();
+
+
+function stringToHexColor(str) {
+ let hash = 0;
+ for (let i = 0; i < str.length; i++) {
+ hash = str.charCodeAt(i) + ((hash << 5) - hash);
+ }
+ let color = "#";
+ for (let i = 0; i < 3; i++) {
+ const value = (hash >> (i * 8)) & 0xFF;
+ color += value.toString(16).padStart(2, '0');
+ }
+ return color;
+}
+
+function stringToHslColor(str) {
+ let stringUniqueHash = [...str].reduce((acc, char) => {
+ return char.charCodeAt(0) + ((acc << 5) - acc);
+ }, 0);
+ return `hsl(${stringUniqueHash % 360}, 65%, 65%)`;
+}
+
function dolike(ident, verb) {
$('#like-rotator-' + ident).show();
@@ -1321,17 +1770,73 @@ function doprofilelike(ident, verb) {
function doreply(parent, ident, owner, hint) {
- var form = $('#comment-edit-form-' + parent.toString());
- form.find('input[name=parent]').val(ident);
- var i = form.find('button[type=submit]');
- var btn = i.html().replace(/<[^>]*>/g, '').trim();
- i.html('<i class="bi bi-arrow-90deg-left"></i> ' + btn);
- var sel = 'wall-item-body-' + ident.toString();
- var quote = window.getSelection().toString().trim();
- form.find('textarea').val("@{" + owner + "}" + ((($(window.getSelection().anchorNode).closest("#" + sel).attr("id") != sel) || (quote.length === 0))? " " : "\n[quote]" + quote + "[/quote]\n"));
- $('#comment-edit-text-' + parent.toString()).focus();
+ const modal = new bootstrap.Modal('#reactions');
+ const modal_content = document.getElementById('reactions_body');
+ const modal_title = document.getElementById('reactions_title');
+ const modal_action = document.getElementById('reactions_action');
+
+ modal_action.style.display = 'none';
+ modal_title.innerHTML = hint;
+
+ const preview = document.getElementById('comment-edit-preview-' + parent.toString());
+ preview.innerHTML = '';
+
+ // Get the form element by ID
+ const form = document.getElementById('comment-edit-wrapper-' + parent.toString());
+ if (!form) return;
+
+ modal_content.innerHTML = '';
+ modal_content.append(form);
+
+ // Set the value of the input named 'parent'
+ const parentInput = form.querySelector('input[name=parent]');
+ if (parentInput) {
+ parentInput.value = ident;
+ }
+
+ // Find the submit button and update its HTML
+ const submitBtn = form.querySelector('button[type=submit]');
+ if (submitBtn) {
+ const btnText = submitBtn.innerHTML.replace(/<[^>]*>/g, '').trim();
+ submitBtn.innerHTML = '<i class="bi bi-arrow-90deg-left"></i> ' + btnText;
+ }
+
+ // Prepare the quote logic
+ const sel = 'wall-item-body-' + ident.toString();
+ const quote = window.getSelection().toString().trim();
+
+ // Check if the selection is inside the correct element
+ let isInSel = false;
+ const anchorNode = window.getSelection().anchorNode;
+ if (anchorNode) {
+ let node = anchorNode.nodeType === 3 ? anchorNode.parentNode : anchorNode;
+ while (node) {
+ if (node.id === sel) {
+ isInSel = true;
+ break;
+ }
+ node = node.parentNode;
+ }
+ }
+
+ modal.show();
+
+ // Set the textarea value
+ const textarea = form.querySelector('textarea');
+ if (textarea) {
+ let commentBody = localStorage.getItem('comment_body-' + ident);
+ if (commentBody) {
+ textarea.value = commentBody;
+ }
+ else {
+ textarea.value = "@{" + owner + "}" + ((!isInSel || quote.length === 0) ? " " : "\n[quote]" + quote + "[/quote]\n");
+ }
+
+ textarea.focus();
+ }
}
+
function doscroll(parent, hidden) {
var id;
var x = '#hide-comments-outer-' + hidden.toString();
@@ -1345,6 +1850,7 @@ function doscroll(parent, hidden) {
var c = '#collapsed-comments-' + x;
if($(c).length !== 0 && (! $(c).is(':visible'))) {
showHideComments(x);
+ collapseHeight();
pos += $(c).height();
}
}
@@ -1542,11 +2048,13 @@ function post_comment(id) {
window.location.href = data.reload;
}
+ close_modal();
localStorage.removeItem("comment_body-" + id);
$("#comment-edit-preview-" + id).hide();
$("#comment-edit-text-" + id).val('').blur().attr('placeholder', aStr.comment);
- $('#wall-item-comment-wrapper-' + id).before(data.html);
+ $('#wall-item-sub-thread-wrapper-' + data.thr_parent_id).append(data.html);
+
updateRelativeTime('.autotime');
$('body').css('cursor', 'unset');
collapseHeight();
diff --git a/view/nb/hmessages.po b/view/nb/hmessages.po
index 3d0fa3dca..4f1d47d8e 100644
--- a/view/nb/hmessages.po
+++ b/view/nb/hmessages.po
@@ -9,8 +9,8 @@ msgid ""
msgstr ""
"Project-Id-Version: 8.6RC1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-09-20 12:31+0200\n"
-"PO-Revision-Date: 2024-11-04 10:19+0100\n"
+"POT-Creation-Date: 2025-06-24 07:52+0000\n"
+"PO-Revision-Date: 2025-07-13 15:47+0200\n"
"Last-Translator: Harald Eilertsen <haraldei@anduin.net>\n"
"Language-Team: Norwegian Bokmål <l10n-no@lister.huftis.org>\n"
"Language: nb_NO\n"
@@ -18,90 +18,633 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1 ? 1 : 0);\n"
-"X-Generator: Lokalize 24.08.2\n"
+"X-Generator: Poedit 3.6\n"
-#: ../../include/bbcode.php:234 ../../include/bbcode.php:994
-#: ../../include/bbcode.php:1659 ../../include/bbcode.php:1667
-msgid "Image/photo"
-msgstr "Bilde/fotografi"
+#: ../../util/nconfig.php:34
+msgid "Source channel not found."
+msgstr "Fant ikke kildekanalen."
-#: ../../include/bbcode.php:286
-msgid "Encrypted content"
-msgstr "Kryptert innhold"
+#: ../../view/theme/redbasic/php/config.php:19 ../../include/text.php:3544
+#: ../../addon/cart/submodules/orderoptions.php:335
+#: ../../addon/cart/submodules/orderoptions.php:359
+#: ../../addon/cart/submodules/orderoptions.php:435
+#: ../../addon/cart/submodules/orderoptions.php:459
+#: ../../Zotlabs/Module/Admin/Site.php:233
+msgid "Default"
+msgstr "Standard"
-#: ../../include/bbcode.php:342
-#, php-format
-msgid "Install %1$s element %2$s"
+#: ../../view/theme/redbasic/php/config.php:20
+#: ../../view/theme/redbasic/php/config.php:23
+msgid "Focus (Hubzilla default)"
+msgstr "Focus (Hubzilla standard)"
+
+#: ../../view/theme/redbasic/php/config.php:193 ../../include/js_strings.php:21
+#: ../../include/conversation.php:1127 ../../addon/irc/irc.php:45
+#: ../../addon/socialauth/Mod_SocialAuth.php:341
+#: ../../addon/hubwall/hubwall.php:96
+#: ../../addon/openclipatar/openclipatar.php:54
+#: ../../addon/hzfiles/hzfiles.php:86 ../../addon/piwik/piwik.php:95
+#: ../../addon/redphotos/redphotos.php:136
+#: ../../addon/pageheader/Mod_Pageheader.php:52
+#: ../../addon/ijpost/Mod_Ijpost.php:72 ../../addon/redred/Mod_Redred.php:88
+#: ../../addon/startpage/Mod_Startpage.php:75
+#: ../../addon/libertree/Mod_Libertree.php:68 ../../addon/logrot/logrot.php:35
+#: ../../addon/dwpost/Mod_Dwpost.php:78 ../../addon/diaspora/diaspora.php:90
+#: ../../addon/diaspora/Mod_Diaspora.php:101
+#: ../../addon/fuzzloc/Mod_Fuzzloc.php:54 ../../addon/mailtest/mailtest.php:100
+#: ../../addon/nsfw/Mod_Nsfw.php:59 ../../addon/pumpio/Mod_Pumpio.php:113
+#: ../../addon/wppost/Mod_Wppost.php:107 ../../addon/wiki/Mod_Wiki.php:218
+#: ../../addon/wiki/Mod_Wiki.php:907 ../../addon/wiki/Widget/Wiki_pages.php:72
+#: ../../addon/xmpp/Mod_Xmpp.php:70 ../../addon/statusnet/statusnet.php:602
+#: ../../addon/statusnet/Mod_Statusnet.php:191
+#: ../../addon/statusnet/Mod_Statusnet.php:249
+#: ../../addon/statusnet/Mod_Statusnet.php:304
+#: ../../addon/ljpost/Mod_Ljpost.php:80 ../../addon/faces/Mod_Faces.php:141
+#: ../../addon/cart/submodules/hzservices.php:645
+#: ../../addon/cart/submodules/subscriptions.php:410
+#: ../../addon/cart/submodules/manualcat.php:248
+#: ../../addon/cart/submodules/orderoptions.php:312
+#: ../../addon/cart/submodules/orderoptions.php:412
+#: ../../addon/cart/cart.php:1424 ../../addon/cart/Settings/Cart.php:132
+#: ../../addon/cart/Settings/Cart.php:142
+#: ../../addon/twitter/Mod_Twitter.php:182
+#: ../../addon/redfiles/redfiles.php:124
+#: ../../addon/openstreetmap/openstreetmap.php:147
+#: ../../addon/workflow/workflow.php:1490
+#: ../../addon/workflow/workflow.php:1549
+#: ../../addon/workflow/workflow.php:1668
+#: ../../addon/workflow/workflow.php:2815
+#: ../../addon/workflow/Settings/Mod_WorkflowSettings.php:94
+#: ../../addon/skeleton/Mod_Skeleton.php:49
+#: ../../addon/content_import/Mod_content_import.php:140
+#: ../../addon/rtof/Mod_Rtof.php:70 ../../addon/nofed/Mod_Nofed.php:51
+#: ../../addon/photocache/Mod_Photocache.php:63
+#: ../../addon/likebanner/likebanner.php:57
+#: ../../Zotlabs/Lib/ThreadItem.php:809 ../../Zotlabs/Storage/Browser.php:390
+#: ../../Zotlabs/Module/Oauth.php:110 ../../Zotlabs/Module/Import_items.php:125
+#: ../../Zotlabs/Module/Thing.php:364 ../../Zotlabs/Module/Thing.php:414
+#: ../../Zotlabs/Module/Tokens.php:294 ../../Zotlabs/Module/Pdledit.php:137
+#: ../../Zotlabs/Module/Connect.php:107
+#: ../../Zotlabs/Module/Filestorage.php:208
+#: ../../Zotlabs/Module/Contactedit.php:415
+#: ../../Zotlabs/Module/Contactedit.php:448
+#: ../../Zotlabs/Module/Pconfig.php:117 ../../Zotlabs/Module/Appman.php:230
+#: ../../Zotlabs/Module/Connedit.php:714 ../../Zotlabs/Module/Defperms.php:262
+#: ../../Zotlabs/Module/Locs.php:125
+#: ../../Zotlabs/Module/Admin/Accounts.php:209
+#: ../../Zotlabs/Module/Admin/Themes.php:174
+#: ../../Zotlabs/Module/Admin/Addons.php:193
+#: ../../Zotlabs/Module/Admin/Features.php:67
+#: ../../Zotlabs/Module/Admin/Profs.php:179
+#: ../../Zotlabs/Module/Admin/Channels.php:169
+#: ../../Zotlabs/Module/Admin/Account_edit.php:78
+#: ../../Zotlabs/Module/Admin/Logs.php:85
+#: ../../Zotlabs/Module/Admin/Site.php:403
+#: ../../Zotlabs/Module/Admin/Security.php:130
+#: ../../Zotlabs/Module/Affinity.php:84 ../../Zotlabs/Module/Permcats.php:257
+#: ../../Zotlabs/Module/Xchan.php:15 ../../Zotlabs/Module/Group.php:151
+#: ../../Zotlabs/Module/Group.php:160 ../../Zotlabs/Module/Invite.php:547
+#: ../../Zotlabs/Module/Mitem.php:257 ../../Zotlabs/Module/Photos.php:1057
+#: ../../Zotlabs/Module/Photos.php:1096 ../../Zotlabs/Module/Photos.php:1197
+#: ../../Zotlabs/Module/Sources.php:123 ../../Zotlabs/Module/Sources.php:160
+#: ../../Zotlabs/Module/Profiles.php:738 ../../Zotlabs/Module/Chat.php:208
+#: ../../Zotlabs/Module/Chat.php:247 ../../Zotlabs/Module/Regate.php:408
+#: ../../Zotlabs/Module/Setup.php:322 ../../Zotlabs/Module/Setup.php:362
+#: ../../Zotlabs/Module/Editpost.php:88 ../../Zotlabs/Module/Oauth2.php:115
+#: ../../Zotlabs/Module/Settings/Display.php:187
+#: ../../Zotlabs/Module/Settings/Network.php:62
+#: ../../Zotlabs/Module/Settings/Channel_home.php:91
+#: ../../Zotlabs/Module/Settings/Account.php:109
+#: ../../Zotlabs/Module/Settings/Editor.php:42
+#: ../../Zotlabs/Module/Settings/Features.php:48
+#: ../../Zotlabs/Module/Settings/Directory.php:42
+#: ../../Zotlabs/Module/Settings/Manage.php:43
+#: ../../Zotlabs/Module/Settings/Privacy.php:123
+#: ../../Zotlabs/Module/Settings/Events.php:42
+#: ../../Zotlabs/Module/Settings/Photos.php:42
+#: ../../Zotlabs/Module/Settings/Conversation.php:44
+#: ../../Zotlabs/Module/Settings/Channel.php:230
+#: ../../Zotlabs/Module/Settings/Profiles.php:52
+#: ../../Zotlabs/Module/Settings/Connections.php:42
+#: ../../Zotlabs/Module/Settings/Calendar.php:42
+#: ../../Zotlabs/Module/Settings/Multifactor.php:85
+#: ../../Zotlabs/Module/Email_validation.php:41
+#: ../../Zotlabs/Module/Import.php:623
+msgid "Submit"
+msgstr "Lagre"
+
+#: ../../view/theme/redbasic/php/config.php:197
+msgid "Theme settings"
+msgstr "Instillinger for utseende"
+
+#: ../../view/theme/redbasic/php/config.php:198
+msgid "Dark style"
+msgstr "Mørk drakt"
+
+#: ../../view/theme/redbasic/php/config.php:199
+msgid "Light style"
+msgstr "Lys drakt"
+
+#: ../../view/theme/redbasic/php/config.php:200
+msgid "Common settings"
+msgstr "Felles innstillinger"
+
+#: ../../view/theme/redbasic/php/config.php:201
+msgid "Primary theme color"
+msgstr "Primærfarge for tema"
+
+#: ../../view/theme/redbasic/php/config.php:201
+#: ../../view/theme/redbasic/php/config.php:202
+#: ../../view/theme/redbasic/php/config.php:203
+#: ../../view/theme/redbasic/php/config.php:204
+#: ../../view/theme/redbasic/php/config.php:205
+msgid "Current color, leave empty for default"
+msgstr "Gjeldende farge, la feltet stå tomt for å bruke standardfarge"
+
+#: ../../view/theme/redbasic/php/config.php:202
+msgid "Success theme color"
+msgstr "Farge for vellykket utførelse"
+
+#: ../../view/theme/redbasic/php/config.php:203
+msgid "Info theme color"
+msgstr "Farge for informasjon"
+
+#: ../../view/theme/redbasic/php/config.php:204
+msgid "Warning theme color"
+msgstr "Farge for advarsler"
+
+#: ../../view/theme/redbasic/php/config.php:205
+msgid "Danger theme color"
+msgstr "Farge for fare"
+
+#: ../../view/theme/redbasic/php/config.php:206
+msgid "Default to dark mode"
+msgstr "Bruk mørk drakt som standard"
+
+#: ../../view/theme/redbasic/php/config.php:206
+#: ../../view/theme/redbasic/php/config.php:207
+#: ../../view/theme/redbasic/php/config.php:208
+#: ../../view/theme/redbasic/php/config.php:220
+#: ../../include/conversation.php:1159
+#: ../../addon/socialauth/Mod_SocialAuth.php:218
+#: ../../addon/ijpost/Mod_Ijpost.php:61 ../../addon/redred/Mod_Redred.php:61
+#: ../../addon/libertree/Mod_Libertree.php:57
+#: ../../addon/dwpost/Mod_Dwpost.php:59 ../../addon/dwpost/Mod_Dwpost.php:63
+#: ../../addon/diaspora/Mod_Diaspora.php:70
+#: ../../addon/pumpio/Mod_Pumpio.php:92 ../../addon/pumpio/Mod_Pumpio.php:96
+#: ../../addon/pumpio/Mod_Pumpio.php:100 ../../addon/wppost/Mod_Wppost.php:84
+#: ../../addon/wppost/Mod_Wppost.php:88 ../../addon/wppost/Mod_Wppost.php:92
+#: ../../addon/wiki/Mod_Wiki.php:230 ../../addon/wiki/Mod_Wiki.php:231
+#: ../../addon/statusnet/Mod_Statusnet.php:258
+#: ../../addon/statusnet/Mod_Statusnet.php:280
+#: ../../addon/statusnet/Mod_Statusnet.php:289
+#: ../../addon/ljpost/Mod_Ljpost.php:61 ../../addon/ljpost/Mod_Ljpost.php:65
+#: ../../addon/ljpost/Mod_Ljpost.php:69
+#: ../../addon/cart/submodules/hzservices.php:67
+#: ../../addon/cart/submodules/hzservices.php:651
+#: ../../addon/cart/submodules/hzservices.php:655
+#: ../../addon/cart/submodules/subscriptions.php:153
+#: ../../addon/cart/submodules/subscriptions.php:425
+#: ../../addon/cart/submodules/paypalbuttonV2.php:88
+#: ../../addon/cart/submodules/paypalbuttonV2.php:98
+#: ../../addon/cart/submodules/paypalbutton.php:87
+#: ../../addon/cart/submodules/paypalbutton.php:95
+#: ../../addon/cart/submodules/manualcat.php:63
+#: ../../addon/cart/submodules/manualcat.php:254
+#: ../../addon/cart/submodules/manualcat.php:258
+#: ../../addon/cart/submodules/orderoptions.php:72
+#: ../../addon/cart/submodules/orderoptions.php:338
+#: ../../addon/cart/submodules/orderoptions.php:362
+#: ../../addon/cart/submodules/orderoptions.php:438
+#: ../../addon/cart/submodules/orderoptions.php:462
+#: ../../addon/cart/cart.php:1418 ../../addon/cart/Settings/Cart.php:61
+#: ../../addon/cart/Settings/Cart.php:73
+#: ../../addon/twitter/Mod_Twitter.php:160
+#: ../../addon/twitter/Mod_Twitter.php:169
+#: ../../addon/content_import/Mod_content_import.php:135
+#: ../../addon/content_import/Mod_content_import.php:136
+#: ../../addon/rtof/Mod_Rtof.php:47 ../../addon/nofed/Mod_Nofed.php:40
+#: ../../boot.php:1766 ../../Zotlabs/Lib/Libzotdir.php:166
+#: ../../Zotlabs/Lib/Libzotdir.php:167 ../../Zotlabs/Lib/Libzotdir.php:169
+#: ../../Zotlabs/Storage/Browser.php:314 ../../Zotlabs/Storage/Browser.php:315
+#: ../../Zotlabs/Storage/Browser.php:316 ../../Zotlabs/Storage/Browser.php:397
+#: ../../Zotlabs/Storage/Browser.php:399 ../../Zotlabs/Storage/Browser.php:562
+#: ../../Zotlabs/Module/Filestorage.php:203
+#: ../../Zotlabs/Module/Filestorage.php:211
+#: ../../Zotlabs/Module/Contactedit.php:270
+#: ../../Zotlabs/Module/Contactedit.php:315
+#: ../../Zotlabs/Module/Register.php:515 ../../Zotlabs/Module/Connedit.php:622
+#: ../../Zotlabs/Module/Defperms.php:195 ../../Zotlabs/Module/Api.php:101
+#: ../../Zotlabs/Module/Menu.php:163 ../../Zotlabs/Module/Menu.php:222
+#: ../../Zotlabs/Module/Admin/Site.php:307
+#: ../../Zotlabs/Module/Permcats.php:247 ../../Zotlabs/Module/Group.php:138
+#: ../../Zotlabs/Module/Group.php:139 ../../Zotlabs/Module/Group.php:148
+#: ../../Zotlabs/Module/Group.php:250 ../../Zotlabs/Module/Group.php:302
+#: ../../Zotlabs/Module/Group.php:303 ../../Zotlabs/Module/Mitem.php:176
+#: ../../Zotlabs/Module/Mitem.php:177 ../../Zotlabs/Module/Mitem.php:254
+#: ../../Zotlabs/Module/Mitem.php:255 ../../Zotlabs/Module/Photos.php:668
+#: ../../Zotlabs/Module/Sources.php:122 ../../Zotlabs/Module/Sources.php:157
+#: ../../Zotlabs/Module/Profiles.php:674 ../../Zotlabs/Module/Profiles.php:684
+#: ../../Zotlabs/Module/Profiles.php:692 ../../Zotlabs/Module/Profiles.php:696
+#: ../../Zotlabs/Module/Settings/Display.php:87
+#: ../../Zotlabs/Module/Settings/Privacy.php:133
+#: ../../Zotlabs/Module/Settings/Privacy.php:134
+#: ../../Zotlabs/Module/Settings/Privacy.php:135
+#: ../../Zotlabs/Module/Settings/Privacy.php:136
+#: ../../Zotlabs/Module/Settings/Privacy.php:137
+#: ../../Zotlabs/Module/Settings/Privacy.php:138
+#: ../../Zotlabs/Module/Settings/Channel.php:225
+#: ../../Zotlabs/Module/Settings/Multifactor.php:82
+#: ../../Zotlabs/Module/Import.php:612 ../../Zotlabs/Module/Import.php:616
+#: ../../Zotlabs/Module/Import.php:617
+msgid "No"
+msgstr "Nei"
+
+#: ../../view/theme/redbasic/php/config.php:206
+#: ../../view/theme/redbasic/php/config.php:207
+#: ../../view/theme/redbasic/php/config.php:208
+#: ../../view/theme/redbasic/php/config.php:220
+#: ../../include/conversation.php:1159
+#: ../../addon/socialauth/Mod_SocialAuth.php:218
+#: ../../addon/ijpost/Mod_Ijpost.php:61 ../../addon/redred/Mod_Redred.php:61
+#: ../../addon/libertree/Mod_Libertree.php:57
+#: ../../addon/dwpost/Mod_Dwpost.php:59 ../../addon/dwpost/Mod_Dwpost.php:63
+#: ../../addon/diaspora/Mod_Diaspora.php:70
+#: ../../addon/pumpio/Mod_Pumpio.php:92 ../../addon/pumpio/Mod_Pumpio.php:96
+#: ../../addon/pumpio/Mod_Pumpio.php:100 ../../addon/wppost/Mod_Wppost.php:84
+#: ../../addon/wppost/Mod_Wppost.php:88 ../../addon/wppost/Mod_Wppost.php:92
+#: ../../addon/wiki/Mod_Wiki.php:230 ../../addon/wiki/Mod_Wiki.php:231
+#: ../../addon/statusnet/Mod_Statusnet.php:258
+#: ../../addon/statusnet/Mod_Statusnet.php:280
+#: ../../addon/statusnet/Mod_Statusnet.php:289
+#: ../../addon/ljpost/Mod_Ljpost.php:61 ../../addon/ljpost/Mod_Ljpost.php:65
+#: ../../addon/ljpost/Mod_Ljpost.php:69
+#: ../../addon/cart/submodules/hzservices.php:67
+#: ../../addon/cart/submodules/hzservices.php:651
+#: ../../addon/cart/submodules/hzservices.php:655
+#: ../../addon/cart/submodules/subscriptions.php:153
+#: ../../addon/cart/submodules/subscriptions.php:425
+#: ../../addon/cart/submodules/paypalbuttonV2.php:88
+#: ../../addon/cart/submodules/paypalbuttonV2.php:98
+#: ../../addon/cart/submodules/paypalbutton.php:87
+#: ../../addon/cart/submodules/paypalbutton.php:95
+#: ../../addon/cart/submodules/manualcat.php:63
+#: ../../addon/cart/submodules/manualcat.php:254
+#: ../../addon/cart/submodules/manualcat.php:258
+#: ../../addon/cart/submodules/orderoptions.php:72
+#: ../../addon/cart/submodules/orderoptions.php:337
+#: ../../addon/cart/submodules/orderoptions.php:361
+#: ../../addon/cart/submodules/orderoptions.php:437
+#: ../../addon/cart/submodules/orderoptions.php:461
+#: ../../addon/cart/cart.php:1418 ../../addon/cart/Settings/Cart.php:61
+#: ../../addon/cart/Settings/Cart.php:73
+#: ../../addon/twitter/Mod_Twitter.php:160
+#: ../../addon/twitter/Mod_Twitter.php:169
+#: ../../addon/content_import/Mod_content_import.php:135
+#: ../../addon/content_import/Mod_content_import.php:136
+#: ../../addon/rtof/Mod_Rtof.php:47 ../../addon/nofed/Mod_Nofed.php:40
+#: ../../boot.php:1766 ../../Zotlabs/Lib/Libzotdir.php:166
+#: ../../Zotlabs/Lib/Libzotdir.php:167 ../../Zotlabs/Lib/Libzotdir.php:169
+#: ../../Zotlabs/Storage/Browser.php:314 ../../Zotlabs/Storage/Browser.php:315
+#: ../../Zotlabs/Storage/Browser.php:316 ../../Zotlabs/Storage/Browser.php:397
+#: ../../Zotlabs/Storage/Browser.php:399 ../../Zotlabs/Storage/Browser.php:562
+#: ../../Zotlabs/Module/Filestorage.php:203
+#: ../../Zotlabs/Module/Filestorage.php:211
+#: ../../Zotlabs/Module/Contactedit.php:270
+#: ../../Zotlabs/Module/Register.php:515 ../../Zotlabs/Module/Defperms.php:195
+#: ../../Zotlabs/Module/Api.php:100 ../../Zotlabs/Module/Menu.php:163
+#: ../../Zotlabs/Module/Menu.php:222 ../../Zotlabs/Module/Admin/Site.php:309
+#: ../../Zotlabs/Module/Permcats.php:247 ../../Zotlabs/Module/Group.php:138
+#: ../../Zotlabs/Module/Group.php:139 ../../Zotlabs/Module/Group.php:148
+#: ../../Zotlabs/Module/Group.php:250 ../../Zotlabs/Module/Group.php:302
+#: ../../Zotlabs/Module/Group.php:303 ../../Zotlabs/Module/Mitem.php:176
+#: ../../Zotlabs/Module/Mitem.php:177 ../../Zotlabs/Module/Mitem.php:254
+#: ../../Zotlabs/Module/Mitem.php:255 ../../Zotlabs/Module/Photos.php:668
+#: ../../Zotlabs/Module/Sources.php:122 ../../Zotlabs/Module/Sources.php:157
+#: ../../Zotlabs/Module/Profiles.php:674 ../../Zotlabs/Module/Profiles.php:684
+#: ../../Zotlabs/Module/Profiles.php:692 ../../Zotlabs/Module/Profiles.php:696
+#: ../../Zotlabs/Module/Settings/Display.php:87
+#: ../../Zotlabs/Module/Settings/Privacy.php:133
+#: ../../Zotlabs/Module/Settings/Privacy.php:134
+#: ../../Zotlabs/Module/Settings/Privacy.php:135
+#: ../../Zotlabs/Module/Settings/Privacy.php:136
+#: ../../Zotlabs/Module/Settings/Privacy.php:137
+#: ../../Zotlabs/Module/Settings/Privacy.php:138
+#: ../../Zotlabs/Module/Settings/Channel.php:225
+#: ../../Zotlabs/Module/Settings/Multifactor.php:82
+#: ../../Zotlabs/Module/Import.php:612 ../../Zotlabs/Module/Import.php:616
+#: ../../Zotlabs/Module/Import.php:617
+msgid "Yes"
+msgstr "Ja"
+
+#: ../../view/theme/redbasic/php/config.php:207
+msgid "Always use light icons for navbar"
+msgstr "Alltid bruk lyse ikoner i navigasjonslinjen"
+
+#: ../../view/theme/redbasic/php/config.php:207
+msgid "Enable this option if you use a dark navbar color in light mode"
msgstr ""
+"Slå på dette valget om du bruker mørk farge for navigasjonslinjen med lys "
+"drakt"
-#: ../../include/bbcode.php:346
+#: ../../view/theme/redbasic/php/config.php:208
+msgid "Narrow navbar"
+msgstr "Smal navigasjonslinje"
+
+#: ../../view/theme/redbasic/php/config.php:209
+msgid "Navigation bar background color"
+msgstr "Navigasjonslinjens bakgrunnsfarge"
+
+#: ../../view/theme/redbasic/php/config.php:210
+msgid "Dark navigation bar background color"
+msgstr "Mørk bakgrunnsfarge for navigasjonslinjen"
+
+#: ../../view/theme/redbasic/php/config.php:211
+msgid "Set the background color"
+msgstr "Angi bakgrunnsfargen"
+
+#: ../../view/theme/redbasic/php/config.php:212
+msgid "Set the dark background color"
+msgstr "Angi bakgrunnsfargen for mørk drakt"
+
+#: ../../view/theme/redbasic/php/config.php:213
+msgid "Set the background image"
+msgstr "Angi bakgrunnsbilde"
+
+#: ../../view/theme/redbasic/php/config.php:214
+msgid "Set the dark background image"
+msgstr "Angi bakgrunnsbilde for mørk drakt"
+
+#: ../../view/theme/redbasic/php/config.php:215
+msgid "Set font-size for the entire application"
+msgstr "Angi skriftstørrelsen for hele programmet"
+
+#: ../../view/theme/redbasic/php/config.php:215
+msgid "Examples: 1rem, 100%, 16px"
+msgstr "For eksempel: 1rem, 100%, 16px"
+
+#: ../../view/theme/redbasic/php/config.php:216
+msgid "Set radius of corners in rem"
+msgstr "Angi hjørneradius i rem"
+
+#: ../../view/theme/redbasic/php/config.php:216
+msgid "Leave empty for default radius"
+msgstr "La feltet stå tomt for å bruke standard radius"
+
+#: ../../view/theme/redbasic/php/config.php:217
+msgid "Set maximum width of content region in rem"
+msgstr "Set maksbredde for hovedregionen i rem"
+
+#: ../../view/theme/redbasic/php/config.php:217
+msgid "Leave empty for default width"
+msgstr "La feltet stå tomt for å bruke standard bredde"
+
+#: ../../view/theme/redbasic/php/config.php:218
+msgid "Set size of conversation author photo"
+msgstr "Angi størrelsen for samtalens forfatterbilde"
+
+#: ../../view/theme/redbasic/php/config.php:218
+#: ../../view/theme/redbasic/php/config.php:219
+msgid "Leave empty for default size"
+msgstr "La feltet stå tomt for å bruke standard størrelse"
+
+#: ../../view/theme/redbasic/php/config.php:219
+msgid "Set size of followup author photos"
+msgstr "Angi størrelsen på forfatterbilder ved oppfølging"
+
+#: ../../view/theme/redbasic/php/config.php:220
+msgid "Show advanced settings"
+msgstr "Vis avanserte innstillinger"
+
+#: ../../include/bookmarks.php:34
#, php-format
-msgid ""
-"This post contains an installable %s element, however you lack permissions "
-"to install it on this site."
-msgstr ""
-"Dette innlegget inneholder det installerbare elementet %s, men du mangler "
-"tillatelse til å installere det på dette nettstedet."
+msgid "%1$s's bookmarks"
+msgstr "%1$s sine bokmerker"
-#: ../../include/bbcode.php:356 ../../Zotlabs/Module/Impel.php:47
-msgid "webpage"
-msgstr "nettside"
+#: ../../include/opengraph.php:56
+#, php-format
+msgid "This is the home page of %s."
+msgstr "Dette er hjemmesiden til %s."
-#: ../../include/bbcode.php:359 ../../Zotlabs/Module/Impel.php:57
-msgid "layout"
-msgstr "layout"
+#: ../../include/auth.php:231
+msgid "Delegation session ended."
+msgstr "Delegasjonsøkt avsluttet."
-#: ../../include/bbcode.php:362 ../../Zotlabs/Module/Impel.php:52
-msgid "block"
-msgstr "byggekloss"
+#: ../../include/auth.php:235
+msgid "Logged out."
+msgstr "Logget ut."
-#: ../../include/bbcode.php:365 ../../Zotlabs/Module/Impel.php:64
-msgid "menu"
-msgstr "meny"
+#: ../../include/auth.php:341
+msgid "Email validation is incomplete. Please check your email."
+msgstr "Verifisering via epost er ikke fullført. Sjekk eposten din."
-#: ../../include/bbcode.php:568
-msgid "card"
-msgstr ""
+#: ../../include/auth.php:357
+msgid "Failed authentication"
+msgstr "Mislykket autentisering"
-#: ../../include/bbcode.php:570
-msgid "article"
-msgstr ""
+#: ../../include/auth.php:367 ../../addon/openid/Mod_Openid.php:189
+msgid "Login failed."
+msgstr "Innlogging mislyktes."
-#: ../../include/bbcode.php:572 ../../include/conversation.php:180
-#: ../../include/text.php:2364 ../../include/markdown.php:208
-#: ../../Zotlabs/Module/Tagger.php:79
-msgid "post"
-msgstr "innlegg"
+#: ../../include/oembed.php:159
+msgid "View PDF"
+msgstr "Vis PDF"
+
+#: ../../include/oembed.php:394
+msgid " by "
+msgstr " av "
+
+#: ../../include/oembed.php:395
+msgid " on "
+msgstr "på"
+
+#: ../../include/oembed.php:428
+msgid "Embedded content"
+msgstr "Innebygget innhold"
+
+#: ../../include/oembed.php:437
+msgid "Embedding disabled"
+msgstr "Innbygging avskrudd"
+
+#: ../../include/event.php:35 ../../include/event.php:141
+msgid "l F d, Y \\@ g:i A"
+msgstr "l F d, Y \\@ g:i A"
+
+#: ../../include/event.php:43
+msgid "Starts:"
+msgstr "Starter:"
+
+#: ../../include/event.php:53
+msgid "Finishes:"
+msgstr "Slutter:"
+
+#: ../../include/event.php:67 ../../include/event.php:159
+#: ../../include/channel.php:1642 ../../Zotlabs/Module/Directory.php:354
+msgid "Location:"
+msgstr "Plassering:"
+
+#: ../../include/event.php:141
+msgid "l F d, Y"
+msgstr "l d. F, Y"
+
+#: ../../include/event.php:145
+msgid "Start:"
+msgstr "Start:"
+
+#: ../../include/event.php:149
+msgid "End:"
+msgstr "Slutt:"
+
+#: ../../include/event.php:154 ../../addon/openid/MysqlProvider.php:67
+msgid "Timezone"
+msgstr "Tidssone"
+
+#: ../../include/event.php:1246
+msgid "This event has been added to your calendar."
+msgstr "Denne hendelsen er lagt til i din kalender."
+
+#: ../../include/event.php:1366 ../../include/conversation.php:153
+#: ../../include/text.php:2380 ../../Zotlabs/Module/Tagger.php:77
+#: ../../Zotlabs/Module/Like.php:441
+#: ../../Zotlabs/Module/Channel_calendar.php:209
+msgid "event"
+msgstr "hendelse"
+
+#: ../../include/event.php:1450
+msgid "Not specified"
+msgstr "Ikke spesifisert"
+
+#: ../../include/event.php:1451
+msgid "Needs Action"
+msgstr "Trenger handling"
+
+#: ../../include/event.php:1452
+msgid "Completed"
+msgstr "Ferdig"
+
+#: ../../include/event.php:1453
+msgid "In Process"
+msgstr "Igang"
+
+#: ../../include/event.php:1454
+msgid "Cancelled"
+msgstr "Avbrutt"
+
+#: ../../include/event.php:1535 ../../include/connections.php:790
+#: ../../Zotlabs/Module/Connedit.php:741 ../../Zotlabs/Module/Cdav.php:1377
+msgid "Mobile"
+msgstr "Mobil"
+
+#: ../../include/event.php:1536 ../../include/connections.php:791
+#: ../../Zotlabs/Module/Connedit.php:742 ../../Zotlabs/Module/Cdav.php:1378
+msgid "Home"
+msgstr "Hjem"
+
+#: ../../include/event.php:1537 ../../include/connections.php:792
+msgid "Home, Voice"
+msgstr "Hjem, telefon"
+
+#: ../../include/event.php:1538 ../../include/connections.php:793
+msgid "Home, Fax"
+msgstr "Hjem, fax"
+
+#: ../../include/event.php:1539 ../../include/connections.php:794
+#: ../../Zotlabs/Module/Connedit.php:743 ../../Zotlabs/Module/Cdav.php:1379
+msgid "Work"
+msgstr "Arbeid"
+
+#: ../../include/event.php:1540 ../../include/connections.php:795
+msgid "Work, Voice"
+msgstr "Arbeid, telefon"
+
+#: ../../include/event.php:1541 ../../include/connections.php:796
+msgid "Work, Fax"
+msgstr "Arbeid, fax"
+
+#: ../../include/event.php:1542 ../../include/event.php:1549
+#: ../../include/selectors.php:64 ../../include/selectors.php:81
+#: ../../include/selectors.php:119 ../../include/selectors.php:155
+#: ../../include/connections.php:797 ../../include/connections.php:804
+#: ../../Zotlabs/Access/PermissionRoles.php:362
+#: ../../Zotlabs/Module/Connedit.php:744 ../../Zotlabs/Module/Cdav.php:1380
+msgid "Other"
+msgstr "Annen"
+
+#: ../../include/feedutils.php:863 ../../include/text.php:1602
+msgid "unknown"
+msgstr "ukjent"
+
+#: ../../include/items.php:484 ../../addon/hzfiles/hzfiles.php:75
+#: ../../addon/redphotos/redphotos.php:119
+#: ../../addon/redfiles/redfiles.php:109
+#: ../../Zotlabs/Module/Import_items.php:116
+#: ../../Zotlabs/Module/Dreport.php:10 ../../Zotlabs/Module/Dreport.php:60
+#: ../../Zotlabs/Module/Subthread.php:89 ../../Zotlabs/Module/Group.php:109
+#: ../../Zotlabs/Module/Like.php:332 ../../Zotlabs/Module/Profperm.php:29
+msgid "Permission denied"
+msgstr "Tillatelse avvist"
+
+#: ../../include/items.php:1278
+msgid "Visible to anybody on the internet."
+msgstr "Synlig for alle."
+
+#: ../../include/items.php:1280
+msgid "Visible to you only."
+msgstr "Synlig bare for deg."
-#: ../../include/bbcode.php:576 ../../include/markdown.php:206
+#: ../../include/items.php:1282
+msgid "Visible to anybody in this network."
+msgstr "Synlig for alle i dette nettverket."
+
+#: ../../include/items.php:1284
+msgid "Visible to anybody authenticated."
+msgstr "Synlig for alle som er autentisert."
+
+#: ../../include/items.php:1286
#, php-format
-msgid "%1$s wrote the following %2$s %3$s"
-msgstr "%1$s skrev følgende %2$s %3$s"
+msgid "Visible to anybody on %s."
+msgstr "Synlig for alle på %s."
-#: ../../include/bbcode.php:653 ../../include/bbcode.php:661
-msgid "Click to open/close"
-msgstr "Klikk for å åpne/lukke"
+#: ../../include/items.php:1288
+msgid "Visible to all connections."
+msgstr "Synlig for alle forbindelser."
-#: ../../include/bbcode.php:661 ../../include/markdown.php:278
-msgid "spoiler"
-msgstr ""
+#: ../../include/items.php:1290
+msgid "Visible to approved connections."
+msgstr "Synlig for godkjente forbindelser."
-#: ../../include/bbcode.php:674
-msgid "View article"
-msgstr ""
+#: ../../include/items.php:1292
+msgid "Visible to specific connections."
+msgstr "Synlig for spesifikke forbindelser."
-#: ../../include/bbcode.php:674
-msgid "View summary"
-msgstr ""
+#: ../../include/items.php:3502 ../../Zotlabs/Lib/Activity.php:2225
+#: ../../Zotlabs/Module/Share.php:124
+#, php-format
+msgid "&#x1f501; Repeated %1$s's %2$s"
+msgstr "&#x1f501; videreformidlet %2$s til %1$s "
-#: ../../include/bbcode.php:1127 ../../include/bbcode.php:1312
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWikiPage.php:630
-msgid "Different viewers will see this text differently"
-msgstr "Denne teksten vil se forskjellig ut for ulike besøkende"
+#: ../../include/items.php:4579 ../../Zotlabs/Module/Group.php:63
+#: ../../Zotlabs/Module/Group.php:207
+msgid "Privacy group not found."
+msgstr "Personverngruppen ble ikke funnet."
-#: ../../include/bbcode.php:1635
-msgid "$1 wrote:"
-msgstr "$1 skrev:"
+#: ../../include/items.php:4595
+msgid "Privacy group is empty."
+msgstr "Personverngruppen er tom."
+
+#: ../../include/items.php:4602
+#, php-format
+msgid "Privacy group: %s"
+msgstr "Personverngruppe: %s"
+
+#: ../../include/items.php:4612
+#, php-format
+msgid "Connection: %s"
+msgstr "Forbindelse: %s"
+
+#: ../../include/items.php:4614
+msgid "Connection not found."
+msgstr "Forbindelsen ble ikke funnet."
#: ../../include/features.php:53 ../../Zotlabs/Module/Admin/Features.php:55
#: ../../Zotlabs/Module/Admin/Features.php:56
@@ -122,19 +665,19 @@ msgstr "Kalender"
#: ../../include/features.php:84
msgid "Start calendar week on Monday"
-msgstr ""
+msgstr "Mandag er ukestart"
#: ../../include/features.php:85
msgid "Default is Sunday"
-msgstr ""
+msgstr "Søndag er standard"
#: ../../include/features.php:92
msgid "Event Timezone Selection"
-msgstr ""
+msgstr "Tidssone for arrangementet"
#: ../../include/features.php:93
msgid "Allow event creation in timezones other than your own."
-msgstr ""
+msgstr "Tillat arrangementer i andre tidssoner enn din egen."
#: ../../include/features.php:102
msgid "Channel Home"
@@ -156,11 +699,11 @@ msgstr "Merkelappsky"
msgid "Provide a personal tag cloud on your channel page"
msgstr "Tilby en personlig merkelappsky på din kanalside"
-#: ../../include/features.php:122 ../../include/features.php:356
+#: ../../include/features.php:122 ../../include/features.php:364
msgid "Use blog/list mode"
msgstr "Bruk blogg/listemodus"
-#: ../../include/features.php:123 ../../include/features.php:357
+#: ../../include/features.php:123 ../../include/features.php:365
msgid "Comments will be displayed separately"
msgstr "Kommentarer blir vist separat fra innlegget"
@@ -180,15 +723,15 @@ msgstr ""
#: ../../include/features.php:144
msgid "Conversation"
-msgstr ""
+msgstr "Samtale"
#: ../../include/features.php:156
msgid "Emoji Reactions"
-msgstr ""
+msgstr "Hurtigsvar med Emojier"
#: ../../include/features.php:157
msgid "Add emoji reaction ability to posts"
-msgstr ""
+msgstr "Legger til en knapp for hurtige svar på innlegg med en emoji"
#: ../../include/features.php:164
msgid "Dislike Posts"
@@ -200,49 +743,57 @@ msgstr "Mulighet til å mislike innlegg/kommentarer"
#: ../../include/features.php:172
msgid "Star Posts"
-msgstr "Stjerneinnlegg"
+msgstr "Favorittmerk innlegg"
#: ../../include/features.php:173
-msgid "Ability to mark special posts with a star indicator"
-msgstr "Mulighet til å merke spesielle innlegg med en stjerne"
+msgid "Ability to mark conversations with a star"
+msgstr "Mulighet til å favorittmerke innlegg med en stjerne"
#: ../../include/features.php:180
-msgid "Reply on comment"
-msgstr ""
+msgid "File Posts"
+msgstr "Arkivér innlegg"
#: ../../include/features.php:181
+msgid "Ability to file posts"
+msgstr "Mulighet til å arkivere innlegg i mapper"
+
+#: ../../include/features.php:188
+msgid "Reply on comment"
+msgstr "Svar på kommentar"
+
+#: ../../include/features.php:189
msgid "Ability to reply on selected comment"
-msgstr ""
+msgstr "Mulighet for å svare på en valgt kommentar"
-#: ../../include/features.php:190 ../../Zotlabs/Lib/Apps.php:353
+#: ../../include/features.php:198 ../../Zotlabs/Lib/Apps.php:353
msgid "Directory"
msgstr "Katalog"
-#: ../../include/features.php:194
+#: ../../include/features.php:202
msgid "Advanced Directory Search"
msgstr "Avansert katalogsøk"
-#: ../../include/features.php:195
+#: ../../include/features.php:203
msgid "Allows creation of complex directory search queries"
msgstr "Gjør det mulig å bruke avanserte kriterier ved søk i katalogen"
-#: ../../include/features.php:204
+#: ../../include/features.php:212
msgid "Editor"
-msgstr ""
+msgstr "Innleggsredigering"
-#: ../../include/features.php:208
+#: ../../include/features.php:216
msgid "Post Categories"
msgstr "Innleggskategorier"
-#: ../../include/features.php:209
+#: ../../include/features.php:217
msgid "Add categories to your posts"
msgstr "Legg kategorier til dine innlegg"
-#: ../../include/features.php:216
+#: ../../include/features.php:224
msgid "Large Photos"
msgstr "Store bilder"
-#: ../../include/features.php:217
+#: ../../include/features.php:225
msgid ""
"Include large (1024px) photo thumbnails in posts. If not enabled, use small "
"(640px) photo thumbnails"
@@ -250,48 +801,48 @@ msgstr ""
"Inkluder store (1024px) småbilder i innlegg. Hvis denne ikke er påskrudd, "
"bruk små (640px) småbilder"
-#: ../../include/features.php:224
+#: ../../include/features.php:232
msgid "Even More Encryption"
msgstr "Enda mer kryptering"
-#: ../../include/features.php:225
+#: ../../include/features.php:233
msgid ""
"Allow optional encryption of content end-to-end with a shared secret key"
msgstr ""
"Tillat valgfri kryptering av innhold ende-til-ende via en delt hemmelig "
"nøkkel"
-#: ../../include/features.php:232
+#: ../../include/features.php:240
msgid "Disable Comments"
msgstr "Slå av kommentarer"
-#: ../../include/features.php:233
+#: ../../include/features.php:241
msgid "Provide the option to disable comments for a post"
-msgstr "Slår på valg for å ikke tillate kommentarer til innlegget"
+msgstr "Gjør det mulig å slå av kommentarer for et innlegg"
-#: ../../include/features.php:240
+#: ../../include/features.php:248
msgid "Delayed Posting"
msgstr "Tidfest publisering"
-#: ../../include/features.php:241
+#: ../../include/features.php:249
msgid "Allow posts to be published at a later date"
msgstr "Tillat innlegg å bli publisert på et senere tidspunkt"
-#: ../../include/features.php:248
+#: ../../include/features.php:256
msgid "Content Expiration"
msgstr "Innholdet utløper"
-#: ../../include/features.php:249
+#: ../../include/features.php:257
msgid "Remove posts/comments and/or private messages at a future time"
msgstr ""
"Fjern innlegg/kommentarer og/eller private meldinger på et angitt tidspunkt "
"i fremtiden"
-#: ../../include/features.php:256
+#: ../../include/features.php:264
msgid "Suppress Duplicate Posts/Comments"
msgstr "Forhindre duplikat av innlegg/kommentarer"
-#: ../../include/features.php:257
+#: ../../include/features.php:265
msgid ""
"Prevent posts with identical content to be published with less than two "
"minutes in between submissions."
@@ -299,11 +850,11 @@ msgstr ""
"Forhindre innlegg med identisk innhold fra å bli publisert hvis det er "
"mindre enn to minutter mellom innsendingene."
-#: ../../include/features.php:264
+#: ../../include/features.php:272
msgid "Auto-save drafts of posts and comments"
msgstr "Automatisk lagring av kladd for innlegg og kommentarer"
-#: ../../include/features.php:265
+#: ../../include/features.php:273
msgid ""
"Automatically saves post and comment drafts in local browser storage to help "
"prevent accidental loss of compositions"
@@ -311,62 +862,63 @@ msgstr ""
"Lagrer automatisk utkast til innlegg og kommentarer i nettleserens interne "
"lager for å unngå å tape innholet ved et uhell"
-#: ../../include/features.php:274
+#: ../../include/features.php:282
msgid "Manage"
-msgstr ""
+msgstr "Administrasjon"
-#: ../../include/features.php:278
+#: ../../include/features.php:286
msgid "Navigation Channel Select"
-msgstr "Navigasjon kanalvalg"
+msgstr "Enklere kanalvalg"
-#: ../../include/features.php:279
+#: ../../include/features.php:287
msgid "Change channels directly from within the navigation dropdown menu"
msgstr "Endre kanaler direkte fra navigasjonsmenyen"
-#: ../../include/features.php:288 ../../Zotlabs/Module/Connections.php:347
+#: ../../include/features.php:296 ../../Zotlabs/Lib/Apps.php:344
#: ../../Zotlabs/Widget/Notifications.php:23
+#: ../../Zotlabs/Module/Connections.php:347
msgid "Network"
msgstr "Nettverk"
-#: ../../include/features.php:292
+#: ../../include/features.php:300
msgid "Events Filter"
msgstr "Arrangementsfilter"
-#: ../../include/features.php:293
+#: ../../include/features.php:301
msgid "Ability to display only events"
msgstr "Mulighet for kun å vise arrangementer"
-#: ../../include/features.php:300
+#: ../../include/features.php:308
msgid "Polls Filter"
msgstr "Filer for Spørreskjema"
-#: ../../include/features.php:301
+#: ../../include/features.php:309
msgid "Ability to display only polls"
msgstr "Mulighet for å kun vise spørreskjema"
-#: ../../include/features.php:308 ../../Zotlabs/Widget/Savedsearch.php:89
+#: ../../include/features.php:316 ../../Zotlabs/Widget/Savedsearch.php:89
msgid "Saved Searches"
msgstr "Lagrede søk"
-#: ../../include/features.php:309
+#: ../../include/features.php:317
msgid "Save search terms for re-use"
msgstr "Lagre søkeuttrykk for senere bruk"
-#: ../../include/features.php:316 ../../include/contact_widgets.php:55
-#: ../../Zotlabs/Widget/Filer.php:33
+#: ../../include/features.php:324 ../../include/contact_widgets.php:55
#: ../../Zotlabs/Widget/Activity_filter.php:196
+#: ../../Zotlabs/Widget/Filer.php:33
msgid "Saved Folders"
msgstr "Lagrede mapper"
-#: ../../include/features.php:317
+#: ../../include/features.php:325
msgid "Ability to file posts under folders"
msgstr "Mulighet til å sortere innlegg i mapper"
-#: ../../include/features.php:324
+#: ../../include/features.php:332
msgid "Alternate Stream Order"
msgstr "Alternativer for sortering"
-#: ../../include/features.php:325
+#: ../../include/features.php:333
msgid ""
"Ability to order the stream by last post date, last comment date or "
"unthreaded activities"
@@ -374,814 +926,780 @@ msgstr ""
"Mulighet for å sortere tidslinjen etter dato for siste innlegg, siste "
"kommentar eller som separate aktiviteter"
-#: ../../include/features.php:332
+#: ../../include/features.php:340
msgid "Contact Filter"
msgstr "Kontaktfilter"
-#: ../../include/features.php:333
+#: ../../include/features.php:341
msgid "Ability to display only posts of a selected contact"
msgstr "Mulighet for å kun vise innlegg fra en bestemt kontakt"
-#: ../../include/features.php:340
+#: ../../include/features.php:348
msgid "Forum Filter"
msgstr "Forumfilter"
-#: ../../include/features.php:341
+#: ../../include/features.php:349
msgid "Ability to display only posts of a specific forum"
msgstr "Mulighet for å kun vise innlegg i et bestemt forum"
-#: ../../include/features.php:348
+#: ../../include/features.php:356
msgid "Personal Posts Filter"
msgstr "Filter for personlige innlegg"
-#: ../../include/features.php:349
+#: ../../include/features.php:357
msgid "Ability to display only posts that you've interacted on"
msgstr "Mulighet til å vise kun innlegg du har deltatt på"
-#: ../../include/features.php:366 ../../include/nav.php:470
-#: ../../Zotlabs/Lib/Apps.php:351 ../../Zotlabs/Module/Fbrowser.php:29
+#: ../../include/features.php:374 ../../include/nav.php:470
+#: ../../Zotlabs/Lib/Apps.php:351
#: ../../Zotlabs/Widget/Channel_activities.php:93
+#: ../../Zotlabs/Module/Fbrowser.php:31
msgid "Photos"
msgstr "Bilder"
-#: ../../include/features.php:370
+#: ../../include/features.php:378
msgid "Photo Location"
msgstr "Bildeplassering"
-#: ../../include/features.php:371
+#: ../../include/features.php:379
msgid "If location data is available on uploaded photos, link this to a map."
msgstr ""
"Hvis plasseringsdata er tilgjengelige i opplastede bilder, plasser dette på "
"et kart."
-#: ../../include/features.php:378
+#: ../../include/features.php:386
msgid "Flag Adult Photos"
-msgstr ""
+msgstr "Merk bilder med voksent innhold"
-#: ../../include/features.php:379
+#: ../../include/features.php:387
msgid ""
"Provide photo edit option to hide inappropriate photos from default album "
"view"
msgstr ""
+"Gir mulighet for å merke bilder med upassende innhold fra standard "
+"albumvisning"
-#: ../../include/features.php:388 ../../Zotlabs/Lib/Apps.php:367
+#: ../../include/features.php:396 ../../Zotlabs/Lib/Apps.php:367
#: ../../Zotlabs/Module/Contactedit.php:430
msgid "Profiles"
-msgstr ""
+msgstr "Profiler"
-#: ../../include/features.php:392
+#: ../../include/features.php:400
msgid "Advanced Profiles"
msgstr "Avanserte profiler"
-#: ../../include/features.php:393
+#: ../../include/features.php:401
msgid "Additional profile sections and selections"
msgstr "Ytterlige seksjoner og utvalg til profilen"
-#: ../../include/features.php:400
+#: ../../include/features.php:408
msgid "Profile Import/Export"
msgstr "Profil-import/-eksport"
-#: ../../include/features.php:401
+#: ../../include/features.php:409
msgid "Save and load profile details across sites/channels"
msgstr "Lagre og åpne profildetaljer på tvers av nettsteder/kanaler"
-#: ../../include/features.php:408
+#: ../../include/features.php:416
msgid "Multiple Profiles"
msgstr "Flere profiler"
-#: ../../include/features.php:409
+#: ../../include/features.php:417
msgid "Ability to create multiple profiles"
msgstr "Mulig å lage flere profiler"
-#: ../../include/bookmarks.php:34
-#, php-format
-msgid "%1$s's bookmarks"
-msgstr "%1$s sine bokmerker"
-
-#: ../../include/feedutils.php:851 ../../include/text.php:1580
-msgid "unknown"
-msgstr "ukjent"
-
-#: ../../include/attach.php:156 ../../include/attach.php:205
-#: ../../include/attach.php:278 ../../include/attach.php:329
-#: ../../include/attach.php:431 ../../include/attach.php:445
-#: ../../include/attach.php:452 ../../include/attach.php:534
-#: ../../include/attach.php:1106 ../../include/attach.php:1179
-#: ../../include/attach.php:1344 ../../include/photos.php:31
-#: ../../include/items.php:3928 ../../Zotlabs/Lib/Chatroom.php:135
-#: ../../Zotlabs/Web/WebServer.php:119 ../../Zotlabs/Module/Menu.php:130
-#: ../../Zotlabs/Module/Menu.php:141 ../../Zotlabs/Module/Editblock.php:67
-#: ../../Zotlabs/Module/Channel.php:234 ../../Zotlabs/Module/Channel.php:391
-#: ../../Zotlabs/Module/Channel.php:429
-#: ../../Zotlabs/Module/New_channel.php:106
-#: ../../Zotlabs/Module/New_channel.php:131 ../../Zotlabs/Module/Block.php:24
-#: ../../Zotlabs/Module/Block.php:74 ../../Zotlabs/Module/Setup.php:220
-#: ../../Zotlabs/Module/Bookmarks.php:70 ../../Zotlabs/Module/Api.php:26
-#: ../../Zotlabs/Module/Channel_calendar.php:232
-#: ../../Zotlabs/Module/Editlayout.php:67
-#: ../../Zotlabs/Module/Editlayout.php:90 ../../Zotlabs/Module/Photos.php:71
-#: ../../Zotlabs/Module/Viewconnections.php:28
-#: ../../Zotlabs/Module/Viewconnections.php:33
-#: ../../Zotlabs/Module/Editpost.php:17 ../../Zotlabs/Module/Common.php:38
-#: ../../Zotlabs/Module/Profiles.php:168 ../../Zotlabs/Module/Profiles.php:611
-#: ../../Zotlabs/Module/Network.php:18 ../../Zotlabs/Module/Authtest.php:13
-#: ../../Zotlabs/Module/Invite.php:65 ../../Zotlabs/Module/Invite.php:316
-#: ../../Zotlabs/Module/Sources.php:80 ../../Zotlabs/Module/Suggest.php:32
-#: ../../Zotlabs/Module/Group.php:15 ../../Zotlabs/Module/Group.php:31
-#: ../../Zotlabs/Module/Settings.php:59 ../../Zotlabs/Module/Layouts.php:71
-#: ../../Zotlabs/Module/Layouts.php:78 ../../Zotlabs/Module/Layouts.php:89
-#: ../../Zotlabs/Module/Viewsrc.php:19 ../../Zotlabs/Module/Cover_photo.php:299
-#: ../../Zotlabs/Module/Cover_photo.php:312 ../../Zotlabs/Module/Chat.php:111
-#: ../../Zotlabs/Module/Chat.php:116 ../../Zotlabs/Module/Achievements.php:34
-#: ../../Zotlabs/Module/Appman.php:163
-#: ../../Zotlabs/Module/Service_limits.php:11
-#: ../../Zotlabs/Module/Connedit.php:299
+#: ../../include/attach.php:157 ../../include/attach.php:206
+#: ../../include/attach.php:279 ../../include/attach.php:330
+#: ../../include/attach.php:432 ../../include/attach.php:446
+#: ../../include/attach.php:453 ../../include/attach.php:535
+#: ../../include/attach.php:1115 ../../include/attach.php:1188
+#: ../../include/attach.php:1359 ../../include/photos.php:32
+#: ../../addon/openid/Mod_Id.php:53 ../../addon/keepout/keepout.php:36
+#: ../../addon/cards/Mod_Cards.php:89 ../../addon/cards/Mod_Card_edit.php:51
+#: ../../addon/pumpio/pumpio.php:44
+#: ../../addon/articles/Mod_Article_edit.php:51
+#: ../../addon/articles/Mod_Articles.php:94 ../../addon/wiki/Mod_Wiki.php:63
+#: ../../addon/wiki/Mod_Wiki.php:288 ../../addon/wiki/Mod_Wiki.php:425
+#: ../../Zotlabs/Lib/Chatroom.php:135 ../../Zotlabs/Module/Page.php:34
+#: ../../Zotlabs/Module/Page.php:133 ../../Zotlabs/Module/Display.php:387
+#: ../../Zotlabs/Module/Cover_photo.php:299
+#: ../../Zotlabs/Module/Cover_photo.php:312
#: ../../Zotlabs/Module/Editwebpage.php:68
#: ../../Zotlabs/Module/Editwebpage.php:89
#: ../../Zotlabs/Module/Editwebpage.php:107
#: ../../Zotlabs/Module/Editwebpage.php:121
-#: ../../Zotlabs/Module/Register.php:201 ../../Zotlabs/Module/Thing.php:309
-#: ../../Zotlabs/Module/Thing.php:331 ../../Zotlabs/Module/Thing.php:372
-#: ../../Zotlabs/Module/Blocks.php:73 ../../Zotlabs/Module/Blocks.php:80
-#: ../../Zotlabs/Module/Defperms.php:181 ../../Zotlabs/Module/Locs.php:98
-#: ../../Zotlabs/Module/Page.php:34 ../../Zotlabs/Module/Page.php:133
-#: ../../Zotlabs/Module/Vote.php:19 ../../Zotlabs/Module/Profile_photo.php:390
-#: ../../Zotlabs/Module/Profile_photo.php:421
-#: ../../Zotlabs/Module/Webpages.php:131
-#: ../../Zotlabs/Module/Connections.php:32
+#: ../../Zotlabs/Module/Webpages.php:130 ../../Zotlabs/Module/Network.php:18
+#: ../../Zotlabs/Module/Service_limits.php:11
+#: ../../Zotlabs/Module/Thing.php:316 ../../Zotlabs/Module/Thing.php:338
+#: ../../Zotlabs/Module/Thing.php:377 ../../Zotlabs/Module/Pdledit.php:35
#: ../../Zotlabs/Module/Filestorage.php:20
#: ../../Zotlabs/Module/Filestorage.php:78
#: ../../Zotlabs/Module/Filestorage.php:96
#: ../../Zotlabs/Module/Filestorage.php:119
-#: ../../Zotlabs/Module/Filestorage.php:165 ../../Zotlabs/Module/Pdledit.php:35
-#: ../../Zotlabs/Module/Moderate.php:15 ../../Zotlabs/Module/Regmod.php:20
+#: ../../Zotlabs/Module/Filestorage.php:165
+#: ../../Zotlabs/Module/Register.php:201 ../../Zotlabs/Module/Appman.php:163
+#: ../../Zotlabs/Module/Viewsrc.php:19 ../../Zotlabs/Module/Item.php:289
+#: ../../Zotlabs/Module/Item.php:308 ../../Zotlabs/Module/Item.php:318
+#: ../../Zotlabs/Module/Item.php:1272 ../../Zotlabs/Module/Achievements.php:34
+#: ../../Zotlabs/Module/Connedit.php:299 ../../Zotlabs/Module/Defperms.php:181
+#: ../../Zotlabs/Module/Suggest.php:32 ../../Zotlabs/Module/Regmod.php:20
+#: ../../Zotlabs/Module/Profile_photo.php:390
+#: ../../Zotlabs/Module/Profile_photo.php:421
+#: ../../Zotlabs/Module/Viewconnections.php:28
+#: ../../Zotlabs/Module/Viewconnections.php:33
+#: ../../Zotlabs/Module/Block.php:24 ../../Zotlabs/Module/Block.php:74
+#: ../../Zotlabs/Module/Api.php:26 ../../Zotlabs/Module/Layouts.php:71
+#: ../../Zotlabs/Module/Layouts.php:78 ../../Zotlabs/Module/Layouts.php:89
+#: ../../Zotlabs/Module/Common.php:38 ../../Zotlabs/Module/Locs.php:98
+#: ../../Zotlabs/Module/Menu.php:130 ../../Zotlabs/Module/Menu.php:141
+#: ../../Zotlabs/Module/Manage.php:10 ../../Zotlabs/Module/Bookmarks.php:70
#: ../../Zotlabs/Module/Attach_edit.php:90
#: ../../Zotlabs/Module/Attach_edit.php:99
-#: ../../Zotlabs/Module/Attach_edit.php:106 ../../Zotlabs/Module/Item.php:512
-#: ../../Zotlabs/Module/Item.php:531 ../../Zotlabs/Module/Item.php:541
-#: ../../Zotlabs/Module/Item.php:1463 ../../Zotlabs/Module/Display.php:387
-#: ../../Zotlabs/Module/Mitem.php:129 ../../Zotlabs/Module/Sharedwithme.php:19
-#: ../../Zotlabs/Module/Like.php:242 ../../Zotlabs/Module/Manage.php:10
-#: ../../Zotlabs/Module/Profile.php:99 ../../Zotlabs/Module/Profile.php:114
-#: ../../extend/addon/hzaddons/articles/Mod_Article_edit.php:51
-#: ../../extend/addon/hzaddons/articles/Mod_Articles.php:94
-#: ../../extend/addon/hzaddons/pumpio/pumpio.php:44
-#: ../../extend/addon/hzaddons/keepout/keepout.php:36
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:63
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:288
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:425
-#: ../../extend/addon/hzaddons/cards/Mod_Card_edit.php:51
-#: ../../extend/addon/hzaddons/cards/Mod_Cards.php:89
-#: ../../extend/addon/hzaddons/openid/Mod_Id.php:53
+#: ../../Zotlabs/Module/Attach_edit.php:106 ../../Zotlabs/Module/Group.php:15
+#: ../../Zotlabs/Module/Group.php:31 ../../Zotlabs/Module/Invite.php:65
+#: ../../Zotlabs/Module/Invite.php:316 ../../Zotlabs/Module/Like.php:230
+#: ../../Zotlabs/Module/Blocks.php:73 ../../Zotlabs/Module/Blocks.php:80
+#: ../../Zotlabs/Module/Mitem.php:129 ../../Zotlabs/Module/New_channel.php:106
+#: ../../Zotlabs/Module/New_channel.php:131 ../../Zotlabs/Module/Photos.php:71
+#: ../../Zotlabs/Module/Channel.php:234 ../../Zotlabs/Module/Channel.php:395
+#: ../../Zotlabs/Module/Channel.php:434 ../../Zotlabs/Module/Profile.php:99
+#: ../../Zotlabs/Module/Profile.php:114 ../../Zotlabs/Module/Moderate.php:15
+#: ../../Zotlabs/Module/Sources.php:80 ../../Zotlabs/Module/Profiles.php:168
+#: ../../Zotlabs/Module/Profiles.php:611
+#: ../../Zotlabs/Module/Sharedwithme.php:19
+#: ../../Zotlabs/Module/Authtest.php:13
+#: ../../Zotlabs/Module/Channel_calendar.php:220
+#: ../../Zotlabs/Module/Chat.php:111 ../../Zotlabs/Module/Chat.php:116
+#: ../../Zotlabs/Module/Editlayout.php:67
+#: ../../Zotlabs/Module/Editlayout.php:90 ../../Zotlabs/Module/Settings.php:59
+#: ../../Zotlabs/Module/Setup.php:220 ../../Zotlabs/Module/Editpost.php:17
+#: ../../Zotlabs/Module/Connections.php:32
+#: ../../Zotlabs/Module/Editblock.php:67 ../../Zotlabs/Module/Vote.php:19
msgid "Permission denied."
msgstr "Tillatelse avslått."
-#: ../../include/attach.php:273 ../../include/attach.php:324
-#: ../../include/attach.php:426
+#: ../../include/attach.php:274 ../../include/attach.php:325
+#: ../../include/attach.php:427
msgid "Item was not found."
msgstr "Elementet ble ikke funnet."
-#: ../../include/attach.php:290
+#: ../../include/attach.php:291
msgid "Unknown error."
-msgstr ""
+msgstr "Ukjent feil."
-#: ../../include/attach.php:621
+#: ../../include/attach.php:622
msgid "No source file."
msgstr "Ingen kildefil."
-#: ../../include/attach.php:643
+#: ../../include/attach.php:644
msgid "Cannot locate file to replace"
msgstr "Kan ikke finne filen som skal byttes ut"
-#: ../../include/attach.php:662
+#: ../../include/attach.php:663
msgid "Cannot locate file to revise/update"
msgstr "Finner ikke filen som skal revideres/oppdateres"
-#: ../../include/attach.php:808
+#: ../../include/attach.php:804 ../../include/attach.php:2609
+msgid "Filename too long"
+msgstr "Filnavnet er for langt"
+
+#: ../../include/attach.php:817
#, php-format
msgid "File exceeds size limit of %d"
msgstr "Filens størrelse overgår grensen på %d"
-#: ../../include/attach.php:829
+#: ../../include/attach.php:838
#, php-format
msgid "You have reached your limit of %1$.0f Mbytes attachment storage."
msgstr "Du har nådd din lagringsgrense for vedlegg på %1$.0f Mbytes."
-#: ../../include/attach.php:1019
+#: ../../include/attach.php:1028
msgid "File upload failed. Possible system limit or action terminated."
msgstr ""
"Mislyktes med å laste opp filen. Mulig systemgrense eller handling avbrutt."
-#: ../../include/attach.php:1048
+#: ../../include/attach.php:1057
msgid "Stored file could not be verified. Upload failed."
msgstr "Lagret fil kunne ikke bekreftes. Opplasting mislyktes."
-#: ../../include/attach.php:1120 ../../include/attach.php:1136
+#: ../../include/attach.php:1129 ../../include/attach.php:1145
msgid "Path not available."
msgstr "Stien er ikke tilgjengelig."
-#: ../../include/attach.php:1184 ../../include/attach.php:1349
+#: ../../include/attach.php:1193 ../../include/attach.php:1364
msgid "Empty pathname"
msgstr "Tomt sti-navn"
-#: ../../include/attach.php:1210
+#: ../../include/attach.php:1199
+msgid "Pathname too long"
+msgstr "Filbanen er for lang"
+
+#: ../../include/attach.php:1225
msgid "duplicate filename or path"
-msgstr "duplikat av filnavn eller sti"
+msgstr "duplikat av filnavn eller filbane"
-#: ../../include/attach.php:1238
+#: ../../include/attach.php:1253
msgid "Path not found."
-msgstr "Stien ble ikke funnet."
+msgstr "Filbanen ble ikke funnet."
-#: ../../include/attach.php:1305
+#: ../../include/attach.php:1320
msgid "mkdir failed."
-msgstr "mkdir mislyktes."
+msgstr "mkdir fikk en feil."
-#: ../../include/attach.php:1309
+#: ../../include/attach.php:1324
msgid "database storage failed."
msgstr "databaselagring mislyktes."
-#: ../../include/attach.php:1355
+#: ../../include/attach.php:1370
msgid "Empty path"
-msgstr "Tom sti"
+msgstr "Tom filbane"
-#: ../../include/attach.php:1984
-#, fuzzy, php-format
-#| msgid "Files: shared with me"
+#: ../../include/attach.php:2007
+#, php-format
msgid "%s shared an %s with you"
-msgstr "Filer: delt med meg"
+msgstr "%s delte en %s med deg"
-#: ../../include/attach.php:1984
+#: ../../include/attach.php:2007
#, php-format
msgid "%s shared a %s with you"
-msgstr ""
+msgstr "%s delte et %s med deg"
-#: ../../include/attach.php:1984 ../../Zotlabs/Module/Like.php:447
-#, fuzzy
-#| msgid "Image"
+#: ../../include/attach.php:2007 ../../Zotlabs/Module/Like.php:438
msgid "image"
-msgstr "Bilde"
+msgstr "bilde"
-#: ../../include/attach.php:1984
-#: ../../extend/addon/hzaddons/redfiles/redfilehelper.php:64
+#: ../../include/attach.php:2007 ../../addon/redfiles/redfilehelper.php:64
msgid "file"
-msgstr ""
-
-#: ../../include/network.php:413
-msgid "url: "
-msgstr ""
-
-#: ../../include/network.php:414
-msgid "error_code: "
-msgstr ""
+msgstr "fil"
-#: ../../include/network.php:415
-msgid "error_string: "
-msgstr ""
+#: ../../include/selectors.php:17
+msgid "Select a profile to assign to this contact"
+msgstr "Velg en profil for denne kontakten"
-#: ../../include/network.php:416
-msgid "content-type: "
-msgstr ""
+#: ../../include/selectors.php:45
+msgid "Frequently"
+msgstr "Ofte"
-#: ../../include/network.php:1774 ../../include/network.php:1775
-msgid "Friendica"
-msgstr "Friendica"
+#: ../../include/selectors.php:46
+msgid "Hourly"
+msgstr "Hver time"
-#: ../../include/network.php:1776
-msgid "OStatus"
-msgstr "OStatus"
+#: ../../include/selectors.php:47
+msgid "Twice daily"
+msgstr "To ganger daglig"
-#: ../../include/network.php:1777
-msgid "GNU-Social"
-msgstr ""
+#: ../../include/selectors.php:48
+msgid "Daily"
+msgstr "Daglig"
-#: ../../include/network.php:1778
-msgid "RSS/Atom"
-msgstr "RSS/Atom"
+#: ../../include/selectors.php:49
+msgid "Weekly"
+msgstr "Ukentlig"
-#: ../../include/network.php:1779
-msgid "ActivityPub"
-msgstr ""
+#: ../../include/selectors.php:50
+msgid "Monthly"
+msgstr "Månedlig"
-#: ../../include/network.php:1780 ../../Zotlabs/Module/Connedit.php:736
-#: ../../Zotlabs/Module/Admin/Accounts.php:316
-#: ../../Zotlabs/Module/Admin/Accounts.php:330
-#: ../../Zotlabs/Module/Cdav.php:1372
-#: ../../extend/addon/hzaddons/rtof/Mod_Rtof.php:55
-#: ../../extend/addon/hzaddons/redred/Mod_Redred.php:69
-#: ../../extend/addon/hzaddons/openid/MysqlProvider.php:56
-#: ../../extend/addon/hzaddons/openid/MysqlProvider.php:57
-msgid "Email"
-msgstr "E-post"
+#: ../../include/selectors.php:64 ../../include/selectors.php:81
+#: ../../include/channel.php:1731 ../../addon/openid/Mod_Id.php:85
+msgid "Male"
+msgstr "Mannlig"
-#: ../../include/network.php:1781
-msgid "Diaspora"
-msgstr "Diaspora"
+#: ../../include/selectors.php:64 ../../include/selectors.php:81
+#: ../../include/channel.php:1729 ../../addon/openid/Mod_Id.php:87
+msgid "Female"
+msgstr "Kvinnelig"
-#: ../../include/network.php:1782
-msgid "Facebook"
-msgstr "Facebook"
+#: ../../include/selectors.php:64
+msgid "Currently Male"
+msgstr "For tiden mann"
-#: ../../include/network.php:1783
-msgid "Zot"
-msgstr "Zot"
+#: ../../include/selectors.php:64
+msgid "Currently Female"
+msgstr "For tiden kvinne"
-#: ../../include/network.php:1784
-msgid "LinkedIn"
-msgstr "LinkedIn"
+#: ../../include/selectors.php:64
+msgid "Mostly Male"
+msgstr "For det meste mann"
-#: ../../include/network.php:1785
-msgid "XMPP/IM"
-msgstr "XMPP/IM"
+#: ../../include/selectors.php:64
+msgid "Mostly Female"
+msgstr "For det meste kvinne"
-#: ../../include/network.php:1786
-msgid "MySpace"
-msgstr "MySpace"
+#: ../../include/selectors.php:64
+msgid "Transgender"
+msgstr "Transkjønnet"
-#: ../../include/auth.php:232
-msgid "Delegation session ended."
-msgstr ""
+#: ../../include/selectors.php:64
+msgid "Intersex"
+msgstr "interkjønnet"
-#: ../../include/auth.php:236
-msgid "Logged out."
-msgstr "Logget ut."
+#: ../../include/selectors.php:64
+msgid "Transsexual"
+msgstr "Transseksuell"
-#: ../../include/auth.php:342
-msgid "Email validation is incomplete. Please check your email."
-msgstr ""
+#: ../../include/selectors.php:64
+msgid "Hermaphrodite"
+msgstr "Hermafroditt"
-#: ../../include/auth.php:358
-msgid "Failed authentication"
-msgstr "Mislykket autentisering"
+#: ../../include/selectors.php:64 ../../include/channel.php:1735
+msgid "Neuter"
+msgstr "Intetkjønn"
-#: ../../include/auth.php:368
-#: ../../extend/addon/hzaddons/openid/Mod_Openid.php:189
-msgid "Login failed."
-msgstr "Innlogging mislyktes."
+#: ../../include/selectors.php:64 ../../include/channel.php:1737
+msgid "Non-specific"
+msgstr "Ubestemt"
-#: ../../include/opengraph.php:56
-#, php-format
-msgid "This is the home page of %s."
-msgstr ""
+#: ../../include/selectors.php:64
+msgid "Undecided"
+msgstr "Ubestemt"
-#: ../../include/nav.php:109
-msgid "Remote authentication"
-msgstr "Fjernautentisering"
+#: ../../include/selectors.php:100 ../../include/selectors.php:119
+msgid "Males"
+msgstr "Menn"
-#: ../../include/nav.php:109
-msgid "Click to authenticate to your home hub"
-msgstr "Klikk for å godkjennes mot din hjemme-hub"
+#: ../../include/selectors.php:100 ../../include/selectors.php:119
+msgid "Females"
+msgstr "Kvinner"
-#: ../../include/nav.php:115 ../../Zotlabs/Module/Admin/Channels.php:168
-#: ../../Zotlabs/Module/Admin.php:118 ../../Zotlabs/Module/Manage.php:162
-#: ../../Zotlabs/Widget/Channel_activities.php:239
-#: ../../Zotlabs/Widget/Admin.php:29
-msgid "Channels"
-msgstr "Kanaler"
+#: ../../include/selectors.php:100
+msgid "Gay"
+msgstr "Homo"
-#: ../../include/nav.php:115
-msgid "Manage your channels"
-msgstr "Behandle kanalene dine"
+#: ../../include/selectors.php:100
+msgid "Lesbian"
+msgstr "Lesbisk"
-#: ../../include/nav.php:118 ../../Zotlabs/Lib/Apps.php:345
-#: ../../Zotlabs/Module/Admin/Addons.php:349
-#: ../../Zotlabs/Module/Admin/Themes.php:141
-#: ../../Zotlabs/Widget/Settings_menu.php:71
-#: ../../Zotlabs/Widget/Newmember.php:60
-msgid "Settings"
-msgstr "Innstillinger"
+#: ../../include/selectors.php:100
+msgid "No Preference"
+msgstr "Ingen preferanse"
-#: ../../include/nav.php:118
-msgid "Account/Channel Settings"
-msgstr "Konto-/kanalinnstillinger"
+#: ../../include/selectors.php:100
+msgid "Bisexual"
+msgstr "Biseksuell"
-#: ../../include/nav.php:124 ../../include/nav.php:154
-#: ../../include/nav.php:175 ../../boot.php:1759
-msgid "Logout"
-msgstr "Logg ut"
+#: ../../include/selectors.php:100
+msgid "Autosexual"
+msgstr "Autoseksuell"
-#: ../../include/nav.php:124 ../../include/nav.php:154
-msgid "End this session"
-msgstr "Avslutt denne økten"
+#: ../../include/selectors.php:100
+msgid "Abstinent"
+msgstr "Avholdende"
-#: ../../include/nav.php:127 ../../include/conversation.php:902
-#: ../../Zotlabs/Lib/Apps.php:350 ../../Zotlabs/Module/Connedit.php:480
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:58
-msgid "View Profile"
-msgstr "Vis profil"
+#: ../../include/selectors.php:100
+msgid "Virgin"
+msgstr "Jomfru"
-#: ../../include/nav.php:127
-msgid "Your profile page"
-msgstr "Din profilside"
+#: ../../include/selectors.php:100
+msgid "Deviant"
+msgstr "Avviker"
-#: ../../include/nav.php:130 ../../include/channel.php:1539
-#: ../../Zotlabs/Module/Profiles.php:851
-msgid "Edit Profiles"
-msgstr "Rediger profiler"
+#: ../../include/selectors.php:100
+msgid "Fetish"
+msgstr "Fetisj"
-#: ../../include/nav.php:130
-msgid "Manage/Edit profiles"
-msgstr "Håndter/endre profiler"
+#: ../../include/selectors.php:100
+msgid "Oodles"
+msgstr "Masse"
-#: ../../include/nav.php:132 ../../include/channel.php:1543
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:59
-msgid "Edit Profile"
-msgstr "Rediger profil"
+#: ../../include/selectors.php:100
+msgid "Nonsexual"
+msgstr "Ikke-seksuell"
-#: ../../include/nav.php:132 ../../Zotlabs/Widget/Newmember.php:42
-msgid "Edit your profile"
-msgstr "Rediger profil"
+#: ../../include/selectors.php:138 ../../include/selectors.php:155
+msgid "Single"
+msgstr "Enslig"
-#: ../../include/nav.php:139 ../../include/nav.php:143 ../../boot.php:1760
-#: ../../Zotlabs/Lib/Apps.php:342
-msgid "Login"
-msgstr "Logg inn"
+#: ../../include/selectors.php:138
+msgid "Lonely"
+msgstr "Ensom"
-#: ../../include/nav.php:139 ../../include/nav.php:143
-msgid "Sign in"
-msgstr "Logg på"
+#: ../../include/selectors.php:138
+msgid "Available"
+msgstr "Tilgjengelig"
-#: ../../include/nav.php:173
-msgid "Take me home"
-msgstr ""
+#: ../../include/selectors.php:138
+msgid "Unavailable"
+msgstr "Ikke tilgjengelig"
-#: ../../include/nav.php:175
-msgid "Log me out of this site"
-msgstr ""
+#: ../../include/selectors.php:138
+msgid "Has crush"
+msgstr "Er forelsket"
-#: ../../include/nav.php:180 ../../boot.php:1737
-#: ../../Zotlabs/Module/Register.php:543
-msgid "Register"
-msgstr "Registrer"
+#: ../../include/selectors.php:138
+msgid "Infatuated"
+msgstr "Betatt"
-#: ../../include/nav.php:180
-msgid "Create an account"
-msgstr "Lag en konto"
+#: ../../include/selectors.php:138 ../../include/selectors.php:155
+msgid "Dating"
+msgstr "Sammen med"
-#: ../../include/nav.php:194 ../../include/nav.php:341
-#: ../../Zotlabs/Lib/Apps.php:354 ../../Zotlabs/Module/Layouts.php:184
-msgid "Help"
-msgstr "Hjelp"
+#: ../../include/selectors.php:138
+msgid "Unfaithful"
+msgstr "Utro"
-#: ../../include/nav.php:194
-msgid "Help and documentation"
-msgstr "Hjelp og dokumentasjon"
+#: ../../include/selectors.php:138
+msgid "Sex Addict"
+msgstr "Sexavhengig"
-#: ../../include/nav.php:208 ../../include/text.php:1190
-#: ../../include/text.php:1202 ../../include/acl_selectors.php:149
-#: ../../Zotlabs/Lib/Apps.php:357 ../../Zotlabs/Module/Connections.php:403
-#: ../../Zotlabs/Module/Search.php:47 ../../Zotlabs/Widget/Sitesearch.php:37
-#: ../../Zotlabs/Widget/Activity_filter.php:210
-msgid "Search"
-msgstr "Søk"
+#: ../../include/selectors.php:138 ../../include/channel.php:467
+#: ../../include/channel.php:470 ../../Zotlabs/Widget/Affinity.php:38
+#: ../../Zotlabs/Module/Contactedit.php:283
+#: ../../Zotlabs/Module/Connedit.php:581
+msgid "Friends"
+msgstr "Venner"
-#: ../../include/nav.php:208
-msgid "Search site @name, !forum, #tag, ?docs, content"
-msgstr "Søk etter @navn, !forum, #emne, ?dokumentasjon eller innhold"
+#: ../../include/selectors.php:138
+msgid "Friends/Benefits"
+msgstr "Venner/Frynsegoder"
-#: ../../include/nav.php:214 ../../Zotlabs/Widget/Admin.php:61
-msgid "Admin"
-msgstr "Administrator"
+#: ../../include/selectors.php:138
+msgid "Casual"
+msgstr "Tilfeldig"
-#: ../../include/nav.php:214
-msgid "Site Setup and Configuration"
-msgstr "Nettstedsoppsett og -konfigurasjon"
+#: ../../include/selectors.php:138
+msgid "Engaged"
+msgstr "Forlovet"
-#: ../../include/nav.php:345 ../../Zotlabs/Module/New_channel.php:158
-#: ../../Zotlabs/Module/New_channel.php:165
-#: ../../Zotlabs/Module/Defperms.php:254 ../../Zotlabs/Widget/Messages.php:36
-#: ../../Zotlabs/Widget/Notifications.php:175
-#, fuzzy
-msgid "Loading"
-msgstr "Laster..."
+#: ../../include/selectors.php:138 ../../include/selectors.php:155
+msgid "Married"
+msgstr "Gift"
-#: ../../include/nav.php:350
-msgid "@name, #tag, ?doc, content"
-msgstr "@navn, #merkelapp, ?dokumentasjon, innhold"
+#: ../../include/selectors.php:138
+msgid "Imaginarily married"
+msgstr "Gift i fantasien"
-#: ../../include/nav.php:351
-msgid "Please wait..."
-msgstr "Vennligst vent..."
+#: ../../include/selectors.php:138
+msgid "Partners"
+msgstr "Partnere"
-#: ../../include/nav.php:357 ../../Zotlabs/Lib/Apps.php:329
-msgid "Apps"
-msgstr "Apper"
+#: ../../include/selectors.php:138 ../../include/selectors.php:155
+msgid "Cohabiting"
+msgstr "Samboer"
-#: ../../include/nav.php:358
-msgid "Channel Apps"
-msgstr ""
+#: ../../include/selectors.php:138
+msgid "Common law"
+msgstr "Samboer"
-#: ../../include/nav.php:359
-msgid "System Apps"
-msgstr ""
+#: ../../include/selectors.php:138
+msgid "Happy"
+msgstr "Lykkelig"
-#: ../../include/nav.php:360
-msgid "Pinned Apps"
-msgstr ""
+#: ../../include/selectors.php:138
+msgid "Not looking"
+msgstr "Ikke på utkikk"
-#: ../../include/nav.php:361
-msgid "Featured Apps"
-msgstr "Fremhevede apper"
+#: ../../include/selectors.php:138
+msgid "Swinger"
+msgstr "Partnerbytte"
-#: ../../include/nav.php:447 ../../Zotlabs/Lib/Apps.php:349
-#: ../../Zotlabs/Module/Admin/Channels.php:176
-msgid "Channel"
-msgstr "Kanal"
+#: ../../include/selectors.php:138
+msgid "Betrayed"
+msgstr "Bedratt"
-#: ../../include/nav.php:450
-msgid "Status Messages and Posts"
-msgstr "Statusmeldinger og -innlegg"
+#: ../../include/selectors.php:138 ../../include/selectors.php:155
+msgid "Separated"
+msgstr "Separert"
-#: ../../include/nav.php:460 ../../Zotlabs/Module/Help.php:239
-msgid "About"
-msgstr "Om"
+#: ../../include/selectors.php:138
+msgid "Unstable"
+msgstr "Ustabilt"
-#: ../../include/nav.php:463
-msgid "Profile Details"
-msgstr "Profildetaljer"
+#: ../../include/selectors.php:138 ../../include/selectors.php:155
+msgid "Divorced"
+msgstr "Skilt"
-#: ../../include/nav.php:473 ../../include/photos.php:722
-msgid "Photo Albums"
-msgstr "Fotoalbum"
+#: ../../include/selectors.php:138
+msgid "Imaginarily divorced"
+msgstr "Skilt i fantasien"
-#: ../../include/nav.php:478 ../../Zotlabs/Lib/Apps.php:346
-#: ../../Zotlabs/Storage/Browser.php:351 ../../Zotlabs/Module/Fbrowser.php:85
-#: ../../Zotlabs/Widget/Channel_activities.php:125
-#: ../../Zotlabs/Widget/Notifications.php:108
-msgid "Files"
-msgstr "Filer"
+#: ../../include/selectors.php:138 ../../include/selectors.php:155
+msgid "Widowed"
+msgstr "Enke"
-#: ../../include/nav.php:481
-msgid "Files and Storage"
-msgstr "Filer og lagring"
+#: ../../include/selectors.php:138
+msgid "Uncertain"
+msgstr "Usikkert"
-#: ../../include/nav.php:503 ../../include/nav.php:506
-#: ../../Zotlabs/Lib/Apps.php:336 ../../Zotlabs/Widget/Chatroom_list.php:22
-msgid "Chatrooms"
-msgstr "Chatrom"
+#: ../../include/selectors.php:138 ../../include/selectors.php:155
+msgid "It's complicated"
+msgstr "Det er komplisert"
-#: ../../include/nav.php:516 ../../Zotlabs/Lib/Apps.php:335
-#: ../../Zotlabs/Module/Bookmarks.php:90
-msgid "Bookmarks"
-msgstr "Bokmerker"
+#: ../../include/selectors.php:138
+msgid "Don't care"
+msgstr "Bryr meg ikke"
-#: ../../include/nav.php:519
-msgid "Saved Bookmarks"
-msgstr "Lagrede bokmerker"
+#: ../../include/selectors.php:138
+msgid "Ask me"
+msgstr "Spør meg"
-#: ../../include/nav.php:527 ../../Zotlabs/Lib/Apps.php:347
-#: ../../Zotlabs/Module/Webpages.php:247
-#: ../../Zotlabs/Widget/Channel_activities.php:168
-msgid "Webpages"
-msgstr "Websider"
+#: ../../include/activities.php:30
+msgid " and "
+msgstr " og "
-#: ../../include/nav.php:530
-msgid "View Webpages"
-msgstr ""
+#: ../../include/activities.php:32
+msgid ", "
+msgstr "."
-#: ../../include/event.php:35 ../../include/event.php:133
-msgid "l F d, Y \\@ g:i A"
-msgstr "l F d, Y \\@ g:i A"
+#: ../../include/activities.php:36 ../../include/activities.php:38
+#: ../../addon/openid/MysqlProvider.php:58
+#: ../../addon/openid/MysqlProvider.php:59
+#: ../../addon/openid/MysqlProvider.php:60 ../../Zotlabs/Lib/Apps.php:365
+#: ../../Zotlabs/Module/Profile_photo.php:227
+msgid "Profile Photo"
+msgstr "Profilbilde"
-#: ../../include/event.php:43
-msgid "Starts:"
-msgstr "Starter:"
+#: ../../include/activities.php:36 ../../Zotlabs/Module/Cover_photo.php:230
+msgid "Cover Photo"
+msgstr "Forsidebilde"
-#: ../../include/event.php:53
-msgid "Finishes:"
-msgstr "Slutter:"
+#: ../../include/activities.php:45
+msgid "public profile"
+msgstr "offentlig profil"
-#: ../../include/event.php:67 ../../include/event.php:158
-#: ../../include/channel.php:1640 ../../Zotlabs/Module/Directory.php:354
-msgid "Location:"
-msgstr "Plassering:"
+#: ../../include/activities.php:62
+#, php-format
+msgid "%1$s %2$s has been updated to %3$s."
+msgstr "%1$s %2$s har blitt oppdatert til %3$s."
-#: ../../include/event.php:133
-msgid "l F d, Y"
-msgstr ""
+#: ../../include/activities.php:65
+#, php-format
+msgid "%1$s updated the %2$s. Changed %3$s."
+msgstr "%1$s har oppdatert %2$s, endret %3$s."
-#: ../../include/event.php:137
-msgid "Start:"
-msgstr ""
+#: ../../include/contact_widgets.php:13
+#, php-format
+msgid "%d invitation available"
+msgid_plural "%d invitations available"
+msgstr[0] "%d invitasjon tilgjengelig"
+msgstr[1] "%d invitasjoner tilgjengelig"
-#: ../../include/event.php:141
-msgid "End:"
-msgstr ""
+#: ../../include/contact_widgets.php:18 ../../include/acl_selectors.php:145
+#: ../../Zotlabs/Module/Admin/Site.php:407
+msgid "Advanced"
+msgstr "Avansert"
-#: ../../include/event.php:146
-#: ../../extend/addon/hzaddons/openid/MysqlProvider.php:67
-msgid "Timezone"
-msgstr "Tidssone"
+#: ../../include/contact_widgets.php:21
+msgid "Find Channels"
+msgstr "Finn kanaler"
-#: ../../include/event.php:1246
-msgid "This event has been added to your calendar."
-msgstr "Denne hendelsen er lagt til i din kalender."
+#: ../../include/contact_widgets.php:22
+msgid "Enter name or interest"
+msgstr "Skriv navn eller interesse"
-#: ../../include/event.php:1400 ../../include/conversation.php:153
-#: ../../include/text.php:2361 ../../Zotlabs/Module/Channel_calendar.php:221
-#: ../../Zotlabs/Module/Tagger.php:75 ../../Zotlabs/Module/Like.php:450
-msgid "event"
-msgstr "hendelse"
+#: ../../include/contact_widgets.php:23
+msgid "Connect/Follow"
+msgstr "Forbindelse/Følg"
-#: ../../include/event.php:1479
-msgid "Not specified"
-msgstr "Ikke spesifisert"
+#: ../../include/contact_widgets.php:24
+msgid "Examples: Robert Morgenstein, Fishing"
+msgstr "Eksempler: Ola Nordmann, fisking"
-#: ../../include/event.php:1480
-msgid "Needs Action"
-msgstr "Trenger handling"
+#: ../../include/contact_widgets.php:25 ../../Zotlabs/Module/Directory.php:435
+#: ../../Zotlabs/Module/Directory.php:440
+#: ../../Zotlabs/Module/Connections.php:407
+msgid "Find"
+msgstr "Finn"
-#: ../../include/event.php:1481
-msgid "Completed"
-msgstr "Ferdig"
+#: ../../include/contact_widgets.php:26 ../../Zotlabs/Module/Suggest.php:77
+#: ../../Zotlabs/Module/Directory.php:439
+msgid "Channel Suggestions"
+msgstr "Kanalforslag"
-#: ../../include/event.php:1482
-msgid "In Process"
-msgstr "Igang"
+#: ../../include/contact_widgets.php:28
+msgid "Random Profile"
+msgstr "Tilfeldig profil"
-#: ../../include/event.php:1483
-msgid "Cancelled"
-msgstr "Avbrutt"
+#: ../../include/contact_widgets.php:29
+msgid "Invite Friends"
+msgstr "Inviter venner"
-#: ../../include/event.php:1564 ../../include/connections.php:790
-#: ../../Zotlabs/Module/Connedit.php:741 ../../Zotlabs/Module/Cdav.php:1377
-#, fuzzy
-msgid "Mobile"
-msgstr "mobil"
+#: ../../include/contact_widgets.php:31
+msgid "Advanced example: name=fred and country=iceland"
+msgstr "Avansert eksempel: navn=fred og land=island"
-#: ../../include/event.php:1565 ../../include/connections.php:791
-#: ../../Zotlabs/Module/Connedit.php:742 ../../Zotlabs/Module/Cdav.php:1378
-#: ../../Zotlabs/Widget/Notifications.php:43
-msgid "Home"
-msgstr "Hjem"
+#: ../../include/contact_widgets.php:58 ../../include/contact_widgets.php:121
+#: ../../include/contact_widgets.php:155
+#: ../../addon/cards/Widget/Cards_categories.php:83
+#: ../../addon/articles/Widget/Articles_categories.php:83
+#: ../../Zotlabs/Widget/Appcategories.php:52 ../../Zotlabs/Widget/Filer.php:36
+msgid "Everything"
+msgstr "Alt"
-#: ../../include/event.php:1566 ../../include/connections.php:792
-msgid "Home, Voice"
-msgstr ""
+#: ../../include/contact_widgets.php:118 ../../include/contact_widgets.php:152
+#: ../../include/taxonomy.php:425 ../../include/taxonomy.php:507
+#: ../../include/taxonomy.php:527 ../../include/taxonomy.php:548
+#: ../../addon/cards/Widget/Cards_categories.php:80
+#: ../../addon/articles/Widget/Articles_categories.php:80
+#: ../../Zotlabs/Storage/Browser.php:297 ../../Zotlabs/Storage/Browser.php:396
+#: ../../Zotlabs/Storage/Browser.php:410 ../../Zotlabs/Module/Cdav.php:1062
+msgid "Categories"
+msgstr "Kategorier"
-#: ../../include/event.php:1567 ../../include/connections.php:793
-msgid "Home, Fax"
-msgstr ""
+#: ../../include/contact_widgets.php:185
+msgid "Common Connections"
+msgstr "Felles forbindelser"
-#: ../../include/event.php:1568 ../../include/connections.php:794
-#: ../../Zotlabs/Module/Connedit.php:743 ../../Zotlabs/Module/Cdav.php:1379
-msgid "Work"
-msgstr ""
+#: ../../include/contact_widgets.php:189
+#, php-format
+msgid "View all %d common connections"
+msgstr "Vis alle %d felles forbindelser"
-#: ../../include/event.php:1569 ../../include/connections.php:795
-msgid "Work, Voice"
-msgstr ""
+#: ../../include/cdav.php:157
+msgid "INVALID EVENT DISMISSED!"
+msgstr "UGYLDIG HELDELSE AVVIST!"
-#: ../../include/event.php:1570 ../../include/connections.php:796
-msgid "Work, Fax"
-msgstr ""
+#: ../../include/cdav.php:158
+msgid "Summary: "
+msgstr "Sammendrag:"
+
+#: ../../include/cdav.php:158 ../../include/cdav.php:159
+#: ../../include/cdav.php:167 ../../Zotlabs/Lib/Apps.php:1168
+#: ../../Zotlabs/Lib/Apps.php:1252 ../../Zotlabs/Lib/Activity.php:1696
+#: ../../Zotlabs/Widget/Album.php:93 ../../Zotlabs/Widget/Portfolio.php:103
+#: ../../Zotlabs/Module/Embedphotos.php:179 ../../Zotlabs/Module/Photos.php:790
+#: ../../Zotlabs/Module/Photos.php:1240
+msgid "Unknown"
+msgstr "Ukjent"
-#: ../../include/event.php:1571 ../../include/event.php:1578
-#: ../../include/connections.php:797 ../../include/connections.php:804
-#: ../../include/selectors.php:64 ../../include/selectors.php:81
-#: ../../include/selectors.php:119 ../../include/selectors.php:155
-#: ../../Zotlabs/Access/PermissionRoles.php:362
-#: ../../Zotlabs/Module/Connedit.php:744 ../../Zotlabs/Module/Cdav.php:1380
-msgid "Other"
-msgstr "Annen"
+#: ../../include/cdav.php:159
+msgid "Date: "
+msgstr "Dato:"
-#: ../../include/photos.php:153
-#, php-format
-msgid "Image exceeds website size limit of %lu bytes"
-msgstr "Bilde overstiger nettstedets størrelsesbegrensning på %lu bytes"
+#: ../../include/cdav.php:160 ../../include/cdav.php:168
+msgid "Reason: "
+msgstr "Grunn:"
-#: ../../include/photos.php:164
-msgid "Image file is empty."
-msgstr "Bildefilen er tom."
+#: ../../include/cdav.php:166
+msgid "INVALID CARD DISMISSED!"
+msgstr "UGYLDIG KORT AVVIST!"
-#: ../../include/photos.php:198 ../../Zotlabs/Module/Cover_photo.php:240
-#: ../../Zotlabs/Module/Profile_photo.php:275
-msgid "Unable to process image"
-msgstr "Kan ikke behandle bildet"
+#: ../../include/cdav.php:167
+msgid "Name: "
+msgstr "Navn:"
-#: ../../include/photos.php:324
-msgid "Photo storage failed."
-msgstr "Bildelagring mislyktes."
+#: ../../include/group.php:23
+msgid ""
+"A deleted group with this name was revived. Existing item permissions "
+"<strong>may</strong> apply to this group and any future members. If this is "
+"not what you intended, please create another group with a different name."
+msgstr ""
+"En slettet gruppe med dette navnet ble gjenopprettet. Eksisterende "
+"tillatelser for elementet <strong>kan</strong> gjelde for denne gruppen og "
+"fremtidige medlemmer. Hvis du ønsket noe annet, vennligst lag en ny gruppe "
+"med et annet navn."
-#: ../../include/photos.php:373
-msgid "a new photo"
-msgstr "et nytt bilde"
+#: ../../include/group.php:271
+msgid "Add new connections to this privacy group"
+msgstr "Legg nye forbindelser i denne personverngruppen"
-#: ../../include/photos.php:377
-#, php-format
-msgctxt "photo_upload"
-msgid "%1$s posted %2$s to %3$s"
-msgstr "%1$s la inn %2$s til %3$s"
+#: ../../include/group.php:305
+msgid "edit"
+msgstr "endre"
-#: ../../include/photos.php:723 ../../Zotlabs/Module/Photos.php:1339
-#: ../../Zotlabs/Module/Photos.php:1352 ../../Zotlabs/Module/Photos.php:1353
-msgid "Recent Photos"
-msgstr "Nye bilder"
+#: ../../include/group.php:327 ../../include/acl_selectors.php:87
+#: ../../Zotlabs/Lib/Apps.php:368 ../../Zotlabs/Widget/Activity_filter.php:95
+#: ../../Zotlabs/Module/Group.php:144
+msgid "Privacy Groups"
+msgstr "Personverngrupper"
-#: ../../include/photos.php:727
-msgid "Upload New Photos"
-msgstr "Last opp nye bilder"
+#: ../../include/group.php:328
+msgid "Edit group"
+msgstr "Endre gruppe"
-#: ../../include/items.php:456 ../../Zotlabs/Web/WebServer.php:118
-#: ../../Zotlabs/Module/Import_items.php:116 ../../Zotlabs/Module/Group.php:109
-#: ../../Zotlabs/Module/Dreport.php:10 ../../Zotlabs/Module/Dreport.php:54
-#: ../../Zotlabs/Module/Profperm.php:29 ../../Zotlabs/Module/Subthread.php:89
-#: ../../Zotlabs/Module/Like.php:344
-#: ../../extend/addon/hzaddons/hzfiles/hzfiles.php:75
-#: ../../extend/addon/hzaddons/redphotos/redphotos.php:119
-#: ../../extend/addon/hzaddons/redfiles/redfiles.php:109
-msgid "Permission denied"
-msgstr "Tillatelse avvist"
+#: ../../include/group.php:329
+msgid "Manage privacy groups"
+msgstr "Administrer personverngrupper"
-#: ../../include/items.php:1241
-msgid "Visible to anybody on the internet."
-msgstr "Synlig for enhver på Internett."
+#: ../../include/group.php:330
+msgid "Channels not in any privacy group"
+msgstr "Kanaler uten personverngruppe"
-#: ../../include/items.php:1243
-msgid "Visible to you only."
-msgstr "Synlig bare for deg."
+#: ../../include/group.php:332 ../../Zotlabs/Widget/Savedsearch.php:90
+msgid "add"
+msgstr "legg til"
-#: ../../include/items.php:1245
-msgid "Visible to anybody in this network."
-msgstr "Synlig for enhver i dette nettverket."
+#: ../../include/taxonomy.php:326
+msgid "Trending"
+msgstr "Populært"
-#: ../../include/items.php:1247
-msgid "Visible to anybody authenticated."
-msgstr "Synlig for enhver som er autentisert."
+#: ../../include/taxonomy.php:326 ../../include/taxonomy.php:465
+#: ../../include/taxonomy.php:486 ../../Zotlabs/Widget/Tagcloud.php:26
+msgid "Tags"
+msgstr "Merkelapper"
-#: ../../include/items.php:1249
-#, php-format
-msgid "Visible to anybody on %s."
-msgstr "Synlig for alle på %s."
+#: ../../include/taxonomy.php:566
+msgid "Keywords"
+msgstr "Nøkkelord"
-#: ../../include/items.php:1251
-msgid "Visible to all connections."
-msgstr "Synlig for alle forbindelser."
+#: ../../include/taxonomy.php:587
+msgid "have"
+msgstr "har"
-#: ../../include/items.php:1253
-msgid "Visible to approved connections."
-msgstr "Synlig for godkjente forbindelser."
+#: ../../include/taxonomy.php:587
+msgid "has"
+msgstr "har"
-#: ../../include/items.php:1255
-msgid "Visible to specific connections."
-msgstr "Synlig for spesifikke forbindelser."
+#: ../../include/taxonomy.php:588
+msgid "want"
+msgstr "ønsker"
-#: ../../include/items.php:3345 ../../Zotlabs/Lib/Activity.php:2196
-#: ../../Zotlabs/Module/Share.php:124
-#, php-format
-msgid "&#x1f501; Repeated %1$s's %2$s"
-msgstr ""
+#: ../../include/taxonomy.php:588
+msgid "wants"
+msgstr "ønsker"
-#: ../../include/items.php:3848 ../../Zotlabs/Module/Viewsrc.php:25
-#: ../../Zotlabs/Module/Thing.php:113 ../../Zotlabs/Module/Filestorage.php:29
-#: ../../Zotlabs/Module/Admin/Addons.php:264
-#: ../../Zotlabs/Module/Admin/Themes.php:73 ../../Zotlabs/Module/Display.php:59
-#: ../../Zotlabs/Module/Display.php:120 ../../Zotlabs/Module/Display.php:391
-#: ../../Zotlabs/Module/Admin.php:63
-#: ../../extend/addon/hzaddons/flashcards/Mod_Flashcards.php:291
-#: ../../extend/addon/hzaddons/flashcards/Mod_Flashcards.php:292
-msgid "Item not found."
-msgstr "Elementet ble ikke funnet."
+#: ../../include/taxonomy.php:589
+msgid "like"
+msgstr "liker"
-#: ../../include/items.php:4430 ../../Zotlabs/Module/Group.php:63
-#: ../../Zotlabs/Module/Group.php:207
-msgid "Privacy group not found."
-msgstr "Personverngruppen ble ikke funnet."
+#: ../../include/taxonomy.php:589
+msgid "likes"
+msgstr "liker"
-#: ../../include/items.php:4446
-msgid "Privacy group is empty."
-msgstr "Personverngruppen er tom."
+#: ../../include/taxonomy.php:590
+msgid "dislike"
+msgstr "misliker"
-#: ../../include/items.php:4453
-#, php-format
-msgid "Privacy group: %s"
-msgstr "Personverngruppe: %s"
+#: ../../include/taxonomy.php:590
+msgid "dislikes"
+msgstr "misliker"
-#: ../../include/items.php:4463
-#, php-format
-msgid "Connection: %s"
-msgstr "Forbindelse: %s"
+#: ../../include/taxonomy.php:677 ../../include/conversation.php:1440
+#: ../../Zotlabs/Module/Photos.php:1118
+msgctxt "noun"
+msgid "Like"
+msgid_plural "Likes"
+msgstr[0] "Liker"
+msgstr[1] "Liker"
-#: ../../include/items.php:4465
-msgid "Connection not found."
-msgstr "Forbindelsen ble ikke funnet."
+#: ../../include/photo/photo_driver.php:439
+#: ../../Zotlabs/Module/Profile_photo.php:168
+#: ../../Zotlabs/Module/Profile_photo.php:337
+msgid "Profile Photos"
+msgstr "Profilbilder"
#: ../../include/account.php:39
msgid "The provided email address is not valid"
-msgstr ""
+msgstr "Epostadressen som er oppgitt er ikke gyldig"
#: ../../include/account.php:42
msgid "The provided email domain is not among those allowed on this site"
msgstr ""
+"Epostadressen som er oppgitt har ikke et domene som er tillatt på dette "
+"nettstedet"
#: ../../include/account.php:49
msgid "The provided email address is already registered at this site"
msgstr ""
+"Epostadressen som ble oppgitt er allerede registrert på dette nettstedet"
#: ../../include/account.php:56
msgid ""
"There is a pending registration for this address - click \"Register\" to "
"continue verification"
msgstr ""
+"Det er en ventende registrering for denne addressen - klikk på \"Registrér\" "
+"for å fortsette verifiseringen"
#: ../../include/account.php:95
msgid "An invitation is required."
@@ -1191,1072 +1709,1519 @@ msgstr "En invitasjon er påkrevd."
msgid "Invitation could not be verified."
msgstr "Invitasjon kunne ikke bekreftes."
-#: ../../include/account.php:192
-msgid "Please enter the required information."
-msgstr "Vennligst skriv inn nødvendig informasjon."
-
-#: ../../include/account.php:259 ../../include/account.php:367
+#: ../../include/account.php:206
msgid "Failed to store account information."
msgstr "Mislyktes med å lagre kontoinformasjon."
-#: ../../include/account.php:436 ../../include/account.php:504
-#: ../../Zotlabs/Module/Register.php:329
+#: ../../include/account.php:275 ../../Zotlabs/Module/Register.php:329
#, php-format
msgid "Registration confirmation for %s"
msgstr "Registreringsbekreftelse for %s"
-#: ../../include/account.php:579
+#: ../../include/account.php:348
#, php-format
msgid "Registration request at %s"
msgstr "Registreringsforespørsel hos %s"
-#: ../../include/account.php:601
+#: ../../include/account.php:370
msgid "your registration password"
msgstr "ditt registreringspassord"
-#: ../../include/account.php:607 ../../include/account.php:696
+#: ../../include/account.php:376 ../../include/account.php:449
#, php-format
msgid "Registration details for %s"
msgstr "Registreringsdetaljer for %s"
-#: ../../include/account.php:707
+#: ../../include/account.php:464
msgid "Account approved."
msgstr "Konto godkjent."
-#: ../../include/account.php:763
+#: ../../include/account.php:516
#, php-format
msgid "Registration revoked for %s"
msgstr "Registrering trukket tilbake for %s"
-#: ../../include/account.php:770
+#: ../../include/account.php:523
#, php-format
msgid "Could not revoke registration for %s"
-msgstr ""
+msgstr "Registreringen for %s kunne ikke tilbakekalles"
-#: ../../include/account.php:1187 ../../include/account.php:1189
+#: ../../include/account.php:940 ../../include/account.php:942
msgid "Click here to upgrade."
msgstr "Klikk her for å oppgradere."
-#: ../../include/account.php:1195
+#: ../../include/account.php:948
msgid "This action exceeds the limits set by your subscription plan."
msgstr "Denne handlingen går utenfor grensene satt i din abonnementsplan."
-#: ../../include/account.php:1200
+#: ../../include/account.php:953
msgid "This action is not available under your subscription plan."
msgstr "Denne handlingen er ikke tilgjengelig i din abonnementsplan."
-#: ../../include/account.php:1260
+#: ../../include/account.php:1013
msgid "open"
-msgstr ""
+msgstr "åpen"
-#: ../../include/account.php:1260
+#: ../../include/account.php:1013
msgid "closed"
-msgstr ""
+msgstr "lukket"
-#: ../../include/account.php:1267
+#: ../../include/account.php:1020
msgid "Registration is currently"
-msgstr ""
+msgstr "Registrering er for øyeblikket"
-#: ../../include/account.php:1276
+#: ../../include/account.php:1029
msgid "please come back"
-msgstr ""
+msgstr "kom tilbake"
-#: ../../include/photo/photo_driver.php:458
-#: ../../Zotlabs/Module/Profile_photo.php:168
-#: ../../Zotlabs/Module/Profile_photo.php:337
-msgid "Profile Photos"
-msgstr "Profilbilder"
+#: ../../include/js_strings.php:5
+msgid "Delete this item?"
+msgstr "Slett dette elementet?"
-#: ../../include/activities.php:30
-msgid " and "
-msgstr " og "
+#: ../../include/js_strings.php:6 ../../Zotlabs/Module/Moderate.php:78
+msgid "Item deleted"
+msgstr "Element slettet"
-#: ../../include/activities.php:32
-#, fuzzy
-#| msgid " "
-msgid ", "
-msgstr " "
+#: ../../include/js_strings.php:7 ../../Zotlabs/Lib/ThreadItem.php:808
+#: ../../Zotlabs/Module/Photos.php:1095 ../../Zotlabs/Module/Photos.php:1196
+msgid "Comment"
+msgstr "Kommentar"
-#: ../../include/activities.php:36 ../../include/activities.php:38
-#: ../../Zotlabs/Lib/Apps.php:365 ../../Zotlabs/Module/Profile_photo.php:227
-#: ../../extend/addon/hzaddons/openid/MysqlProvider.php:58
-#: ../../extend/addon/hzaddons/openid/MysqlProvider.php:59
-#: ../../extend/addon/hzaddons/openid/MysqlProvider.php:60
-msgid "Profile Photo"
-msgstr "Profilbilde"
+#: ../../include/js_strings.php:8
+msgid "expand"
+msgstr "utvid"
-#: ../../include/activities.php:36 ../../Zotlabs/Module/Cover_photo.php:230
-#, fuzzy
-#| msgid "Cover Photos"
-msgid "Cover Photo"
-msgstr "Forsidebilder"
+#: ../../include/js_strings.php:9
+msgid "collapse"
+msgstr "kollaps"
-#: ../../include/activities.php:45
-msgid "public profile"
-msgstr "offentlig profil"
+#: ../../include/js_strings.php:10
+msgid "Password too short"
+msgstr "Passordet er for kort"
-#: ../../include/activities.php:62
-#, fuzzy, php-format
-#| msgid "%1$s has an updated %2$s, changing %3$s."
-msgid "%1$s %2$s has been updated to %3$s."
-msgstr "%1$s har oppdatert %2$s, endret %3$s."
+#: ../../include/js_strings.php:11 ../../Zotlabs/Module/Register.php:162
+msgid "Passwords do not match"
+msgstr "Passordene er ikke like"
-#: ../../include/activities.php:65
-#, fuzzy, php-format
-#| msgid "%1$s has an updated %2$s, changing %3$s."
-msgid "%1$s updated the %2$s. Changed %3$s."
-msgstr "%1$s har oppdatert %2$s, endret %3$s."
+#: ../../include/js_strings.php:12
+msgid "everybody"
+msgstr "alle"
-#: ../../include/group.php:23
-msgid ""
-"A deleted group with this name was revived. Existing item permissions "
-"<strong>may</strong> apply to this group and any future members. If this is "
-"not what you intended, please create another group with a different name."
+#: ../../include/js_strings.php:13
+msgid "Secret Passphrase"
+msgstr "Hemmelig passordsetning"
+
+#: ../../include/js_strings.php:14
+msgid "Passphrase hint"
+msgstr "Hint om passordsetning"
+
+#: ../../include/js_strings.php:15
+msgid "Notice: Permissions have changed but have not yet been submitted."
msgstr ""
-"En slettet gruppe med dette navnet ble gjenopprettet. Eksisterende "
-"tillatelser for elementet <strong>kan</strong> gjelde for denne gruppen og "
-"fremtidige medlemmer. Hvis du ønsket noe annet, vennligst lag en ny gruppe "
-"med et annet navn."
+"Varsel: Tillatelser har blitt endret, men de har ennå ikke blitt sendt inn."
-#: ../../include/group.php:271
-msgid "Add new connections to this privacy group"
-msgstr "Legg nye forbindelser i denne personverngruppen"
+#: ../../include/js_strings.php:16
+msgid "close all"
+msgstr "lukk alle"
-#: ../../include/group.php:305
-msgid "edit"
-msgstr "endre"
+#: ../../include/js_strings.php:17
+msgid "Nothing new here"
+msgstr "Ikke noe nytt her"
-#: ../../include/group.php:327 ../../include/acl_selectors.php:87
-#: ../../Zotlabs/Lib/Apps.php:368 ../../Zotlabs/Module/Group.php:144
-#: ../../Zotlabs/Widget/Activity_filter.php:95
-msgid "Privacy Groups"
-msgstr "Personverngrupper"
+#: ../../include/js_strings.php:18
+msgid "Rate This Channel (this is public)"
+msgstr "Vurder denne kanalen (dette er offentlig)"
-#: ../../include/group.php:328
-msgid "Edit group"
-msgstr "Endre gruppe"
+#: ../../include/js_strings.php:19
+msgid "Rating"
+msgstr "Vurdering"
-#: ../../include/group.php:329
-msgid "Manage privacy groups"
+#: ../../include/js_strings.php:20
+msgid "Describe (optional)"
+msgstr "Beskriv (valgfritt)"
+
+#: ../../include/js_strings.php:22
+msgid "Please enter a link URL"
+msgstr "Vennligst skriv inn en lenke URL"
+
+#: ../../include/js_strings.php:23
+msgid "Unsaved changes. Are you sure you wish to leave this page?"
msgstr ""
+"Endringene er ikke lagret. Er du sikker på at du ønsker å forlate denne "
+"siden?"
-#: ../../include/group.php:330
-msgid "Channels not in any privacy group"
-msgstr "Kanaler uten personverngruppe"
+#: ../../include/js_strings.php:24 ../../Zotlabs/Module/Locs.php:121
+#: ../../Zotlabs/Module/Pubsites.php:55 ../../Zotlabs/Module/Profiles.php:476
+#: ../../Zotlabs/Module/Profiles.php:749 ../../Zotlabs/Module/Cdav.php:1006
+msgid "Location"
+msgstr "Plassering"
-#: ../../include/group.php:332 ../../Zotlabs/Widget/Savedsearch.php:90
-msgid "add"
-msgstr "legg til"
+#: ../../include/js_strings.php:25
+msgid "lovely"
+msgstr "fint"
-#: ../../include/language.php:423 ../../include/text.php:2199
-msgid "default"
-msgstr "standard"
+#: ../../include/js_strings.php:26
+msgid "wonderful"
+msgstr "nydelig"
-#: ../../include/language.php:436
-msgid "Select an alternate language"
-msgstr "Velg et annet språk"
+#: ../../include/js_strings.php:27
+msgid "fantastic"
+msgstr "fantastisk"
-#: ../../include/import.php:31
-msgid "Unable to import a removed channel."
-msgstr ""
+#: ../../include/js_strings.php:28
+msgid "great"
+msgstr "kjempebra"
-#: ../../include/import.php:57
+#: ../../include/js_strings.php:29
msgid ""
-"Cannot create a duplicate channel identifier on this system. Import failed."
+"Your chosen nickname was either already taken or not valid. Please use our "
+"suggestion ("
msgstr ""
-"Kan ikke lage en kopi av kanal-identifikatoren på dette systemet. Import "
-"mislyktes."
+"Kallenavnet du har valgt er enten allerede opptatt, eller ikke gyldig. Bruk "
+"vårt forslag ("
-#: ../../include/import.php:78
-#: ../../extend/addon/hzaddons/diaspora/import_diaspora.php:43
-msgid "Unable to create a unique channel address. Import failed."
-msgstr "Klarte ikke å lage en unik kanaladresse. Import mislyktes."
+#: ../../include/js_strings.php:30
+msgid ") or enter a new one."
+msgstr ") eller velg et annet."
-#: ../../include/import.php:129
-msgid "Cloned channel not found. Import failed."
-msgstr "Klonet kanal ble ikke funnet. Import mislyktes."
+#: ../../include/js_strings.php:31
+msgid "Thank you, this nickname is valid."
+msgstr "Takk, dette kallenavnet er gyldig."
-#: ../../include/datetime.php:58 ../../Zotlabs/Module/Profiles.php:751
-#: ../../Zotlabs/Widget/Newmember.php:58
-msgid "Miscellaneous"
-msgstr "Forskjellig"
+#: ../../include/js_strings.php:32
+msgid "A channel name is required."
+msgstr "Et kanalnavn er påkrevet."
-#: ../../include/datetime.php:140
-#, fuzzy
-msgid "Birthday"
-msgstr "Fødselsdag:"
+#: ../../include/js_strings.php:33
+msgid "This is a "
+msgstr "Dette er et"
-#: ../../include/datetime.php:140
-msgid "Age: "
-msgstr "Alder: "
+#: ../../include/js_strings.php:34
+msgid " channel name"
+msgstr "kanalnavn"
-#: ../../include/datetime.php:140
-msgid "YYYY-MM-DD or MM-DD"
-msgstr "YYYY-MM-DD eller MM-DD"
+#: ../../include/js_strings.php:35
+msgid "Back to reply"
+msgstr "Tilbake til svar"
-#: ../../include/datetime.php:211 ../../include/js_strings.php:124
-#: ../../Zotlabs/Module/Profiles.php:760 ../../Zotlabs/Module/Profiles.php:764
-#: ../../Zotlabs/Module/Appman.php:218 ../../Zotlabs/Module/Appman.php:219
-#: ../../Zotlabs/Module/Register.php:501
-#: ../../Zotlabs/Module/Settings/Multifactor.php:84
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:334
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:358
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:434
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:458
-msgid "Required"
-msgstr "Påkrevd"
+#: ../../include/js_strings.php:36
+msgid "Pinned"
+msgstr "Festet"
-#: ../../include/datetime.php:238 ../../boot.php:2774
-msgid "never"
-msgstr "aldri"
+#: ../../include/js_strings.php:37 ../../Zotlabs/Lib/ThreadItem.php:454
+msgid "Pin to the top"
+msgstr "Fest til toppen"
-#: ../../include/datetime.php:244
-msgid "less than a second ago"
-msgstr "for mindre enn ett sekund siden"
+#: ../../include/js_strings.php:38 ../../Zotlabs/Lib/ThreadItem.php:454
+#: ../../Zotlabs/Widget/Pinned.php:153
+msgid "Unpin from the top"
+msgstr "Ikke fest til toppen lengre"
-#: ../../include/datetime.php:262
+#: ../../include/js_strings.php:39
+msgid "Double click to exit zoom"
+msgstr "Dobbelklikk for å gå ut av forstørring"
+
+#: ../../include/js_strings.php:45
#, php-format
-msgctxt "e.g. 22 hours ago, 1 minute ago"
-msgid "%1$d %2$s ago"
-msgstr "%1$d %2$s siden"
+msgid "%d minutes"
+msgid_plural "%d minutes"
+msgstr[0] "%d minutter"
+msgstr[1] "%d minutter"
-#: ../../include/datetime.php:273
-msgctxt "relative_date"
-msgid "year"
-msgid_plural "years"
-msgstr[0] "år"
-msgstr[1] "år"
+#: ../../include/js_strings.php:46
+#, php-format
+msgid "about %d hours"
+msgid_plural "about %d hours"
+msgstr[0] "omtrent %d timer"
+msgstr[1] "omtrent %d timer"
-#: ../../include/datetime.php:276
-msgctxt "relative_date"
+#: ../../include/js_strings.php:47
+#, php-format
+msgid "%d days"
+msgid_plural "%d days"
+msgstr[0] "%d dager"
+msgstr[1] "%d dager"
+
+#: ../../include/js_strings.php:48
+#, php-format
+msgid "%d months"
+msgid_plural "%d months"
+msgstr[0] "%d måneder"
+msgstr[1] "%d måneder"
+
+#: ../../include/js_strings.php:49
+#, php-format
+msgid "%d years"
+msgid_plural "%d years"
+msgstr[0] "%d år"
+msgstr[1] "%d år"
+
+#: ../../include/js_strings.php:51 ../../include/text.php:1525
+msgid "January"
+msgstr "januar"
+
+#: ../../include/js_strings.php:52 ../../include/text.php:1525
+msgid "February"
+msgstr "februar"
+
+#: ../../include/js_strings.php:53 ../../include/text.php:1525
+msgid "March"
+msgstr "mars"
+
+#: ../../include/js_strings.php:54 ../../include/text.php:1525
+msgid "April"
+msgstr "april"
+
+#: ../../include/js_strings.php:55
+msgctxt "long"
+msgid "May"
+msgstr "mai"
+
+#: ../../include/js_strings.php:56 ../../include/text.php:1525
+msgid "June"
+msgstr "juni"
+
+#: ../../include/js_strings.php:57 ../../include/text.php:1525
+msgid "July"
+msgstr "juli"
+
+#: ../../include/js_strings.php:58 ../../include/text.php:1525
+msgid "August"
+msgstr "august"
+
+#: ../../include/js_strings.php:59 ../../include/text.php:1525
+msgid "September"
+msgstr "september"
+
+#: ../../include/js_strings.php:60 ../../include/text.php:1525
+msgid "October"
+msgstr "oktober"
+
+#: ../../include/js_strings.php:61 ../../include/text.php:1525
+msgid "November"
+msgstr "november"
+
+#: ../../include/js_strings.php:62 ../../include/text.php:1525
+msgid "December"
+msgstr "desember"
+
+#: ../../include/js_strings.php:63
+msgid "Jan"
+msgstr "Jan"
+
+#: ../../include/js_strings.php:64
+msgid "Feb"
+msgstr "Feb"
+
+#: ../../include/js_strings.php:65
+msgid "Mar"
+msgstr "Mar"
+
+#: ../../include/js_strings.php:66
+msgid "Apr"
+msgstr "Apr"
+
+#: ../../include/js_strings.php:67
+msgctxt "short"
+msgid "May"
+msgstr "mai"
+
+#: ../../include/js_strings.php:68
+msgid "Jun"
+msgstr "Jun"
+
+#: ../../include/js_strings.php:69
+msgid "Jul"
+msgstr "Jul"
+
+#: ../../include/js_strings.php:70
+msgid "Aug"
+msgstr "Aug"
+
+#: ../../include/js_strings.php:71
+msgid "Sep"
+msgstr "Sep"
+
+#: ../../include/js_strings.php:72
+msgid "Oct"
+msgstr "Okt"
+
+#: ../../include/js_strings.php:73
+msgid "Nov"
+msgstr "Nov"
+
+#: ../../include/js_strings.php:74
+msgid "Dec"
+msgstr "Des"
+
+#: ../../include/js_strings.php:75 ../../include/text.php:1521
+msgid "Sunday"
+msgstr "søndag"
+
+#: ../../include/js_strings.php:76 ../../include/text.php:1521
+msgid "Monday"
+msgstr "mandag"
+
+#: ../../include/js_strings.php:77 ../../include/text.php:1521
+msgid "Tuesday"
+msgstr "tirsdag"
+
+#: ../../include/js_strings.php:78 ../../include/text.php:1521
+msgid "Wednesday"
+msgstr "onsdag"
+
+#: ../../include/js_strings.php:79 ../../include/text.php:1521
+msgid "Thursday"
+msgstr "torsdag"
+
+#: ../../include/js_strings.php:80 ../../include/text.php:1521
+msgid "Friday"
+msgstr "fredag"
+
+#: ../../include/js_strings.php:81 ../../include/text.php:1521
+msgid "Saturday"
+msgstr "lørdag"
+
+#: ../../include/js_strings.php:82
+msgid "Sun"
+msgstr "Søn"
+
+#: ../../include/js_strings.php:83
+msgid "Mon"
+msgstr "Man"
+
+#: ../../include/js_strings.php:84
+msgid "Tue"
+msgstr "Tirs"
+
+#: ../../include/js_strings.php:85
+msgid "Wed"
+msgstr "Ons"
+
+#: ../../include/js_strings.php:86
+msgid "Thu"
+msgstr "Tors"
+
+#: ../../include/js_strings.php:87
+msgid "Fri"
+msgstr "Fre"
+
+#: ../../include/js_strings.php:88
+msgid "Sat"
+msgstr "Lør"
+
+#: ../../include/js_strings.php:89
+msgctxt "calendar"
+msgid "today"
+msgstr "idag"
+
+#: ../../include/js_strings.php:90
+msgctxt "calendar"
msgid "month"
-msgid_plural "months"
-msgstr[0] "måned"
-msgstr[1] "måneder"
+msgstr "måned"
-#: ../../include/datetime.php:279
-msgctxt "relative_date"
+#: ../../include/js_strings.php:91
+msgctxt "calendar"
msgid "week"
-msgid_plural "weeks"
-msgstr[0] "uke"
-msgstr[1] "uker"
+msgstr "uke"
-#: ../../include/datetime.php:282
-msgctxt "relative_date"
+#: ../../include/js_strings.php:92
+msgctxt "calendar"
msgid "day"
-msgid_plural "days"
-msgstr[0] "dag"
-msgstr[1] "dager"
-
-#: ../../include/datetime.php:285
-msgctxt "relative_date"
-msgid "hour"
-msgid_plural "hours"
-msgstr[0] "time"
-msgstr[1] "timer"
+msgstr "dag"
-#: ../../include/datetime.php:288
-msgctxt "relative_date"
-msgid "minute"
-msgid_plural "minutes"
-msgstr[0] "minutt"
-msgstr[1] "minutter"
+#: ../../include/js_strings.php:93
+msgctxt "calendar"
+msgid "All day"
+msgstr "Hele dagen"
-#: ../../include/datetime.php:291
-msgctxt "relative_date"
-msgid "second"
-msgid_plural "seconds"
-msgstr[0] "sekund"
-msgstr[1] "sekunder"
+#: ../../include/js_strings.php:96
+msgid "Please stand by while your download is being prepared."
+msgstr "Nedlastingen din blir klargjort."
-#: ../../include/datetime.php:520
-#, php-format
-msgid "%1$s's birthday"
-msgstr "%1$s sin fødselsdag"
+#: ../../include/js_strings.php:99
+msgid "Email address not valid"
+msgstr "Epostadressen er ikke gyldig"
+
+#: ../../include/js_strings.php:100 ../../include/datetime.php:211
+#: ../../addon/cart/submodules/orderoptions.php:334
+#: ../../addon/cart/submodules/orderoptions.php:358
+#: ../../addon/cart/submodules/orderoptions.php:434
+#: ../../addon/cart/submodules/orderoptions.php:458
+#: ../../Zotlabs/Module/Register.php:501 ../../Zotlabs/Module/Appman.php:218
+#: ../../Zotlabs/Module/Appman.php:219 ../../Zotlabs/Module/Profiles.php:760
+#: ../../Zotlabs/Module/Profiles.php:764
+#: ../../Zotlabs/Module/Settings/Multifactor.php:84
+msgid "Required"
+msgstr "Påkrevd"
-#: ../../include/datetime.php:521
+#: ../../include/zid.php:420
#, php-format
-msgid "Happy Birthday %1$s"
-msgstr "Gratulerer med dagen, %1$s"
+msgid "OpenWebAuth: %1$s welcomes %2$s"
+msgstr "OpenWebAuth: %1$s ønsker %2$s velkommen"
-#: ../../include/conversation.php:149 ../../include/text.php:2358
-#: ../../Zotlabs/Module/Tagger.php:71 ../../Zotlabs/Module/Subthread.php:112
-#: ../../extend/addon/hzaddons/diaspora/Receiver.php:1693
-#: ../../extend/addon/hzaddons/redphotos/redphotohelper.php:71
+#: ../../include/conversation.php:149 ../../include/text.php:2377
+#: ../../addon/redphotos/redphotohelper.php:71
+#: ../../addon/diaspora/Receiver.php:1705
+#: ../../Zotlabs/Module/Subthread.php:112 ../../Zotlabs/Module/Tagger.php:73
msgid "photo"
msgstr "foto"
-#: ../../include/conversation.php:157 ../../Zotlabs/Module/Like.php:183
+#: ../../include/conversation.php:157 ../../Zotlabs/Module/Like.php:171
msgid "channel"
msgstr "kanal"
-#: ../../include/conversation.php:182 ../../include/text.php:2366
-#: ../../Zotlabs/Module/Tagger.php:81
-msgid "comment"
-msgstr "kommentar"
+#: ../../include/conversation.php:180 ../../include/text.php:2385
+msgid "message"
+msgstr "melding"
-#: ../../include/conversation.php:196 ../../Zotlabs/Module/Like.php:483
-#: ../../extend/addon/hzaddons/diaspora/Receiver.php:1628
+#: ../../include/conversation.php:194 ../../addon/diaspora/Receiver.php:1640
+#: ../../Zotlabs/Module/Like.php:474
#, php-format
msgid "%1$s likes %2$s's %3$s"
msgstr "%1$s liker %2$s sin %3$s"
-#: ../../include/conversation.php:198
+#: ../../include/conversation.php:196
#, php-format
msgid "likes %1$s's %2$s"
-msgstr ""
+msgstr "liker %2$s til %1$s"
-#: ../../include/conversation.php:201 ../../Zotlabs/Module/Like.php:485
+#: ../../include/conversation.php:199 ../../Zotlabs/Module/Like.php:476
#, php-format
msgid "%1$s doesn't like %2$s's %3$s"
msgstr "%1$s liker ikke %2$s sin %3$s"
-#: ../../include/conversation.php:202
+#: ../../include/conversation.php:200
#, php-format
msgid "doesn't like %1$s's %2$s"
-msgstr ""
+msgstr "liker ikke %2$s til %1$s"
-#: ../../include/conversation.php:205
-#, fuzzy, php-format
-#| msgid "%1$s may attend %2$s's %3$s"
+#: ../../include/conversation.php:203
+#, php-format
msgid "%1$s repeated %2$s's %3$s"
-msgstr "%1$s deltar kanskje på %2$ss %3$s"
+msgstr "%1$s videreformidlet %3$s til %2$s"
-#: ../../include/conversation.php:206
-#, fuzzy, php-format
-#| msgid "Visit %1$s's %2$s"
+#: ../../include/conversation.php:204
+#, php-format
msgid "repeated %1$s's %2$s"
-msgstr "Besøk %1$s sitt %2$s"
+msgstr "videreformidlet %2$s til %1$s"
-#: ../../include/conversation.php:334 ../../Zotlabs/Lib/ThreadItem.php:536
+#: ../../include/conversation.php:332 ../../Zotlabs/Lib/ThreadItem.php:472
msgid "This is an unsaved preview"
-msgstr ""
-
-#: ../../include/conversation.php:471 ../../Zotlabs/Module/Photos.php:1110
-msgctxt "title"
-msgid "Likes"
-msgstr "Liker"
-
-#: ../../include/conversation.php:472 ../../Zotlabs/Module/Photos.php:1110
-msgctxt "title"
-msgid "Dislikes"
-msgstr "Liker ikke"
+msgstr "Dette er en forhåndsvisning som ikke er lagret"
-#: ../../include/conversation.php:473 ../../Zotlabs/Module/Photos.php:1111
-#: ../../Zotlabs/Widget/Pinned.php:73
-msgctxt "title"
-msgid "Attending"
-msgstr "Deltar"
-
-#: ../../include/conversation.php:474 ../../Zotlabs/Module/Photos.php:1111
-#: ../../Zotlabs/Widget/Pinned.php:74
-msgctxt "title"
-msgid "Not attending"
-msgstr "Deltar ikke"
-
-#: ../../include/conversation.php:475 ../../Zotlabs/Module/Photos.php:1111
-#: ../../Zotlabs/Widget/Pinned.php:75
-msgctxt "title"
-msgid "Might attend"
-msgstr "Deltar kanskje"
-
-#: ../../include/conversation.php:477
-msgctxt "title"
-msgid "Repeats"
-msgstr ""
-
-#: ../../include/conversation.php:546
+#: ../../include/conversation.php:533
msgid "Select"
msgstr "Velg"
-#: ../../include/conversation.php:547 ../../include/conversation.php:608
-#: ../../Zotlabs/Lib/Apps.php:618 ../../Zotlabs/Lib/ThreadItem.php:183
-#: ../../Zotlabs/Lib/ThreadItem.php:544 ../../Zotlabs/Storage/Browser.php:388
-#: ../../Zotlabs/Module/Editblock.php:139
-#: ../../Zotlabs/Module/Editlayout.php:138 ../../Zotlabs/Module/Photos.php:1173
-#: ../../Zotlabs/Module/Oauth.php:172 ../../Zotlabs/Module/Group.php:252
-#: ../../Zotlabs/Module/Oauth2.php:193 ../../Zotlabs/Module/Connedit.php:540
-#: ../../Zotlabs/Module/Connedit.php:749
-#: ../../Zotlabs/Module/Editwebpage.php:167 ../../Zotlabs/Module/Thing.php:295
-#: ../../Zotlabs/Module/Blocks.php:160 ../../Zotlabs/Module/Webpages.php:252
-#: ../../Zotlabs/Module/Admin/Accounts.php:320
+#: ../../include/conversation.php:534 ../../include/conversation.php:595
+#: ../../addon/cards/Mod_Card_edit.php:124
+#: ../../addon/articles/Mod_Article_edit.php:124 ../../Zotlabs/Lib/Apps.php:618
+#: ../../Zotlabs/Lib/ThreadItem.php:180 ../../Zotlabs/Lib/ThreadItem.php:480
+#: ../../Zotlabs/Storage/Browser.php:392
+#: ../../Zotlabs/Module/Editwebpage.php:167
+#: ../../Zotlabs/Module/Webpages.php:251 ../../Zotlabs/Module/Oauth.php:172
+#: ../../Zotlabs/Module/Thing.php:302 ../../Zotlabs/Module/Tokens.php:295
+#: ../../Zotlabs/Module/Contactedit.php:651
+#: ../../Zotlabs/Module/Connedit.php:540 ../../Zotlabs/Module/Connedit.php:749
+#: ../../Zotlabs/Module/Admin/Accounts.php:220
+#: ../../Zotlabs/Module/Admin/Profs.php:177
#: ../../Zotlabs/Module/Admin/Channels.php:171
-#: ../../Zotlabs/Module/Admin/Profs.php:177 ../../Zotlabs/Module/Tokens.php:295
-#: ../../Zotlabs/Module/Permcats.php:261 ../../Zotlabs/Module/Cdav.php:1047
-#: ../../Zotlabs/Module/Cdav.php:1385 ../../Zotlabs/Module/Contactedit.php:651
-#: ../../extend/addon/hzaddons/articles/Mod_Article_edit.php:128
-#: ../../extend/addon/hzaddons/cards/Mod_Card_edit.php:130
+#: ../../Zotlabs/Module/Permcats.php:261 ../../Zotlabs/Module/Group.php:252
+#: ../../Zotlabs/Module/Blocks.php:160 ../../Zotlabs/Module/Photos.php:1162
+#: ../../Zotlabs/Module/Editlayout.php:138 ../../Zotlabs/Module/Cdav.php:1047
+#: ../../Zotlabs/Module/Cdav.php:1385 ../../Zotlabs/Module/Oauth2.php:193
+#: ../../Zotlabs/Module/Editblock.php:139
msgid "Delete"
msgstr "Slett"
-#: ../../include/conversation.php:553 ../../Zotlabs/Lib/ThreadItem.php:287
+#: ../../include/conversation.php:540 ../../Zotlabs/Lib/ThreadItem.php:242
msgid "Toggle Star Status"
msgstr "Skru av og på stjernestatus"
-#: ../../include/conversation.php:559
+#: ../../include/conversation.php:546
msgid "Private Message"
msgstr "Privat melding"
-#: ../../include/conversation.php:568 ../../Zotlabs/Lib/ThreadItem.php:297
+#: ../../include/conversation.php:555 ../../Zotlabs/Lib/ThreadItem.php:251
#: ../../Zotlabs/Widget/Pinned.php:84
msgid "Message signature validated"
msgstr "Innleggets signatur er bekreftet"
-#: ../../include/conversation.php:569 ../../Zotlabs/Lib/ThreadItem.php:298
+#: ../../include/conversation.php:556 ../../Zotlabs/Lib/ThreadItem.php:252
#: ../../Zotlabs/Widget/Pinned.php:85
msgid "Message signature incorrect"
msgstr "Innleggets signatur er feil"
-#: ../../include/conversation.php:607 ../../Zotlabs/Lib/ThreadItem.php:543
+#: ../../include/conversation.php:594 ../../Zotlabs/Lib/ThreadItem.php:479
+#: ../../Zotlabs/Module/Admin/Accounts.php:218
#: ../../Zotlabs/Module/Connections.php:358
#: ../../Zotlabs/Module/Connections.php:409
-#: ../../Zotlabs/Module/Admin/Accounts.php:318
msgid "Approve"
msgstr "Godkjenn"
-#: ../../include/conversation.php:613
+#: ../../include/conversation.php:600
#, php-format
msgid "View %s's profile @ %s"
msgstr "Vis %s sin profile @ %s"
-#: ../../include/conversation.php:635
+#: ../../include/conversation.php:622
msgid "Categories:"
msgstr "Kategorier:"
-#: ../../include/conversation.php:636
+#: ../../include/conversation.php:623
msgid "Filed under:"
msgstr "Sortert under:"
-#: ../../include/conversation.php:642 ../../Zotlabs/Lib/ThreadItem.php:455
-#: ../../Zotlabs/Widget/Pinned.php:128
+#: ../../include/conversation.php:629 ../../Zotlabs/Lib/ThreadItem.php:411
+#: ../../Zotlabs/Widget/Pinned.php:130
#, php-format
msgid "from %s"
msgstr "fra %s"
-#: ../../include/conversation.php:645 ../../Zotlabs/Lib/ThreadItem.php:458
-#: ../../Zotlabs/Widget/Pinned.php:131
+#: ../../include/conversation.php:632 ../../Zotlabs/Widget/Pinned.php:133
#, php-format
msgid "last edited: %s"
msgstr "sist endret: %s"
-#: ../../include/conversation.php:646 ../../Zotlabs/Lib/ThreadItem.php:459
-#: ../../Zotlabs/Widget/Pinned.php:132
+#: ../../include/conversation.php:633 ../../Zotlabs/Widget/Pinned.php:134
#, php-format
msgid "Expires: %s"
msgstr "Utløper: %s"
-#: ../../include/conversation.php:661
-#: ../../extend/addon/hzaddons/articles/articles.php:83
-#: ../../extend/addon/hzaddons/cards/cards.php:82
+#: ../../include/conversation.php:648 ../../addon/cards/cards.php:82
+#: ../../addon/articles/articles.php:83
msgid "View in context"
msgstr "Vis i sammenheng"
-#: ../../include/conversation.php:663 ../../Zotlabs/Lib/ThreadItem.php:537
-#: ../../Zotlabs/Module/Photos.php:1077
+#: ../../include/conversation.php:650 ../../Zotlabs/Lib/ThreadItem.php:473
+#: ../../Zotlabs/Module/Photos.php:1078
msgid "Please wait"
msgstr "Vennligst vent"
-#: ../../include/conversation.php:764
+#: ../../include/conversation.php:746
msgid "remove"
msgstr "fjern"
-#: ../../include/conversation.php:768
+#: ../../include/conversation.php:750
msgid "Loading..."
msgstr "Laster..."
-#: ../../include/conversation.php:769 ../../Zotlabs/Lib/ThreadItem.php:314
+#: ../../include/conversation.php:751 ../../Zotlabs/Lib/ThreadItem.php:268
msgid "Conversation Features"
msgstr "Samtaleinnstillinger"
-#: ../../include/conversation.php:770
+#: ../../include/conversation.php:752
msgid "Delete Selected Items"
msgstr "Slett valgte elementer"
-#: ../../include/conversation.php:806
+#: ../../include/conversation.php:788
msgid "View Source"
msgstr "Vis kilde"
-#: ../../include/conversation.php:816
+#: ../../include/conversation.php:798
msgid "Follow Thread"
msgstr "Følg tråd"
-#: ../../include/conversation.php:825
+#: ../../include/conversation.php:807
msgid "Unfollow Thread"
msgstr "Ikke følg tråd"
-#: ../../include/conversation.php:914 ../../Zotlabs/Module/Connedit.php:501
+#: ../../include/conversation.php:884 ../../include/nav.php:127
+#: ../../addon/openclipatar/openclipatar.php:58 ../../Zotlabs/Lib/Apps.php:350
+#: ../../Zotlabs/Module/Connedit.php:480
+msgid "View Profile"
+msgstr "Vis profil"
+
+#: ../../include/conversation.php:896 ../../Zotlabs/Module/Connedit.php:501
msgid "Recent Activity"
msgstr "Nylig aktivitet"
-#: ../../include/conversation.php:926 ../../include/channel.php:1625
-#: ../../include/connections.php:150 ../../Zotlabs/Module/Suggest.php:69
+#: ../../include/conversation.php:908 ../../include/connections.php:150
+#: ../../include/channel.php:1627 ../../Zotlabs/Widget/Suggestions.php:51
+#: ../../Zotlabs/Widget/Follow.php:37 ../../Zotlabs/Module/Suggest.php:69
#: ../../Zotlabs/Module/Directory.php:371
#: ../../Zotlabs/Module/Connections.php:365
-#: ../../Zotlabs/Widget/Suggestions.php:51 ../../Zotlabs/Widget/Follow.php:37
msgid "Connect"
msgstr "Koble"
-#: ../../include/conversation.php:938
+#: ../../include/conversation.php:920
msgid "Edit Connection"
msgstr "Endre forbindelse"
-#: ../../include/conversation.php:1006 ../../include/cdav.php:158
-#: ../../include/cdav.php:159 ../../include/cdav.php:167
-#: ../../Zotlabs/Lib/Apps.php:1168 ../../Zotlabs/Lib/Apps.php:1252
-#: ../../Zotlabs/Lib/Activity.php:1684 ../../Zotlabs/Module/Photos.php:788
-#: ../../Zotlabs/Module/Photos.php:1246
-#: ../../Zotlabs/Module/Embedphotos.php:177 ../../Zotlabs/Widget/Pinned.php:257
-#: ../../Zotlabs/Widget/Album.php:90 ../../Zotlabs/Widget/Portfolio.php:99
-msgid "Unknown"
-msgstr "Ukjent"
-
-#: ../../include/conversation.php:1008
-#, fuzzy
-#| msgid "Remove this file"
-msgid "Approve this item"
-msgstr "Fjern denne filen"
-
-#: ../../include/conversation.php:1008
-#, fuzzy
-#| msgid "Delete this item?"
-msgid "Delete this item"
-msgstr "Slett dette elementet?"
-
-#: ../../include/conversation.php:1062
+#: ../../include/conversation.php:949
#, php-format
msgid "%s likes this."
msgstr "%s liker dette."
-#: ../../include/conversation.php:1062
+#: ../../include/conversation.php:949
#, php-format
msgid "%s doesn't like this."
msgstr "%s liker ikke dette."
-#: ../../include/conversation.php:1066
+#: ../../include/conversation.php:953
#, php-format
msgid "<span %1$s>%2$d people</span> like this."
msgid_plural "<span %1$s>%2$d people</span> like this."
msgstr[0] "<span %1$s>%2$d person</span> liker dette."
msgstr[1] "<span %1$s>%2$d personer</span> liker dette."
-#: ../../include/conversation.php:1068
+#: ../../include/conversation.php:955
#, php-format
msgid "<span %1$s>%2$d people</span> don't like this."
msgid_plural "<span %1$s>%2$d people</span> don't like this."
msgstr[0] "<span %1$s>%2$d person</span> liker ikke dette."
msgstr[1] "<span %1$s>%2$d personer</span> liker ikke dette."
-#: ../../include/conversation.php:1074
+#: ../../include/conversation.php:961
msgid "and"
msgstr "og"
-#: ../../include/conversation.php:1077
+#: ../../include/conversation.php:964
#, php-format
msgid ", and %d other people"
msgid_plural ", and %d other people"
msgstr[0] ", og %d annen person"
msgstr[1] ", og %d andre personer"
-#: ../../include/conversation.php:1078
+#: ../../include/conversation.php:965
#, php-format
msgid "%s like this."
msgstr "%s liker dette."
-#: ../../include/conversation.php:1078
+#: ../../include/conversation.php:965
#, php-format
msgid "%s don't like this."
msgstr "%s liker ikke dette."
-#: ../../include/conversation.php:1129
-#: ../../extend/addon/hzaddons/hsse/hsse.php:82
+#: ../../include/conversation.php:1014 ../../addon/hsse/hsse.php:82
msgid "Set your location"
msgstr "Angi din plassering"
-#: ../../include/conversation.php:1130
-#: ../../extend/addon/hzaddons/hsse/hsse.php:83
+#: ../../include/conversation.php:1015 ../../addon/hsse/hsse.php:83
msgid "Clear browser location"
msgstr "Fjern nettleserplassering"
-#: ../../include/conversation.php:1142 ../../Zotlabs/Module/Editblock.php:116
-#: ../../Zotlabs/Module/Chat.php:219 ../../Zotlabs/Module/Editwebpage.php:143
-#: ../../extend/addon/hzaddons/articles/Mod_Article_edit.php:99
-#: ../../extend/addon/hzaddons/hsse/hsse.php:95
-#: ../../extend/addon/hzaddons/cards/Mod_Card_edit.php:101
+#: ../../include/conversation.php:1027 ../../addon/cards/Mod_Card_edit.php:94
+#: ../../addon/articles/Mod_Article_edit.php:94 ../../addon/hsse/hsse.php:95
+#: ../../Zotlabs/Module/Editwebpage.php:143 ../../Zotlabs/Module/Chat.php:219
+#: ../../Zotlabs/Module/Editblock.php:116
msgid "Insert web link"
msgstr "Sett inn web-lenke"
-#: ../../include/conversation.php:1146
-#: ../../extend/addon/hzaddons/hsse/hsse.php:99
+#: ../../include/conversation.php:1031 ../../addon/hsse/hsse.php:99
+#: ../../Zotlabs/Lib/ThreadItem.php:816
msgid "Embed (existing) photo from your photo albums"
msgstr "Sett inn (eksisterende) bilde fra fotoalbumene dine"
-#: ../../include/conversation.php:1179 ../../Zotlabs/Module/Chat.php:217
-#: ../../extend/addon/hzaddons/hsse/hsse.php:134
+#: ../../include/conversation.php:1064 ../../addon/hsse/hsse.php:134
+#: ../../Zotlabs/Module/Chat.php:217
msgid "Please enter a link URL:"
msgstr "Vennligst skriv inn en lenke URL:"
-#: ../../include/conversation.php:1180
-#: ../../extend/addon/hzaddons/hsse/hsse.php:135
+#: ../../include/conversation.php:1065 ../../addon/hsse/hsse.php:135
msgid "Tag term:"
msgstr "Merkelapp:"
-#: ../../include/conversation.php:1181
-#: ../../extend/addon/hzaddons/hsse/hsse.php:136
+#: ../../include/conversation.php:1066 ../../addon/hsse/hsse.php:136
msgid "Where are you right now?"
msgstr "Hvor er du akkurat nå?"
-#: ../../include/conversation.php:1184 ../../Zotlabs/Module/Cover_photo.php:388
+#: ../../include/conversation.php:1069 ../../addon/wiki/Mod_Wiki.php:400
+#: ../../addon/hsse/hsse.php:139 ../../Zotlabs/Module/Cover_photo.php:388
#: ../../Zotlabs/Module/Profile_photo.php:555
-#: ../../extend/addon/hzaddons/hsse/hsse.php:139
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:400
msgid "Choose images to embed"
msgstr "Velg bilder"
-#: ../../include/conversation.php:1185 ../../Zotlabs/Module/Cover_photo.php:389
+#: ../../include/conversation.php:1070 ../../addon/wiki/Mod_Wiki.php:401
+#: ../../addon/hsse/hsse.php:140 ../../Zotlabs/Module/Cover_photo.php:389
#: ../../Zotlabs/Module/Profile_photo.php:556
-#: ../../extend/addon/hzaddons/hsse/hsse.php:140
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:401
msgid "Choose an album"
msgstr "Velg et album"
-#: ../../include/conversation.php:1186
-#: ../../extend/addon/hzaddons/hsse/hsse.php:141
+#: ../../include/conversation.php:1071 ../../addon/hsse/hsse.php:141
msgid "Choose a different album..."
msgstr "Velg et annet album..."
-#: ../../include/conversation.php:1187 ../../Zotlabs/Module/Cover_photo.php:391
+#: ../../include/conversation.php:1072 ../../addon/wiki/Mod_Wiki.php:403
+#: ../../addon/hsse/hsse.php:142 ../../Zotlabs/Module/Cover_photo.php:391
#: ../../Zotlabs/Module/Profile_photo.php:558
-#: ../../extend/addon/hzaddons/hsse/hsse.php:142
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:403
msgid "Error getting album list"
-msgstr ""
+msgstr "Klarte ikke å hente albumlisten"
-#: ../../include/conversation.php:1188 ../../Zotlabs/Module/Cover_photo.php:392
+#: ../../include/conversation.php:1073 ../../addon/wiki/Mod_Wiki.php:404
+#: ../../addon/hsse/hsse.php:143 ../../Zotlabs/Module/Cover_photo.php:392
#: ../../Zotlabs/Module/Profile_photo.php:559
-#: ../../extend/addon/hzaddons/hsse/hsse.php:143
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:404
msgid "Error getting photo link"
-msgstr ""
+msgstr "Klarte ikke å hente fotolenken"
-#: ../../include/conversation.php:1189 ../../Zotlabs/Module/Cover_photo.php:393
+#: ../../include/conversation.php:1074 ../../addon/wiki/Mod_Wiki.php:405
+#: ../../addon/hsse/hsse.php:144 ../../Zotlabs/Module/Cover_photo.php:393
#: ../../Zotlabs/Module/Profile_photo.php:560
-#: ../../extend/addon/hzaddons/hsse/hsse.php:144
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:405
msgid "Error getting album"
-msgstr ""
+msgstr "Klarte ikke å hente album"
-#: ../../include/conversation.php:1190
-#: ../../extend/addon/hzaddons/hsse/hsse.php:145
+#: ../../include/conversation.php:1075 ../../addon/hsse/hsse.php:145
msgid "Comments enabled"
-msgstr ""
+msgstr "Kommentarer er tillatt"
-#: ../../include/conversation.php:1191
-#: ../../extend/addon/hzaddons/hsse/hsse.php:146
+#: ../../include/conversation.php:1076 ../../addon/hsse/hsse.php:146
+#: ../../Zotlabs/Lib/ThreadItem.php:470
msgid "Comments disabled"
-msgstr ""
+msgstr "Komentarer er ikke tillatt"
-#: ../../include/conversation.php:1192
-#, fuzzy
-#| msgid "Profile deleted."
+#: ../../include/conversation.php:1077
msgid "Confirm delete"
-msgstr "Profilen er slettet."
+msgstr "Bekreft sletting"
-#: ../../include/conversation.php:1209 ../../Zotlabs/Lib/ThreadItem.php:878
-#: ../../Zotlabs/Module/Photos.php:1096 ../../Zotlabs/Module/Webpages.php:257
-#: ../../extend/addon/hzaddons/hsse/hsse.php:153
+#: ../../include/conversation.php:1094 ../../addon/hsse/hsse.php:153
+#: ../../Zotlabs/Lib/ThreadItem.php:820 ../../Zotlabs/Module/Webpages.php:256
+#: ../../Zotlabs/Module/Photos.php:1097
msgid "Preview"
msgstr "Forhåndsvisning"
-#: ../../include/conversation.php:1242 ../../Zotlabs/Lib/ThreadItem.php:344
-#: ../../Zotlabs/Module/Photos.php:1076 ../../Zotlabs/Module/Layouts.php:192
-#: ../../Zotlabs/Module/Blocks.php:159 ../../Zotlabs/Module/Webpages.php:251
-#: ../../Zotlabs/Widget/Cdav.php:142
-#: ../../extend/addon/hzaddons/hsse/hsse.php:186
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:304
-msgid "Share"
-msgstr "Del"
+#: ../../include/conversation.php:1128
+msgid "Start a conversation"
+msgstr "Start en samtale"
-#: ../../include/conversation.php:1251
-#: ../../extend/addon/hzaddons/hsse/hsse.php:195
+#: ../../include/conversation.php:1136 ../../addon/hsse/hsse.php:195
msgid "Page link name"
msgstr "Sidens lenkenavn"
-#: ../../include/conversation.php:1254
-#: ../../extend/addon/hzaddons/hsse/hsse.php:198
+#: ../../include/conversation.php:1139 ../../addon/hsse/hsse.php:198
msgid "Post as"
msgstr "Lag innlegg som"
-#: ../../include/conversation.php:1256 ../../Zotlabs/Lib/ThreadItem.php:869
-#: ../../extend/addon/hzaddons/hsse/hsse.php:200
+#: ../../include/conversation.php:1141 ../../addon/hsse/hsse.php:200
+#: ../../Zotlabs/Lib/ThreadItem.php:810
msgid "Bold"
msgstr "Uthevet"
-#: ../../include/conversation.php:1257 ../../Zotlabs/Lib/ThreadItem.php:870
-#: ../../extend/addon/hzaddons/hsse/hsse.php:201
+#: ../../include/conversation.php:1142 ../../addon/hsse/hsse.php:201
+#: ../../Zotlabs/Lib/ThreadItem.php:811
msgid "Italic"
msgstr "Kursiv"
-#: ../../include/conversation.php:1258 ../../Zotlabs/Lib/ThreadItem.php:871
-#: ../../extend/addon/hzaddons/hsse/hsse.php:202
+#: ../../include/conversation.php:1143 ../../Zotlabs/Lib/ThreadItem.php:812
+msgid "Highlight selected text"
+msgstr "Fremhev valgt tekst"
+
+#: ../../include/conversation.php:1144 ../../addon/hsse/hsse.php:202
+#: ../../Zotlabs/Lib/ThreadItem.php:813
msgid "Underline"
msgstr "Understreket"
-#: ../../include/conversation.php:1259 ../../Zotlabs/Lib/ThreadItem.php:872
-#: ../../extend/addon/hzaddons/hsse/hsse.php:203
+#: ../../include/conversation.php:1145 ../../addon/hsse/hsse.php:203
+#: ../../Zotlabs/Lib/ThreadItem.php:814
msgid "Quote"
msgstr "Sitat"
-#: ../../include/conversation.php:1260 ../../Zotlabs/Lib/ThreadItem.php:873
-#: ../../extend/addon/hzaddons/hsse/hsse.php:204
+#: ../../include/conversation.php:1146 ../../addon/hsse/hsse.php:204
+#: ../../Zotlabs/Lib/ThreadItem.php:815
msgid "Code"
msgstr "Kode"
-#: ../../include/conversation.php:1261 ../../Zotlabs/Lib/ThreadItem.php:875
-#: ../../extend/addon/hzaddons/hsse/hsse.php:205
+#: ../../include/conversation.php:1147 ../../addon/hsse/hsse.php:205
+#: ../../Zotlabs/Lib/ThreadItem.php:817
msgid "Attach/Upload file"
msgstr "Last opp fil/vedlegg"
-#: ../../include/conversation.php:1264
-#: ../../extend/addon/hzaddons/hsse/hsse.php:208
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:397
+#: ../../include/conversation.php:1150 ../../addon/wiki/Mod_Wiki.php:397
+#: ../../addon/hsse/hsse.php:208
msgid "Embed an image from your albums"
-msgstr ""
+msgstr "Sett inn et bilde fra albumene dine"
-#: ../../include/conversation.php:1265 ../../include/conversation.php:1320
-#: ../../Zotlabs/Storage/Browser.php:387 ../../Zotlabs/Module/Editblock.php:141
-#: ../../Zotlabs/Module/Editlayout.php:140 ../../Zotlabs/Module/Oauth.php:110
-#: ../../Zotlabs/Module/Oauth.php:136 ../../Zotlabs/Module/Editpost.php:114
-#: ../../Zotlabs/Module/Oauth2.php:115 ../../Zotlabs/Module/Oauth2.php:143
-#: ../../Zotlabs/Module/Cover_photo.php:386 ../../Zotlabs/Module/Tagrm.php:15
-#: ../../Zotlabs/Module/Tagrm.php:138 ../../Zotlabs/Module/Connedit.php:750
-#: ../../Zotlabs/Module/Editwebpage.php:169 ../../Zotlabs/Module/Filer.php:56
-#: ../../Zotlabs/Module/Fbrowser.php:66 ../../Zotlabs/Module/Fbrowser.php:88
-#: ../../Zotlabs/Module/Profile_photo.php:553
-#: ../../Zotlabs/Module/Admin/Addons.php:431 ../../Zotlabs/Module/Cdav.php:1049
-#: ../../Zotlabs/Module/Cdav.php:1386
-#: ../../extend/addon/hzaddons/articles/Mod_Article_edit.php:130
-#: ../../extend/addon/hzaddons/hsse/hsse.php:209
-#: ../../extend/addon/hzaddons/hsse/hsse.php:258
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:365
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:398
-#: ../../extend/addon/hzaddons/cards/Mod_Card_edit.php:132
+#: ../../include/conversation.php:1151 ../../include/conversation.php:1206
+#: ../../addon/cards/Mod_Card_edit.php:126
+#: ../../addon/articles/Mod_Article_edit.php:126
+#: ../../addon/wiki/Mod_Wiki.php:365 ../../addon/wiki/Mod_Wiki.php:398
+#: ../../addon/hsse/hsse.php:209 ../../addon/hsse/hsse.php:258
+#: ../../Zotlabs/Storage/Browser.php:391
+#: ../../Zotlabs/Module/Cover_photo.php:386
+#: ../../Zotlabs/Module/Editwebpage.php:169 ../../Zotlabs/Module/Oauth.php:111
+#: ../../Zotlabs/Module/Oauth.php:136 ../../Zotlabs/Module/Connedit.php:750
+#: ../../Zotlabs/Module/Profile_photo.php:553 ../../Zotlabs/Module/Tagrm.php:15
+#: ../../Zotlabs/Module/Tagrm.php:138 ../../Zotlabs/Module/Fbrowser.php:68
+#: ../../Zotlabs/Module/Fbrowser.php:90 ../../Zotlabs/Module/Editlayout.php:140
+#: ../../Zotlabs/Module/Editpost.php:115 ../../Zotlabs/Module/Cdav.php:1049
+#: ../../Zotlabs/Module/Cdav.php:1386 ../../Zotlabs/Module/Oauth2.php:116
+#: ../../Zotlabs/Module/Oauth2.php:143 ../../Zotlabs/Module/Editblock.php:141
+#: ../../Zotlabs/Module/Filer.php:67
msgid "Cancel"
msgstr "Avbryt"
-#: ../../include/conversation.php:1266 ../../include/conversation.php:1319
-#: ../../Zotlabs/Module/Cover_photo.php:387
+#: ../../include/conversation.php:1152 ../../include/conversation.php:1205
+#: ../../addon/wiki/Mod_Wiki.php:399 ../../addon/hsse/hsse.php:210
+#: ../../addon/hsse/hsse.php:257 ../../Zotlabs/Module/Cover_photo.php:387
#: ../../Zotlabs/Module/Profile_photo.php:554
-#: ../../extend/addon/hzaddons/hsse/hsse.php:210
-#: ../../extend/addon/hzaddons/hsse/hsse.php:257
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:399
msgid "OK"
msgstr "OK"
-#: ../../include/conversation.php:1268
-#: ../../extend/addon/hzaddons/hsse/hsse.php:212
+#: ../../include/conversation.php:1154 ../../addon/hsse/hsse.php:212
msgid "Toggle voting"
msgstr "Skru av eller på stemming"
-#: ../../include/conversation.php:1269
+#: ../../include/conversation.php:1155
msgid "Toggle poll"
msgstr "Spørreskjema (på/av)"
-#: ../../include/conversation.php:1270
+#: ../../include/conversation.php:1156
msgid "Option"
msgstr "Valg"
-#: ../../include/conversation.php:1271
+#: ../../include/conversation.php:1157
msgid "Add option"
msgstr "Legg til valg"
-#: ../../include/conversation.php:1272
+#: ../../include/conversation.php:1158
msgid "Minutes"
msgstr "Minutter"
-#: ../../include/conversation.php:1272
-#, fuzzy
+#: ../../include/conversation.php:1158
msgid "Hours"
-msgstr "timer"
+msgstr "Timer"
-#: ../../include/conversation.php:1272
-#, fuzzy
+#: ../../include/conversation.php:1158
msgid "Days"
-msgstr "dager"
+msgstr "Dager"
-#: ../../include/conversation.php:1273
+#: ../../include/conversation.php:1159
msgid "Allow multiple answers"
msgstr "Tillat flere svar"
-#: ../../include/conversation.php:1273
-#: ../../view/theme/redbasic/php/config.php:201
-#: ../../view/theme/redbasic/php/config.php:202
-#: ../../view/theme/redbasic/php/config.php:203
-#: ../../view/theme/redbasic/php/config.php:215 ../../boot.php:1765
-#: ../../Zotlabs/Lib/Libzotdir.php:166 ../../Zotlabs/Lib/Libzotdir.php:167
-#: ../../Zotlabs/Lib/Libzotdir.php:169 ../../Zotlabs/Storage/Browser.php:310
-#: ../../Zotlabs/Storage/Browser.php:311 ../../Zotlabs/Storage/Browser.php:312
-#: ../../Zotlabs/Storage/Browser.php:393 ../../Zotlabs/Storage/Browser.php:395
-#: ../../Zotlabs/Storage/Browser.php:559 ../../Zotlabs/Module/Menu.php:163
-#: ../../Zotlabs/Module/Menu.php:222 ../../Zotlabs/Module/Api.php:101
-#: ../../Zotlabs/Module/Photos.php:666 ../../Zotlabs/Module/Profiles.php:674
-#: ../../Zotlabs/Module/Profiles.php:684 ../../Zotlabs/Module/Profiles.php:692
-#: ../../Zotlabs/Module/Profiles.php:696 ../../Zotlabs/Module/Sources.php:122
-#: ../../Zotlabs/Module/Sources.php:157 ../../Zotlabs/Module/Group.php:138
-#: ../../Zotlabs/Module/Group.php:139 ../../Zotlabs/Module/Group.php:148
-#: ../../Zotlabs/Module/Group.php:250 ../../Zotlabs/Module/Group.php:302
-#: ../../Zotlabs/Module/Group.php:303 ../../Zotlabs/Module/Connedit.php:622
-#: ../../Zotlabs/Module/Import.php:611 ../../Zotlabs/Module/Import.php:615
-#: ../../Zotlabs/Module/Import.php:616 ../../Zotlabs/Module/Register.php:515
-#: ../../Zotlabs/Module/Defperms.php:195
-#: ../../Zotlabs/Module/Filestorage.php:203
-#: ../../Zotlabs/Module/Filestorage.php:211
-#: ../../Zotlabs/Module/Admin/Site.php:306 ../../Zotlabs/Module/Mitem.php:176
-#: ../../Zotlabs/Module/Mitem.php:177 ../../Zotlabs/Module/Mitem.php:256
-#: ../../Zotlabs/Module/Mitem.php:257 ../../Zotlabs/Module/Permcats.php:247
-#: ../../Zotlabs/Module/Settings/Channel.php:225
-#: ../../Zotlabs/Module/Settings/Multifactor.php:82
-#: ../../Zotlabs/Module/Settings/Privacy.php:133
-#: ../../Zotlabs/Module/Settings/Privacy.php:134
-#: ../../Zotlabs/Module/Settings/Privacy.php:135
-#: ../../Zotlabs/Module/Settings/Privacy.php:136
-#: ../../Zotlabs/Module/Settings/Privacy.php:137
-#: ../../Zotlabs/Module/Settings/Privacy.php:138
-#: ../../Zotlabs/Module/Settings/Display.php:87
-#: ../../Zotlabs/Module/Contactedit.php:270
-#: ../../Zotlabs/Module/Contactedit.php:315
-#: ../../extend/addon/hzaddons/cart/cart.php:1418
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:72
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:338
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:362
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:438
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:462
-#: ../../extend/addon/hzaddons/cart/submodules/subscriptions.php:153
-#: ../../extend/addon/hzaddons/cart/submodules/subscriptions.php:425
-#: ../../extend/addon/hzaddons/cart/submodules/manualcat.php:63
-#: ../../extend/addon/hzaddons/cart/submodules/manualcat.php:254
-#: ../../extend/addon/hzaddons/cart/submodules/manualcat.php:258
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbuttonV2.php:88
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbuttonV2.php:98
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbutton.php:87
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbutton.php:95
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:67
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:651
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:655
-#: ../../extend/addon/hzaddons/cart/Settings/Cart.php:61
-#: ../../extend/addon/hzaddons/cart/Settings/Cart.php:73
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:218
-#: ../../extend/addon/hzaddons/pumpio/Mod_Pumpio.php:92
-#: ../../extend/addon/hzaddons/pumpio/Mod_Pumpio.php:96
-#: ../../extend/addon/hzaddons/pumpio/Mod_Pumpio.php:100
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:258
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:280
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:289
-#: ../../extend/addon/hzaddons/ljpost/Mod_Ljpost.php:61
-#: ../../extend/addon/hzaddons/ljpost/Mod_Ljpost.php:65
-#: ../../extend/addon/hzaddons/ljpost/Mod_Ljpost.php:69
-#: ../../extend/addon/hzaddons/nofed/Mod_Nofed.php:40
-#: ../../extend/addon/hzaddons/twitter/Mod_Twitter.php:160
-#: ../../extend/addon/hzaddons/twitter/Mod_Twitter.php:169
-#: ../../extend/addon/hzaddons/diaspora/Mod_Diaspora.php:70
-#: ../../extend/addon/hzaddons/dwpost/Mod_Dwpost.php:59
-#: ../../extend/addon/hzaddons/dwpost/Mod_Dwpost.php:63
-#: ../../extend/addon/hzaddons/content_import/Mod_content_import.php:135
-#: ../../extend/addon/hzaddons/content_import/Mod_content_import.php:136
-#: ../../extend/addon/hzaddons/rtof/Mod_Rtof.php:47
-#: ../../extend/addon/hzaddons/redred/Mod_Redred.php:61
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:230
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:231
-#: ../../extend/addon/hzaddons/libertree/Mod_Libertree.php:57
-#: ../../extend/addon/hzaddons/pubcrawl/Mod_Pubcrawl.php:45
-#: ../../extend/addon/hzaddons/ijpost/Mod_Ijpost.php:61
-#: ../../extend/addon/hzaddons/wppost/Mod_Wppost.php:84
-#: ../../extend/addon/hzaddons/wppost/Mod_Wppost.php:88
-#: ../../extend/addon/hzaddons/wppost/Mod_Wppost.php:92
-msgid "No"
-msgstr "Nei"
-
-#: ../../include/conversation.php:1273
-#: ../../view/theme/redbasic/php/config.php:201
-#: ../../view/theme/redbasic/php/config.php:202
-#: ../../view/theme/redbasic/php/config.php:203
-#: ../../view/theme/redbasic/php/config.php:215 ../../boot.php:1765
-#: ../../Zotlabs/Lib/Libzotdir.php:166 ../../Zotlabs/Lib/Libzotdir.php:167
-#: ../../Zotlabs/Lib/Libzotdir.php:169 ../../Zotlabs/Storage/Browser.php:310
-#: ../../Zotlabs/Storage/Browser.php:311 ../../Zotlabs/Storage/Browser.php:312
-#: ../../Zotlabs/Storage/Browser.php:393 ../../Zotlabs/Storage/Browser.php:395
-#: ../../Zotlabs/Storage/Browser.php:559 ../../Zotlabs/Module/Menu.php:163
-#: ../../Zotlabs/Module/Menu.php:222 ../../Zotlabs/Module/Api.php:100
-#: ../../Zotlabs/Module/Photos.php:666 ../../Zotlabs/Module/Profiles.php:674
-#: ../../Zotlabs/Module/Profiles.php:684 ../../Zotlabs/Module/Profiles.php:692
-#: ../../Zotlabs/Module/Profiles.php:696 ../../Zotlabs/Module/Sources.php:122
-#: ../../Zotlabs/Module/Sources.php:157 ../../Zotlabs/Module/Group.php:138
-#: ../../Zotlabs/Module/Group.php:139 ../../Zotlabs/Module/Group.php:148
-#: ../../Zotlabs/Module/Group.php:250 ../../Zotlabs/Module/Group.php:302
-#: ../../Zotlabs/Module/Group.php:303 ../../Zotlabs/Module/Import.php:611
-#: ../../Zotlabs/Module/Import.php:615 ../../Zotlabs/Module/Import.php:616
-#: ../../Zotlabs/Module/Register.php:515 ../../Zotlabs/Module/Defperms.php:195
-#: ../../Zotlabs/Module/Filestorage.php:203
-#: ../../Zotlabs/Module/Filestorage.php:211
-#: ../../Zotlabs/Module/Admin/Site.php:308 ../../Zotlabs/Module/Mitem.php:176
-#: ../../Zotlabs/Module/Mitem.php:177 ../../Zotlabs/Module/Mitem.php:256
-#: ../../Zotlabs/Module/Mitem.php:257 ../../Zotlabs/Module/Permcats.php:247
-#: ../../Zotlabs/Module/Settings/Channel.php:225
-#: ../../Zotlabs/Module/Settings/Multifactor.php:82
-#: ../../Zotlabs/Module/Settings/Privacy.php:133
-#: ../../Zotlabs/Module/Settings/Privacy.php:134
-#: ../../Zotlabs/Module/Settings/Privacy.php:135
-#: ../../Zotlabs/Module/Settings/Privacy.php:136
-#: ../../Zotlabs/Module/Settings/Privacy.php:137
-#: ../../Zotlabs/Module/Settings/Privacy.php:138
-#: ../../Zotlabs/Module/Settings/Display.php:87
-#: ../../Zotlabs/Module/Contactedit.php:270
-#: ../../extend/addon/hzaddons/cart/cart.php:1418
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:72
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:337
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:361
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:437
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:461
-#: ../../extend/addon/hzaddons/cart/submodules/subscriptions.php:153
-#: ../../extend/addon/hzaddons/cart/submodules/subscriptions.php:425
-#: ../../extend/addon/hzaddons/cart/submodules/manualcat.php:63
-#: ../../extend/addon/hzaddons/cart/submodules/manualcat.php:254
-#: ../../extend/addon/hzaddons/cart/submodules/manualcat.php:258
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbuttonV2.php:88
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbuttonV2.php:98
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbutton.php:87
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbutton.php:95
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:67
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:651
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:655
-#: ../../extend/addon/hzaddons/cart/Settings/Cart.php:61
-#: ../../extend/addon/hzaddons/cart/Settings/Cart.php:73
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:218
-#: ../../extend/addon/hzaddons/pumpio/Mod_Pumpio.php:92
-#: ../../extend/addon/hzaddons/pumpio/Mod_Pumpio.php:96
-#: ../../extend/addon/hzaddons/pumpio/Mod_Pumpio.php:100
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:258
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:280
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:289
-#: ../../extend/addon/hzaddons/ljpost/Mod_Ljpost.php:61
-#: ../../extend/addon/hzaddons/ljpost/Mod_Ljpost.php:65
-#: ../../extend/addon/hzaddons/ljpost/Mod_Ljpost.php:69
-#: ../../extend/addon/hzaddons/nofed/Mod_Nofed.php:40
-#: ../../extend/addon/hzaddons/twitter/Mod_Twitter.php:160
-#: ../../extend/addon/hzaddons/twitter/Mod_Twitter.php:169
-#: ../../extend/addon/hzaddons/diaspora/Mod_Diaspora.php:70
-#: ../../extend/addon/hzaddons/dwpost/Mod_Dwpost.php:59
-#: ../../extend/addon/hzaddons/dwpost/Mod_Dwpost.php:63
-#: ../../extend/addon/hzaddons/content_import/Mod_content_import.php:135
-#: ../../extend/addon/hzaddons/content_import/Mod_content_import.php:136
-#: ../../extend/addon/hzaddons/rtof/Mod_Rtof.php:47
-#: ../../extend/addon/hzaddons/redred/Mod_Redred.php:61
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:230
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:231
-#: ../../extend/addon/hzaddons/libertree/Mod_Libertree.php:57
-#: ../../extend/addon/hzaddons/pubcrawl/Mod_Pubcrawl.php:45
-#: ../../extend/addon/hzaddons/ijpost/Mod_Ijpost.php:61
-#: ../../extend/addon/hzaddons/wppost/Mod_Wppost.php:84
-#: ../../extend/addon/hzaddons/wppost/Mod_Wppost.php:88
-#: ../../extend/addon/hzaddons/wppost/Mod_Wppost.php:92
-msgid "Yes"
-msgstr "Ja"
-
-#: ../../include/conversation.php:1275
-#: ../../extend/addon/hzaddons/hsse/hsse.php:215
+#: ../../include/conversation.php:1161 ../../addon/hsse/hsse.php:215
msgid "Disable comments"
msgstr "Slå av kommentarer"
-#: ../../include/conversation.php:1276
-#: ../../extend/addon/hzaddons/hsse/hsse.php:216
+#: ../../include/conversation.php:1162 ../../addon/hsse/hsse.php:216
msgid "Toggle comments"
-msgstr ""
+msgstr "Slå av/på kommentarer"
-#: ../../include/conversation.php:1282 ../../Zotlabs/Module/Editblock.php:129
-#: ../../Zotlabs/Module/Photos.php:667 ../../Zotlabs/Module/Photos.php:1042
-#: ../../extend/addon/hzaddons/articles/Mod_Article_edit.php:116
-#: ../../extend/addon/hzaddons/hsse/hsse.php:221
-#: ../../extend/addon/hzaddons/cards/Mod_Card_edit.php:118
+#: ../../include/conversation.php:1168 ../../addon/cards/Mod_Card_edit.php:111
+#: ../../addon/articles/Mod_Article_edit.php:111 ../../addon/hsse/hsse.php:221
+#: ../../Zotlabs/Module/Photos.php:669 ../../Zotlabs/Module/Photos.php:1043
+#: ../../Zotlabs/Module/Editblock.php:129
msgid "Title (optional)"
msgstr "Tittel (valgfri)"
-#: ../../include/conversation.php:1283
+#: ../../include/conversation.php:1169
msgid "Summary (optional)"
msgstr "Sammendrag (valgfritt)"
-#: ../../include/conversation.php:1286
-#: ../../extend/addon/hzaddons/hsse/hsse.php:224
+#: ../../include/conversation.php:1172 ../../addon/hsse/hsse.php:224
msgid "Categories (optional, comma-separated list)"
msgstr "Kategorier (valgfri, kommaseparert liste)"
-#: ../../include/conversation.php:1287
-#: ../../extend/addon/hzaddons/hsse/hsse.php:225
+#: ../../include/conversation.php:1173 ../../addon/hsse/hsse.php:225
msgid "Permission settings"
msgstr "Tillatelser - innstillinger"
-#: ../../include/conversation.php:1309
-#: ../../extend/addon/hzaddons/hsse/hsse.php:247
+#: ../../include/conversation.php:1195 ../../addon/hsse/hsse.php:247
msgid "Other networks and post services"
msgstr "Andre nettverk og innleggstjenester"
-#: ../../include/conversation.php:1312
-#: ../../extend/addon/hzaddons/hsse/hsse.php:250
+#: ../../include/conversation.php:1198 ../../addon/hsse/hsse.php:250
msgid "Set expiration date"
msgstr "Angi utløpsdato"
-#: ../../include/conversation.php:1315
-#: ../../extend/addon/hzaddons/hsse/hsse.php:253
+#: ../../include/conversation.php:1201 ../../addon/hsse/hsse.php:253
msgid "Set publish date"
msgstr "Angi publiseringsdato"
-#: ../../include/conversation.php:1317 ../../Zotlabs/Lib/ThreadItem.php:881
-#: ../../Zotlabs/Module/Chat.php:218
-#: ../../extend/addon/hzaddons/hsse/hsse.php:255
+#: ../../include/conversation.php:1203 ../../addon/hsse/hsse.php:255
+#: ../../Zotlabs/Lib/ThreadItem.php:823 ../../Zotlabs/Module/Chat.php:218
msgid "Encrypt text"
msgstr "Krypter tekst"
-#: ../../include/conversation.php:1560 ../../include/taxonomy.php:677
-#: ../../Zotlabs/Module/Photos.php:1129
-msgctxt "noun"
-msgid "Like"
-msgid_plural "Likes"
-msgstr[0] "Liker"
-msgstr[1] "Liker"
-
-#: ../../include/conversation.php:1563
+#: ../../include/conversation.php:1443
msgctxt "noun"
msgid "Repeat"
msgid_plural "Repeats"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "Videresending"
+msgstr[1] "Videresendinger"
-#: ../../include/conversation.php:1566 ../../Zotlabs/Module/Photos.php:1134
+#: ../../include/conversation.php:1446 ../../Zotlabs/Module/Photos.php:1123
msgctxt "noun"
msgid "Dislike"
msgid_plural "Dislikes"
msgstr[0] "Liker ikke"
msgstr[1] "Liker ikke"
-#: ../../include/conversation.php:1569
+#: ../../include/conversation.php:1449
+msgctxt "noun"
+msgid "Comment"
+msgid_plural "Comments"
+msgstr[0] "Kommentar"
+msgstr[1] "Kommentarer"
+
+#: ../../include/conversation.php:1449
+msgctxt "noun"
+msgid "Reply"
+msgid_plural "Replies"
+msgstr[0] "Svar"
+msgstr[1] "Svar"
+
+#: ../../include/conversation.php:1452
msgctxt "noun"
msgid "Attending"
msgid_plural "Attending"
msgstr[0] "Deltar"
msgstr[1] "Deltar"
-#: ../../include/conversation.php:1572
+#: ../../include/conversation.php:1455
+#, fuzzy
+#| msgctxt "title"
+#| msgid "Not attending"
msgctxt "noun"
-msgid "Not Attending"
-msgid_plural "Not Attending"
+msgid "Not attending"
+msgid_plural "Not attending"
msgstr[0] "Deltar ikke"
msgstr[1] "Deltar ikke"
-#: ../../include/conversation.php:1575
+#: ../../include/conversation.php:1458
msgctxt "noun"
msgid "Undecided"
msgid_plural "Undecided"
msgstr[0] "Ikke bestemt"
msgstr[1] "Ikke bestemt"
+#: ../../include/nav.php:109
+msgid "Remote authentication"
+msgstr "Fjernautentisering"
+
+#: ../../include/nav.php:109
+msgid "Click to authenticate to your home hub"
+msgstr "Klikk for å godkjennes mot din hjemme-hub"
+
+#: ../../include/nav.php:115 ../../Zotlabs/Widget/Channel_activities.php:239
+#: ../../Zotlabs/Widget/Admin.php:29
+#: ../../Zotlabs/Module/Admin/Channels.php:168
+#: ../../Zotlabs/Module/Manage.php:162 ../../Zotlabs/Module/Admin.php:118
+msgid "Channels"
+msgstr "Kanaler"
+
+#: ../../include/nav.php:115
+msgid "Manage your channels"
+msgstr "Behandle kanalene dine"
+
+#: ../../include/nav.php:118 ../../Zotlabs/Lib/Apps.php:345
+#: ../../Zotlabs/Widget/Newmember.php:60
+#: ../../Zotlabs/Widget/Settings_menu.php:71
+#: ../../Zotlabs/Module/Admin/Themes.php:141
+#: ../../Zotlabs/Module/Admin/Addons.php:127
+msgid "Settings"
+msgstr "Innstillinger"
+
+#: ../../include/nav.php:118
+msgid "Account/Channel Settings"
+msgstr "Konto-/kanalinnstillinger"
+
+#: ../../include/nav.php:124 ../../include/nav.php:154
+#: ../../include/nav.php:175 ../../boot.php:1760
+msgid "Logout"
+msgstr "Logg ut"
+
+#: ../../include/nav.php:124 ../../include/nav.php:154
+msgid "End this session"
+msgstr "Avslutt denne økten"
+
+#: ../../include/nav.php:127
+msgid "Your profile page"
+msgstr "Din profilside"
+
+#: ../../include/nav.php:130 ../../include/channel.php:1541
+#: ../../Zotlabs/Module/Profiles.php:851
+msgid "Edit Profiles"
+msgstr "Rediger profiler"
+
+#: ../../include/nav.php:130
+msgid "Manage/Edit profiles"
+msgstr "Håndter/endre profiler"
+
+#: ../../include/nav.php:132 ../../include/channel.php:1545
+#: ../../addon/openclipatar/openclipatar.php:59
+msgid "Edit Profile"
+msgstr "Rediger profil"
+
+#: ../../include/nav.php:132 ../../Zotlabs/Widget/Newmember.php:42
+msgid "Edit your profile"
+msgstr "Rediger profil"
+
+#: ../../include/nav.php:139 ../../include/nav.php:143 ../../boot.php:1761
+#: ../../Zotlabs/Lib/Apps.php:342 ../../Zotlabs/Module/Login.php:15
+msgid "Login"
+msgstr "Logg inn"
+
+#: ../../include/nav.php:139 ../../include/nav.php:143
+msgid "Sign in"
+msgstr "Logg på"
+
+#: ../../include/nav.php:173
+msgid "Take me home"
+msgstr "Ta meg hjem"
+
+#: ../../include/nav.php:175
+msgid "Log me out of this site"
+msgstr "Logg meg ut fra dette nettstedet"
+
+#: ../../include/nav.php:180 ../../boot.php:1738
+#: ../../Zotlabs/Module/Register.php:543
+msgid "Register"
+msgstr "Registrer"
+
+#: ../../include/nav.php:180
+msgid "Create an account"
+msgstr "Lag en konto"
+
+#: ../../include/nav.php:194 ../../include/nav.php:341
+#: ../../Zotlabs/Lib/Apps.php:354 ../../Zotlabs/Module/Layouts.php:184
+msgid "Help"
+msgstr "Hjelp"
+
+#: ../../include/nav.php:194
+msgid "Help and documentation"
+msgstr "Hjelp og dokumentasjon"
+
+#: ../../include/nav.php:208 ../../include/acl_selectors.php:149
+#: ../../include/text.php:1190 ../../include/text.php:1202
+#: ../../Zotlabs/Lib/Apps.php:357 ../../Zotlabs/Widget/Sitesearch.php:37
+#: ../../Zotlabs/Widget/Activity_filter.php:210
+#: ../../Zotlabs/Module/Search.php:47 ../../Zotlabs/Module/Connections.php:403
+msgid "Search"
+msgstr "Søk"
+
+#: ../../include/nav.php:208
+msgid "Search site @name, !forum, #tag, ?docs, content"
+msgstr "Søk etter @navn, !forum, #emne, ?dokumentasjon eller innhold"
+
+#: ../../include/nav.php:214 ../../Zotlabs/Widget/Admin.php:61
+msgid "Admin"
+msgstr "Administrator"
+
+#: ../../include/nav.php:214
+msgid "Site Setup and Configuration"
+msgstr "Nettstedsoppsett og -konfigurasjon"
+
+#: ../../include/nav.php:345 ../../Zotlabs/Widget/Notifications.php:175
+#: ../../Zotlabs/Widget/Messages.php:52 ../../Zotlabs/Module/Defperms.php:254
+#: ../../Zotlabs/Module/New_channel.php:158
+#: ../../Zotlabs/Module/New_channel.php:165
+msgid "Loading"
+msgstr "Laster"
+
+#: ../../include/nav.php:350
+msgid "@name, #tag, ?doc, content"
+msgstr "@navn, #merkelapp, ?dokumentasjon, innhold"
+
+#: ../../include/nav.php:351
+msgid "Please wait..."
+msgstr "Vennligst vent..."
+
+#: ../../include/nav.php:357 ../../Zotlabs/Lib/Apps.php:329
+msgid "Apps"
+msgstr "Apper"
+
+#: ../../include/nav.php:358
+msgid "Channel Apps"
+msgstr "Kanalapper"
+
+#: ../../include/nav.php:359
+msgid "System Apps"
+msgstr "Systemapper"
+
+#: ../../include/nav.php:360
+msgid "Pinned Apps"
+msgstr "Festede apper"
+
+#: ../../include/nav.php:361
+msgid "Featured Apps"
+msgstr "Fremhevede apper"
+
+#: ../../include/nav.php:447 ../../Zotlabs/Lib/Apps.php:349
+#: ../../Zotlabs/Widget/Notifications.php:43
+#: ../../Zotlabs/Module/Admin/Channels.php:176
+msgid "Channel"
+msgstr "Kanal"
+
+#: ../../include/nav.php:450
+msgid "Status Messages and Posts"
+msgstr "Statusmeldinger og -innlegg"
+
+#: ../../include/nav.php:460 ../../Zotlabs/Module/Help.php:240
+msgid "About"
+msgstr "Om"
+
+#: ../../include/nav.php:463
+msgid "Profile Details"
+msgstr "Profildetaljer"
+
+#: ../../include/nav.php:473 ../../include/photos.php:747
+msgid "Photo Albums"
+msgstr "Fotoalbum"
+
+#: ../../include/nav.php:478 ../../Zotlabs/Lib/Apps.php:346
+#: ../../Zotlabs/Widget/Notifications.php:108
+#: ../../Zotlabs/Widget/Channel_activities.php:125
+#: ../../Zotlabs/Storage/Browser.php:355 ../../Zotlabs/Module/Fbrowser.php:87
+msgid "Files"
+msgstr "Filer"
+
+#: ../../include/nav.php:481
+msgid "Files and Storage"
+msgstr "Filer og lagring"
+
+#: ../../include/nav.php:503 ../../include/nav.php:506
+#: ../../Zotlabs/Lib/Apps.php:336 ../../Zotlabs/Widget/Chatroom_list.php:22
+msgid "Chatrooms"
+msgstr "Chatrom"
+
+#: ../../include/nav.php:516 ../../Zotlabs/Lib/Apps.php:335
+#: ../../Zotlabs/Module/Bookmarks.php:90
+msgid "Bookmarks"
+msgstr "Bokmerker"
+
+#: ../../include/nav.php:519
+msgid "Saved Bookmarks"
+msgstr "Lagrede bokmerker"
+
+#: ../../include/nav.php:527 ../../Zotlabs/Lib/Apps.php:347
+#: ../../Zotlabs/Widget/Channel_activities.php:168
+#: ../../Zotlabs/Module/Webpages.php:246
+msgid "Webpages"
+msgstr "Websider"
+
+#: ../../include/nav.php:530
+msgid "View Webpages"
+msgstr "Vis websider"
+
+#: ../../include/security.php:633
+msgid ""
+"The form security token was not correct. This probably happened because the "
+"form has been opened for too long (>3 hours) before submitting it."
+msgstr ""
+"Skjemaets sikkerhetspollett var ikke gyldig. Dette skjedde antakelig fordi "
+"skjemaet har vært åpnet for lenge (>3 timer) før det ble sendt inn."
+
+#: ../../include/datetime.php:58 ../../Zotlabs/Widget/Newmember.php:58
+#: ../../Zotlabs/Module/Profiles.php:751
+msgid "Miscellaneous"
+msgstr "Forskjellig"
+
+#: ../../include/datetime.php:140
+msgid "Birthday"
+msgstr "Fødselsdag"
+
+#: ../../include/datetime.php:140
+msgid "Age: "
+msgstr "Alder: "
+
+#: ../../include/datetime.php:140
+msgid "YYYY-MM-DD or MM-DD"
+msgstr "YYYY-MM-DD eller MM-DD"
+
+#: ../../include/datetime.php:238 ../../boot.php:2680
+msgid "never"
+msgstr "aldri"
+
+#: ../../include/datetime.php:244
+msgid "less than a second ago"
+msgstr "for mindre enn ett sekund siden"
+
+#: ../../include/datetime.php:262
+#, php-format
+msgctxt "e.g. 22 hours ago, 1 minute ago"
+msgid "%1$d %2$s ago"
+msgstr "%1$d %2$s siden"
+
+#: ../../include/datetime.php:284
+msgid "ago"
+msgstr "siden"
+
+#: ../../include/datetime.php:287
+msgid "in"
+msgstr "i"
+
+#: ../../include/datetime.php:304
+msgid "now"
+msgstr "nå"
+
+#: ../../include/datetime.php:312
+msgctxt "relative_date"
+msgid "year"
+msgid_plural "years"
+msgstr[0] "år"
+msgstr[1] "år"
+
+#: ../../include/datetime.php:315
+msgctxt "relative_date"
+msgid "month"
+msgid_plural "months"
+msgstr[0] "måned"
+msgstr[1] "måneder"
+
+#: ../../include/datetime.php:318
+msgctxt "relative_date"
+msgid "week"
+msgid_plural "weeks"
+msgstr[0] "uke"
+msgstr[1] "uker"
+
+#: ../../include/datetime.php:321
+msgctxt "relative_date"
+msgid "day"
+msgid_plural "days"
+msgstr[0] "dag"
+msgstr[1] "dager"
+
+#: ../../include/datetime.php:324
+msgctxt "relative_date"
+msgid "hour"
+msgid_plural "hours"
+msgstr[0] "time"
+msgstr[1] "timer"
+
+#: ../../include/datetime.php:327
+msgctxt "relative_date"
+msgid "minute"
+msgid_plural "minutes"
+msgstr[0] "minutt"
+msgstr[1] "minutter"
+
+#: ../../include/datetime.php:330
+msgctxt "relative_date"
+msgid "second"
+msgid_plural "seconds"
+msgstr[0] "sekund"
+msgstr[1] "sekunder"
+
+#: ../../include/datetime.php:559
+#, php-format
+msgid "%1$s's birthday"
+msgstr "%1$s sin fødselsdag"
+
+#: ../../include/datetime.php:560
+#, php-format
+msgid "Happy Birthday %1$s"
+msgstr "Gratulerer med dagen, %1$s"
+
+#: ../../include/photos.php:154
+#, php-format
+msgid "Image exceeds website size limit of %lu bytes"
+msgstr "Bilde overstiger nettstedets størrelsesbegrensning på %lu bytes"
+
+#: ../../include/photos.php:165
+msgid "Image file is empty."
+msgstr "Bildefilen er tom."
+
+#: ../../include/photos.php:199 ../../Zotlabs/Module/Cover_photo.php:240
+#: ../../Zotlabs/Module/Profile_photo.php:275
+msgid "Unable to process image"
+msgstr "Kan ikke behandle bildet"
+
+#: ../../include/photos.php:325
+msgid "Photo storage failed."
+msgstr "Bildelagring mislyktes."
+
+#: ../../include/photos.php:374
+msgid "a new photo"
+msgstr "et nytt bilde"
+
+#: ../../include/photos.php:378
+#, php-format
+msgctxt "photo_upload"
+msgid "%1$s posted %2$s to %3$s"
+msgstr "%1$s la inn %2$s til %3$s"
+
+#: ../../include/photos.php:748 ../../Zotlabs/Module/Photos.php:1333
+#: ../../Zotlabs/Module/Photos.php:1346 ../../Zotlabs/Module/Photos.php:1347
+msgid "Recent Photos"
+msgstr "Nye bilder"
+
+#: ../../include/photos.php:752
+msgid "Upload New Photos"
+msgstr "Last opp nye bilder"
+
+#: ../../include/connections.php:174
+msgid "New window"
+msgstr "Nytt vindu"
+
+#: ../../include/connections.php:175
+msgid "Open the selected location in a different window or browser tab"
+msgstr "Åpne det valgte stedet i et annet vindu eller nettleser-fane"
+
+#: ../../include/network.php:414
+msgid "url: "
+msgstr "url:"
+
+#: ../../include/network.php:415
+msgid "error_code: "
+msgstr "feilkode:"
+
+#: ../../include/network.php:416
+msgid "error_string: "
+msgstr "feilmelding"
+
+#: ../../include/network.php:417
+msgid "content-type: "
+msgstr ""
+
+#: ../../include/network.php:1774 ../../include/network.php:1775
+msgid "Friendica"
+msgstr "Friendica"
+
+#: ../../include/network.php:1776
+msgid "OStatus"
+msgstr "OStatus"
+
+#: ../../include/network.php:1777
+msgid "GNU-Social"
+msgstr ""
+
+#: ../../include/network.php:1778
+msgid "RSS/Atom"
+msgstr "RSS/Atom"
+
+#: ../../include/network.php:1779
+msgid "ActivityPub"
+msgstr ""
+
+#: ../../include/network.php:1780 ../../addon/openid/MysqlProvider.php:56
+#: ../../addon/openid/MysqlProvider.php:57 ../../addon/redred/Mod_Redred.php:69
+#: ../../addon/rtof/Mod_Rtof.php:55 ../../Zotlabs/Module/Connedit.php:736
+#: ../../Zotlabs/Module/Admin/Accounts.php:216
+#: ../../Zotlabs/Module/Admin/Accounts.php:230
+#: ../../Zotlabs/Module/Cdav.php:1372
+msgid "Email"
+msgstr "E-post"
+
+#: ../../include/network.php:1781
+msgid "Diaspora"
+msgstr "Diaspora"
+
+#: ../../include/network.php:1782
+msgid "Facebook"
+msgstr "Facebook"
+
+#: ../../include/network.php:1783
+msgid "Zot"
+msgstr "Zot"
+
+#: ../../include/network.php:1784
+msgid "LinkedIn"
+msgstr "LinkedIn"
+
+#: ../../include/network.php:1785
+msgid "XMPP/IM"
+msgstr "XMPP/IM"
+
+#: ../../include/network.php:1786
+msgid "MySpace"
+msgstr "MySpace"
+
+#: ../../include/markdown.php:190 ../../include/bbcode.php:576
+#, php-format
+msgid "%1$s wrote the following %2$s %3$s"
+msgstr "%1$s skrev følgende %2$s %3$s"
+
+#: ../../include/markdown.php:192 ../../include/bbcode.php:572
+#: ../../Zotlabs/Module/Tagger.php:81
+msgid "post"
+msgstr "innlegg"
+
+#: ../../include/markdown.php:262 ../../include/bbcode.php:661
+msgid "spoiler"
+msgstr "røpealarm"
+
+#: ../../include/menu.php:120 ../../include/channel.php:1541
+#: ../../include/channel.php:1545 ../../addon/cards/cards.php:74
+#: ../../addon/articles/articles.php:75 ../../addon/wiki/Mod_Wiki.php:214
+#: ../../addon/wiki/Mod_Wiki.php:381 ../../Zotlabs/Lib/Apps.php:617
+#: ../../Zotlabs/Lib/ThreadItem.php:159 ../../Zotlabs/Widget/Cdav.php:144
+#: ../../Zotlabs/Widget/Cdav.php:181 ../../Zotlabs/Module/Editwebpage.php:142
+#: ../../Zotlabs/Module/Webpages.php:249 ../../Zotlabs/Module/Oauth.php:171
+#: ../../Zotlabs/Module/Thing.php:301 ../../Zotlabs/Module/Layouts.php:191
+#: ../../Zotlabs/Module/Menu.php:176 ../../Zotlabs/Module/Admin/Profs.php:176
+#: ../../Zotlabs/Module/Group.php:246 ../../Zotlabs/Module/Blocks.php:158
+#: ../../Zotlabs/Module/Editlayout.php:114
+#: ../../Zotlabs/Module/Connections.php:338
+#: ../../Zotlabs/Module/Connections.php:387
+#: ../../Zotlabs/Module/Connections.php:408 ../../Zotlabs/Module/Oauth2.php:192
+#: ../../Zotlabs/Module/Editblock.php:114
+msgid "Edit"
+msgstr "Rediger"
+
+#: ../../include/acl_selectors.php:33
+#: ../../Zotlabs/Lib/PermissionDescription.php:34
+msgid "Visible to your default audience"
+msgstr "Synlig for ditt standard publikum"
+
+#: ../../include/acl_selectors.php:100
+msgid "Profile-Based Privacy Groups"
+msgstr "Profilbaserte personverngrupper"
+
+#: ../../include/acl_selectors.php:119
+msgid "Private Forum"
+msgstr "Privat forum"
+
+#: ../../include/acl_selectors.php:125
+#: ../../Zotlabs/Widget/Notifications.php:131
+#: ../../Zotlabs/Widget/Activity_filter.php:130
+#: ../../Zotlabs/Widget/Forums.php:77
+msgid "Forums"
+msgstr "Forum"
+
+#: ../../include/acl_selectors.php:136
+#: ../../Zotlabs/Lib/PermissionDescription.php:107
+#: ../../Zotlabs/Module/Settings/Privacy.php:66
+msgid "Only me"
+msgstr "Kun meg"
+
+#: ../../include/acl_selectors.php:143
+msgid "Share with"
+msgstr "Del med"
+
+#: ../../include/acl_selectors.php:144
+msgid "Custom selection"
+msgstr "Tilpasset utvalg"
+
+#: ../../include/acl_selectors.php:146
+msgid ""
+"Select \"Allow\" to allow viewing. \"Don't allow\" lets you override and "
+"limit the scope of \"Allow\"."
+msgstr ""
+"Velg \"Tillat\" for å tillate visning. \"Ikke tillat\" lar deg overstyre og "
+"begrense gyldigheten av \"Tillat\"."
+
+#: ../../include/acl_selectors.php:147 ../../Zotlabs/Module/Authorize.php:32
+msgid "Allow"
+msgstr "Tillat"
+
+#: ../../include/acl_selectors.php:148
+msgid "Don't allow"
+msgstr "Ikke tillat"
+
+#: ../../include/acl_selectors.php:154 ../../Zotlabs/Module/Thing.php:357
+#: ../../Zotlabs/Module/Thing.php:407 ../../Zotlabs/Module/Filestorage.php:195
+#: ../../Zotlabs/Module/Photos.php:673 ../../Zotlabs/Module/Photos.php:1046
+#: ../../Zotlabs/Module/Chat.php:240
+msgid "Permissions"
+msgstr "Tillatelser"
+
+#: ../../include/acl_selectors.php:156 ../../Zotlabs/Lib/ThreadItem.php:467
+#: ../../Zotlabs/Widget/Pinned.php:156 ../../Zotlabs/Storage/Browser.php:418
+#: ../../Zotlabs/Module/Photos.php:1260
+msgid "Close"
+msgstr "Lukk"
+
+#: ../../include/acl_selectors.php:181
+#, php-format
+msgid ""
+"Post permissions %s cannot be changed %s after a post is shared.</br />These "
+"permissions set who is allowed to view the post."
+msgstr ""
+"Tillatelsene til innlegget %s kan ikke endres %s etter at innlegget er delt."
+"</br />Disse tillatelsene avgjør hvem som kan se innlegget."
+
#: ../../include/text.php:562
msgid "prev"
msgstr "forrige"
@@ -2284,27 +3249,27 @@ msgstr "nyere"
#: ../../include/text.php:1086 ../../Zotlabs/Module/Viewconnections.php:80
#: ../../Zotlabs/Module/Connections.php:306
msgid "Accepts"
-msgstr ""
+msgstr "Godtar"
#: ../../include/text.php:1089 ../../Zotlabs/Module/Viewconnections.php:83
#: ../../Zotlabs/Module/Connections.php:309
msgid "Comments"
-msgstr ""
+msgstr "Kommentarer"
#: ../../include/text.php:1094 ../../Zotlabs/Module/Viewconnections.php:88
#: ../../Zotlabs/Module/Connections.php:314
msgid "Stream items"
-msgstr ""
+msgstr "Elementer fra tidslinjen"
#: ../../include/text.php:1099 ../../Zotlabs/Module/Viewconnections.php:93
#: ../../Zotlabs/Module/Connections.php:319
msgid "Wall posts"
-msgstr ""
+msgstr "Veggposter"
#: ../../include/text.php:1103 ../../Zotlabs/Module/Viewconnections.php:97
#: ../../Zotlabs/Module/Connections.php:323
msgid "Nothing"
-msgstr ""
+msgstr "Ingenting"
#: ../../include/text.php:1116
#, php-format
@@ -2314,784 +3279,222 @@ msgstr "Vis alle %s forbindelser"
#: ../../include/text.php:1179
#, php-format
msgid "Network: %s"
-msgstr ""
+msgstr "Nettverk: %s"
#: ../../include/text.php:1191 ../../include/text.php:1203
-#: ../../Zotlabs/Module/Rbmark.php:29 ../../Zotlabs/Module/Rbmark.php:85
-#: ../../Zotlabs/Module/Filer.php:54
+#: ../../addon/cards/Mod_Cards.php:116 ../../addon/cards/Mod_Card_edit.php:92
+#: ../../addon/articles/Mod_Article_edit.php:92
+#: ../../addon/articles/Mod_Articles.php:120
#: ../../Zotlabs/Module/Admin/Queueworker.php:115
#: ../../Zotlabs/Module/Admin/Profs.php:95
-#: ../../Zotlabs/Module/Admin/Profs.php:115
-#: ../../extend/addon/hzaddons/articles/Mod_Articles.php:120
-#: ../../extend/addon/hzaddons/cards/Mod_Cards.php:116
+#: ../../Zotlabs/Module/Admin/Profs.php:115 ../../Zotlabs/Module/Rbmark.php:29
+#: ../../Zotlabs/Module/Rbmark.php:85 ../../Zotlabs/Module/Filer.php:65
msgid "Save"
msgstr "Lagre"
-#: ../../include/text.php:1499 ../../include/js_strings.php:100
-msgid "Monday"
-msgstr "mandag"
-
-#: ../../include/text.php:1499 ../../include/js_strings.php:101
-msgid "Tuesday"
-msgstr "tirsdag"
-
-#: ../../include/text.php:1499 ../../include/js_strings.php:102
-msgid "Wednesday"
-msgstr "onsdag"
-
-#: ../../include/text.php:1499 ../../include/js_strings.php:103
-msgid "Thursday"
-msgstr "torsdag"
-
-#: ../../include/text.php:1499 ../../include/js_strings.php:104
-msgid "Friday"
-msgstr "fredag"
-
-#: ../../include/text.php:1499 ../../include/js_strings.php:105
-msgid "Saturday"
-msgstr "lørdag"
-
-#: ../../include/text.php:1499 ../../include/js_strings.php:99
-msgid "Sunday"
-msgstr "søndag"
-
-#: ../../include/text.php:1503 ../../include/js_strings.php:75
-msgid "January"
-msgstr "januar"
-
-#: ../../include/text.php:1503 ../../include/js_strings.php:76
-msgid "February"
-msgstr "februar"
-
-#: ../../include/text.php:1503 ../../include/js_strings.php:77
-msgid "March"
-msgstr "mars"
-
-#: ../../include/text.php:1503 ../../include/js_strings.php:78
-msgid "April"
-msgstr "april"
-
-#: ../../include/text.php:1503
+#: ../../include/text.php:1525
msgid "May"
msgstr "mai"
-#: ../../include/text.php:1503 ../../include/js_strings.php:80
-msgid "June"
-msgstr "juni"
-
-#: ../../include/text.php:1503 ../../include/js_strings.php:81
-msgid "July"
-msgstr "juli"
-
-#: ../../include/text.php:1503 ../../include/js_strings.php:82
-msgid "August"
-msgstr "august"
-
-#: ../../include/text.php:1503 ../../include/js_strings.php:83
-msgid "September"
-msgstr "september"
-
-#: ../../include/text.php:1503 ../../include/js_strings.php:84
-msgid "October"
-msgstr "oktober"
-
-#: ../../include/text.php:1503 ../../include/js_strings.php:85
-msgid "November"
-msgstr "november"
-
-#: ../../include/text.php:1503 ../../include/js_strings.php:86
-msgid "December"
-msgstr "desember"
-
-#: ../../include/text.php:1577
-#, fuzzy
-#| msgid "Unknown Attachment"
+#: ../../include/text.php:1599
msgid "Unknown attachment"
msgstr "Ukjent vedlegg"
-#: ../../include/text.php:1580 ../../Zotlabs/Storage/Browser.php:383
+#: ../../include/text.php:1602 ../../Zotlabs/Storage/Browser.php:387
#: ../../Zotlabs/Module/Sharedwithme.php:109
msgid "Size"
msgstr "Størrelse"
-#: ../../include/text.php:1623
+#: ../../include/text.php:1643
msgid "remove category"
msgstr "fjern kategori"
-#: ../../include/text.php:1701
+#: ../../include/text.php:1721
msgid "remove from file"
msgstr "fjern fra fil"
-#: ../../include/text.php:1888
+#: ../../include/text.php:1908
msgid "Download binary/encrypted content"
-msgstr ""
+msgstr "Last ned binært/kryptert innhold"
-#: ../../include/text.php:1946 ../../include/text.php:1955
-#: ../../include/text.php:1982 ../../include/text.php:1991
+#: ../../include/text.php:1966 ../../include/text.php:1975
+#: ../../include/text.php:2002 ../../include/text.php:2011
#, php-format
msgctxt "noun"
msgid "%d Vote"
msgid_plural "%d Votes"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d stemme"
+msgstr[1] "%d stemmer"
-#: ../../include/text.php:1998
+#: ../../include/text.php:2018
#, php-format
msgctxt "noun"
msgid "%d Vote in total"
msgid_plural "%d Votes in total"
-msgstr[0] ""
-msgstr[1] ""
+msgstr[0] "%d stemme totalt"
+msgstr[1] "%d stemmer totalt"
-#: ../../include/text.php:2004
+#: ../../include/text.php:2024
msgid "Poll has ended"
msgstr "Spørreskjema har utløpt"
-#: ../../include/text.php:2007
+#: ../../include/text.php:2027
#, php-format
-msgid "Poll ends in %s"
-msgstr "Spørreskjema utløper om %s"
+msgid "Poll ends %s"
+msgstr "Spørreskjema utløper %s"
-#: ../../include/text.php:2014 ../../Zotlabs/Lib/ThreadItem.php:471
+#: ../../include/text.php:2034 ../../Zotlabs/Lib/ThreadItem.php:427
msgid "Vote"
-msgstr ""
+msgstr "Stem"
-#: ../../include/text.php:2174
+#: ../../include/text.php:2193
msgid "Link to Source"
msgstr "Lenke til kilde"
-#: ../../include/text.php:2207
+#: ../../include/text.php:2218 ../../Zotlabs/Module/Lang.php:75
+msgid "default"
+msgstr "standard"
+
+#: ../../include/text.php:2226
msgid "Page layout"
msgstr "Sidens layout"
-#: ../../include/text.php:2207
+#: ../../include/text.php:2226
msgid "You can create your own with the layouts tool"
msgstr "Du kan lage din egen med layout-verktøyet"
-#: ../../include/text.php:2217
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:220
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:368
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:903
-#: ../../extend/addon/hzaddons/wiki/Widget/Wiki_pages.php:68
+#: ../../include/text.php:2236 ../../addon/wiki/Mod_Wiki.php:220
+#: ../../addon/wiki/Mod_Wiki.php:368 ../../addon/wiki/Mod_Wiki.php:903
+#: ../../addon/wiki/Widget/Wiki_pages.php:68
msgid "BBcode"
msgstr "BBcode"
-#: ../../include/text.php:2218
+#: ../../include/text.php:2237
msgid "HTML"
msgstr ""
-#: ../../include/text.php:2219
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:220
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:368
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:903
-#: ../../extend/addon/hzaddons/wiki/Widget/Wiki_pages.php:68
-#: ../../extend/addon/hzaddons/mdpost/mdpost.php:41
+#: ../../include/text.php:2238 ../../addon/wiki/Mod_Wiki.php:220
+#: ../../addon/wiki/Mod_Wiki.php:368 ../../addon/wiki/Mod_Wiki.php:903
+#: ../../addon/wiki/Widget/Wiki_pages.php:68 ../../addon/mdpost/mdpost.php:41
msgid "Markdown"
msgstr "Markdown"
-#: ../../include/text.php:2220
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:220
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:903
-#: ../../extend/addon/hzaddons/wiki/Widget/Wiki_pages.php:68
+#: ../../include/text.php:2239 ../../addon/wiki/Mod_Wiki.php:220
+#: ../../addon/wiki/Mod_Wiki.php:903 ../../addon/wiki/Widget/Wiki_pages.php:68
msgid "Text"
msgstr "Tekst"
-#: ../../include/text.php:2221
+#: ../../include/text.php:2240
msgid "Comanche Layout"
-msgstr ""
+msgstr "Comanche Utlegg"
-#: ../../include/text.php:2226
+#: ../../include/text.php:2245
msgid "PHP"
msgstr ""
-#: ../../include/text.php:2238
+#: ../../include/text.php:2257
msgid "Page content type"
msgstr "Sidens innholdstype"
-#: ../../include/text.php:2371
+#: ../../include/text.php:2383
+msgid "conversation"
+msgstr "samtale"
+
+#: ../../include/text.php:2390
msgid "activity"
msgstr "aktivitet"
-#: ../../include/text.php:2374
+#: ../../include/text.php:2394
msgid "poll"
msgstr "spørreskjema"
-#: ../../include/text.php:2487
+#: ../../include/text.php:2508
msgid "a-z, 0-9, -, and _ only"
-msgstr ""
+msgstr "kun a-z, 0-9, - og _"
-#: ../../include/text.php:2795
+#: ../../include/text.php:2818
msgid "Design Tools"
msgstr "Designverktøy"
-#: ../../include/text.php:2798 ../../Zotlabs/Module/Blocks.php:152
+#: ../../include/text.php:2821 ../../Zotlabs/Module/Blocks.php:152
msgid "Blocks"
msgstr "Byggeklosser"
-#: ../../include/text.php:2799 ../../Zotlabs/Module/Menu.php:171
+#: ../../include/text.php:2822 ../../Zotlabs/Module/Menu.php:171
msgid "Menus"
msgstr "Menyer"
-#: ../../include/text.php:2800 ../../Zotlabs/Module/Layouts.php:182
+#: ../../include/text.php:2823 ../../Zotlabs/Module/Layouts.php:182
msgid "Layouts"
msgstr "Layout"
-#: ../../include/text.php:2801
+#: ../../include/text.php:2824
msgid "Pages"
msgstr "Sider"
-#: ../../include/text.php:2813
+#: ../../include/text.php:2836
msgid "Import"
msgstr "Importer"
-#: ../../include/text.php:2814
+#: ../../include/text.php:2837
msgid "Import website..."
-msgstr ""
+msgstr "Importer vevside"
-#: ../../include/text.php:2815
+#: ../../include/text.php:2838
msgid "Select folder to import"
-msgstr ""
+msgstr "Velg mappe som skal importeres"
-#: ../../include/text.php:2816
+#: ../../include/text.php:2839
msgid "Import from a zipped folder:"
-msgstr ""
+msgstr "Importer fra en zippet mappe:"
-#: ../../include/text.php:2817
+#: ../../include/text.php:2840
msgid "Import from cloud files:"
-msgstr ""
+msgstr "Importer fra filer i skyen:"
-#: ../../include/text.php:2818
+#: ../../include/text.php:2841
msgid "/cloud/channel/path/to/folder"
-msgstr ""
+msgstr "/cloud/channel/bane/til/mappe"
-#: ../../include/text.php:2819
+#: ../../include/text.php:2842
msgid "Enter path to website files"
-msgstr ""
+msgstr "Oppgi bane til vesidens filer"
-#: ../../include/text.php:2820
+#: ../../include/text.php:2843
msgid "Select folder"
-msgstr ""
+msgstr "Velg mappe"
-#: ../../include/text.php:2821
+#: ../../include/text.php:2844
msgid "Export website..."
-msgstr ""
+msgstr "Eksporter vevside…"
-#: ../../include/text.php:2822
+#: ../../include/text.php:2845
msgid "Export to a zip file"
-msgstr ""
+msgstr "Eksporter til zip-fil"
-#: ../../include/text.php:2823
+#: ../../include/text.php:2846
msgid "website.zip"
msgstr ""
-#: ../../include/text.php:2824
+#: ../../include/text.php:2847
msgid "Enter a name for the zip file."
-msgstr ""
+msgstr "Oppgi et navn til zip-filen."
-#: ../../include/text.php:2825
+#: ../../include/text.php:2848
msgid "Export to cloud files"
-msgstr ""
+msgstr "Eksporter til filen i skyen"
-#: ../../include/text.php:2826
+#: ../../include/text.php:2849
msgid "/path/to/export/folder"
-msgstr ""
+msgstr "/bane/til/eksport/mappe"
-#: ../../include/text.php:2827
+#: ../../include/text.php:2850
msgid "Enter a path to a cloud files destination."
-msgstr ""
+msgstr "Oppgi bane i skyen hvor filene skal lagres"
-#: ../../include/text.php:2828
+#: ../../include/text.php:2851
msgid "Specify folder"
-msgstr ""
-
-#: ../../include/text.php:3520 ../../view/theme/redbasic/php/config.php:18
-#: ../../Zotlabs/Module/Admin/Site.php:232
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:335
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:359
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:435
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:459
-msgid "Default"
-msgstr "Standard"
-
-#: ../../include/oembed.php:155
-msgid "View PDF"
-msgstr ""
-
-#: ../../include/oembed.php:390
-msgid " by "
-msgstr " av "
-
-#: ../../include/oembed.php:391
-#, fuzzy
-msgid " on "
-msgstr "På"
-
-#: ../../include/oembed.php:424
-msgid "Embedded content"
-msgstr "Innebygget innhold"
-
-#: ../../include/oembed.php:433
-msgid "Embedding disabled"
-msgstr "Innbygging avskrudd"
-
-#: ../../include/js_strings.php:5
-msgid "Delete this item?"
-msgstr "Slett dette elementet?"
-
-#: ../../include/js_strings.php:6 ../../Zotlabs/Module/Moderate.php:78
-msgid "Item deleted"
-msgstr ""
-
-#: ../../include/js_strings.php:7 ../../Zotlabs/Lib/ThreadItem.php:867
-#: ../../Zotlabs/Module/Photos.php:1094 ../../Zotlabs/Module/Photos.php:1207
-msgid "Comment"
-msgstr "Kommentar"
-
-#: ../../include/js_strings.php:8 ../../Zotlabs/Lib/ThreadItem.php:576
-#, fuzzy
-#| msgid "View all"
-msgid "show all"
-msgstr "Vis alle"
-
-#: ../../include/js_strings.php:9
-#, fuzzy
-#| msgid "Show Oldest First"
-msgid "show less"
-msgstr "Vis eldste først"
-
-#: ../../include/js_strings.php:10
-msgid "expand"
-msgstr ""
-
-#: ../../include/js_strings.php:11
-msgid "collapse"
-msgstr ""
-
-#: ../../include/js_strings.php:12
-msgid "Password too short"
-msgstr "Passordet er for kort"
-
-#: ../../include/js_strings.php:13 ../../Zotlabs/Module/Register.php:162
-msgid "Passwords do not match"
-msgstr "Passordene er ikke like"
-
-#: ../../include/js_strings.php:14
-msgid "everybody"
-msgstr "alle"
-
-#: ../../include/js_strings.php:15
-msgid "Secret Passphrase"
-msgstr "Hemmelig passordsetning"
-
-#: ../../include/js_strings.php:16
-msgid "Passphrase hint"
-msgstr "Hint om passordsetning"
-
-#: ../../include/js_strings.php:17
-msgid "Notice: Permissions have changed but have not yet been submitted."
-msgstr ""
-"Varsel: Tillatelser har blitt endret, men de har ennå ikke blitt sendt inn."
-
-#: ../../include/js_strings.php:18
-msgid "close all"
-msgstr "lukk alle"
-
-#: ../../include/js_strings.php:19
-msgid "Nothing new here"
-msgstr "Ikke noe nytt her"
-
-#: ../../include/js_strings.php:20
-msgid "Rate This Channel (this is public)"
-msgstr "Vurder denne kanalen (dette er offentlig)"
-
-#: ../../include/js_strings.php:21
-msgid "Rating"
-msgstr "Vurdering"
-
-#: ../../include/js_strings.php:22
-msgid "Describe (optional)"
-msgstr "Beskriv (valgfritt)"
-
-#: ../../include/js_strings.php:23 ../../view/theme/redbasic/php/config.php:188
-#: ../../Zotlabs/Lib/ThreadItem.php:868 ../../Zotlabs/Storage/Browser.php:386
-#: ../../Zotlabs/Module/Import_items.php:125 ../../Zotlabs/Module/Setup.php:319
-#: ../../Zotlabs/Module/Setup.php:359 ../../Zotlabs/Module/Regate.php:408
-#: ../../Zotlabs/Module/Photos.php:1056 ../../Zotlabs/Module/Photos.php:1095
-#: ../../Zotlabs/Module/Photos.php:1208 ../../Zotlabs/Module/Oauth.php:109
-#: ../../Zotlabs/Module/Editpost.php:88 ../../Zotlabs/Module/Profiles.php:738
-#: ../../Zotlabs/Module/Invite.php:564 ../../Zotlabs/Module/Sources.php:123
-#: ../../Zotlabs/Module/Sources.php:160 ../../Zotlabs/Module/Group.php:151
-#: ../../Zotlabs/Module/Group.php:160 ../../Zotlabs/Module/Oauth2.php:114
-#: ../../Zotlabs/Module/Chat.php:208 ../../Zotlabs/Module/Chat.php:247
-#: ../../Zotlabs/Module/Appman.php:230 ../../Zotlabs/Module/Connedit.php:714
-#: ../../Zotlabs/Module/Import.php:622 ../../Zotlabs/Module/Pconfig.php:117
-#: ../../Zotlabs/Module/Xchan.php:15 ../../Zotlabs/Module/Thing.php:357
-#: ../../Zotlabs/Module/Thing.php:409 ../../Zotlabs/Module/Defperms.php:262
-#: ../../Zotlabs/Module/Locs.php:125 ../../Zotlabs/Module/Connect.php:107
-#: ../../Zotlabs/Module/Affinity.php:84
-#: ../../Zotlabs/Module/Filestorage.php:208
-#: ../../Zotlabs/Module/Pdledit.php:137 ../../Zotlabs/Module/Admin/Site.php:402
-#: ../../Zotlabs/Module/Admin/Accounts.php:309
-#: ../../Zotlabs/Module/Admin/Account_edit.php:73
-#: ../../Zotlabs/Module/Admin/Security.php:130
-#: ../../Zotlabs/Module/Admin/Channels.php:169
-#: ../../Zotlabs/Module/Admin/Features.php:66
-#: ../../Zotlabs/Module/Admin/Addons.php:446
-#: ../../Zotlabs/Module/Admin/Profs.php:179
-#: ../../Zotlabs/Module/Admin/Themes.php:174
-#: ../../Zotlabs/Module/Admin/Logs.php:85 ../../Zotlabs/Module/Mitem.php:259
-#: ../../Zotlabs/Module/Email_validation.php:41
-#: ../../Zotlabs/Module/Tokens.php:294 ../../Zotlabs/Module/Permcats.php:257
-#: ../../Zotlabs/Module/Settings/Channel.php:230
-#: ../../Zotlabs/Module/Settings/Editor.php:42
-#: ../../Zotlabs/Module/Settings/Photos.php:42
-#: ../../Zotlabs/Module/Settings/Profiles.php:52
-#: ../../Zotlabs/Module/Settings/Network.php:62
-#: ../../Zotlabs/Module/Settings/Directory.php:42
-#: ../../Zotlabs/Module/Settings/Channel_home.php:91
-#: ../../Zotlabs/Module/Settings/Calendar.php:42
-#: ../../Zotlabs/Module/Settings/Conversation.php:44
-#: ../../Zotlabs/Module/Settings/Features.php:48
-#: ../../Zotlabs/Module/Settings/Connections.php:42
-#: ../../Zotlabs/Module/Settings/Multifactor.php:85
-#: ../../Zotlabs/Module/Settings/Events.php:42
-#: ../../Zotlabs/Module/Settings/Account.php:109
-#: ../../Zotlabs/Module/Settings/Privacy.php:123
-#: ../../Zotlabs/Module/Settings/Display.php:188
-#: ../../Zotlabs/Module/Settings/Manage.php:43
-#: ../../Zotlabs/Module/Contactedit.php:415
-#: ../../Zotlabs/Module/Contactedit.php:448
-#: ../../extend/addon/hzaddons/cart/cart.php:1424
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:312
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:412
-#: ../../extend/addon/hzaddons/cart/submodules/subscriptions.php:410
-#: ../../extend/addon/hzaddons/cart/submodules/manualcat.php:248
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:645
-#: ../../extend/addon/hzaddons/cart/Settings/Cart.php:132
-#: ../../extend/addon/hzaddons/cart/Settings/Cart.php:142
-#: ../../extend/addon/hzaddons/hzfiles/hzfiles.php:86
-#: ../../extend/addon/hzaddons/openstreetmap/openstreetmap.php:147
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:341
-#: ../../extend/addon/hzaddons/pumpio/Mod_Pumpio.php:113
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:54
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:191
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:249
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:304
-#: ../../extend/addon/hzaddons/statusnet/statusnet.php:602
-#: ../../extend/addon/hzaddons/ljpost/Mod_Ljpost.php:80
-#: ../../extend/addon/hzaddons/pageheader/Mod_Pageheader.php:52
-#: ../../extend/addon/hzaddons/nofed/Mod_Nofed.php:51
-#: ../../extend/addon/hzaddons/likebanner/likebanner.php:57
-#: ../../extend/addon/hzaddons/piwik/piwik.php:95
-#: ../../extend/addon/hzaddons/startpage/Mod_Startpage.php:71
-#: ../../extend/addon/hzaddons/twitter/Mod_Twitter.php:182
-#: ../../extend/addon/hzaddons/fuzzloc/Mod_Fuzzloc.php:54
-#: ../../extend/addon/hzaddons/diaspora/Mod_Diaspora.php:101
-#: ../../extend/addon/hzaddons/diaspora/diaspora.php:90
-#: ../../extend/addon/hzaddons/redphotos/redphotos.php:136
-#: ../../extend/addon/hzaddons/skeleton/Mod_Skeleton.php:49
-#: ../../extend/addon/hzaddons/dwpost/Mod_Dwpost.php:78
-#: ../../extend/addon/hzaddons/content_import/Mod_content_import.php:140
-#: ../../extend/addon/hzaddons/rtof/Mod_Rtof.php:70
-#: ../../extend/addon/hzaddons/redfiles/redfiles.php:124
-#: ../../extend/addon/hzaddons/mailtest/mailtest.php:100
-#: ../../extend/addon/hzaddons/redred/Mod_Redred.php:88
-#: ../../extend/addon/hzaddons/workflow/workflow.php:1490
-#: ../../extend/addon/hzaddons/workflow/workflow.php:1549
-#: ../../extend/addon/hzaddons/workflow/workflow.php:1668
-#: ../../extend/addon/hzaddons/workflow/workflow.php:2786
-#: ../../extend/addon/hzaddons/workflow/Settings/Mod_WorkflowSettings.php:94
-#: ../../extend/addon/hzaddons/nsfw/Mod_Nsfw.php:59
-#: ../../extend/addon/hzaddons/photocache/Mod_Photocache.php:63
-#: ../../extend/addon/hzaddons/logrot/logrot.php:35
-#: ../../extend/addon/hzaddons/xmpp/Mod_Xmpp.php:70
-#: ../../extend/addon/hzaddons/faces/Mod_Faces.php:141
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:218
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:907
-#: ../../extend/addon/hzaddons/wiki/Widget/Wiki_pages.php:72
-#: ../../extend/addon/hzaddons/libertree/Mod_Libertree.php:68
-#: ../../extend/addon/hzaddons/hubwall/hubwall.php:96
-#: ../../extend/addon/hzaddons/irc/irc.php:45
-#: ../../extend/addon/hzaddons/pubcrawl/Mod_Pubcrawl.php:61
-#: ../../extend/addon/hzaddons/flashcards/Mod_Flashcards.php:269
-#: ../../extend/addon/hzaddons/ijpost/Mod_Ijpost.php:72
-#: ../../extend/addon/hzaddons/wppost/Mod_Wppost.php:107
-msgid "Submit"
-msgstr "Lagre"
-
-#: ../../include/js_strings.php:24
-msgid "Please enter a link URL"
-msgstr "Vennligst skriv inn en lenke URL"
-
-#: ../../include/js_strings.php:25
-msgid "Unsaved changes. Are you sure you wish to leave this page?"
-msgstr ""
-"Endringene er ikke lagret. Er du sikker på at du ønsker å forlate denne "
-"siden?"
-
-#: ../../include/js_strings.php:26 ../../Zotlabs/Module/Profiles.php:476
-#: ../../Zotlabs/Module/Profiles.php:749 ../../Zotlabs/Module/Pubsites.php:55
-#: ../../Zotlabs/Module/Locs.php:121 ../../Zotlabs/Module/Cdav.php:1006
-msgid "Location"
-msgstr "Plassering"
-
-#: ../../include/js_strings.php:27
-msgid "lovely"
-msgstr ""
-
-#: ../../include/js_strings.php:28
-msgid "wonderful"
-msgstr ""
-
-#: ../../include/js_strings.php:29
-msgid "fantastic"
-msgstr ""
-
-#: ../../include/js_strings.php:30
-msgid "great"
-msgstr ""
-
-#: ../../include/js_strings.php:31
-msgid ""
-"Your chosen nickname was either already taken or not valid. Please use our "
-"suggestion ("
-msgstr ""
-
-#: ../../include/js_strings.php:32
-msgid ") or enter a new one."
-msgstr ""
-
-#: ../../include/js_strings.php:33
-msgid "Thank you, this nickname is valid."
-msgstr ""
-
-#: ../../include/js_strings.php:34
-msgid "A channel name is required."
-msgstr ""
-
-#: ../../include/js_strings.php:35
-msgid "This is a "
-msgstr ""
-
-#: ../../include/js_strings.php:36
-#, fuzzy
-msgid " channel name"
-msgstr "Kanalnavn"
-
-#: ../../include/js_strings.php:37
-msgid "Back to reply"
-msgstr ""
-
-#: ../../include/js_strings.php:38
-msgid "Pinned"
-msgstr ""
-
-#: ../../include/js_strings.php:39 ../../Zotlabs/Lib/ThreadItem.php:498
-msgid "Pin to the top"
-msgstr ""
-
-#: ../../include/js_strings.php:40 ../../Zotlabs/Lib/ThreadItem.php:498
-#: ../../Zotlabs/Widget/Pinned.php:151
-msgid "Unpin from the top"
-msgstr ""
-
-#: ../../include/js_strings.php:46
-#, php-format
-msgid "%d minutes"
-msgid_plural "%d minutes"
-msgstr[0] "%d minutter"
-msgstr[1] "%d minutter"
-
-#: ../../include/js_strings.php:47
-#, php-format
-msgid "about %d hours"
-msgid_plural "about %d hours"
-msgstr[0] "omtrent %d timer"
-msgstr[1] "omtrent %d timer"
-
-#: ../../include/js_strings.php:48
-#, php-format
-msgid "%d days"
-msgid_plural "%d days"
-msgstr[0] "%d dager"
-msgstr[1] "%d dager"
-
-#: ../../include/js_strings.php:49
-#, php-format
-msgid "%d months"
-msgid_plural "%d months"
-msgstr[0] "%d måneder"
-msgstr[1] "%d måneder"
-
-#: ../../include/js_strings.php:50
-#, php-format
-msgid "%d years"
-msgid_plural "%d years"
-msgstr[0] "%d år"
-msgstr[1] "%d år"
-
-#: ../../include/js_strings.php:55
-msgid "timeago.prefixAgo"
-msgstr "timeago.prefixAgo"
-
-#: ../../include/js_strings.php:56
-msgid "timeago.prefixFromNow"
-msgstr "timeago.prefixFromNow"
-
-#: ../../include/js_strings.php:57
-msgid "timeago.suffixAgo"
-msgstr "siden"
-
-#: ../../include/js_strings.php:58
-msgid "timeago.suffixFromNow"
-msgstr ""
-
-#: ../../include/js_strings.php:61
-msgid "less than a minute"
-msgstr "mindre enn ett minutt"
-
-#: ../../include/js_strings.php:62
-msgid "about a minute"
-msgstr "omtrent et minutt"
-
-#: ../../include/js_strings.php:64
-msgid "about an hour"
-msgstr "omtrent en time"
-
-#: ../../include/js_strings.php:66
-msgid "a day"
-msgstr "en dag"
-
-#: ../../include/js_strings.php:68
-msgid "about a month"
-msgstr "omtrent en måned"
-
-#: ../../include/js_strings.php:70
-msgid "about a year"
-msgstr "omtrent et år"
-
-#: ../../include/js_strings.php:72
-msgid " "
-msgstr " "
-
-#: ../../include/js_strings.php:73
-msgid "timeago.numbers"
-msgstr "timeago.numbers"
-
-#: ../../include/js_strings.php:79
-msgctxt "long"
-msgid "May"
-msgstr "mai"
-
-#: ../../include/js_strings.php:87
-msgid "Jan"
-msgstr "Jan"
-
-#: ../../include/js_strings.php:88
-msgid "Feb"
-msgstr "Feb"
-
-#: ../../include/js_strings.php:89
-msgid "Mar"
-msgstr "Mar"
-
-#: ../../include/js_strings.php:90
-msgid "Apr"
-msgstr "Apr"
-
-#: ../../include/js_strings.php:91
-msgctxt "short"
-msgid "May"
-msgstr "mai"
-
-#: ../../include/js_strings.php:92
-msgid "Jun"
-msgstr "Jun"
-
-#: ../../include/js_strings.php:93
-msgid "Jul"
-msgstr "Jul"
-
-#: ../../include/js_strings.php:94
-msgid "Aug"
-msgstr "Aug"
-
-#: ../../include/js_strings.php:95
-msgid "Sep"
-msgstr "Sep"
-
-#: ../../include/js_strings.php:96
-msgid "Oct"
-msgstr "Okt"
-
-#: ../../include/js_strings.php:97
-msgid "Nov"
-msgstr "Nov"
-
-#: ../../include/js_strings.php:98
-msgid "Dec"
-msgstr "Des"
-
-#: ../../include/js_strings.php:106
-msgid "Sun"
-msgstr "Søn"
-
-#: ../../include/js_strings.php:107
-msgid "Mon"
-msgstr "Man"
-
-#: ../../include/js_strings.php:108
-msgid "Tue"
-msgstr "Tirs"
-
-#: ../../include/js_strings.php:109
-msgid "Wed"
-msgstr "Ons"
-
-#: ../../include/js_strings.php:110
-msgid "Thu"
-msgstr "Tors"
-
-#: ../../include/js_strings.php:111
-msgid "Fri"
-msgstr "Fre"
-
-#: ../../include/js_strings.php:112
-msgid "Sat"
-msgstr "Lør"
-
-#: ../../include/js_strings.php:113
-msgctxt "calendar"
-msgid "today"
-msgstr "idag"
-
-#: ../../include/js_strings.php:114
-msgctxt "calendar"
-msgid "month"
-msgstr "måned"
-
-#: ../../include/js_strings.php:115
-msgctxt "calendar"
-msgid "week"
-msgstr "uke"
-
-#: ../../include/js_strings.php:116
-msgctxt "calendar"
-msgid "day"
-msgstr "dag"
-
-#: ../../include/js_strings.php:117
-msgctxt "calendar"
-msgid "All day"
-msgstr "Hele dagen"
-
-#: ../../include/js_strings.php:120
-msgid "Please stand by while your download is being prepared."
-msgstr ""
-
-#: ../../include/js_strings.php:123
-msgid "Email address not valid"
-msgstr ""
+msgstr "Velg mappe"
#: ../../include/channel.php:50
msgid "Unable to obtain identity information from database"
@@ -3105,20 +3508,20 @@ msgstr "Mangler navn"
msgid "Name too long"
msgstr "Navnet er for langt"
-#: ../../include/channel.php:203
+#: ../../include/channel.php:205
msgid "No account identifier"
msgstr "Ingen kontoidentifikator"
-#: ../../include/channel.php:215 ../../Zotlabs/Module/Register.php:96
+#: ../../include/channel.php:217 ../../Zotlabs/Module/Register.php:96
msgid "Nickname is required."
msgstr "Kallenavn er påkrevd."
-#: ../../include/channel.php:229 ../../include/channel.php:668
+#: ../../include/channel.php:231 ../../include/channel.php:670
#: ../../Zotlabs/Module/Register.php:101 ../../Zotlabs/Module/Changeaddr.php:46
msgid "Reserved nickname. Please choose another."
msgstr "Reservert kallenavn. Vennligst velg et annet."
-#: ../../include/channel.php:234 ../../include/channel.php:673
+#: ../../include/channel.php:236 ../../include/channel.php:675
#: ../../Zotlabs/Module/Register.php:106 ../../Zotlabs/Module/Changeaddr.php:51
msgid ""
"Nickname has unsupported characters or is already being used on this site."
@@ -3126,1447 +3529,3186 @@ msgstr ""
"Kallenavnet inneholder tegn som ikke er støttet eller det er allerede i bruk "
"på dette nettstedet."
-#: ../../include/channel.php:300
+#: ../../include/channel.php:302
msgid "Unable to retrieve created identity"
msgstr "Klarer ikke å hente den lagede identiteten"
-#: ../../include/channel.php:411
+#: ../../include/channel.php:413
msgid "Default Profile"
msgstr "Standardprofil"
-#: ../../include/channel.php:465 ../../include/channel.php:468
-#: ../../include/selectors.php:138 ../../Zotlabs/Module/Connedit.php:581
-#: ../../Zotlabs/Module/Contactedit.php:283
-#: ../../Zotlabs/Widget/Affinity.php:38
-msgid "Friends"
-msgstr "Venner"
-
-#: ../../include/channel.php:601 ../../include/channel.php:690
+#: ../../include/channel.php:603 ../../include/channel.php:692
msgid "Unable to retrieve modified identity"
-msgstr ""
+msgstr "Ikke i stand til å hente endret identitet"
-#: ../../include/channel.php:1382
+#: ../../include/channel.php:1384
msgid "Requested channel is not available"
-msgstr ""
+msgstr "Kanalen du prøver å nå er ikke tilgjengelig"
-#: ../../include/channel.php:1436 ../../Zotlabs/Module/Menu.php:92
-#: ../../Zotlabs/Module/Editblock.php:31 ../../Zotlabs/Module/Editlayout.php:31
-#: ../../Zotlabs/Module/Layouts.php:31 ../../Zotlabs/Module/Achievements.php:15
-#: ../../Zotlabs/Module/Editwebpage.php:32 ../../Zotlabs/Module/Hcard.php:12
-#: ../../Zotlabs/Module/Blocks.php:33 ../../Zotlabs/Module/Webpages.php:39
+#: ../../include/channel.php:1438 ../../addon/cards/Mod_Cards.php:42
+#: ../../addon/articles/Mod_Articles.php:46
+#: ../../addon/flashcards/Mod_Flashcards.php:74
+#: ../../addon/gallery/Mod_Gallery.php:49
+#: ../../Zotlabs/Module/Editwebpage.php:32 ../../Zotlabs/Module/Webpages.php:39
#: ../../Zotlabs/Module/Connect.php:17 ../../Zotlabs/Module/Filestorage.php:59
-#: ../../Zotlabs/Module/Profile.php:27
-#: ../../extend/addon/hzaddons/articles/Mod_Articles.php:46
-#: ../../extend/addon/hzaddons/gallery/Mod_Gallery.php:49
-#: ../../extend/addon/hzaddons/cards/Mod_Cards.php:42
+#: ../../Zotlabs/Module/Achievements.php:15 ../../Zotlabs/Module/Layouts.php:31
+#: ../../Zotlabs/Module/Menu.php:92 ../../Zotlabs/Module/Blocks.php:33
+#: ../../Zotlabs/Module/Hcard.php:12 ../../Zotlabs/Module/Profile.php:27
+#: ../../Zotlabs/Module/Editlayout.php:31 ../../Zotlabs/Module/Editblock.php:31
msgid "Requested profile is not available."
msgstr "Forespurt profil er ikke tilgjengelig."
-#: ../../include/channel.php:1532 ../../Zotlabs/Module/Profiles.php:743
+#: ../../include/channel.php:1534 ../../Zotlabs/Module/Profiles.php:743
msgid "Change profile photo"
msgstr "Endre profilbilde"
-#: ../../include/channel.php:1539 ../../include/channel.php:1543
-#: ../../include/menu.php:120 ../../Zotlabs/Lib/Apps.php:617
-#: ../../Zotlabs/Lib/ThreadItem.php:162 ../../Zotlabs/Module/Menu.php:176
-#: ../../Zotlabs/Module/Editblock.php:114
-#: ../../Zotlabs/Module/Editlayout.php:114 ../../Zotlabs/Module/Oauth.php:171
-#: ../../Zotlabs/Module/Group.php:246 ../../Zotlabs/Module/Layouts.php:191
-#: ../../Zotlabs/Module/Oauth2.php:192 ../../Zotlabs/Module/Editwebpage.php:142
-#: ../../Zotlabs/Module/Thing.php:294 ../../Zotlabs/Module/Blocks.php:158
-#: ../../Zotlabs/Module/Webpages.php:250
-#: ../../Zotlabs/Module/Connections.php:338
-#: ../../Zotlabs/Module/Connections.php:387
-#: ../../Zotlabs/Module/Connections.php:408
-#: ../../Zotlabs/Module/Admin/Profs.php:176 ../../Zotlabs/Widget/Cdav.php:144
-#: ../../Zotlabs/Widget/Cdav.php:181
-#: ../../extend/addon/hzaddons/articles/Mod_Article_edit.php:97
-#: ../../extend/addon/hzaddons/articles/articles.php:75
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:214
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:381
-#: ../../extend/addon/hzaddons/cards/cards.php:74
-#: ../../extend/addon/hzaddons/cards/Mod_Card_edit.php:99
-msgid "Edit"
-msgstr "Endre"
-
-#: ../../include/channel.php:1540
+#: ../../include/channel.php:1542
msgid "Create New Profile"
msgstr "Lag ny profil"
-#: ../../include/channel.php:1558 ../../Zotlabs/Module/Profiles.php:841
+#: ../../include/channel.php:1560 ../../Zotlabs/Module/Profiles.php:841
msgid "Profile Image"
msgstr "Profilbilde"
-#: ../../include/channel.php:1561
+#: ../../include/channel.php:1563
msgid "Visible to everybody"
-msgstr ""
+msgstr "Synlig for alle"
-#: ../../include/channel.php:1562 ../../Zotlabs/Module/Profiles.php:740
+#: ../../include/channel.php:1564 ../../Zotlabs/Module/Profiles.php:740
#: ../../Zotlabs/Module/Profiles.php:845
msgid "Edit visibility"
msgstr "Endre synlighet"
-#: ../../include/channel.php:1644 ../../include/channel.php:1772
+#: ../../include/channel.php:1646 ../../include/channel.php:1774
msgid "Gender:"
msgstr "Kjønn:"
-#: ../../include/channel.php:1645 ../../include/channel.php:1818
+#: ../../include/channel.php:1647 ../../include/channel.php:1820
msgid "Status:"
msgstr "Status:"
-#: ../../include/channel.php:1646 ../../include/channel.php:1842
+#: ../../include/channel.php:1648 ../../include/channel.php:1844
msgid "Homepage:"
msgstr "Hjemmeside:"
-#: ../../include/channel.php:1647 ../../include/channel.php:1844
+#: ../../include/channel.php:1649 ../../include/channel.php:1846
#: ../../Zotlabs/Module/Directory.php:368
msgid "Hometown:"
msgstr "Hjemby:"
-#: ../../include/channel.php:1648
+#: ../../include/channel.php:1650
msgid "Online Now"
msgstr "Online nå"
-#: ../../include/channel.php:1697
+#: ../../include/channel.php:1699
msgid "This channel has not added a profile description yet"
msgstr "Denne kanalen har ikke lagt til en profilbeskrivelse enda"
-#: ../../include/channel.php:1699
+#: ../../include/channel.php:1701
msgid "Change your profile photo"
-msgstr ""
-
-#: ../../include/channel.php:1727 ../../include/selectors.php:64
-#: ../../include/selectors.php:81
-#: ../../extend/addon/hzaddons/openid/Mod_Id.php:87
-msgid "Female"
-msgstr "Kvinnelig"
+msgstr "Endre profilbildet ditt"
-#: ../../include/channel.php:1729 ../../include/selectors.php:64
-#: ../../include/selectors.php:81
-#: ../../extend/addon/hzaddons/openid/Mod_Id.php:85
-msgid "Male"
-msgstr "Mannlig"
-
-#: ../../include/channel.php:1731
+#: ../../include/channel.php:1733
msgid "Trans"
msgstr ""
-#: ../../include/channel.php:1733 ../../include/selectors.php:64
-msgid "Neuter"
-msgstr "Intetkjønn"
-
-#: ../../include/channel.php:1735 ../../include/selectors.php:64
-msgid "Non-specific"
-msgstr "Ubestemt"
-
-#: ../../include/channel.php:1770
+#: ../../include/channel.php:1772
msgid "Full Name:"
msgstr "Fullt navn:"
-#: ../../include/channel.php:1803
+#: ../../include/channel.php:1805
msgid "j F, Y"
msgstr "j F, Y"
-#: ../../include/channel.php:1804
+#: ../../include/channel.php:1806
msgid "j F"
msgstr "j F"
-#: ../../include/channel.php:1811
+#: ../../include/channel.php:1813
msgid "Birthday:"
msgstr "Fødselsdag:"
-#: ../../include/channel.php:1815 ../../Zotlabs/Module/Directory.php:349
+#: ../../include/channel.php:1817 ../../Zotlabs/Module/Directory.php:349
msgid "Age:"
msgstr "Alder:"
-#: ../../include/channel.php:1824
+#: ../../include/channel.php:1826
#, php-format
msgid "for %1$d %2$s"
msgstr "for %1$d %2$s"
-#: ../../include/channel.php:1836
+#: ../../include/channel.php:1838
msgid "Tags:"
msgstr "Merkelapper:"
-#: ../../include/channel.php:1840
+#: ../../include/channel.php:1842
msgid "Sexual Preference:"
msgstr "Seksuell preferanse:"
-#: ../../include/channel.php:1846
+#: ../../include/channel.php:1848
msgid "Political Views:"
msgstr "Politiske synspunkter:"
-#: ../../include/channel.php:1848
+#: ../../include/channel.php:1850
msgid "Religion:"
msgstr "Religion:"
-#: ../../include/channel.php:1850 ../../Zotlabs/Module/Directory.php:370
+#: ../../include/channel.php:1852 ../../Zotlabs/Module/Directory.php:370
msgid "About:"
msgstr "Om:"
-#: ../../include/channel.php:1852
+#: ../../include/channel.php:1854
msgid "Hobbies/Interests:"
msgstr "Hobbyer/interesser:"
-#: ../../include/channel.php:1854
+#: ../../include/channel.php:1856
msgid "Likes:"
msgstr "Liker:"
-#: ../../include/channel.php:1856
+#: ../../include/channel.php:1858
msgid "Dislikes:"
msgstr "Misliker:"
-#: ../../include/channel.php:1858
+#: ../../include/channel.php:1860
msgid "Contact information and Social Networks:"
msgstr "Kontaktinformasjon og sosiale nettverk:"
-#: ../../include/channel.php:1860
+#: ../../include/channel.php:1862
msgid "My other channels:"
msgstr "Mine andre kanaler:"
-#: ../../include/channel.php:1862
+#: ../../include/channel.php:1864
msgid "Musical interests:"
msgstr "Musikkinteresse:"
-#: ../../include/channel.php:1864
+#: ../../include/channel.php:1866
msgid "Books, literature:"
msgstr "Bøker, litteratur:"
-#: ../../include/channel.php:1866
+#: ../../include/channel.php:1868
msgid "Television:"
msgstr "TV:"
-#: ../../include/channel.php:1868
+#: ../../include/channel.php:1870
msgid "Film/dance/culture/entertainment:"
msgstr "Film/dans/kultur/underholdning:"
-#: ../../include/channel.php:1870
+#: ../../include/channel.php:1872
msgid "Love/Romance:"
msgstr "Kjærlighet/romantikk:"
-#: ../../include/channel.php:1872
+#: ../../include/channel.php:1874
msgid "Work/employment:"
msgstr "Arbeid/sysselsetting:"
-#: ../../include/channel.php:1874
+#: ../../include/channel.php:1876
msgid "School/education:"
msgstr "Skole/utdannelse:"
-#: ../../include/channel.php:1895 ../../Zotlabs/Lib/Apps.php:366
-#: ../../Zotlabs/Module/Profperm.php:114
+#: ../../include/channel.php:1897 ../../Zotlabs/Lib/Apps.php:366
+#: ../../Zotlabs/Module/Profperm.php:116
msgid "Profile"
msgstr "Profil"
-#: ../../include/channel.php:1897
+#: ../../include/channel.php:1899
msgid "Like this thing"
msgstr "Lik denne tingen"
-#: ../../include/channel.php:1898
+#: ../../include/channel.php:1900
msgid "Export"
msgstr "Eksport"
-#: ../../include/channel.php:2351
+#: ../../include/channel.php:2353
msgid "cover photo"
msgstr "forsidebilde"
-#: ../../include/channel.php:2632 ../../boot.php:1761
+#: ../../include/channel.php:2628 ../../boot.php:1762
#: ../../Zotlabs/Module/Rmagic.php:96
msgid "Remote Authentication"
msgstr "Fjernautentisering"
-#: ../../include/channel.php:2633 ../../Zotlabs/Module/Rmagic.php:97
+#: ../../include/channel.php:2629 ../../Zotlabs/Module/Rmagic.php:97
msgid "Enter your channel address (e.g. channel@example.com)"
msgstr "Skriv din kanaladresse (for eksempel channel@exampel.com)"
-#: ../../include/channel.php:2634 ../../Zotlabs/Module/Rmagic.php:98
+#: ../../include/channel.php:2630 ../../Zotlabs/Module/Rmagic.php:98
msgid "Authenticate"
msgstr "Autentiser"
-#: ../../include/channel.php:2794 ../../Zotlabs/Module/Admin/Accounts.php:184
+#: ../../include/channel.php:2790 ../../Zotlabs/Module/Admin/Accounts.php:84
#, php-format
msgid "Account '%s' deleted"
msgstr "Kontoen '%s' slettet"
-#: ../../include/contact_widgets.php:13
+#: ../../include/bbcode.php:234 ../../include/bbcode.php:994
+#: ../../include/bbcode.php:1676 ../../include/bbcode.php:1684
+msgid "Image/photo"
+msgstr "Bilde/fotografi"
+
+#: ../../include/bbcode.php:286
+msgid "Encrypted content"
+msgstr "Kryptert innhold"
+
+#: ../../include/bbcode.php:342
#, php-format
-msgid "%d invitation available"
-msgid_plural "%d invitations available"
-msgstr[0] "%d invitasjon tilgjengelig"
-msgstr[1] "%d invitasjoner tilgjengelig"
+msgid "Install %1$s element %2$s"
+msgstr "Installer %1$s element %2$s"
-#: ../../include/contact_widgets.php:18 ../../include/acl_selectors.php:145
-#: ../../Zotlabs/Module/Admin/Site.php:406
-msgid "Advanced"
-msgstr "Avansert"
+#: ../../include/bbcode.php:346
+#, php-format
+msgid ""
+"This post contains an installable %s element, however you lack permissions "
+"to install it on this site."
+msgstr ""
+"Dette innlegget inneholder det installerbare elementet %s, men du mangler "
+"tillatelse til å installere det på dette nettstedet."
-#: ../../include/contact_widgets.php:21
-msgid "Find Channels"
-msgstr "Finn kanaler"
+#: ../../include/bbcode.php:356 ../../Zotlabs/Module/Impel.php:47
+msgid "webpage"
+msgstr "nettside"
-#: ../../include/contact_widgets.php:22
-msgid "Enter name or interest"
-msgstr "Skriv navn eller interesse"
+#: ../../include/bbcode.php:359 ../../Zotlabs/Module/Impel.php:57
+msgid "layout"
+msgstr "layout"
-#: ../../include/contact_widgets.php:23
-msgid "Connect/Follow"
-msgstr "Forbindelse/Følg"
+#: ../../include/bbcode.php:362 ../../Zotlabs/Module/Impel.php:52
+msgid "block"
+msgstr "byggekloss"
-#: ../../include/contact_widgets.php:24
-msgid "Examples: Robert Morgenstein, Fishing"
-msgstr "Eksempler: Ola Nordmann, fisking"
+#: ../../include/bbcode.php:365 ../../Zotlabs/Module/Impel.php:64
+msgid "menu"
+msgstr "meny"
-#: ../../include/contact_widgets.php:25 ../../Zotlabs/Module/Directory.php:435
-#: ../../Zotlabs/Module/Directory.php:440
-#: ../../Zotlabs/Module/Connections.php:407
-msgid "Find"
-msgstr "Finn"
+#: ../../include/bbcode.php:568
+msgid "card"
+msgstr "kort"
-#: ../../include/contact_widgets.php:26 ../../Zotlabs/Module/Suggest.php:77
-#: ../../Zotlabs/Module/Directory.php:439
-msgid "Channel Suggestions"
-msgstr "Kanalforslag"
+#: ../../include/bbcode.php:570
+msgid "article"
+msgstr "artikkel"
-#: ../../include/contact_widgets.php:28
-msgid "Random Profile"
-msgstr "Tilfeldig profil"
+#: ../../include/bbcode.php:653 ../../include/bbcode.php:661
+msgid "Click to open/close"
+msgstr "Klikk for å åpne/lukke"
-#: ../../include/contact_widgets.php:29
-msgid "Invite Friends"
-msgstr "Inviter venner"
+#: ../../include/bbcode.php:674
+msgid "View article"
+msgstr "Vis artikkel"
-#: ../../include/contact_widgets.php:31
-msgid "Advanced example: name=fred and country=iceland"
-msgstr "Avansert eksempel: navn=fred og land=island"
+#: ../../include/bbcode.php:674
+msgid "View summary"
+msgstr "Vis sammendrag"
-#: ../../include/contact_widgets.php:58 ../../include/contact_widgets.php:121
-#: ../../include/contact_widgets.php:155 ../../Zotlabs/Widget/Filer.php:36
-#: ../../Zotlabs/Widget/Appcategories.php:52
-#: ../../extend/addon/hzaddons/articles/Widget/Articles_categories.php:83
-#: ../../extend/addon/hzaddons/cards/Widget/Cards_categories.php:83
-msgid "Everything"
-msgstr "Alt"
+#: ../../include/bbcode.php:1127 ../../include/bbcode.php:1314
+#: ../../addon/wiki/Lib/NativeWikiPage.php:634
+msgid "Different viewers will see this text differently"
+msgstr "Denne teksten vil se forskjellig ut for ulike besøkende"
-#: ../../include/contact_widgets.php:118 ../../include/contact_widgets.php:152
-#: ../../include/taxonomy.php:425 ../../include/taxonomy.php:507
-#: ../../include/taxonomy.php:527 ../../include/taxonomy.php:548
-#: ../../Zotlabs/Storage/Browser.php:293 ../../Zotlabs/Storage/Browser.php:392
-#: ../../Zotlabs/Storage/Browser.php:407 ../../Zotlabs/Module/Cdav.php:1062
-#: ../../extend/addon/hzaddons/articles/Widget/Articles_categories.php:80
-#: ../../extend/addon/hzaddons/cards/Widget/Cards_categories.php:80
-msgid "Categories"
-msgstr "Kategorier"
+#: ../../include/bbcode.php:1652
+msgid "$1 wrote:"
+msgstr "$1 skrev:"
-#: ../../include/contact_widgets.php:185
-msgid "Common Connections"
+#: ../../include/import.php:31
+msgid "Unable to import a removed channel."
+msgstr "Ikke i stand til å importere en kanal som er slettet."
+
+#: ../../include/import.php:57
+msgid ""
+"Cannot create a duplicate channel identifier on this system. Import failed."
msgstr ""
+"Kan ikke lage en kopi av kanal-identifikatoren på dette systemet. Import "
+"mislyktes."
-#: ../../include/contact_widgets.php:189
+#: ../../include/import.php:78 ../../addon/diaspora/import_diaspora.php:56
+msgid "Unable to create a unique channel address. Import failed."
+msgstr "Klarte ikke å lage en unik kanaladresse. Import mislyktes."
+
+#: ../../include/import.php:129
+msgid "Cloned channel not found. Import failed."
+msgstr "Klonet kanal ble ikke funnet. Import mislyktes."
+
+#: ../../addon/irc/Mod_Irc.php:23 ../../addon/irc/irc.php:41
+msgid "Popular Channels"
+msgstr "Populære kanaler"
+
+#: ../../addon/irc/irc.php:37
+msgid "Channels to auto connect"
+msgstr "Kanaler som skal kobles automatisk"
+
+#: ../../addon/irc/irc.php:37 ../../addon/irc/irc.php:41
+msgid "Comma separated list"
+msgstr "Kommaseparert liste"
+
+#: ../../addon/irc/irc.php:45
+msgid "IRC Settings"
+msgstr "Innstillinger for IRC"
+
+#: ../../addon/irc/irc.php:54
+msgid "IRC settings saved."
+msgstr "Innstillinger for IRC lagret."
+
+#: ../../addon/irc/irc.php:58
+msgid "IRC Chatroom"
+msgstr "IRC-kanal"
+
+#: ../../addon/fediquest/fediquest.php:215
+msgid "ERROR: word length is not correct!"
+msgstr "FEIL: lengden på ordet er ikke riktig!"
+
+#: ../../addon/fediquest/Mod_Fediquest.php:22
+msgid "Fediquest App"
+msgstr "Fediquest"
+
+#: ../../addon/fediquest/Mod_Fediquest.php:23
+msgid "A distributed quest for a given word (game)."
+msgstr "En distribuert gjettelek om ord (spill)"
+
+#: ../../addon/fediquest/Mod_Fediquest.php:24
+msgid ""
+"To start a game, enter [fediquest]your_word[/fediquest] somewhere in a "
+"toplevel post."
+msgstr ""
+"For å starte et spill, skriv [fediquest]ditt_ord[/fediquest] et sted i et "
+"nytt innlegg."
+
+#: ../../addon/fediquest/Mod_Fediquest.php:25
+msgid "Your contacts can post their guess in the comments."
+msgstr ""
+"Forbindelsene dine kan poste sine gjetninger i kommentarene til innlegget."
+
+#: ../../addon/fediquest/Mod_Fediquest.php:26
+msgid ""
+"Your channel will evaluate the guess and automatically post the response."
+msgstr ""
+"Kanalen din vil automatisk vurdere svarene og poste resultatet i tråden."
+
+#: ../../addon/fediquest/Mod_Fediquest.php:28
+msgid "Correct letters"
+msgstr "Riktige bokstaver"
+
+#: ../../addon/fediquest/Mod_Fediquest.php:29
+msgid "Letters contained in the word but at the wrong spot"
+msgstr "Bokstaver som er riktige, men på feil plass"
+
+#: ../../addon/fediquest/Mod_Fediquest.php:30
+msgid "Letters not contained in the word"
+msgstr "Bokstaver som ikke finnes i ordet"
+
+#: ../../addon/socialauth/Mod_SocialAuth.php:106
+#: ../../addon/socialauth/Mod_SocialAuth.php:179
+msgid "Network error"
+msgstr "Nettverksfeil"
+
+#: ../../addon/socialauth/Mod_SocialAuth.php:110
+#: ../../addon/socialauth/Mod_SocialAuth.php:183
+msgid "API error"
+msgstr "API-feil"
+
+#: ../../addon/socialauth/Mod_SocialAuth.php:114
+#: ../../addon/socialauth/Mod_SocialAuth.php:187
+msgid "Unknown issue"
+msgstr "Ukjent problem"
+
+#: ../../addon/socialauth/Mod_SocialAuth.php:152
+msgid "Unable to retrieve email address from remote identity provider"
+msgstr "Fikk ikke til å hente epostadressen din fra identitetstilbyderen"
+
+#: ../../addon/socialauth/Mod_SocialAuth.php:171
+msgid "Unable to login using email address "
+msgstr "Fikk ikke til å logge inn ved bruk av epostadresse"
+
+#: ../../addon/socialauth/Mod_SocialAuth.php:211
+msgid "Social Authentication using your social media account"
+msgstr "Sosial Autentisering ved bruk av din sosiale nettverkskonto"
+
+#: ../../addon/socialauth/Mod_SocialAuth.php:215
+msgid ""
+"This app enables one or more social provider sign-in buttons on the login "
+"page."
+msgstr ""
+"Denne appen legger til en eller flere login-knapper fra sosiale nettverk på "
+"innloggingssiden."
+
+#: ../../addon/socialauth/Mod_SocialAuth.php:233
+msgid "Add an identity provider"
+msgstr "Legg til en identitetstilbyder"
+
+#: ../../addon/socialauth/Mod_SocialAuth.php:260
+msgid "Enable "
+msgstr "Skru på "
+
+#: ../../addon/socialauth/Mod_SocialAuth.php:267
+msgid "Key"
+msgstr "Nøkkel"
+
+#: ../../addon/socialauth/Mod_SocialAuth.php:267
+#: ../../addon/socialauth/Mod_SocialAuth.php:272
+#: ../../addon/socialauth/Mod_SocialAuth.php:288
+#: ../../addon/socialauth/Mod_SocialAuth.php:299
+#: ../../addon/socialauth/Mod_SocialAuth.php:308
+msgid "Word"
+msgstr "Ord"
+
+#: ../../addon/socialauth/Mod_SocialAuth.php:272
+msgid "Secret"
+msgstr "Hemmelighet"
+
+#: ../../addon/socialauth/Mod_SocialAuth.php:308
+msgid "Add a custom provider"
+msgstr "Legg til en egendefinert tilbyder"
+
+#: ../../addon/socialauth/Mod_SocialAuth.php:328
+msgid "Remove an identity provider"
+msgstr "Fjern en identitetstilbyder"
+
+#: ../../addon/socialauth/Mod_SocialAuth.php:338
+msgid "Social authentication"
+msgstr "Sosial autentisering"
+
+#: ../../addon/socialauth/Mod_SocialAuth.php:380
+msgid "Error while saving provider settings"
+msgstr "Det oppsto en feil ved lagring av innstillingene for tilbyderen"
+
+#: ../../addon/socialauth/Mod_SocialAuth.php:403
+msgid "Custom provider already exists"
+msgstr "Den egendefinerte tilbyderen finnes allerede"
+
+#: ../../addon/socialauth/Mod_SocialAuth.php:420
+msgid "Social authentication settings saved."
+msgstr "Innstillingene for sosial autentisering ble lagret."
+
+#: ../../addon/upload_limits/upload_limits.php:25
+msgid "Show Upload Limits"
+msgstr "Vis opplastingsgrenser"
+
+#: ../../addon/upload_limits/upload_limits.php:27
+msgid "Hubzilla configured maximum size: "
+msgstr "Maksimum størrelse definert av Hubzilla:"
+
+#: ../../addon/upload_limits/upload_limits.php:28
+msgid "PHP upload_max_filesize: "
+msgstr ""
+
+#: ../../addon/upload_limits/upload_limits.php:29
+msgid "PHP post_max_size (must be larger than upload_max_filesize): "
+msgstr "PHP post_max_size (må være større enn upload_max_filesize):"
+
+#: ../../addon/sendzid/Mod_Sendzid.php:21
+msgid "Send your identity to all websites"
+msgstr "Send din identitet til alle nettsteder"
+
+#: ../../addon/sendzid/Mod_Sendzid.php:29
+msgid "Send ZID"
+msgstr ""
+
+#: ../../addon/hubwall/hubwall.php:19
+msgid "Send email to all members"
+msgstr "Send epost til alle medlemmer"
+
+#: ../../addon/hubwall/hubwall.php:33 ../../Zotlabs/Lib/Enotify.php:66
#, php-format
-msgid "View all %d common connections"
+msgid "%s Administrator"
+msgstr "%s administrator"
+
+#: ../../addon/hubwall/hubwall.php:50 ../../addon/mailtest/mailtest.php:50
+msgid "No recipients found."
+msgstr "Fant ingen mottakere"
+
+#: ../../addon/hubwall/hubwall.php:74
+#, php-format
+msgid "%1$d of %2$d messages sent."
+msgstr "%1$d av %2$d meldinger sendt."
+
+#: ../../addon/hubwall/hubwall.php:82
+msgid "Send email to all hub members."
+msgstr "Send epost til alle medlemmer av hub'en"
+
+#: ../../addon/hubwall/hubwall.php:93 ../../addon/mailtest/mailtest.php:96
+msgid "Message subject"
+msgstr "Emne for meldingen"
+
+#: ../../addon/hubwall/hubwall.php:94
+msgid "Sender Email address"
+msgstr "Avsenderens epostadresse"
+
+#: ../../addon/hubwall/hubwall.php:95
+msgid "Test mode (only send to hub administrator)"
+msgstr "Testmodus (send kun til hubens administrator)"
+
+#: ../../addon/rainbowtag/Mod_Rainbowtag.php:22
+msgid "Rainbow Tag App"
msgstr ""
-#: ../../include/security.php:633
+#: ../../addon/rainbowtag/Mod_Rainbowtag.php:23
+msgid "Add some colour to tag clouds"
+msgstr ""
+
+#: ../../addon/rainbowtag/Mod_Rainbowtag.php:30
+msgid "Rainbow Tag"
+msgstr ""
+
+#: ../../addon/openid/openid.php:49
msgid ""
-"The form security token was not correct. This probably happened because the "
-"form has been opened for too long (>3 hours) before submitting it."
+"We encountered a problem while logging in with the OpenID you provided. "
+"Please check the correct spelling of the ID."
msgstr ""
-"Skjemaets sikkerhetspollett var ikke gyldig. Dette skjedde antakelig fordi "
-"skjemaet har vært åpnet for lenge (>3 timer) før det ble sendt inn."
+"Vi støtte på et problem under innloggingen med din OpenID. Vennligst sjekk "
+"at ID-en er stavet riktig."
-#: ../../include/taxonomy.php:326
-msgid "Trending"
+#: ../../addon/openid/openid.php:49
+msgid "The error message was:"
+msgstr "Feilmeldingen var:"
+
+#: ../../addon/openid/Mod_Openid.php:32
+msgid "OpenID protocol error. No ID returned."
+msgstr "OpenID protokollfeil. Ingen ID ble returnert."
+
+#: ../../addon/openid/Mod_Openid.php:78 ../../addon/openid/Mod_Openid.php:179
+#, php-format
+msgid "Welcome %s. Remote authentication successful."
+msgstr "Velkommen %s. Ekstern autentisering er vellykket."
+
+#: ../../addon/openid/MysqlProvider.php:52
+msgid "First Name"
+msgstr "Fornavn"
+
+#: ../../addon/openid/MysqlProvider.php:53
+msgid "Last Name"
+msgstr "Etternavn"
+
+#: ../../addon/openid/MysqlProvider.php:54 ../../addon/redred/Mod_Redred.php:73
+#: ../../boot.php:1755
+msgid "Nickname"
+msgstr "Kallenavn"
+
+#: ../../addon/openid/MysqlProvider.php:55
+msgid "Full Name"
+msgstr "Fullt navn"
+
+#: ../../addon/openid/MysqlProvider.php:61
+msgid "Profile Photo 16px"
+msgstr "Profilbilde 16px"
+
+#: ../../addon/openid/MysqlProvider.php:62
+msgid "Profile Photo 32px"
+msgstr "Profilbilde 32px"
+
+#: ../../addon/openid/MysqlProvider.php:63
+msgid "Profile Photo 48px"
+msgstr "Profilbilde 48px"
+
+#: ../../addon/openid/MysqlProvider.php:64
+msgid "Profile Photo 64px"
+msgstr "Profilbilde 64px"
+
+#: ../../addon/openid/MysqlProvider.php:65
+msgid "Profile Photo 80px"
+msgstr "Profilbilde 80px"
+
+#: ../../addon/openid/MysqlProvider.php:66
+msgid "Profile Photo 128px"
+msgstr "Profilbilde 128px"
+
+#: ../../addon/openid/MysqlProvider.php:68
+#: ../../Zotlabs/Module/Profiles.php:782
+msgid "Homepage URL"
+msgstr "Hjemmeside URL"
+
+#: ../../addon/openid/MysqlProvider.php:69 ../../Zotlabs/Lib/Apps.php:363
+msgid "Language"
+msgstr "Språk"
+
+#: ../../addon/openid/MysqlProvider.php:70
+msgid "Birth Year"
+msgstr "Fødselsår"
+
+#: ../../addon/openid/MysqlProvider.php:71
+msgid "Birth Month"
+msgstr "Fødselsmåne"
+
+#: ../../addon/openid/MysqlProvider.php:72
+msgid "Birth Day"
+msgstr "Fødselsdag"
+
+#: ../../addon/openid/MysqlProvider.php:73
+msgid "Birthdate"
+msgstr "Fødselsdato"
+
+#: ../../addon/openid/MysqlProvider.php:74
+#: ../../Zotlabs/Module/Profiles.php:453
+msgid "Gender"
+msgstr "Kjønn"
+
+#: ../../addon/openclipatar/openclipatar.php:51
+#: ../../addon/openclipatar/openclipatar.php:129
+msgid "System defaults:"
msgstr ""
-#: ../../include/taxonomy.php:326 ../../include/taxonomy.php:465
-#: ../../include/taxonomy.php:486 ../../Zotlabs/Widget/Tagcloud.php:27
-msgid "Tags"
-msgstr "Merkelapper"
+#: ../../addon/openclipatar/openclipatar.php:55
+msgid "Preferred Clipart IDs"
+msgstr ""
-#: ../../include/taxonomy.php:566
-msgid "Keywords"
-msgstr "Nøkkelord"
+#: ../../addon/openclipatar/openclipatar.php:55
+msgid "List of preferred clipart ids. These will be shown first."
+msgstr ""
-#: ../../include/taxonomy.php:587
-msgid "have"
-msgstr "har"
+#: ../../addon/openclipatar/openclipatar.php:56
+msgid "Default Search Term"
+msgstr "Standard søkeord"
-#: ../../include/taxonomy.php:587
-msgid "has"
-msgstr "har"
+#: ../../addon/openclipatar/openclipatar.php:56
+msgid "The default search term. These will be shown second."
+msgstr "Standard søkeord. Disse vil vises som nummer to."
-#: ../../include/taxonomy.php:588
-msgid "want"
-msgstr "ønsker"
+#: ../../addon/openclipatar/openclipatar.php:57
+msgid "Return After"
+msgstr ""
-#: ../../include/taxonomy.php:588
-msgid "wants"
-msgstr "ønsker"
+#: ../../addon/openclipatar/openclipatar.php:57
+msgid "Page to load after image selection."
+msgstr ""
-#: ../../include/taxonomy.php:589 ../../Zotlabs/Lib/ThreadItem.php:335
-msgid "like"
-msgstr "liker"
+#: ../../addon/openclipatar/openclipatar.php:60
+msgid "Profile List"
+msgstr ""
-#: ../../include/taxonomy.php:589
-msgid "likes"
-msgstr "liker"
+#: ../../addon/openclipatar/openclipatar.php:62
+msgid "Order of Preferred"
+msgstr ""
-#: ../../include/taxonomy.php:590 ../../Zotlabs/Lib/ThreadItem.php:336
-msgid "dislike"
-msgstr "misliker"
+#: ../../addon/openclipatar/openclipatar.php:62
+msgid "Sort order of preferred clipart ids."
+msgstr ""
-#: ../../include/taxonomy.php:590
-msgid "dislikes"
-msgstr "misliker"
+#: ../../addon/openclipatar/openclipatar.php:63
+#: ../../addon/openclipatar/openclipatar.php:69
+msgid "Newest first"
+msgstr ""
-#: ../../include/connections.php:174
-msgid "New window"
-msgstr "Nytt vindu"
+#: ../../addon/openclipatar/openclipatar.php:66
+msgid "As entered"
+msgstr ""
-#: ../../include/connections.php:175
-msgid "Open the selected location in a different window or browser tab"
-msgstr "Åpne det valgte stedet i et annet vindu eller nettleser-fane"
+#: ../../addon/openclipatar/openclipatar.php:68
+msgid "Order of other"
+msgstr ""
-#: ../../include/acl_selectors.php:33
-#: ../../Zotlabs/Lib/PermissionDescription.php:34
-msgid "Visible to your default audience"
-msgstr "Synlig for ditt standard publikum"
+#: ../../addon/openclipatar/openclipatar.php:68
+msgid "Sort order of other clipart ids."
+msgstr ""
-#: ../../include/acl_selectors.php:100
-msgid "Profile-Based Privacy Groups"
+#: ../../addon/openclipatar/openclipatar.php:70
+msgid "Most downloaded first"
msgstr ""
-#: ../../include/acl_selectors.php:119
-msgid "Private Forum"
+#: ../../addon/openclipatar/openclipatar.php:71
+msgid "Most liked first"
msgstr ""
-#: ../../include/acl_selectors.php:125 ../../Zotlabs/Widget/Forums.php:77
-#: ../../Zotlabs/Widget/Activity_filter.php:130
-#: ../../Zotlabs/Widget/Notifications.php:131
-#: ../../Zotlabs/Widget/Notifications.php:132
-msgid "Forums"
-msgstr "Forum"
+#: ../../addon/openclipatar/openclipatar.php:73
+msgid "Preferred IDs Message"
+msgstr ""
-#: ../../include/acl_selectors.php:136
-#: ../../Zotlabs/Lib/PermissionDescription.php:107
-#: ../../Zotlabs/Module/Settings/Privacy.php:66
-msgid "Only me"
-msgstr "Kun meg"
+#: ../../addon/openclipatar/openclipatar.php:73
+msgid "Message to display above preferred results."
+msgstr ""
-#: ../../include/acl_selectors.php:143
-msgid "Share with"
-msgstr "Del med"
+#: ../../addon/openclipatar/openclipatar.php:79
+msgid "Uploaded by: "
+msgstr ""
-#: ../../include/acl_selectors.php:144
-msgid "Custom selection"
-msgstr "Tilpasset utvalg"
+#: ../../addon/openclipatar/openclipatar.php:79
+msgid "Drawn by: "
+msgstr ""
-#: ../../include/acl_selectors.php:146
+#: ../../addon/openclipatar/openclipatar.php:183
+#: ../../addon/openclipatar/openclipatar.php:195
+msgid "Use this image"
+msgstr ""
+
+#: ../../addon/openclipatar/openclipatar.php:193
+msgid "Or select from a free OpenClipart.org image:"
+msgstr ""
+
+#: ../../addon/openclipatar/openclipatar.php:196
+msgid "Search Term"
+msgstr "Søkeord"
+
+#: ../../addon/openclipatar/openclipatar.php:233
+msgid "Unknown error. Please try again later."
+msgstr ""
+
+#: ../../addon/openclipatar/openclipatar.php:299
+#: ../../Zotlabs/Module/Profile_photo.php:269
msgid ""
-"Select \"Allow\" to allow viewing. \"Don't allow\" lets you override and "
-"limit the scope of \"Allow\"."
+"Shift-reload the page or clear browser cache if the new photo does not "
+"display immediately."
+msgstr ""
+"Hold nede Shift-knappen og last siden på nytt eller tøm nettleserens "
+"mellomlager hvis det nye bildet ikke vises umiddelbart."
+
+#: ../../addon/openclipatar/openclipatar.php:309
+msgid "Profile photo updated successfully."
msgstr ""
-#: ../../include/acl_selectors.php:147 ../../Zotlabs/Module/Authorize.php:32
-msgid "Allow"
+#: ../../addon/nsabait/Mod_Nsabait.php:23
+msgid "NSA Bait App"
msgstr ""
-#: ../../include/acl_selectors.php:148
-msgid "Don't allow"
+#: ../../addon/nsabait/Mod_Nsabait.php:25
+msgid "Make yourself a political target."
msgstr ""
-#: ../../include/acl_selectors.php:154 ../../Zotlabs/Module/Photos.php:671
-#: ../../Zotlabs/Module/Photos.php:1045 ../../Zotlabs/Module/Chat.php:240
-#: ../../Zotlabs/Module/Thing.php:350 ../../Zotlabs/Module/Thing.php:402
-#: ../../Zotlabs/Module/Filestorage.php:195
-#: ../../extend/addon/hzaddons/flashcards/Mod_Flashcards.php:261
-msgid "Permissions"
-msgstr "Tillatelser"
+#: ../../addon/visage/Mod_Visage.php:23
+msgid "Recent Channel/Profile Viewers"
+msgstr ""
-#: ../../include/acl_selectors.php:156 ../../Zotlabs/Lib/ThreadItem.php:531
-#: ../../Zotlabs/Storage/Browser.php:415 ../../Zotlabs/Module/Photos.php:1266
-#: ../../Zotlabs/Widget/Pinned.php:154
-msgid "Close"
-msgstr "Lukk"
+#: ../../addon/visage/Mod_Visage.php:34
+msgid "No entries."
+msgstr ""
-#: ../../include/acl_selectors.php:181
+#: ../../addon/hzfiles/hzfiles.php:81
+msgid "Hubzilla File Storage Import"
+msgstr ""
+
+#: ../../addon/hzfiles/hzfiles.php:82
+msgid "This will import all your cloud files from another server."
+msgstr ""
+
+#: ../../addon/hzfiles/hzfiles.php:83
+msgid "Hubzilla Server base URL"
+msgstr ""
+
+#: ../../addon/hzfiles/hzfiles.php:84
+#: ../../addon/content_import/Mod_content_import.php:138
+msgid "Since modified date yyyy-mm-dd"
+msgstr ""
+
+#: ../../addon/hzfiles/hzfiles.php:85
+#: ../../addon/content_import/Mod_content_import.php:139
+msgid "Until modified date yyyy-mm-dd"
+msgstr ""
+
+#: ../../addon/upgrade_info/upgrade_info.php:48
+msgid "Your channel has been upgraded to $Projectname version"
+msgstr ""
+
+#: ../../addon/upgrade_info/upgrade_info.php:50
+msgid "Please have a look at the"
+msgstr ""
+
+#: ../../addon/upgrade_info/upgrade_info.php:52
+msgid "git history"
+msgstr ""
+
+#: ../../addon/upgrade_info/upgrade_info.php:54
+msgid "change log"
+msgstr ""
+
+#: ../../addon/upgrade_info/upgrade_info.php:55
+msgid "for further info."
+msgstr ""
+
+#: ../../addon/upgrade_info/upgrade_info.php:60
+#, fuzzy
+#| msgid "$Projectname"
+msgid "$Projectname Upgrade Info"
+msgstr "$Projectname"
+
+#: ../../addon/upgrade_info/upgrade_info.php:64
+msgid "Do not show this again"
+msgstr ""
+
+#: ../../addon/opensearch/opensearch.php:26
#, php-format
+msgctxt "opensearch"
+msgid "Search %1$s (%2$s)"
+msgstr "Søk %1$s (%2$s)"
+
+#: ../../addon/opensearch/opensearch.php:28
+msgctxt "opensearch"
+msgid "$Projectname"
+msgstr "$Projectname"
+
+#: ../../addon/opensearch/opensearch.php:42 ../../Zotlabs/Lib/Enotify.php:67
+#: ../../Zotlabs/Module/Home.php:92 ../../Zotlabs/Module/Home.php:100
+#: ../../Zotlabs/Module/Invite.php:239 ../../Zotlabs/Module/Invite.php:491
+#: ../../Zotlabs/Module/Invite.php:505
+msgid "$Projectname"
+msgstr "$Projectname"
+
+#: ../../addon/opensearch/opensearch.php:43
+msgid "Search $Projectname"
+msgstr ""
+
+#: ../../addon/dirstats/dirstats.php:94
+msgid "Hubzilla Directory Stats"
+msgstr ""
+
+#: ../../addon/dirstats/dirstats.php:95
+msgid "Total Hubs"
+msgstr ""
+
+#: ../../addon/dirstats/dirstats.php:97
+msgid "Hubzilla Hubs"
+msgstr ""
+
+#: ../../addon/dirstats/dirstats.php:99
+msgid "Friendica Hubs"
+msgstr ""
+
+#: ../../addon/dirstats/dirstats.php:101
+msgid "Diaspora Pods"
+msgstr ""
+
+#: ../../addon/dirstats/dirstats.php:103
+msgid "Hubzilla Channels"
+msgstr ""
+
+#: ../../addon/dirstats/dirstats.php:105
+msgid "Friendica Channels"
+msgstr ""
+
+#: ../../addon/dirstats/dirstats.php:107
+msgid "Diaspora Channels"
+msgstr ""
+
+#: ../../addon/dirstats/dirstats.php:109
+msgid "Aged 35 and above"
+msgstr ""
+
+#: ../../addon/dirstats/dirstats.php:111
+msgid "Aged 34 and under"
+msgstr ""
+
+#: ../../addon/dirstats/dirstats.php:113
+msgid "Average Age"
+msgstr ""
+
+#: ../../addon/dirstats/dirstats.php:115
+msgid "Known Chatrooms"
+msgstr ""
+
+#: ../../addon/dirstats/dirstats.php:117
+msgid "Known Tags"
+msgstr ""
+
+#: ../../addon/dirstats/dirstats.php:119
msgid ""
-"Post permissions %s cannot be changed %s after a post is shared.</br />These "
-"permissions set who is allowed to view the post."
+"Please note Diaspora and Friendica statistics are merely those **this "
+"directory** is aware of, and not all those known in the network. This also "
+"applies to chatrooms,"
msgstr ""
-"Tillatelsene til innlegget %s kan ikke endres %s etter at innlegget er delt."
-"</br />Disse tillatelsene avgjør hvem som kan se innlegget."
-#: ../../include/cdav.php:157
-msgid "INVALID EVENT DISMISSED!"
+#: ../../addon/piwik/piwik.php:85
+msgid ""
+"This website is tracked using the <a href='http://www.piwik.org'>Piwik</a> "
+"analytics tool."
msgstr ""
-#: ../../include/cdav.php:158
-#, fuzzy
-msgid "Summary: "
-msgstr "Sammendrag"
+#: ../../addon/piwik/piwik.php:88
+#, php-format
+msgid ""
+"If you do not want that your visits are logged this way you <a href='%s'>can "
+"set a cookie to prevent Piwik from tracking further visits of the site</a> "
+"(opt-out)."
+msgstr ""
-#: ../../include/cdav.php:159
-#, fuzzy
-msgid "Date: "
-msgstr "Dato"
+#: ../../addon/piwik/piwik.php:96
+msgid "Piwik Base URL"
+msgstr ""
-#: ../../include/cdav.php:160 ../../include/cdav.php:168
-msgid "Reason: "
+#: ../../addon/piwik/piwik.php:96
+msgid ""
+"Absolute path to your Piwik installation. (without protocol (http/s), with "
+"trailing slash)"
msgstr ""
-#: ../../include/cdav.php:166
-msgid "INVALID CARD DISMISSED!"
+#: ../../addon/piwik/piwik.php:97
+msgid "Site ID"
msgstr ""
-#: ../../include/cdav.php:167
-#, fuzzy
-msgid "Name: "
-msgstr "Navn"
+#: ../../addon/piwik/piwik.php:98
+msgid "Show opt-out cookie link?"
+msgstr ""
-#: ../../include/selectors.php:17
-msgid "Select a profile to assign to this contact"
-msgstr "Velg en profil for denne kontakten"
+#: ../../addon/piwik/piwik.php:99
+msgid "Asynchronous tracking"
+msgstr ""
-#: ../../include/selectors.php:45
-msgid "Frequently"
-msgstr "Ofte"
+#: ../../addon/piwik/piwik.php:100
+msgid "Enable frontend JavaScript error tracking"
+msgstr ""
-#: ../../include/selectors.php:46
-msgid "Hourly"
-msgstr "Hver time"
+#: ../../addon/piwik/piwik.php:100
+msgid "This feature requires Piwik >= 2.2.0"
+msgstr ""
-#: ../../include/selectors.php:47
-msgid "Twice daily"
-msgstr "To ganger daglig"
+#: ../../addon/piwik/piwik.php:116 ../../addon/logrot/logrot.php:54
+#: ../../addon/diaspora/diaspora.php:108 ../../addon/msgfooter/msgfooter.php:54
+#: ../../addon/rendezvous/rendezvous.php:82 ../../addon/xmpp/xmpp.php:54
+#: ../../addon/faces/faces.php:291 ../../addon/twitter/twitter.php:493
+#: ../../addon/openstreetmap/openstreetmap.php:163
+#: ../../Zotlabs/Module/Defperms.php:111
+#: ../../Zotlabs/Module/Settings/Channel.php:151
+msgid "Settings updated."
+msgstr "Innstillinger oppdatert."
-#: ../../include/selectors.php:48
-msgid "Daily"
-msgstr "Daglig"
+#: ../../addon/redphotos/redphotos.php:106
+msgid "Photos imported"
+msgstr ""
-#: ../../include/selectors.php:49
-msgid "Weekly"
-msgstr "Ukentlig"
+#: ../../addon/redphotos/redphotos.php:129
+msgid "Redmatrix Photo Album Import"
+msgstr ""
-#: ../../include/selectors.php:50
-msgid "Monthly"
-msgstr "Månedlig"
+#: ../../addon/redphotos/redphotos.php:130
+msgid "This will import all your Redmatrix photo albums to this channel."
+msgstr ""
-#: ../../include/selectors.php:64
-msgid "Currently Male"
-msgstr "For tiden mann"
+#: ../../addon/redphotos/redphotos.php:131
+#: ../../addon/redfiles/redfiles.php:121
+msgid "Redmatrix Server base URL"
+msgstr ""
-#: ../../include/selectors.php:64
-msgid "Currently Female"
-msgstr "For tiden kvinne"
+#: ../../addon/redphotos/redphotos.php:132
+#: ../../addon/redfiles/redfiles.php:122
+msgid "Redmatrix Login Username"
+msgstr ""
-#: ../../include/selectors.php:64
-msgid "Mostly Male"
-msgstr "For det meste mann"
+#: ../../addon/redphotos/redphotos.php:133
+#: ../../addon/redfiles/redfiles.php:123
+msgid "Redmatrix Login Password"
+msgstr ""
-#: ../../include/selectors.php:64
-msgid "Mostly Female"
-msgstr "For det meste kvinne"
+#: ../../addon/redphotos/redphotos.php:134
+msgid "Import just this album"
+msgstr ""
-#: ../../include/selectors.php:64
-msgid "Transgender"
-msgstr "Transkjønnet"
+#: ../../addon/redphotos/redphotos.php:134
+msgid "Leave blank to import all albums"
+msgstr ""
-#: ../../include/selectors.php:64
-msgid "Intersex"
-msgstr "interkjønnet"
+#: ../../addon/redphotos/redphotos.php:135
+msgid "Maximum count to import"
+msgstr ""
-#: ../../include/selectors.php:64
-msgid "Transsexual"
-msgstr "Transseksuell"
+#: ../../addon/redphotos/redphotos.php:135
+msgid "0 or blank to import all available"
+msgstr ""
-#: ../../include/selectors.php:64
-msgid "Hermaphrodite"
-msgstr "Hermafroditt"
+#: ../../addon/pageheader/Mod_Pageheader.php:22
+msgid "pageheader Settings saved."
+msgstr ""
-#: ../../include/selectors.php:64
-msgid "Undecided"
-msgstr "Ubestemt"
+#: ../../addon/pageheader/Mod_Pageheader.php:41
+msgid "Message to display on every page on this server"
+msgstr ""
-#: ../../include/selectors.php:100 ../../include/selectors.php:119
-msgid "Males"
-msgstr "Menn"
+#: ../../addon/pageheader/Mod_Pageheader.php:49
+msgid "Page Header"
+msgstr ""
-#: ../../include/selectors.php:100 ../../include/selectors.php:119
-msgid "Females"
-msgstr "Kvinner"
+#: ../../addon/buglink/buglink.php:16 ../../Zotlabs/Lib/Apps.php:334
+msgid "Report Bug"
+msgstr ""
-#: ../../include/selectors.php:100
-msgid "Gay"
-msgstr "Homo"
+#: ../../addon/hideaside/Mod_Hideaside.php:28
+msgid "Hide Aside App"
+msgstr ""
-#: ../../include/selectors.php:100
-msgid "Lesbian"
-msgstr "Lesbisk"
+#: ../../addon/hideaside/Mod_Hideaside.php:29
+msgid "Fade out aside areas after a while when using endless scroll"
+msgstr ""
-#: ../../include/selectors.php:100
-msgid "No Preference"
-msgstr "Ingen preferanse"
+#: ../../addon/ijpost/ijpost.php:44
+msgid "Post to Insane Journal"
+msgstr ""
-#: ../../include/selectors.php:100
-msgid "Bisexual"
-msgstr "Biseksuell"
+#: ../../addon/ijpost/Mod_Ijpost.php:23
+msgid "Insane Journal Crosspost Connector Settings saved."
+msgstr ""
-#: ../../include/selectors.php:100
-msgid "Autosexual"
-msgstr "Autoseksuell"
+#: ../../addon/ijpost/Mod_Ijpost.php:35
+msgid "Insane Journal Crosspost Connector App"
+msgstr ""
-#: ../../include/selectors.php:100
-msgid "Abstinent"
-msgstr "Avholdende"
+#: ../../addon/ijpost/Mod_Ijpost.php:35 ../../addon/xmpp/Mod_Xmpp.php:35
+#: ../../Zotlabs/Module/Invite.php:70 ../../Zotlabs/Module/Lang.php:20
+msgid "Not Installed"
+msgstr ""
-#: ../../include/selectors.php:100
-msgid "Virgin"
-msgstr "Jomfru"
+#: ../../addon/ijpost/Mod_Ijpost.php:36
+msgid "Relay public postings to Insane Journal"
+msgstr ""
-#: ../../include/selectors.php:100
-msgid "Deviant"
-msgstr "Avviker"
+#: ../../addon/ijpost/Mod_Ijpost.php:53
+msgid "InsaneJournal username"
+msgstr ""
-#: ../../include/selectors.php:100
-msgid "Fetish"
-msgstr "Fetisj"
+#: ../../addon/ijpost/Mod_Ijpost.php:57
+msgid "InsaneJournal password"
+msgstr ""
-#: ../../include/selectors.php:100
-msgid "Oodles"
-msgstr "Masse"
+#: ../../addon/ijpost/Mod_Ijpost.php:61
+msgid "Post to InsaneJournal by default"
+msgstr ""
-#: ../../include/selectors.php:100
-msgid "Nonsexual"
-msgstr "Ikke-seksuell"
+#: ../../addon/ijpost/Mod_Ijpost.php:69
+msgid "Insane Journal Crosspost Connector"
+msgstr ""
-#: ../../include/selectors.php:138 ../../include/selectors.php:155
-msgid "Single"
-msgstr "Enslig"
+#: ../../addon/wholikesme/wholikesme.php:30
+msgid "Who likes me?"
+msgstr ""
-#: ../../include/selectors.php:138
-msgid "Lonely"
-msgstr "Ensom"
+#: ../../addon/redred/redred.php:50
+msgid "Post to Hubzilla"
+msgstr ""
-#: ../../include/selectors.php:138
-msgid "Available"
-msgstr "Tilgjengelig"
+#: ../../addon/redred/Mod_Redred.php:24
+msgid "Channel is required."
+msgstr ""
-#: ../../include/selectors.php:138
-msgid "Unavailable"
-msgstr "Ikke tilgjengelig"
+#: ../../addon/redred/Mod_Redred.php:29 ../../Zotlabs/Module/Network.php:317
+#, fuzzy
+msgid "Invalid channel."
+msgstr "Ugyldig kanal"
-#: ../../include/selectors.php:138
-msgid "Has crush"
-msgstr "Er forelsket"
+#: ../../addon/redred/Mod_Redred.php:38
+msgid "Hubzilla Crosspost Connector Settings saved."
+msgstr ""
-#: ../../include/selectors.php:138
-msgid "Infatuated"
-msgstr "Betatt"
+#: ../../addon/redred/Mod_Redred.php:61
+msgid "Send public postings to Hubzilla channel by default"
+msgstr ""
-#: ../../include/selectors.php:138 ../../include/selectors.php:155
-msgid "Dating"
-msgstr "Sammen med"
+#: ../../addon/redred/Mod_Redred.php:65
+msgid "Hubzilla API Path"
+msgstr ""
-#: ../../include/selectors.php:138
-msgid "Unfaithful"
-msgstr "Utro"
+#: ../../addon/redred/Mod_Redred.php:65 ../../addon/rtof/Mod_Rtof.php:51
+msgid "https://{sitename}/api"
+msgstr ""
-#: ../../include/selectors.php:138
-msgid "Sex Addict"
-msgstr "Sexavhengig"
+#: ../../addon/redred/Mod_Redred.php:69
+msgid "Hubzilla login name"
+msgstr ""
-#: ../../include/selectors.php:138
-msgid "Friends/Benefits"
-msgstr "Venner/Frynsegoder"
+#: ../../addon/redred/Mod_Redred.php:73
+msgid "Hubzilla channel name"
+msgstr ""
-#: ../../include/selectors.php:138
-msgid "Casual"
-msgstr "Tilfeldig"
+#: ../../addon/redred/Mod_Redred.php:77
+msgid "Hubzilla password"
+msgstr ""
-#: ../../include/selectors.php:138
-msgid "Engaged"
-msgstr "Forlovet"
+#: ../../addon/redred/Mod_Redred.php:85
+msgid "Hubzilla Crosspost Connector"
+msgstr ""
-#: ../../include/selectors.php:138 ../../include/selectors.php:155
-msgid "Married"
-msgstr "Gift"
+#: ../../addon/cards/cards.php:48 ../../addon/cards/cards.php:161
+#: ../../addon/cards/Mod_Cards.php:209 ../../Zotlabs/Lib/Apps.php:332
+msgid "Cards"
+msgstr ""
-#: ../../include/selectors.php:138
-msgid "Imaginarily married"
-msgstr "Gift i fantasien"
+#: ../../addon/cards/cards.php:51 ../../addon/wiki/wiki.php:48
+msgid "View Cards"
+msgstr ""
-#: ../../include/selectors.php:138
-msgid "Partners"
-msgstr "Partnere"
+#: ../../addon/cards/cards.php:104 ../../addon/articles/articles.php:105
+#: ../../addon/wiki/Lib/NativeWikiPage.php:549
+#: ../../Zotlabs/Module/Page.php:136 ../../Zotlabs/Module/Display.php:155
+#: ../../Zotlabs/Module/Block.php:77 ../../Zotlabs/Module/Help.php:173
+#: ../../Zotlabs/Web/Router.php:194
+msgid "Page not found."
+msgstr "Siden ikke funnet."
-#: ../../include/selectors.php:138 ../../include/selectors.php:155
-msgid "Cohabiting"
-msgstr "Samboer"
+#: ../../addon/cards/Mod_Cards.php:115
+msgid "Add Card"
+msgstr ""
-#: ../../include/selectors.php:138
-msgid "Common law"
-msgstr "Samboer"
+#: ../../addon/cards/Mod_Card_edit.php:17
+#: ../../addon/cards/Mod_Card_edit.php:33
+#: ../../addon/articles/Mod_Article_edit.php:17
+#: ../../addon/articles/Mod_Article_edit.php:33
+#: ../../Zotlabs/Module/Editwebpage.php:80
+#: ../../Zotlabs/Module/Editlayout.php:79 ../../Zotlabs/Module/Editpost.php:24
+#: ../../Zotlabs/Module/Editblock.php:79 ../../Zotlabs/Module/Editblock.php:95
+msgid "Item not found"
+msgstr "Elementet ble ikke funnet."
-#: ../../include/selectors.php:138
-msgid "Happy"
-msgstr "Lykkelig"
+#: ../../addon/cards/Mod_Card_edit.php:44
+#: ../../addon/articles/Mod_Article_edit.php:44
+#: ../../Zotlabs/Module/Page.php:75 ../../Zotlabs/Module/Block.php:41
+#: ../../Zotlabs/Module/Cal.php:31 ../../Zotlabs/Module/Wall_upload.php:30
+#: ../../Zotlabs/Module/Attach_edit.php:52 ../../Zotlabs/Module/Attach.php:22
+#: ../../Zotlabs/Module/Chanview.php:95
+msgid "Channel not found."
+msgstr "Kanalen ble ikke funnet."
-#: ../../include/selectors.php:138
-msgid "Not looking"
-msgstr "Ikke på utkikk"
+#: ../../addon/cards/Mod_Card_edit.php:123
+msgid "Edit Card"
+msgstr ""
-#: ../../include/selectors.php:138
-msgid "Swinger"
-msgstr "Partnerbytte"
+#: ../../addon/startpage/Mod_Startpage.php:61
+msgid "Page to load after login"
+msgstr ""
-#: ../../include/selectors.php:138
-msgid "Betrayed"
-msgstr "Bedratt"
+#: ../../addon/startpage/Mod_Startpage.php:63
+msgid ""
+"Examples: \"apps\", \"network?f=&gid=37\" (privacy collection), \"channel\" "
+"or \"notifications/system\" (leave blank for default.)"
+msgstr ""
-#: ../../include/selectors.php:138 ../../include/selectors.php:155
-msgid "Separated"
-msgstr "Separert"
+#: ../../addon/startpage/Mod_Startpage.php:72
+msgid "Startpage"
+msgstr ""
-#: ../../include/selectors.php:138
-msgid "Unstable"
-msgstr "Ustabilt"
+#: ../../addon/libertree/libertree.php:43
+msgid "Post to Libertree"
+msgstr ""
-#: ../../include/selectors.php:138 ../../include/selectors.php:155
-msgid "Divorced"
-msgstr "Skilt"
+#: ../../addon/libertree/Mod_Libertree.php:25
+msgid "Libertree Crosspost Connector Settings saved."
+msgstr ""
-#: ../../include/selectors.php:138
-msgid "Imaginarily divorced"
-msgstr "Skilt i fantasien"
+#: ../../addon/libertree/Mod_Libertree.php:49
+msgid "Libertree API token"
+msgstr ""
-#: ../../include/selectors.php:138 ../../include/selectors.php:155
-msgid "Widowed"
-msgstr "Enke"
+#: ../../addon/libertree/Mod_Libertree.php:53
+msgid "Libertree site URL"
+msgstr ""
-#: ../../include/selectors.php:138
-msgid "Uncertain"
-msgstr "Usikkert"
+#: ../../addon/libertree/Mod_Libertree.php:57
+msgid "Post to Libertree by default"
+msgstr ""
-#: ../../include/selectors.php:138 ../../include/selectors.php:155
-msgid "It's complicated"
-msgstr "Det er komplisert"
+#: ../../addon/libertree/Mod_Libertree.php:65
+msgid "Libertree Crosspost Connector"
+msgstr ""
-#: ../../include/selectors.php:138
-msgid "Don't care"
-msgstr "Bryr meg ikke"
+#: ../../addon/logrot/logrot.php:36
+msgid "Logfile archive directory"
+msgstr ""
-#: ../../include/selectors.php:138
-msgid "Ask me"
-msgstr "Spør meg"
+#: ../../addon/logrot/logrot.php:36
+msgid "Directory to store rotated logs"
+msgstr ""
-#: ../../include/zid.php:417
+#: ../../addon/logrot/logrot.php:37
+msgid "Logfile size in bytes before rotating"
+msgstr ""
+
+#: ../../addon/logrot/logrot.php:38
+msgid "Number of logfiles to retain"
+msgstr ""
+
+#: ../../addon/testdrive/testdrive.php:104
#, php-format
-msgid "OpenWebAuth: %1$s welcomes %2$s"
-msgstr "OpenWebAuth: %1$s ønsker %2$s velkommen"
+msgid "Your account on %s will expire in a few days."
+msgstr ""
-#: ../../view/theme/redbasic/php/config.php:19
-#: ../../view/theme/redbasic/php/config.php:22
-msgid "Focus (Hubzilla default)"
-msgstr "Focus (Hubzilla standard)"
+#: ../../addon/testdrive/testdrive.php:105
+msgid "Your test account is about to expire."
+msgstr ""
-#: ../../view/theme/redbasic/php/config.php:192
-msgid "Theme settings"
-msgstr "Instillinger for utseende"
+#: ../../addon/pubcrawl/Mod_Pubcrawl.php:24
+msgid ""
+"The activitypub protocol does not support location independence. Connections "
+"you make within that network may be unreachable from alternate channel "
+"locations."
+msgstr ""
-#: ../../view/theme/redbasic/php/config.php:193
-msgid "Dark style"
+#: ../../addon/pubcrawl/Mod_Pubcrawl.php:31
+msgid "Activitypub Protocol"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:194
-msgid "Light style"
+#: ../../addon/pubcrawl/pubcrawl.php:1099 ../../addon/diaspora/diaspora.php:417
+#: ../../Zotlabs/Module/Contactedit.php:494
+#, fuzzy
+#| msgid "Refresh"
+msgid "Refresh failed"
+msgstr "Forny"
+
+#: ../../addon/pubcrawl/pubcrawl.php:1106 ../../addon/diaspora/diaspora.php:422
+#: ../../Zotlabs/Module/Contactedit.php:491
+msgid "Refresh succeeded"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:195
-msgid "Common settings"
+#: ../../addon/bookmarker/bookmarker.php:38
+#: ../../Zotlabs/Lib/ThreadItem.php:455
+msgid "Save Bookmarks"
+msgstr "Lagre bokmerker"
+
+#: ../../addon/planets/Mod_Planets.php:23
+msgid "Random Planet App"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:196
-msgid "Primary theme color"
+#: ../../addon/planets/Mod_Planets.php:25
+msgid ""
+"Set a random planet from the Star Wars Empire as your location when posting"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:196
-#: ../../view/theme/redbasic/php/config.php:197
-#: ../../view/theme/redbasic/php/config.php:198
-#: ../../view/theme/redbasic/php/config.php:199
-#: ../../view/theme/redbasic/php/config.php:200
-#, fuzzy
-#| msgid "Leave empty for default width"
-msgid "Current color, leave empty for default"
-msgstr "La feltet stå tomt for å bruke standard bredde"
+#: ../../addon/dwpost/dwpost.php:49
+msgid "Post to Dreamwidth"
+msgstr ""
-#: ../../view/theme/redbasic/php/config.php:197
-msgid "Success theme color"
+#: ../../addon/dwpost/dwpost.php:134 ../../addon/dwpost/Mod_Dwpost.php:67
+#: ../../addon/wppost/wppost.php:175 ../../addon/wppost/Mod_Wppost.php:96
+#: ../../addon/wiki/Mod_Wiki.php:381 ../../addon/ljpost/ljpost.php:134
+msgid "Source"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:198
-#, fuzzy
-#| msgid "Link hover color"
-msgid "Info theme color"
-msgstr "Lenkefarge når musepekeren er over"
+#: ../../addon/dwpost/Mod_Dwpost.php:26
+msgid "Dreamwidth Crosspost Connector Settings saved."
+msgstr ""
-#: ../../view/theme/redbasic/php/config.php:199
-#, fuzzy
-#| msgid "Link hover color"
-msgid "Warning theme color"
-msgstr "Lenkefarge når musepekeren er over"
+#: ../../addon/dwpost/Mod_Dwpost.php:51
+msgid "Dreamwidth username"
+msgstr ""
-#: ../../view/theme/redbasic/php/config.php:200
-#, fuzzy
-#| msgid "Link hover color"
-msgid "Danger theme color"
-msgstr "Lenkefarge når musepekeren er over"
+#: ../../addon/dwpost/Mod_Dwpost.php:55
+msgid "Dreamwidth password"
+msgstr ""
-#: ../../view/theme/redbasic/php/config.php:201
-#, fuzzy
-#| msgid "Default photo upload folder"
-msgid "Default to dark mode"
-msgstr "Standard mappe for opplasting av bilder"
+#: ../../addon/dwpost/Mod_Dwpost.php:59
+msgid "Post to Dreamwidth by default"
+msgstr ""
-#: ../../view/theme/redbasic/php/config.php:202
-msgid "Always use light icons for navbar"
+#: ../../addon/dwpost/Mod_Dwpost.php:63 ../../addon/wppost/Mod_Wppost.php:92
+#: ../../addon/ljpost/Mod_Ljpost.php:69
+msgid "Add link to original post"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:202
-msgid "Enable this option if you use a dark navbar color in light mode"
+#: ../../addon/dwpost/Mod_Dwpost.php:67 ../../addon/wppost/Mod_Wppost.php:96
+msgid "Link description (default:"
msgstr ""
-#: ../../view/theme/redbasic/php/config.php:203
-msgid "Narrow navbar"
-msgstr "Smal navigasjonslinje"
+#: ../../addon/dwpost/Mod_Dwpost.php:75
+msgid "Dreamwidth Crosspost Connector"
+msgstr ""
-#: ../../view/theme/redbasic/php/config.php:204
-msgid "Navigation bar background color"
-msgstr "Navigasjonslinjens bakgrunnsfarge"
+#: ../../addon/diaspora/diaspora.php:81
+msgid ""
+"Please install the statistics addon to be able to configure a diaspora relay"
+msgstr ""
-#: ../../view/theme/redbasic/php/config.php:205
-#, fuzzy
-#| msgid "Navigation bar background color"
-msgid "Dark navigation bar background color"
-msgstr "Navigasjonslinjens bakgrunnsfarge"
+#: ../../addon/diaspora/diaspora.php:91
+msgid "Diaspora Relay Handle"
+msgstr ""
-#: ../../view/theme/redbasic/php/config.php:206
-msgid "Set the background color"
-msgstr "Angi bakgrunnsfargen"
+#: ../../addon/diaspora/diaspora.php:91
+msgid "Address of a diaspora relay. Example: relay@diasporarelay.tld"
+msgstr ""
-#: ../../view/theme/redbasic/php/config.php:207
-#, fuzzy
-#| msgid "Set the background color"
-msgid "Set the dark background color"
-msgstr "Angi bakgrunnsfargen"
+#: ../../addon/diaspora/diaspora.php:111
+msgid "Diaspora relay could not be imported"
+msgstr ""
-#: ../../view/theme/redbasic/php/config.php:208
-msgid "Set the background image"
-msgstr "Angi bakgrunnsbilde"
+#: ../../addon/diaspora/diaspora.php:1112
+msgid "No subject"
+msgstr ""
-#: ../../view/theme/redbasic/php/config.php:209
+#: ../../addon/diaspora/import_diaspora.php:16
#, fuzzy
-#| msgid "Set the background image"
-msgid "Set the dark background image"
-msgstr "Angi bakgrunnsbilde"
+#| msgid "Nothing to import."
+msgid "No account to import to."
+msgstr "Ingenting å importere."
-#: ../../view/theme/redbasic/php/config.php:210
-msgid "Set font-size for the entire application"
-msgstr "Angi skriftstørrelsen for hele programmet"
+#: ../../addon/diaspora/import_diaspora.php:21
+msgid "Incompatible data version - aborting"
+msgstr ""
-#: ../../view/theme/redbasic/php/config.php:210
-msgid "Examples: 1rem, 100%, 16px"
-msgstr "For eksempel: 1rem, 100%, 16px"
+#: ../../addon/diaspora/import_diaspora.php:27
+msgid "No username found in import file."
+msgstr "Ingen brukernavn ble funnet i importfilen."
-#: ../../view/theme/redbasic/php/config.php:211
-#, fuzzy
-#| msgid "Set radius of corners"
-msgid "Set radius of corners in rem"
-msgstr "Angi hjørneradius"
+#: ../../addon/diaspora/import_diaspora.php:170
+msgid "Import completed."
+msgstr "Import ferdig."
-#: ../../view/theme/redbasic/php/config.php:211
-#, fuzzy
-#| msgid "Leave empty for default width"
-msgid "Leave empty for default radius"
-msgstr "La feltet stå tomt for å bruke standard bredde"
+#: ../../addon/diaspora/p.php:48 ../../addon/diaspora/util.php:346
+#: ../../addon/diaspora/util.php:359 ../../Zotlabs/Lib/Enotify.php:62
+msgid "$projectname"
+msgstr "$projectname"
-#: ../../view/theme/redbasic/php/config.php:212
-msgid "Set maximum width of content region in rem"
-msgstr "Set maksbredde for hovedregionen i rem"
+#: ../../addon/diaspora/Receiver.php:1644
+#, php-format
+msgid "%1$s dislikes %2$s's %3$s"
+msgstr ""
-#: ../../view/theme/redbasic/php/config.php:212
-msgid "Leave empty for default width"
-msgstr "La feltet stå tomt for å bruke standard bredde"
+#: ../../addon/diaspora/Receiver.php:1705
+#: ../../Zotlabs/Module/Subthread.php:112 ../../Zotlabs/Module/Like.php:447
+msgid "status"
+msgstr "status"
-#: ../../view/theme/redbasic/php/config.php:213
-msgid "Set size of conversation author photo"
-msgstr "Angi størrelsen for samtalens forfatterbilde"
+#: ../../addon/diaspora/Receiver.php:2266 ../../Zotlabs/Module/Like.php:478
+#, php-format
+msgid "%1$s is attending %2$s's %3$s"
+msgstr "%1$s deltar på %2$ss %3$s"
-#: ../../view/theme/redbasic/php/config.php:213
-#: ../../view/theme/redbasic/php/config.php:214
+#: ../../addon/diaspora/Receiver.php:2268 ../../Zotlabs/Module/Like.php:480
+#, php-format
+msgid "%1$s is not attending %2$s's %3$s"
+msgstr "%1$s deltar ikke på %2$ss %3$s"
+
+#: ../../addon/diaspora/Receiver.php:2270 ../../Zotlabs/Module/Like.php:482
+#, php-format
+msgid "%1$s may attend %2$s's %3$s"
+msgstr "%1$s deltar kanskje på %2$ss %3$s"
+
+#: ../../addon/diaspora/Mod_Diaspora.php:43
+msgid "Diaspora Protocol Settings updated."
+msgstr ""
+
+#: ../../addon/diaspora/Mod_Diaspora.php:52
+msgid ""
+"The diaspora protocol does not support location independence. Connections "
+"you make within that network may be unreachable from alternate channel "
+"locations."
+msgstr ""
+
+#: ../../addon/diaspora/Mod_Diaspora.php:80
+msgid "Prevent your hashtags from being redirected to other sites"
+msgstr ""
+
+#: ../../addon/diaspora/Mod_Diaspora.php:84
+msgid "Sign and forward posts and comments with no existing Diaspora signature"
+msgstr ""
+
+#: ../../addon/diaspora/Mod_Diaspora.php:89
+msgid "Followed hashtags (comma separated, do not include the #)"
+msgstr ""
+
+#: ../../addon/diaspora/Mod_Diaspora.php:98
+msgid "Diaspora Protocol"
+msgstr ""
+
+#: ../../addon/fuzzloc/Mod_Fuzzloc.php:22
+msgid "Fuzzloc Settings updated."
+msgstr ""
+
+#: ../../addon/fuzzloc/Mod_Fuzzloc.php:38
+msgid "Minimum offset in meters"
+msgstr ""
+
+#: ../../addon/fuzzloc/Mod_Fuzzloc.php:42
+msgid "Maximum offset in meters"
+msgstr ""
+
+#: ../../addon/fuzzloc/Mod_Fuzzloc.php:51
+msgid "Fuzzy Location"
+msgstr ""
+
+#: ../../addon/qrator/qrator.php:48
+msgid "QR code"
+msgstr ""
+
+#: ../../addon/qrator/qrator.php:63
+msgid "QR Generator"
+msgstr ""
+
+#: ../../addon/qrator/qrator.php:64
+msgid "Enter some text"
+msgstr ""
+
+#: ../../addon/superblock/Mod_Superblock.php:117
+msgid "superblock settings updated"
+msgstr "innstillingene til superblokk ble oppdatert"
+
+#: ../../addon/superblock/Mod_Superblock.php:141
+msgid "Currently blocked"
+msgstr "Blokkert for øyeblikket"
+
+#: ../../addon/superblock/Mod_Superblock.php:143
+msgid "No channels currently blocked"
+msgstr "Ingen kanaler er blokkert i øyeblikket"
+
+#: ../../addon/superblock/Mod_Superblock.php:145
+#: ../../Zotlabs/Module/Cover_photo.php:382 ../../Zotlabs/Module/Tagrm.php:137
+#: ../../Zotlabs/Module/Photos.php:995
+msgid "Remove"
+msgstr "Fjern"
+
+#: ../../addon/superblock/superblock.php:378
+msgid "Block Completely"
+msgstr "Blokker helt"
+
+#: ../../addon/superblock/superblock.php:387
#, fuzzy
-#| msgid "Leave empty for default width"
-msgid "Leave empty for default size"
-msgstr "La feltet stå tomt for å bruke standard bredde"
+#| msgid "Block Title"
+msgid "Block from site"
+msgstr "Byggeklossens tittel"
-#: ../../view/theme/redbasic/php/config.php:214
-msgid "Set size of followup author photos"
-msgstr "Angi størrelsen på forfatterbilder ved oppfølging"
+#: ../../addon/randpost/randpost.php:101
+msgid "You're welcome."
+msgstr ""
-#: ../../view/theme/redbasic/php/config.php:215
-msgid "Show advanced settings"
-msgstr "Vis avanserte innstillinger"
+#: ../../addon/randpost/randpost.php:102
+msgid "Ah shucks..."
+msgstr ""
-#: ../../boot.php:1736
-msgid "Create an account to access services and applications"
+#: ../../addon/randpost/randpost.php:103
+msgid "Don't mention it."
msgstr ""
-#: ../../boot.php:1754
-msgid "Email or nickname"
-msgstr "Epost eller brukernavn"
+#: ../../addon/randpost/randpost.php:104
+msgid "&lt;blush&gt;"
+msgstr ""
-#: ../../boot.php:1754 ../../extend/addon/hzaddons/redred/Mod_Redred.php:73
-#: ../../extend/addon/hzaddons/openid/MysqlProvider.php:54
-msgid "Nickname"
-msgstr "Kallenavn"
+#: ../../addon/msgfooter/msgfooter.php:46 ../../addon/xmpp/xmpp.php:43
+#: ../../addon/gravatar/gravatar.php:150
+msgid "Save Settings"
+msgstr ""
-#: ../../boot.php:1764
-msgid "Password"
-msgstr "Passord"
+#: ../../addon/msgfooter/msgfooter.php:47
+msgid "text to include in all outgoing posts from this site"
+msgstr ""
-#: ../../boot.php:1765
-msgid "Remember me"
-msgstr "Husk meg"
+#: ../../addon/mailtest/mailtest.php:19
+msgid "Send test email"
+msgstr ""
-#: ../../boot.php:1768
-msgid "Forgot your password?"
-msgstr "Glemt passordet ditt?"
+#: ../../addon/mailtest/mailtest.php:66
+msgid "Mail sent."
+msgstr ""
-#: ../../boot.php:1769 ../../Zotlabs/Module/Lostpass.php:91
-msgid "Password Reset"
-msgstr "Tilbakestill passord"
+#: ../../addon/mailtest/mailtest.php:68
+msgid "Sending of mail failed."
+msgstr ""
-#: ../../boot.php:2647
-#, php-format
-msgid "[$Projectname] Website SSL error for %s"
+#: ../../addon/mailtest/mailtest.php:77
+msgid "Mail Test"
msgstr ""
-#: ../../boot.php:2652
-msgid "Website SSL certificate is not valid. Please correct."
-msgstr "Nettstedets SSL-sertifikat er ikke gyldig. Vennligst fiks dette."
+#: ../../addon/nsfw/Mod_Nsfw.php:22
+msgid "NSFW Settings saved."
+msgstr ""
-#: ../../boot.php:2768
+#: ../../addon/nsfw/Mod_Nsfw.php:42
+msgid ""
+"This app looks in posts for the words/text you specify below, and collapses "
+"any content containing those keywords so it is not displayed at "
+"inappropriate times, such as sexual innuendo that may be improper in a work "
+"setting. It is polite and recommended to tag any content containing nudity "
+"with #NSFW. This filter can also match any other word/text you specify, and "
+"can thereby be used as a general purpose content filter."
+msgstr ""
+
+#: ../../addon/nsfw/Mod_Nsfw.php:47
+msgid "Comma separated list of keywords to hide"
+msgstr ""
+
+#: ../../addon/nsfw/Mod_Nsfw.php:47
+msgid "Word, /regular-expression/, lang=xx, lang!=xx"
+msgstr ""
+
+#: ../../addon/nsfw/Mod_Nsfw.php:56
+msgid "NSFW"
+msgstr ""
+
+#: ../../addon/nsfw/nsfw.php:153
+msgid "Possible adult content"
+msgstr ""
+
+#: ../../addon/nsfw/nsfw.php:168
#, php-format
-msgid "[$Projectname] Cron tasks not running on %s"
+msgid "%s - view"
msgstr ""
-#: ../../boot.php:2773
-msgid "Cron/Scheduled tasks not running."
-msgstr "Cron/planlagte oppgaver kjører ikke."
+#: ../../addon/rendezvous/rendezvous.php:57
+msgid "Errors encountered deleting database table "
+msgstr ""
-#: ../../Zotlabs/Lib/Techlevels.php:10
-msgid "0. Beginner/Basic"
+#: ../../addon/rendezvous/rendezvous.php:95 ../../addon/twitter/twitter.php:502
+msgid "Submit Settings"
msgstr ""
-#: ../../Zotlabs/Lib/Techlevels.php:11
-msgid "1. Novice - not skilled but willing to learn"
+#: ../../addon/rendezvous/rendezvous.php:96
+msgid "Drop tables when uninstalling?"
msgstr ""
-#: ../../Zotlabs/Lib/Techlevels.php:12
-msgid "2. Intermediate - somewhat comfortable"
+#: ../../addon/rendezvous/rendezvous.php:96
+msgid ""
+"If checked, the Rendezvous database tables will be deleted when the plugin "
+"is uninstalled."
msgstr ""
-#: ../../Zotlabs/Lib/Techlevels.php:13
-msgid "3. Advanced - very comfortable"
+#: ../../addon/rendezvous/rendezvous.php:97
+msgid "Mapbox Access Token"
msgstr ""
-#: ../../Zotlabs/Lib/Techlevels.php:14
-msgid "4. Expert - I can write computer code"
+#: ../../addon/rendezvous/rendezvous.php:97
+msgid ""
+"If you enter a Mapbox access token, it will be used to retrieve map tiles "
+"from Mapbox instead of the default OpenStreetMap tile server."
msgstr ""
-#: ../../Zotlabs/Lib/Techlevels.php:15
-msgid "5. Wizard - I probably know more than you do"
+#: ../../addon/rendezvous/rendezvous.php:162
+msgid "Rendezvous"
msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:330
-msgid "Affinity Tool"
-msgstr "Nærhetsverktøy"
+#: ../../addon/rendezvous/rendezvous.php:167
+msgid ""
+"This identity has been deleted by another member due to inactivity. Please "
+"press the \"New identity\" button or refresh the page to register a new "
+"identity. You may use the same name."
+msgstr ""
+
+#: ../../addon/rendezvous/rendezvous.php:168
+msgid "Welcome to Rendezvous!"
+msgstr ""
+
+#: ../../addon/rendezvous/rendezvous.php:169
+msgid ""
+"Enter your name to join this rendezvous. To begin sharing your location with "
+"the other members, tap the GPS control. When your location is discovered, a "
+"red dot will appear and others will be able to see you on the map."
+msgstr ""
+
+#: ../../addon/rendezvous/rendezvous.php:171
+msgid "Let's meet here"
+msgstr ""
+
+#: ../../addon/rendezvous/rendezvous.php:172 ../../addon/wiki/Mod_Wiki.php:221
+#: ../../addon/wiki/Lib/NativeWikiPage.php:592
+#: ../../addon/wiki/Widget/Wiki_page_history.php:28
+#: ../../Zotlabs/Storage/Browser.php:385 ../../Zotlabs/Module/Oauth.php:112
+#: ../../Zotlabs/Module/Oauth.php:137 ../../Zotlabs/Module/Connedit.php:732
+#: ../../Zotlabs/Module/Admin/Channels.php:181
+#: ../../Zotlabs/Module/Sharedwithme.php:107 ../../Zotlabs/Module/Chat.php:256
+#: ../../Zotlabs/Module/Cdav.php:1368 ../../Zotlabs/Module/Oauth2.php:117
+#: ../../Zotlabs/Module/Oauth2.php:144
+msgid "Name"
+msgstr "Navn"
+
+#: ../../addon/rendezvous/rendezvous.php:173
+#: ../../addon/cart/submodules/hzservices.php:657
+#: ../../addon/cart/submodules/manualcat.php:260
+#: ../../Zotlabs/Module/Appman.php:220 ../../Zotlabs/Module/Rbmark.php:82
+#: ../../Zotlabs/Module/Cdav.php:1005
+msgid "Description"
+msgstr "Beskrivelse"
+
+#: ../../addon/rendezvous/rendezvous.php:174
+msgid "New marker"
+msgstr ""
+
+#: ../../addon/rendezvous/rendezvous.php:175
+msgid "Edit marker"
+msgstr ""
+
+#: ../../addon/rendezvous/rendezvous.php:176
+msgid "New identity"
+msgstr ""
+
+#: ../../addon/rendezvous/rendezvous.php:177
+msgid "Delete marker"
+msgstr ""
+
+#: ../../addon/rendezvous/rendezvous.php:178
+msgid "Delete member"
+msgstr ""
+
+#: ../../addon/rendezvous/rendezvous.php:179
+msgid "Edit proximity alert"
+msgstr ""
+
+#: ../../addon/rendezvous/rendezvous.php:180
+msgid ""
+"A proximity alert will be issued when this member is within a certain radius "
+"of you.<br><br>Enter a radius in meters (0 to disable):"
+msgstr ""
+
+#: ../../addon/rendezvous/rendezvous.php:180
+#: ../../addon/rendezvous/rendezvous.php:185
+msgid "distance"
+msgstr ""
+
+#: ../../addon/rendezvous/rendezvous.php:181
+msgid "Proximity alert distance (meters)"
+msgstr ""
+
+#: ../../addon/rendezvous/rendezvous.php:182
+#: ../../addon/rendezvous/rendezvous.php:184
+msgid ""
+"A proximity alert will be issued when you are within a certain radius of the "
+"marker location.<br><br>Enter a radius in meters (0 to disable):"
+msgstr ""
+
+#: ../../addon/rendezvous/rendezvous.php:183
+msgid "Marker proximity alert"
+msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:331
-#: ../../extend/addon/hzaddons/articles/articles.php:48
-#: ../../extend/addon/hzaddons/articles/articles.php:160
-#: ../../extend/addon/hzaddons/articles/Mod_Articles.php:228
+#: ../../addon/rendezvous/rendezvous.php:186
+msgid "Reminder note"
+msgstr ""
+
+#: ../../addon/rendezvous/rendezvous.php:187
+msgid ""
+"Enter a note to be displayed when you are within the specified proximity..."
+msgstr ""
+
+#: ../../addon/rendezvous/rendezvous.php:199
+msgid "Add new rendezvous"
+msgstr ""
+
+#: ../../addon/rendezvous/rendezvous.php:200
+msgid ""
+"Create a new rendezvous and share the access link with those you wish to "
+"invite to the group. Those who open the link become members of the "
+"rendezvous. They can view other member locations, add markers to the map, or "
+"share their own locations with the group."
+msgstr ""
+
+#: ../../addon/rendezvous/rendezvous.php:232
+msgid "You have no rendezvous. Press the button above to create a rendezvous!"
+msgstr ""
+
+#: ../../addon/rendezvous/rendezvous.php:401 ../../Zotlabs/Module/Setup.php:754
+msgid "Errors encountered creating database tables."
+msgstr "Feil oppstod under opprettelsen av databasetabeller."
+
+#: ../../addon/pumpio/pumpio.php:152
+msgid "You are now authenticated to pumpio."
+msgstr ""
+
+#: ../../addon/pumpio/pumpio.php:153
+msgid "return to the featured settings page"
+msgstr "tilbake til funksjonsinnstillinger"
+
+#: ../../addon/pumpio/pumpio.php:168
+msgid "Post to Pump.io"
+msgstr ""
+
+#: ../../addon/pumpio/Mod_Pumpio.php:40
+msgid "Pump.io Settings saved."
+msgstr ""
+
+#: ../../addon/pumpio/Mod_Pumpio.php:71
+msgid "Pump.io servername"
+msgstr ""
+
+#: ../../addon/pumpio/Mod_Pumpio.php:71
+msgid "Without \"http://\" or \"https://\""
+msgstr ""
+
+#: ../../addon/pumpio/Mod_Pumpio.php:75
+msgid "Pump.io username"
+msgstr ""
+
+#: ../../addon/pumpio/Mod_Pumpio.php:75
+msgid "Without the servername"
+msgstr ""
+
+#: ../../addon/pumpio/Mod_Pumpio.php:86
+msgid "You are not authenticated to pumpio"
+msgstr ""
+
+#: ../../addon/pumpio/Mod_Pumpio.php:88
+msgid "(Re-)Authenticate your pump.io connection"
+msgstr ""
+
+#: ../../addon/pumpio/Mod_Pumpio.php:92
+msgid "Post to pump.io by default"
+msgstr ""
+
+#: ../../addon/pumpio/Mod_Pumpio.php:96
+msgid "Should posts be public"
+msgstr ""
+
+#: ../../addon/pumpio/Mod_Pumpio.php:100
+msgid "Mirror all public posts"
+msgstr ""
+
+#: ../../addon/pumpio/Mod_Pumpio.php:110
+msgid "Pump.io Crosspost Connector"
+msgstr ""
+
+#: ../../addon/wppost/wppost.php:47
+msgid "Post to WordPress"
+msgstr ""
+
+#: ../../addon/wppost/Mod_Wppost.php:30
+msgid "Wordpress Settings saved."
+msgstr ""
+
+#: ../../addon/wppost/Mod_Wppost.php:67
+msgid "WordPress username"
+msgstr ""
+
+#: ../../addon/wppost/Mod_Wppost.php:71
+msgid "WordPress password"
+msgstr ""
+
+#: ../../addon/wppost/Mod_Wppost.php:75
+msgid "WordPress API URL"
+msgstr ""
+
+#: ../../addon/wppost/Mod_Wppost.php:76
+msgid "Typically https://your-blog.tld/xmlrpc.php"
+msgstr ""
+
+#: ../../addon/wppost/Mod_Wppost.php:79
+msgid "WordPress blogid"
+msgstr ""
+
+#: ../../addon/wppost/Mod_Wppost.php:80
+msgid "For multi-user sites such as wordpress.com, otherwise leave blank"
+msgstr ""
+
+#: ../../addon/wppost/Mod_Wppost.php:84
+msgid "Post to WordPress by default"
+msgstr ""
+
+#: ../../addon/wppost/Mod_Wppost.php:88
+msgid "Forward comments (requires hubzilla_wp plugin)"
+msgstr ""
+
+#: ../../addon/wppost/Mod_Wppost.php:104
+msgid "Wordpress Post"
+msgstr ""
+
+#: ../../addon/articles/Mod_Article_edit.php:123
+msgid "Edit Article"
+msgstr ""
+
+#: ../../addon/articles/articles.php:48 ../../addon/articles/articles.php:161
+#: ../../addon/articles/Mod_Articles.php:228 ../../Zotlabs/Lib/Apps.php:331
msgid "Articles"
msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:332
-#: ../../extend/addon/hzaddons/cards/cards.php:48
-#: ../../extend/addon/hzaddons/cards/cards.php:160
-#: ../../extend/addon/hzaddons/cards/Mod_Cards.php:209
-msgid "Cards"
+#: ../../addon/articles/articles.php:51
+msgid "View Articles"
msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:333
-msgid "Site Admin"
-msgstr "Nettstedsadministrator"
+#: ../../addon/articles/Mod_Articles.php:119
+msgid "Add Article"
+msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:334
-#: ../../extend/addon/hzaddons/buglink/buglink.php:16
-msgid "Report Bug"
+#: ../../addon/wiki/Mod_Wiki.php:36
+#: ../../addon/flashcards/Mod_Flashcards.php:58
+#: ../../addon/faces/Mod_Faces.php:64 ../../addon/cart/cart.php:1458
+msgid "Profile Unavailable."
+msgstr "Profilen er ikke tilgjengelig."
+
+#: ../../addon/wiki/Mod_Wiki.php:81 ../../addon/cart/manual_payments.php:93
+#: ../../addon/cart/submodules/paypalbuttonV2.php:486
+#: ../../addon/cart/submodules/paypalbutton.php:456
+#: ../../addon/cart/myshop.php:37 ../../addon/cart/cart.php:1608
+msgid "Invalid channel"
+msgstr "Ugyldig kanal"
+
+#: ../../addon/wiki/Mod_Wiki.php:136
+msgid "Error retrieving wiki"
+msgstr "Det oppstod en feil ved henting av wikien"
+
+#: ../../addon/wiki/Mod_Wiki.php:143
+msgid "Error creating zip file export folder"
+msgstr "Det oppstod en feil ved eksport av mappe til zip fil"
+
+#: ../../addon/wiki/Mod_Wiki.php:194
+msgid "Error downloading wiki: "
msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:337
-msgid "Content Filter"
+#: ../../addon/wiki/Mod_Wiki.php:209 ../../addon/wiki/Widget/Wiki_list.php:23
+#: ../../addon/wiki/wiki.php:45 ../../addon/wiki/wiki.php:99
+msgid "Wikis"
+msgstr "Wikier"
+
+#: ../../addon/wiki/Mod_Wiki.php:215 ../../Zotlabs/Storage/Browser.php:411
+msgid "Download"
msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:338
-#: ../../extend/addon/hzaddons/content_import/Mod_content_import.php:133
-msgid "Content Import"
+#: ../../addon/wiki/Mod_Wiki.php:216 ../../addon/wiki/Mod_Wiki.php:406
+#: ../../Zotlabs/Module/Webpages.php:255 ../../Zotlabs/Module/Layouts.php:196
+#: ../../Zotlabs/Module/Pubsites.php:63 ../../Zotlabs/Module/Blocks.php:164
+msgid "View"
+msgstr "Vis"
+
+#: ../../addon/wiki/Mod_Wiki.php:217 ../../Zotlabs/Module/Manage.php:137
+#: ../../Zotlabs/Module/Profiles.php:852
+msgid "Create New"
+msgstr "Lag ny profil"
+
+#: ../../addon/wiki/Mod_Wiki.php:219
+msgid "Wiki name"
msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:340
-msgid "Remote Diagnostics"
+#: ../../addon/wiki/Mod_Wiki.php:220
+msgid "Content type"
msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:341
-msgid "Suggest Channels"
-msgstr "Foreslå kanaler"
+#: ../../addon/wiki/Mod_Wiki.php:222 ../../Zotlabs/Storage/Browser.php:386
+msgid "Type"
+msgstr "Type"
-#: ../../Zotlabs/Lib/Apps.php:343
-msgid "Channel Manager"
-msgstr "Kanalstyring"
+#: ../../addon/wiki/Mod_Wiki.php:223
+msgid "Any&nbsp;type"
+msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:344
-msgid "Stream"
-msgstr "Tidslinje"
+#: ../../addon/wiki/Mod_Wiki.php:230
+msgid "Lock content type"
+msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:348
-msgid "Wiki"
+#: ../../addon/wiki/Mod_Wiki.php:231
+msgid "Create a status post for this wiki"
msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:355
-msgid "Mail"
+#: ../../addon/wiki/Mod_Wiki.php:232
+msgid "Edit Wiki Name"
+msgstr ""
+
+#: ../../addon/wiki/Mod_Wiki.php:277
+#, fuzzy
+msgid "Wiki not found"
+msgstr "Fant ikke wikien."
+
+#: ../../addon/wiki/Mod_Wiki.php:303
+msgid "Rename page"
+msgstr ""
+
+#: ../../addon/wiki/Mod_Wiki.php:304 ../../addon/hsse/hsse.php:186
+#: ../../Zotlabs/Lib/ThreadItem.php:294 ../../Zotlabs/Widget/Cdav.php:142
+#: ../../Zotlabs/Module/Webpages.php:250 ../../Zotlabs/Module/Layouts.php:192
+#: ../../Zotlabs/Module/Blocks.php:159 ../../Zotlabs/Module/Photos.php:1077
+msgid "Share"
+msgstr "Del"
+
+#: ../../addon/wiki/Mod_Wiki.php:318
+msgid "Error retrieving page content"
+msgstr ""
+
+#: ../../addon/wiki/Mod_Wiki.php:326 ../../addon/wiki/Mod_Wiki.php:328
+msgid "New page"
+msgstr ""
+
+#: ../../addon/wiki/Mod_Wiki.php:363
+msgid "Revision Comparison"
+msgstr ""
+
+#: ../../addon/wiki/Mod_Wiki.php:364
+#: ../../addon/wiki/Lib/NativeWikiPage.php:595
+#: ../../addon/wiki/Widget/Wiki_page_history.php:31
+msgid "Revert"
+msgstr "Tilbakestill"
+
+#: ../../addon/wiki/Mod_Wiki.php:371
+msgid "Short description of your changes (optional)"
+msgstr ""
+
+#: ../../addon/wiki/Mod_Wiki.php:391
+msgid "New page name"
+msgstr ""
+
+#: ../../addon/wiki/Mod_Wiki.php:396
+msgid "Embed image from photo albums"
+msgstr ""
+
+#: ../../addon/wiki/Mod_Wiki.php:402 ../../Zotlabs/Module/Cover_photo.php:390
+#: ../../Zotlabs/Module/Profile_photo.php:557
+msgid "Choose a different album"
+msgstr "Velg et annet album"
+
+#: ../../addon/wiki/Mod_Wiki.php:407
+msgid "History"
+msgstr ""
+
+#: ../../addon/wiki/Mod_Wiki.php:485
+msgid "Error creating wiki. Invalid name."
+msgstr ""
+
+#: ../../addon/wiki/Mod_Wiki.php:492
+msgid "A wiki with this name already exists."
+msgstr ""
+
+#: ../../addon/wiki/Mod_Wiki.php:505
+msgid "Wiki created, but error creating Home page."
+msgstr ""
+
+#: ../../addon/wiki/Mod_Wiki.php:512
+msgid "Error creating wiki"
+msgstr ""
+
+#: ../../addon/wiki/Mod_Wiki.php:536
+msgid "Error updating wiki. Invalid name."
+msgstr ""
+
+#: ../../addon/wiki/Mod_Wiki.php:555
+msgid "Error updating wiki"
+msgstr ""
+
+#: ../../addon/wiki/Mod_Wiki.php:570
+msgid "Wiki delete permission denied."
+msgstr ""
+
+#: ../../addon/wiki/Mod_Wiki.php:580
+msgid "Error deleting wiki"
+msgstr ""
+
+#: ../../addon/wiki/Mod_Wiki.php:613
+msgid "New page created"
+msgstr ""
+
+#: ../../addon/wiki/Mod_Wiki.php:739
+msgid "Cannot delete Home"
+msgstr ""
+
+#: ../../addon/wiki/Mod_Wiki.php:815
+msgid "Current Revision"
+msgstr ""
+
+#: ../../addon/wiki/Mod_Wiki.php:815
+msgid "Selected Revision"
+msgstr ""
+
+#: ../../addon/wiki/Mod_Wiki.php:870
+msgid "You must be authenticated."
+msgstr ""
+
+#: ../../addon/wiki/Mod_Wiki.php:899 ../../addon/wiki/Widget/Wiki_pages.php:64
+msgid "Add new page"
+msgstr "Legg til ny side"
+
+#: ../../addon/wiki/Mod_Wiki.php:906 ../../addon/wiki/Widget/Wiki_pages.php:71
+#: ../../Zotlabs/Module/Dreport.php:138
+msgid "Options"
+msgstr "Valg"
+
+#: ../../addon/wiki/Lib/NativeWiki.php:146
+msgid "Wiki updated successfully"
+msgstr "Wikien ble oppdatert"
+
+#: ../../addon/wiki/Lib/NativeWiki.php:206
+msgid "Wiki files deleted successfully"
+msgstr "Wikifiler ble slettet"
+
+#: ../../addon/wiki/Lib/NativeWikiPage.php:42
+#: ../../addon/wiki/Lib/NativeWikiPage.php:110
+msgid "(No Title)"
+msgstr "(Ingen tittel)"
+
+#: ../../addon/wiki/Lib/NativeWikiPage.php:124
+msgid "Wiki page create failed."
+msgstr "Kunne ikke opprette wikiside."
+
+#: ../../addon/wiki/Lib/NativeWikiPage.php:138
+msgid "Wiki not found."
+msgstr "Fant ikke wikien."
+
+#: ../../addon/wiki/Lib/NativeWikiPage.php:149
+msgid "Destination name already exists"
+msgstr "Navnet finnes allerede"
+
+#: ../../addon/wiki/Lib/NativeWikiPage.php:182
+#: ../../addon/wiki/Lib/NativeWikiPage.php:377
+msgid "Page not found"
+msgstr "Fant ikke siden"
+
+#: ../../addon/wiki/Lib/NativeWikiPage.php:212
+msgid "Error reading page content"
+msgstr "Det oppstod en feil under lesing av innholder på siden"
+
+#: ../../addon/wiki/Lib/NativeWikiPage.php:368
+#: ../../addon/wiki/Lib/NativeWikiPage.php:426
+#: ../../addon/wiki/Lib/NativeWikiPage.php:494
+#: ../../addon/wiki/Lib/NativeWikiPage.php:538
+msgid "Error reading wiki"
+msgstr "Det oppstod en feil ved lesing av wikien"
+
+#: ../../addon/wiki/Lib/NativeWikiPage.php:412
+msgid "Page update failed."
+msgstr "Oppdatering av siden mislyktes."
+
+#: ../../addon/wiki/Lib/NativeWikiPage.php:448
+msgid "Nothing deleted"
+msgstr "Ingenting ble slettet"
+
+#: ../../addon/wiki/Lib/NativeWikiPage.php:518
+msgid "Compare: object not found."
+msgstr "Sammanlign: fant ikke objektet."
+
+#: ../../addon/wiki/Lib/NativeWikiPage.php:525
+msgid "Page updated"
+msgstr "Siden ble oppdatert"
+
+#: ../../addon/wiki/Lib/NativeWikiPage.php:533
+msgid "Wiki resource_id required for git commit"
+msgstr "Wiki ressurs_id er nødvendig for å lagre i git"
+
+#: ../../addon/wiki/Lib/NativeWikiPage.php:593
+#: ../../addon/wiki/Widget/Wiki_page_history.php:29
+msgctxt "wiki_history"
+msgid "Message"
msgstr "Melding"
-#: ../../Zotlabs/Lib/Apps.php:356
-msgid "Chat"
-msgstr "Chat"
+#: ../../addon/wiki/Lib/NativeWikiPage.php:594
+#: ../../addon/wiki/Widget/Wiki_page_history.php:30
+msgid "Date"
+msgstr "Dato"
-#: ../../Zotlabs/Lib/Apps.php:358
-msgid "Probe"
-msgstr "Undersøk"
+#: ../../addon/wiki/Lib/NativeWikiPage.php:596
+#: ../../addon/wiki/Widget/Wiki_page_history.php:32
+msgid "Compare"
+msgstr "Sammenlign"
-#: ../../Zotlabs/Lib/Apps.php:359
-msgid "Suggest"
-msgstr "Forreslå"
+#: ../../addon/wiki/Widget/Wiki_pages.php:58
+msgid "Wiki Pages"
+msgstr "Wikisider"
-#: ../../Zotlabs/Lib/Apps.php:360
-msgid "Random Channel"
-msgstr "Tilfeldig kanal"
+#: ../../addon/wiki/Widget/Wiki_pages.php:69
+msgid "Page name"
+msgstr "Sidenavn"
-#: ../../Zotlabs/Lib/Apps.php:361
-msgid "Invite"
-msgstr "Inviter"
+#: ../../addon/donate/donate.php:21
+msgid "Project Servers and Resources"
+msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:362 ../../Zotlabs/Widget/Admin.php:31
-msgid "Features"
-msgstr "Funksjoner"
+#: ../../addon/donate/donate.php:22
+msgid "Project Creator and Tech Lead"
+msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:363
-#: ../../extend/addon/hzaddons/openid/MysqlProvider.php:69
-msgid "Language"
-msgstr "Språk"
+#: ../../addon/donate/donate.php:49
+msgid ""
+"And the hundreds of other people and organisations who helped make the "
+"Hubzilla possible."
+msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:364 ../../Zotlabs/Storage/Browser.php:411
-msgid "Post"
-msgstr "Innlegg"
+#: ../../addon/donate/donate.php:52
+msgid ""
+"The Redmatrix/Hubzilla projects are provided primarily by volunteers giving "
+"their time and expertise - and often paying out of pocket for services they "
+"share with others."
+msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:369
-msgid "Notifications"
-msgstr "Varsler"
+#: ../../addon/donate/donate.php:53
+msgid ""
+"There is no corporate funding and no ads, and we do not collect and sell "
+"your personal information. (We don't control your personal information - "
+"<strong>you do</strong>.)"
+msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:370
-msgid "Order Apps"
+#: ../../addon/donate/donate.php:54
+msgid ""
+"Help support our ground-breaking work in decentralisation, web identity, and "
+"privacy."
msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:371
-msgid "CardDAV"
+#: ../../addon/donate/donate.php:56
+msgid ""
+"Your donations keep servers and services running and also helps us to "
+"provide innovative new features and continued development."
msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:372 ../../Zotlabs/Module/Sources.php:105
-msgid "Channel Sources"
-msgstr "Kanalkilder"
+#: ../../addon/donate/donate.php:59
+msgid "Donate"
+msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:373 ../../Zotlabs/Module/Tokens.php:288
-msgid "Guest Access"
+#: ../../addon/donate/donate.php:61
+msgid ""
+"Choose a project, developer, or public hub to support with a one-time "
+"donation"
msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:374 ../../Zotlabs/Widget/Notes.php:38
-#: ../../extend/addon/hzaddons/workflow/workflow.php:2661
-msgid "Notes"
-msgstr "Merknader"
+#: ../../addon/donate/donate.php:62
+msgid "Donate Now"
+msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:375
-msgid "OAuth Apps Manager"
+#: ../../addon/donate/donate.php:63
+msgid ""
+"<strong><em>Or</em></strong> become a project sponsor (Hubzilla Project only)"
msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:376
-msgid "OAuth2 Apps Manager"
+#: ../../addon/donate/donate.php:64
+msgid ""
+"Please indicate if you would like your first name or full name (or nothing) "
+"to appear in our sponsor listing"
msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:377
-msgid "PDL Editor"
+#: ../../addon/donate/donate.php:65
+msgid "Sponsor"
msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:378 ../../Zotlabs/Module/Permcats.php:248
-msgid "Contact Roles"
+#: ../../addon/donate/donate.php:68
+msgid "Special thanks to: "
msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:379 ../../Zotlabs/Module/Pubstream.php:109
-#: ../../Zotlabs/Widget/Notifications.php:154
-msgid "Public Stream"
+#: ../../addon/xmpp/xmpp.php:44
+msgid "Jabber BOSH host"
msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:380
-msgid "My Chatrooms"
+#: ../../addon/xmpp/xmpp.php:45
+msgid "Use central userbase"
msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:381
-msgid "Channel Export"
+#: ../../addon/xmpp/xmpp.php:45
+msgid ""
+"If enabled, members will automatically login to an ejabberd server that has "
+"to be installed on this machine with synchronized credentials via the "
+"\"auth_ejabberd.php\" script."
msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:562 ../../Zotlabs/Module/Oauth.php:53
-#: ../../Zotlabs/Module/Oauth.php:135 ../../Zotlabs/Module/Oauth2.php:58
-#: ../../Zotlabs/Module/Oauth2.php:142 ../../Zotlabs/Module/Connedit.php:748
-#: ../../Zotlabs/Module/Admin/Addons.php:461 ../../Zotlabs/Module/Cdav.php:1044
-#: ../../Zotlabs/Module/Cdav.php:1384
-msgid "Update"
-msgstr "Oppdater"
+#: ../../addon/xmpp/Mod_Xmpp.php:23
+msgid "XMPP settings updated."
+msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:562 ../../Zotlabs/Module/Admin/Addons.php:430
-msgid "Install"
-msgstr "Installer"
+#: ../../addon/xmpp/Mod_Xmpp.php:35
+msgid "XMPP App"
+msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:592 ../../Zotlabs/Lib/Apps.php:614
-msgid "Purchase"
-msgstr "Kjøp"
+#: ../../addon/xmpp/Mod_Xmpp.php:36
+msgid "Embedded XMPP (Jabber) client"
+msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:619
-msgid "Undelete"
+#: ../../addon/xmpp/Mod_Xmpp.php:52
+msgid "Individual credentials"
msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:627
-msgid "Add to app-tray"
-msgstr "Legg til i meny"
+#: ../../addon/xmpp/Mod_Xmpp.php:58
+msgid "Jabber BOSH server"
+msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:628
-msgid "Remove from app-tray"
-msgstr "Fjern fra meny"
+#: ../../addon/xmpp/Mod_Xmpp.php:67
+msgid "XMPP Settings"
+msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:629
-msgid "Pin to navbar"
-msgstr "Fest til navigasjonslinjen"
+#: ../../addon/notifyadmin/notifyadmin.php:34
+msgid "New registration"
+msgstr ""
-#: ../../Zotlabs/Lib/Apps.php:630
-msgid "Unpin from navbar"
-msgstr "Fjern fra navigasjonslinjen"
+#: ../../addon/notifyadmin/notifyadmin.php:40
+#: ../../Zotlabs/Module/Invite.php:266
+#, php-format
+msgid "%s : Message delivery failed."
+msgstr "%s : meldingslevering feilet."
-#: ../../Zotlabs/Lib/Libzotdir.php:164
-msgid "Directory Options"
-msgstr "Kataloginnstillinger"
+#: ../../addon/notifyadmin/notifyadmin.php:42
+#, php-format
+msgid "Message sent to %s. New account registration: %s"
+msgstr ""
-#: ../../Zotlabs/Lib/Libzotdir.php:166
-msgid "Safe Mode"
-msgstr "Trygg modus"
+#: ../../addon/statusnet/statusnet.php:145
+msgid "Post to GNU social"
+msgstr ""
-#: ../../Zotlabs/Lib/Libzotdir.php:167
-msgid "Public Forums Only"
-msgstr "Bare offentlige forum"
+#: ../../addon/statusnet/statusnet.php:593
+#: ../../Zotlabs/Module/Admin/Site.php:411
+msgid "Site name"
+msgstr "Nettstedets navn"
-#: ../../Zotlabs/Lib/Libzotdir.php:169
-msgid "This Website Only"
-msgstr "Kun dette nettstedet"
+#: ../../addon/statusnet/statusnet.php:594
+msgid "API URL"
+msgstr ""
-#: ../../Zotlabs/Lib/AccessList.php:26
+#: ../../addon/statusnet/statusnet.php:595 ../../addon/twitter/twitter.php:505
+#: ../../Zotlabs/Module/Oauth.php:114 ../../Zotlabs/Module/Oauth.php:139
+#: ../../Zotlabs/Module/Oauth2.php:118 ../../Zotlabs/Module/Oauth2.php:145
+msgid "Consumer Secret"
+msgstr "Consumer Secret"
+
+#: ../../addon/statusnet/statusnet.php:596 ../../addon/twitter/twitter.php:504
+#: ../../Zotlabs/Module/Oauth.php:113 ../../Zotlabs/Module/Oauth.php:138
+msgid "Consumer Key"
+msgstr "Consumer Key"
+
+#: ../../addon/statusnet/statusnet.php:597
+msgid "Application name"
+msgstr ""
+
+#: ../../addon/statusnet/Mod_Statusnet.php:61
msgid ""
-"A deleted privacy group with this name was revived. Existing item "
-"permissions <strong>may</strong> apply to this privacy group and any future "
-"members. If this is not what you intended, please create another privacy "
-"group with a different name."
+"Please contact your site administrator.<br />The provided API URL is not "
+"valid."
msgstr ""
+"Den oppgitte URLen for APIet er ikke gyldig. Kontakt serverens administrator."
-#: ../../Zotlabs/Lib/AccessList.php:268
-msgid "Select a privacy group"
+#: ../../addon/statusnet/Mod_Statusnet.php:98
+msgid "We could not contact the GNU social API with the Path you entered."
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:110
-msgid "Restricted message"
-msgstr "Begrenset melding"
+#: ../../addon/statusnet/Mod_Statusnet.php:130
+msgid "GNU social settings updated."
+msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:117
-msgid "Direct message"
-msgstr "Direktemelding"
+#: ../../addon/statusnet/Mod_Statusnet.php:179
+msgid "Globally Available GNU social OAuthKeys"
+msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:122
-msgid "Public Policy"
+#: ../../addon/statusnet/Mod_Statusnet.php:181
+msgid ""
+"There are preconfigured OAuth key pairs for some GNU social servers "
+"available. If you are using one of them, please use these credentials.<br /"
+">If not feel free to connect to any other GNU social instance (see below)."
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:156
-msgid "Privacy conflict. Discretion advised."
+#: ../../addon/statusnet/Mod_Statusnet.php:196
+msgid "Provide your own OAuth Credentials"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:187 ../../Zotlabs/Storage/Browser.php:373
-msgid "Admin Delete"
+#: ../../addon/statusnet/Mod_Statusnet.php:198
+msgid ""
+"No consumer key pair for GNU social found. Register your Hubzilla Account as "
+"an desktop client on your GNU social account, copy the consumer key pair "
+"here and enter the API base root.<br />Before you register your own OAuth "
+"key pair ask the administrator if there is already a key pair for this "
+"Hubzilla installation at your favourite GNU social installation."
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:190 ../../Zotlabs/Module/Filer.php:55
-msgid "Save to Folder"
-msgstr "Lagre i mappe"
+#: ../../addon/statusnet/Mod_Statusnet.php:202
+msgid "OAuth Consumer Key"
+msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:217 ../../Zotlabs/Widget/Pinned.php:77
-msgid "I will attend"
-msgstr "Jeg vil delta"
+#: ../../addon/statusnet/Mod_Statusnet.php:206
+msgid "OAuth Consumer Secret"
+msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:217 ../../Zotlabs/Widget/Pinned.php:77
-msgid "I will not attend"
-msgstr "Jeg deltar ikke"
+#: ../../addon/statusnet/Mod_Statusnet.php:210
+msgid "Base API Path"
+msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:217 ../../Zotlabs/Widget/Pinned.php:77
-msgid "I might attend"
-msgstr "Jeg vil kanskje delta"
+#: ../../addon/statusnet/Mod_Statusnet.php:210
+msgid "Remember the trailing /"
+msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:335 ../../Zotlabs/Module/Photos.php:1074
-msgid "I like this (toggle)"
-msgstr "Jeg liker dette (skru av og på)"
+#: ../../addon/statusnet/Mod_Statusnet.php:214
+msgid "GNU social application name"
+msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:336 ../../Zotlabs/Module/Photos.php:1075
-msgid "I don't like this (toggle)"
-msgstr "Jeg liker ikke dette (skru av og på)"
+#: ../../addon/statusnet/Mod_Statusnet.php:237
+msgid ""
+"To connect to your GNU social account click the button below to get a "
+"security code from GNU social which you have to copy into the input box "
+"below and submit the form. Only your <strong>public</strong> posts will be "
+"posted to GNU social."
+msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:337
-#, fuzzy
-#| msgid "Delete this menu"
-msgid "Reply to this comment"
-msgstr "Slett denne menyen"
+#: ../../addon/statusnet/Mod_Statusnet.php:239
+msgid "Log in with GNU social"
+msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:337
-msgid "reply"
+#: ../../addon/statusnet/Mod_Statusnet.php:242
+msgid "Copy the security code from GNU social here"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:337
-msgid "Reply to"
+#: ../../addon/statusnet/Mod_Statusnet.php:252
+msgid "Cancel Connection Process"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:344 ../../Zotlabs/Widget/Pinned.php:95
-msgid "share"
-msgstr "del"
+#: ../../addon/statusnet/Mod_Statusnet.php:254
+msgid "Current GNU social API is"
+msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:348
-msgid "Repeat"
+#: ../../addon/statusnet/Mod_Statusnet.php:258
+msgid "Cancel GNU social Connection"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:348
-msgid "repeat"
+#: ../../addon/statusnet/Mod_Statusnet.php:270
+#: ../../addon/twitter/Mod_Twitter.php:145
+msgid "Currently connected to: "
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:359
-msgid "Delivery Report"
-msgstr "Leveringsrapport"
+#: ../../addon/statusnet/Mod_Statusnet.php:275
+msgid ""
+"<strong>Note</strong>: Due your privacy settings (<em>Hide your profile "
+"details from unknown viewers?</em>) the link potentially included in public "
+"postings relayed to GNU social will lead the visitor to a blank page "
+"informing the visitor that the access to your profile has been restricted."
+msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:380
-#, fuzzy, php-format
-#| msgid "%d comment"
-#| msgid_plural "%d comments"
-msgid "%d comment"
-msgid_plural "%d comments"
-msgstr[0] "%d kommentar"
-msgstr[1] "%d kommentarer"
+#: ../../addon/statusnet/Mod_Statusnet.php:280
+msgid "Post to GNU social by default"
+msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:381
-#, php-format
-msgid "%d unseen"
-msgstr "%d uleste"
+#: ../../addon/statusnet/Mod_Statusnet.php:280
+msgid ""
+"If enabled your public postings will be posted to the associated GNU-social "
+"account by default"
+msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:431
-msgid "Forum"
+#: ../../addon/statusnet/Mod_Statusnet.php:289
+#: ../../addon/twitter/Mod_Twitter.php:169
+msgid "Clear OAuth configuration"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:438
-msgid "to"
-msgstr "til"
+#: ../../addon/statusnet/Mod_Statusnet.php:301
+msgid "GNU-Social Crosspost Connector"
+msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:439 ../../Zotlabs/Widget/Pinned.php:123
-#: ../../Zotlabs/Widget/Messages.php:146 ../../Zotlabs/Widget/Messages.php:149
-msgid "via"
-msgstr "via"
+#: ../../addon/flashcards/Mod_Flashcards.php:161
+#: ../../Zotlabs/Module/Contactedit.php:431
+#: ../../Zotlabs/Module/Connedit.php:572
+msgid "Affinity"
+msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:440
-msgid "Wall-to-Wall"
-msgstr "Vegg-til-vegg"
+#: ../../addon/ljpost/ljpost.php:49
+msgid "Post to Livejournal"
+msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:441
-msgid "via Wall-To-Wall:"
-msgstr "via vegg-til-vegg:"
+#: ../../addon/ljpost/ljpost.php:127
+msgid "Posted by"
+msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:469
-msgid "Attend"
+#: ../../addon/ljpost/Mod_Ljpost.php:53
+msgid "Livejournal username"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:470 ../../Zotlabs/Widget/Pinned.php:137
-msgid "Attendance Options"
+#: ../../addon/ljpost/Mod_Ljpost.php:57
+msgid "Livejournal password"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:472 ../../Zotlabs/Widget/Pinned.php:138
-msgid "Voting Options"
+#: ../../addon/ljpost/Mod_Ljpost.php:61
+msgid "Post to Livejournal by default"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:487
-msgid "Go to previous comment"
+#: ../../addon/ljpost/Mod_Ljpost.php:65
+msgid "Send wall-to-wall posts to Livejournal"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:496 ../../Zotlabs/Widget/Pinned.php:150
-msgid "Pinned post"
+#: ../../addon/ljpost/Mod_Ljpost.php:77
+msgid "Livejournal Crosspost Connector"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:499
-#: ../../extend/addon/hzaddons/bookmarker/bookmarker.php:38
-msgid "Save Bookmarks"
-msgstr "Lagre bokmerker"
+#: ../../addon/cart/manual_payments.php:7
+msgid "Error: order mismatch. Please try again."
+msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:500
-msgid "Add to Calendar"
-msgstr "Legg til i kalender"
+#: ../../addon/cart/manual_payments.php:61
+msgid "Manual payments are not enabled."
+msgstr ""
+
+#: ../../addon/cart/manual_payments.php:68
+#: ../../addon/cart/submodules/paypalbuttonV2.php:417
+#: ../../addon/cart/submodules/paypalbutton.php:392
+#: ../../addon/cart/cart.php:1630
+msgid "Order not found."
+msgstr ""
+
+#: ../../addon/cart/manual_payments.php:77
+msgid "Finished"
+msgstr ""
+
+#: ../../addon/cart/submodules/hzservices.php:65
+msgid "Enable Hubzilla Services Module"
+msgstr ""
+
+#: ../../addon/cart/submodules/hzservices.php:164
+#: ../../addon/cart/submodules/manualcat.php:172
+msgid "New Sku"
+msgstr ""
+
+#: ../../addon/cart/submodules/hzservices.php:199
+#: ../../addon/cart/submodules/manualcat.php:208
+msgid "Cannot save edits to locked item."
+msgstr ""
+
+#: ../../addon/cart/submodules/hzservices.php:248
+#: ../../addon/cart/submodules/hzservices.php:335
+msgid "SKU not found."
+msgstr ""
+
+#: ../../addon/cart/submodules/hzservices.php:301
+#: ../../addon/cart/submodules/hzservices.php:305
+msgid "Invalid Activation Directive."
+msgstr ""
+
+#: ../../addon/cart/submodules/hzservices.php:376
+#: ../../addon/cart/submodules/hzservices.php:380
+msgid "Invalid Deactivation Directive."
+msgstr ""
+
+#: ../../addon/cart/submodules/hzservices.php:566
+msgid "Add to this privacy group"
+msgstr ""
+
+#: ../../addon/cart/submodules/hzservices.php:582
+msgid "Set user service class"
+msgstr ""
+
+#: ../../addon/cart/submodules/hzservices.php:609
+msgid "You must be using a local account to purchase this service."
+msgstr ""
+
+#: ../../addon/cart/submodules/hzservices.php:649
+#: ../../addon/cart/submodules/manualcat.php:252
+msgid "Changes Locked"
+msgstr ""
+
+#: ../../addon/cart/submodules/hzservices.php:653
+#: ../../addon/cart/submodules/manualcat.php:256
+msgid "Item available for purchase."
+msgstr ""
+
+#: ../../addon/cart/submodules/hzservices.php:660
+#: ../../addon/cart/submodules/manualcat.php:263
+#: ../../addon/cart/widgets/catalogitem.php:57
+msgid "Price"
+msgstr ""
+
+#: ../../addon/cart/submodules/hzservices.php:663
+#: ../../addon/cart/submodules/manualcat.php:266
+msgid "Photo URL"
+msgstr ""
+
+#: ../../addon/cart/submodules/hzservices.php:667
+msgid "Add buyer to privacy group"
+msgstr ""
+
+#: ../../addon/cart/submodules/hzservices.php:672
+msgid "Add buyer as connection"
+msgstr ""
+
+#: ../../addon/cart/submodules/hzservices.php:680
+#: ../../addon/cart/submodules/hzservices.php:722
+msgid "Set Service Class"
+msgstr ""
+
+#: ../../addon/cart/submodules/subscriptions.php:151
+msgid "Enable Subscription Management Module"
+msgstr ""
+
+#: ../../addon/cart/submodules/subscriptions.php:223
+msgid ""
+"Cannot include subscription items with different terms in the same order."
+msgstr ""
+
+#: ../../addon/cart/submodules/subscriptions.php:372
+msgid "Select Subscription to Edit"
+msgstr ""
+
+#: ../../addon/cart/submodules/subscriptions.php:380
+msgid "Edit Subscriptions"
+msgstr ""
+
+#: ../../addon/cart/submodules/subscriptions.php:414
+msgid "Subscription SKU"
+msgstr ""
+
+#: ../../addon/cart/submodules/subscriptions.php:419
+msgid "Catalog Description"
+msgstr ""
+
+#: ../../addon/cart/submodules/subscriptions.php:423
+msgid "Subscription available for purchase."
+msgstr ""
+
+#: ../../addon/cart/submodules/subscriptions.php:428
+msgid "Maximum active subscriptions to this item per account."
+msgstr ""
+
+#: ../../addon/cart/submodules/subscriptions.php:431
+msgid "Subscription price."
+msgstr ""
+
+#: ../../addon/cart/submodules/subscriptions.php:435
+msgid "Quantity"
+msgstr ""
+
+#: ../../addon/cart/submodules/subscriptions.php:439
+msgid "Term"
+msgstr ""
+
+#: ../../addon/cart/submodules/paypalbuttonV2.php:86
+msgid "Enable Paypal Button Module (API-v2)"
+msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:508
+#: ../../addon/cart/submodules/paypalbuttonV2.php:96
+#: ../../addon/cart/submodules/paypalbutton.php:93
+msgid "Use Production Key"
+msgstr ""
+
+#: ../../addon/cart/submodules/paypalbuttonV2.php:103
+#: ../../addon/cart/submodules/paypalbutton.php:100
+msgid "Paypal Sandbox Client Key"
+msgstr ""
+
+#: ../../addon/cart/submodules/paypalbuttonV2.php:110
+#: ../../addon/cart/submodules/paypalbutton.php:107
+msgid "Paypal Sandbox Secret Key"
+msgstr ""
+
+#: ../../addon/cart/submodules/paypalbuttonV2.php:116
+#: ../../addon/cart/submodules/paypalbutton.php:113
+msgid "Paypal Production Client Key"
+msgstr ""
+
+#: ../../addon/cart/submodules/paypalbuttonV2.php:123
+#: ../../addon/cart/submodules/paypalbutton.php:120
+msgid "Paypal Production Secret Key"
+msgstr ""
+
+#: ../../addon/cart/submodules/paypalbuttonV2.php:271
+#: ../../addon/cart/submodules/paypalbutton.php:252
+msgid "Paypal button payments are not enabled."
+msgstr ""
+
+#: ../../addon/cart/submodules/paypalbuttonV2.php:289
+#: ../../addon/cart/submodules/paypalbutton.php:270
+msgid ""
+"Paypal button payments are not properly configured. Please choose another "
+"payment option."
+msgstr ""
+
+#: ../../addon/cart/submodules/paypalbutton.php:85
+msgid "Enable Paypal Button Module"
+msgstr ""
+
+#: ../../addon/cart/submodules/manualcat.php:61
+msgid "Enable Manual Cart Module"
+msgstr ""
+
+#: ../../addon/cart/submodules/orderoptions.php:70
+msgid "Enable Order/Item Options"
+msgstr ""
+
+#: ../../addon/cart/submodules/orderoptions.php:333
+#: ../../addon/cart/submodules/orderoptions.php:357
+#: ../../addon/cart/submodules/orderoptions.php:433
+#: ../../addon/cart/submodules/orderoptions.php:457
+msgid "Label"
+msgstr ""
+
+#: ../../addon/cart/submodules/orderoptions.php:336
+#: ../../addon/cart/submodules/orderoptions.php:360
+#: ../../addon/cart/submodules/orderoptions.php:436
+#: ../../addon/cart/submodules/orderoptions.php:460
+msgid "Instructions"
+msgstr ""
+
+#: ../../addon/cart/myshop.php:30
#, fuzzy
-#| msgid "Mark all events seen"
-msgid "Mark all comments seen"
-msgstr "Merk alle hendelser som sett"
+msgid "Access Denied."
+msgstr "Ingen tilgang"
+
+#: ../../addon/cart/myshop.php:113 ../../addon/cart/cart.php:1494
+msgid "Order Not Found"
+msgstr ""
+
+#: ../../addon/cart/myshop.php:145 ../../addon/cart/myshop.php:181
+#: ../../addon/cart/myshop.php:215 ../../addon/cart/myshop.php:265
+#: ../../addon/cart/myshop.php:300 ../../addon/cart/myshop.php:323
+msgid "Access Denied"
+msgstr "Ingen tilgang"
-#: ../../Zotlabs/Lib/ThreadItem.php:547
+#: ../../addon/cart/myshop.php:190 ../../addon/cart/myshop.php:224
+#: ../../addon/cart/myshop.php:275 ../../addon/cart/myshop.php:333
#, fuzzy
-#| msgid "Add Photos"
-msgid "Add yours"
-msgstr "Legg til bilder"
+msgid "Invalid Item"
+msgstr "Ugyldig element."
+
+#: ../../addon/cart/cart.php:259
+msgid "DB Cleanup Failure"
+msgstr ""
+
+#: ../../addon/cart/cart.php:692
+msgid "[cart] Item Added"
+msgstr ""
+
+#: ../../addon/cart/cart.php:1107
+msgid "Order already checked out."
+msgstr ""
+
+#: ../../addon/cart/cart.php:1416
+msgid "Drop database tables when uninstalling."
+msgstr ""
+
+#: ../../addon/cart/cart.php:1423 ../../addon/cart/Settings/Cart.php:129
+msgid "Cart Settings"
+msgstr ""
+
+#: ../../addon/cart/cart.php:1435 ../../addon/cart/cart.php:1438
+msgid "Shop"
+msgstr ""
+
+#: ../../addon/cart/cart.php:1597
+msgid "You must be logged into the Grid to shop."
+msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:547
+#: ../../addon/cart/cart.php:1646
#, fuzzy
-#| msgid "Remove term"
-msgid "Remove yours"
-msgstr "Fjern begrep"
+msgid "Access denied."
+msgstr "Ingen tilgang"
-#: ../../Zotlabs/Lib/ThreadItem.php:865 ../../Zotlabs/Module/Photos.php:1092
-#: ../../Zotlabs/Module/Photos.php:1205
-msgid "This is you"
-msgstr "Dette er deg"
+#: ../../addon/cart/cart.php:1698 ../../addon/cart/cart.php:1841
+msgid "No Order Found"
+msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:874
-msgid "Image"
-msgstr "Bilde"
+#: ../../addon/cart/cart.php:1707
+msgid "An unknown error has occurred Please start again."
+msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:876
-msgid "Insert Link"
-msgstr "Sett inn lenke"
+#: ../../addon/cart/cart.php:1850
+msgid "Requirements not met."
+msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:877
-msgid "Video"
-msgstr "Video"
+#: ../../addon/cart/cart.php:1850
+msgid "Review your order and complete any needed requirements."
+msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:886
-msgid "Your full name (required)"
+#: ../../addon/cart/cart.php:1876
+msgid "Invalid Payment Type. Please start again."
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:887
-msgid "Your email address (required)"
+#: ../../addon/cart/cart.php:1883
+msgid "Order not found"
msgstr ""
-#: ../../Zotlabs/Lib/ThreadItem.php:888
-msgid "Your website URL (optional)"
+#: ../../addon/cart/Settings/Cart.php:58
+msgid "Enable Test Catalog"
msgstr ""
-#: ../../Zotlabs/Lib/Activity.php:2173
-#, php-format
-msgid "Likes %1$s's %2$s"
+#: ../../addon/cart/Settings/Cart.php:70
+msgid "Enable Manual Payments"
msgstr ""
-#: ../../Zotlabs/Lib/Activity.php:2176
-#, php-format
-msgid "Doesn't like %1$s's %2$s"
+#: ../../addon/cart/Settings/Cart.php:90
+msgid "Base Merchant Currency"
msgstr ""
-#: ../../Zotlabs/Lib/Activity.php:2182
-#, php-format
-msgid "Will attend %s's event"
+#: ../../addon/twitter/twitter.php:109
+msgid "Post to Twitter"
msgstr ""
-#: ../../Zotlabs/Lib/Activity.php:2185
-#, php-format
-msgid "Will not attend %s's event"
+#: ../../addon/twitter/Mod_Twitter.php:65
+msgid "Twitter settings updated."
msgstr ""
-#: ../../Zotlabs/Lib/Activity.php:2188
-#, php-format
-msgid "May attend %s's event"
+#: ../../addon/twitter/Mod_Twitter.php:101
+msgid ""
+"No consumer key pair for Twitter found. Please contact your site "
+"administrator."
msgstr ""
-#: ../../Zotlabs/Lib/Activity.php:2191
-#, php-format
-msgid "May not attend %s's event"
+#: ../../addon/twitter/Mod_Twitter.php:123
+msgid ""
+"At this Hubzilla instance the Twitter plugin was enabled but you have not "
+"yet connected your account to your Twitter account. To do so click the "
+"button below to get a PIN from Twitter which you have to copy into the input "
+"box below and submit the form. Only your <strong>public</strong> posts will "
+"be posted to Twitter."
msgstr ""
-#: ../../Zotlabs/Lib/Chatroom.php:25
-msgid "Missing room name"
-msgstr "Mangler romnavn"
+#: ../../addon/twitter/Mod_Twitter.php:125
+msgid "Log in with Twitter"
+msgstr ""
-#: ../../Zotlabs/Lib/Chatroom.php:34
-msgid "Duplicate room name"
-msgstr "Duplikat romnavn"
+#: ../../addon/twitter/Mod_Twitter.php:128
+msgid "Copy the PIN from Twitter here"
+msgstr ""
-#: ../../Zotlabs/Lib/Chatroom.php:84 ../../Zotlabs/Lib/Chatroom.php:92
-msgid "Invalid room specifier."
-msgstr "Ugyldig rom-spesifisering."
+#: ../../addon/twitter/Mod_Twitter.php:150
+msgid ""
+"<strong>Note:</strong> Due your privacy settings (<em>Hide your profile "
+"details from unknown viewers?</em>) the link potentially included in public "
+"postings relayed to Twitter will lead the visitor to a blank page informing "
+"the visitor that the access to your profile has been restricted."
+msgstr ""
-#: ../../Zotlabs/Lib/Chatroom.php:124
-msgid "Room not found."
-msgstr "Rommet ble ikke funnet."
+#: ../../addon/twitter/Mod_Twitter.php:155
+msgid "Twitter post length"
+msgstr ""
-#: ../../Zotlabs/Lib/Chatroom.php:145
-msgid "Room is full"
-msgstr "Rommet er fullt"
+#: ../../addon/twitter/Mod_Twitter.php:155
+msgid "Maximum tweet length"
+msgstr ""
+
+#: ../../addon/twitter/Mod_Twitter.php:160
+msgid "Send public postings to Twitter by default"
+msgstr ""
+
+#: ../../addon/twitter/Mod_Twitter.php:160
+msgid ""
+"If enabled your public postings will be posted to the associated Twitter "
+"account by default"
+msgstr ""
+
+#: ../../addon/twitter/Mod_Twitter.php:179
+msgid "Twitter Crosspost Connector"
+msgstr ""
+
+#: ../../addon/gravatar/gravatar.php:123
+msgid "generic profile image"
+msgstr ""
+
+#: ../../addon/gravatar/gravatar.php:124
+msgid "random geometric pattern"
+msgstr ""
+
+#: ../../addon/gravatar/gravatar.php:125
+msgid "monster face"
+msgstr ""
+
+#: ../../addon/gravatar/gravatar.php:126
+msgid "computer generated face"
+msgstr ""
+
+#: ../../addon/gravatar/gravatar.php:127
+msgid "retro arcade style face"
+msgstr ""
+
+#: ../../addon/gravatar/gravatar.php:128
+msgid "Hub default profile photo"
+msgstr ""
+
+#: ../../addon/gravatar/gravatar.php:143
+msgid "Information"
+msgstr ""
+
+#: ../../addon/gravatar/gravatar.php:143
+msgid ""
+"Libravatar addon is installed, too. Please disable Libravatar addon or this "
+"Gravatar addon.<br>The Libravatar addon will fall back to Gravatar if "
+"nothing was found at Libravatar."
+msgstr ""
+
+#: ../../addon/gravatar/gravatar.php:151
+msgid "Default avatar image"
+msgstr ""
+
+#: ../../addon/gravatar/gravatar.php:151
+msgid "Select default avatar image if none was found at Gravatar. See README"
+msgstr ""
+
+#: ../../addon/gravatar/gravatar.php:152
+msgid "Rating of images"
+msgstr ""
+
+#: ../../addon/gravatar/gravatar.php:152
+msgid "Select the appropriate avatar rating for your site. See README"
+msgstr ""
+
+#: ../../addon/gravatar/gravatar.php:165
+msgid "Gravatar settings updated."
+msgstr ""
+
+#: ../../addon/hsse/Mod_Hsse.php:15
+msgid "WYSIWYG status editor"
+msgstr ""
+
+#: ../../addon/hsse/Mod_Hsse.php:24
+msgid "WYSIWYG Status App"
+msgstr ""
+
+#: ../../addon/hsse/Mod_Hsse.php:32
+msgid "WYSIWYG Status"
+msgstr ""
+
+#: ../../addon/redfiles/redfiles.php:119
+msgid "Redmatrix File Storage Import"
+msgstr ""
+
+#: ../../addon/redfiles/redfiles.php:120
+msgid "This will import all your Redmatrix cloud files to this channel."
+msgstr ""
+
+#: ../../addon/openstreetmap/openstreetmap.php:125
+msgid "View Larger"
+msgstr ""
+
+#: ../../addon/openstreetmap/openstreetmap.php:148
+msgid "Tile Server URL"
+msgstr ""
+
+#: ../../addon/openstreetmap/openstreetmap.php:148
+msgid ""
+"A list of <a href=\"http://wiki.openstreetmap.org/wiki/TMS\" "
+"target=\"_blank\">public tile servers</a>"
+msgstr ""
+
+#: ../../addon/openstreetmap/openstreetmap.php:149
+msgid "Nominatim (reverse geocoding) Server URL"
+msgstr ""
+
+#: ../../addon/openstreetmap/openstreetmap.php:149
+msgid ""
+"A list of <a href=\"http://wiki.openstreetmap.org/wiki/Nominatim\" "
+"target=\"_blank\">Nominatim servers</a>"
+msgstr ""
+
+#: ../../addon/openstreetmap/openstreetmap.php:150
+msgid "Default zoom"
+msgstr ""
+
+#: ../../addon/openstreetmap/openstreetmap.php:150
+msgid ""
+"The default zoom level. (1:world, 18:highest, also depends on tile server)"
+msgstr ""
+
+#: ../../addon/openstreetmap/openstreetmap.php:151
+msgid "Include marker on map"
+msgstr ""
+
+#: ../../addon/openstreetmap/openstreetmap.php:151
+msgid "Include a marker on the map."
+msgstr ""
+
+#: ../../addon/workflow/workflow.php:224
+msgid "Workflow user."
+msgstr ""
+
+#: ../../addon/workflow/workflow.php:275
+msgid "This channel"
+msgstr ""
+
+#: ../../addon/workflow/workflow.php:288 ../../Zotlabs/Module/Locs.php:123
+msgid "Primary"
+msgstr "Primær"
+
+#: ../../addon/workflow/workflow.php:330
+msgid "Create New Workflow Item"
+msgstr ""
+
+#: ../../addon/workflow/workflow.php:564 ../../addon/workflow/workflow.php:1466
+#: ../../addon/workflow/workflow.php:1485
+msgid "Workflow"
+msgstr ""
+
+#: ../../addon/workflow/workflow.php:1454
+msgid "No Workflows Available"
+msgstr ""
+
+#: ../../addon/workflow/workflow.php:1484
+msgid "Add item to which workflow"
+msgstr ""
+
+#: ../../addon/workflow/workflow.php:1544
+#: ../../addon/workflow/workflow.php:1663
+msgid "Create Workflow Item"
+msgstr ""
+
+#: ../../addon/workflow/workflow.php:2661
+msgid "Link"
+msgstr ""
+
+#: ../../addon/workflow/workflow.php:2663
+msgid "Web link."
+msgstr ""
+
+#: ../../addon/workflow/workflow.php:2682
+#: ../../addon/workflow/workflow.php:2751 ../../Zotlabs/Module/Connedit.php:734
+#: ../../Zotlabs/Module/Cdav.php:1370
+msgid "Title"
+msgstr "Tittel"
+
+#: ../../addon/workflow/workflow.php:2684
+#: ../../addon/workflow/workflow.php:2753
+msgid "Brief description or title"
+msgstr ""
+
+#: ../../addon/workflow/workflow.php:2690 ../../Zotlabs/Lib/Apps.php:374
+#: ../../Zotlabs/Widget/Notes.php:41
+msgid "Notes"
+msgstr "Merknader"
+
+#: ../../addon/workflow/workflow.php:2692
+#: ../../addon/workflow/workflow.php:2761
+msgid "Notes and Info"
+msgstr ""
+
+#: ../../addon/workflow/workflow.php:2697
+#: ../../Zotlabs/Module/Admin/Queue.php:47
+msgid "Priority"
+msgstr "Prioritet"
+
+#: ../../addon/workflow/workflow.php:2699
+msgid "Used to order links"
+msgstr ""
+
+#: ../../addon/workflow/workflow.php:2759
+msgid "Body"
+msgstr ""
+
+#: ../../addon/workflow/Settings/Mod_WorkflowSettings.php:101
+msgid "Workflow Settings"
+msgstr ""
+
+#: ../../addon/workflow/Settings/WorkflowSettingsUtil.php:145
+#, fuzzy
+#| msgid "Editor Settings"
+msgid "Workflow settings"
+msgstr "Instillinger for redigering"
+
+#: ../../addon/skeleton/Mod_Skeleton.php:38
+msgid "Some setting"
+msgstr ""
+
+#: ../../addon/skeleton/Mod_Skeleton.php:38
+msgid "A setting"
+msgstr ""
+
+#: ../../addon/skeleton/Mod_Skeleton.php:46
+msgid "Skeleton Settings"
+msgstr ""
+
+#: ../../addon/authchoose/Mod_Authchoose.php:30
+msgid ""
+"Allow magic authentication only to websites of your immediate connections"
+msgstr ""
+
+#: ../../addon/authchoose/Mod_Authchoose.php:36
+msgid "Authchoose"
+msgstr ""
+
+#: ../../addon/content_import/Mod_content_import.php:27
+msgid "No server specified"
+msgstr ""
+
+#: ../../addon/content_import/Mod_content_import.php:72
+msgid "Posts imported"
+msgstr ""
+
+#: ../../addon/content_import/Mod_content_import.php:112
+msgid "Files imported"
+msgstr ""
+
+#: ../../addon/content_import/Mod_content_import.php:133
+#: ../../Zotlabs/Lib/Apps.php:338
+msgid "Content Import"
+msgstr ""
+
+#: ../../addon/content_import/Mod_content_import.php:134
+msgid ""
+"This will import all your conversations and cloud files from a cloned "
+"channel on another server. This may take a while if you have lots of posts "
+"and or files."
+msgstr ""
+
+#: ../../addon/content_import/Mod_content_import.php:135
+msgid "Include posts"
+msgstr ""
+
+#: ../../addon/content_import/Mod_content_import.php:135
+msgid "Conversations, Articles, Cards, and other posted content"
+msgstr ""
+
+#: ../../addon/content_import/Mod_content_import.php:136
+msgid "Include files"
+msgstr ""
+
+#: ../../addon/content_import/Mod_content_import.php:136
+msgid "Files, Photos and other cloud storage"
+msgstr ""
+
+#: ../../addon/content_import/Mod_content_import.php:137
+msgid "Original Server base URL"
+msgstr ""
+
+#: ../../addon/mdpost/mdpost.php:42
+msgid "Use markdown for editing posts"
+msgstr ""
+
+#: ../../addon/rtof/Mod_Rtof.php:24
+msgid "Friendica Crosspost Connector Settings saved."
+msgstr ""
+
+#: ../../addon/rtof/Mod_Rtof.php:47
+msgid "Send public postings to Friendica by default"
+msgstr ""
+
+#: ../../addon/rtof/Mod_Rtof.php:51
+msgid "Friendica API Path"
+msgstr ""
+
+#: ../../addon/rtof/Mod_Rtof.php:55
+msgid "Friendica login name"
+msgstr ""
+
+#: ../../addon/rtof/Mod_Rtof.php:59
+msgid "Friendica password"
+msgstr ""
+
+#: ../../addon/rtof/Mod_Rtof.php:67
+msgid "Friendica Crosspost Connector"
+msgstr ""
+
+#: ../../addon/rtof/rtof.php:51
+msgid "Post to Friendica"
+msgstr ""
+
+#: ../../addon/ldapauth/ldapauth.php:101
+msgid "An account has been created for you."
+msgstr ""
+
+#: ../../addon/ldapauth/ldapauth.php:108
+msgid "Authentication successful but rejected: account creation is disabled."
+msgstr ""
+
+#: ../../addon/nofed/nofed.php:47
+msgid "Federate"
+msgstr ""
+
+#: ../../addon/nofed/Mod_Nofed.php:21
+msgid "nofed Settings saved."
+msgstr ""
+
+#: ../../addon/nofed/Mod_Nofed.php:40
+msgid "Federate posts by default"
+msgstr ""
+
+#: ../../addon/nofed/Mod_Nofed.php:48
+msgid "No Federation"
+msgstr ""
+
+#: ../../addon/photocache/Mod_Photocache.php:27
+msgid "Photo Cache settings saved."
+msgstr ""
+
+#: ../../addon/photocache/Mod_Photocache.php:43
+msgid ""
+"Saves a copy of images from external sites locally to increase your "
+"anonymity in the web."
+msgstr ""
+
+#: ../../addon/photocache/Mod_Photocache.php:49
+msgid "Minimal photo size for caching"
+msgstr ""
+
+#: ../../addon/photocache/Mod_Photocache.php:51
+msgid "In pixels. From 1 up to 1024, 0 will be replaced with system default."
+msgstr ""
+
+#: ../../addon/photocache/Mod_Photocache.php:60
+msgid "Photo Cache"
+msgstr ""
+
+#: ../../addon/gallery/gallery.php:43 ../../addon/gallery/Mod_Gallery.php:135
+msgid "Gallery"
+msgstr ""
+
+#: ../../addon/gallery/gallery.php:46
+msgid "Photo Gallery"
+msgstr ""
+
+#: ../../addon/likebanner/likebanner.php:51
+msgid "Your Webbie:"
+msgstr ""
+
+#: ../../addon/likebanner/likebanner.php:54
+msgid "Fontsize (px):"
+msgstr ""
+
+#: ../../addon/likebanner/likebanner.php:68
+msgid "Link:"
+msgstr ""
+
+#: ../../addon/likebanner/likebanner.php:70
+msgid "Like us on Hubzilla"
+msgstr ""
+
+#: ../../addon/likebanner/likebanner.php:72
+msgid "Embed:"
+msgstr ""
+
+#: ../../addon/tictac/tictac.php:21
+msgid "Three Dimensional Tic-Tac-Toe"
+msgstr ""
+
+#: ../../addon/tictac/tictac.php:54
+msgid "3D Tic-Tac-Toe"
+msgstr ""
+
+#: ../../addon/tictac/tictac.php:59
+msgid "New game"
+msgstr ""
+
+#: ../../addon/tictac/tictac.php:60
+msgid "New game with handicap"
+msgstr ""
+
+#: ../../addon/tictac/tictac.php:61
+msgid ""
+"Three dimensional tic-tac-toe is just like the traditional game except that "
+"it is played on multiple levels simultaneously. "
+msgstr ""
+
+#: ../../addon/tictac/tictac.php:62
+msgid ""
+"In this case there are three levels. You win by getting three in a row on "
+"any level, as well as up, down, and diagonally across the different levels."
+msgstr ""
+
+#: ../../addon/tictac/tictac.php:64
+msgid ""
+"The handicap game disables the center position on the middle level because "
+"the player claiming this square often has an unfair advantage."
+msgstr ""
+
+#: ../../addon/tictac/tictac.php:183
+msgid "You go first..."
+msgstr ""
+
+#: ../../addon/tictac/tictac.php:188
+msgid "I'm going first this time..."
+msgstr ""
+
+#: ../../addon/tictac/tictac.php:194
+msgid "You won!"
+msgstr ""
+
+#: ../../addon/tictac/tictac.php:200 ../../addon/tictac/tictac.php:225
+msgid "\"Cat\" game!"
+msgstr ""
+
+#: ../../addon/tictac/tictac.php:223
+msgid "I won!"
+msgstr ""
+
+#: ../../boot.php:1737
+msgid "Create an account to access services and applications"
+msgstr ""
+
+#: ../../boot.php:1755
+msgid "Email or nickname"
+msgstr "Epost eller brukernavn"
+
+#: ../../boot.php:1765
+msgid "Password"
+msgstr "Passord"
+
+#: ../../boot.php:1766
+msgid "Remember me"
+msgstr "Husk meg"
+
+#: ../../boot.php:1769
+msgid "Forgot your password?"
+msgstr "Glemt passordet ditt?"
+
+#: ../../boot.php:1770 ../../Zotlabs/Module/Lostpass.php:91
+msgid "Password Reset"
+msgstr "Tilbakestill passord"
+
+#: ../../boot.php:2551
+#, php-format
+msgid "[$Projectname] Website SSL error for %s"
+msgstr ""
+
+#: ../../boot.php:2556
+msgid "Website SSL certificate is not valid. Please correct."
+msgstr "Nettstedets SSL-sertifikat er ikke gyldig. Vennligst fiks dette."
+
+#: ../../boot.php:2674
+#, php-format
+msgid "[$Projectname] Cron tasks not running on %s"
+msgstr ""
+
+#: ../../boot.php:2679
+msgid "Cron/Scheduled tasks not running."
+msgstr "Cron/planlagte oppgaver kjører ikke."
#: ../../Zotlabs/Lib/Enotify.php:61
msgid "$Projectname Notification"
msgstr "$Projectname varsling"
-#: ../../Zotlabs/Lib/Enotify.php:62
-#: ../../extend/addon/hzaddons/diaspora/p.php:48
-#: ../../extend/addon/hzaddons/diaspora/util.php:346
-#: ../../extend/addon/hzaddons/diaspora/util.php:359
-msgid "$projectname"
-msgstr "$projectname"
-
#: ../../Zotlabs/Lib/Enotify.php:64
msgid "Thank You,"
msgstr "Tusen takk,"
-#: ../../Zotlabs/Lib/Enotify.php:66
-#: ../../extend/addon/hzaddons/hubwall/hubwall.php:33
-#, php-format
-msgid "%s Administrator"
-msgstr "%s administrator"
-
#: ../../Zotlabs/Lib/Enotify.php:67
#, php-format
msgid "This email was sent by %1$s at %2$s."
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:67 ../../Zotlabs/Module/Invite.php:239
-#: ../../Zotlabs/Module/Invite.php:508 ../../Zotlabs/Module/Invite.php:522
-#: ../../Zotlabs/Module/Home.php:88 ../../Zotlabs/Module/Home.php:96
-#: ../../extend/addon/hzaddons/opensearch/opensearch.php:42
-msgid "$Projectname"
-msgstr "$Projectname"
-
#: ../../Zotlabs/Lib/Enotify.php:68
#, php-format
msgid ""
@@ -4595,8 +6737,9 @@ msgid "[$Projectname:Notify] New direct message received at %s"
msgstr "[$Projectname:Notify] Ny direktemelding mottatt kl. %s"
#: ../../Zotlabs/Lib/Enotify.php:134
-#, php-format
-msgid "%1$s sent you a new direct message at %2$s"
+#, fuzzy, php-format
+#| msgid "%1$s sent you a new direct message at %2$s"
+msgid "%1$s sent you a new private message at %2$s"
msgstr "%1$s sendte deg en ny direktemelding kl. %2$s"
#: ../../Zotlabs/Lib/Enotify.php:135
@@ -4609,315 +6752,416 @@ msgid "a direct message"
msgstr "en direktemelding"
#: ../../Zotlabs/Lib/Enotify.php:136
-#, php-format
-msgid "Please visit %s to view and/or reply to your direct messages."
+#, fuzzy, php-format
+#| msgid "Please visit %s to view and/or reply to your direct messages."
+msgid "Please visit %s to view and/or reply to your private messages."
msgstr "Gå til %s for å vise og/eller svare på dine direktemeldinger."
#: ../../Zotlabs/Lib/Enotify.php:149
-msgid "requested to comment on"
-msgstr ""
+#, fuzzy
+#| msgid "requested to like"
+msgid "requested to post in"
+msgstr "vil like"
#: ../../Zotlabs/Lib/Enotify.php:149
-msgid "commented on"
-msgstr ""
-
-#: ../../Zotlabs/Lib/Enotify.php:162 ../../Zotlabs/Lib/Enotify.php:318
#, fuzzy
-#| msgid "Request date"
+#| msgid "posted"
+msgid "posted in"
+msgstr "lagt inn"
+
+#: ../../Zotlabs/Lib/Enotify.php:162 ../../Zotlabs/Lib/Enotify.php:325
msgid "requested to like"
-msgstr "Dato for forespørsel"
+msgstr "vil like"
-#: ../../Zotlabs/Lib/Enotify.php:162 ../../Zotlabs/Lib/Enotify.php:318
+#: ../../Zotlabs/Lib/Enotify.php:162 ../../Zotlabs/Lib/Enotify.php:325
msgid "liked"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:165 ../../Zotlabs/Lib/Enotify.php:321
+#: ../../Zotlabs/Lib/Enotify.php:165 ../../Zotlabs/Lib/Enotify.php:328
msgid "requested to dislike"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:165 ../../Zotlabs/Lib/Enotify.php:321
+#: ../../Zotlabs/Lib/Enotify.php:165 ../../Zotlabs/Lib/Enotify.php:328
msgid "disliked"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:168
+#: ../../Zotlabs/Lib/Enotify.php:168 ../../Zotlabs/Lib/Enotify.php:331
#, fuzzy
-#| msgid "Created"
+#| msgid "requested to like"
+msgid "requested to repeat"
+msgstr "vil like"
+
+#: ../../Zotlabs/Lib/Enotify.php:168 ../../Zotlabs/Lib/Enotify.php:331
msgid "repeated"
-msgstr "Laget"
+msgstr "videresendte"
#: ../../Zotlabs/Lib/Enotify.php:173
msgid "voted on"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:216
+#: ../../Zotlabs/Lib/Enotify.php:217
#, php-format
msgid "%1$s %2$s [zrl=%3$s]a %4$s[/zrl]"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:224
+#: ../../Zotlabs/Lib/Enotify.php:227
#, php-format
msgid "%1$s %2$s [zrl=%3$s]%4$s's %5$s[/zrl]"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:233 ../../Zotlabs/Lib/Enotify.php:325
+#: ../../Zotlabs/Lib/Enotify.php:239 ../../Zotlabs/Lib/Enotify.php:335
#, php-format
msgid "%1$s %2$s [zrl=%3$s]your %4$s[/zrl]"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:245
+#: ../../Zotlabs/Lib/Enotify.php:253
#, php-format
msgid "[$Projectname:Notify] Moderated Comment to conversation #%1$d by %2$s"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:247
+#: ../../Zotlabs/Lib/Enotify.php:255
#, php-format
msgid "[$Projectname:Notify] Comment to conversation #%1$d by %2$s"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:248
+#: ../../Zotlabs/Lib/Enotify.php:256
#, php-format
msgid "%1$s commented on an item/conversation you have been following"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:251 ../../Zotlabs/Lib/Enotify.php:345
-#: ../../Zotlabs/Lib/Enotify.php:361 ../../Zotlabs/Lib/Enotify.php:385
-#: ../../Zotlabs/Lib/Enotify.php:402 ../../Zotlabs/Lib/Enotify.php:415
+#: ../../Zotlabs/Lib/Enotify.php:259 ../../Zotlabs/Lib/Enotify.php:356
+#: ../../Zotlabs/Lib/Enotify.php:372 ../../Zotlabs/Lib/Enotify.php:396
+#: ../../Zotlabs/Lib/Enotify.php:413 ../../Zotlabs/Lib/Enotify.php:427
#, php-format
msgid "Please visit %s to view and/or reply to the conversation."
msgstr "Vennligst besøk %s for å se og/eller svare i samtalen."
-#: ../../Zotlabs/Lib/Enotify.php:255 ../../Zotlabs/Lib/Enotify.php:256
+#: ../../Zotlabs/Lib/Enotify.php:263 ../../Zotlabs/Lib/Enotify.php:264
#, php-format
msgid "Please visit %s to approve or reject this comment."
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:341
+#: ../../Zotlabs/Lib/Enotify.php:352
#, php-format
msgid "[$Projectname:Notify] Like received to conversation #%1$d by %2$s"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:342
+#: ../../Zotlabs/Lib/Enotify.php:353
#, php-format
msgid "%1$s liked an item/conversation you created"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:353
+#: ../../Zotlabs/Lib/Enotify.php:364
#, php-format
msgid "[$Projectname:Notify] %s posted to your profile wall"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:355
+#: ../../Zotlabs/Lib/Enotify.php:366
#, php-format
msgid "%1$s posted to your profile wall at %2$s"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:357
+#: ../../Zotlabs/Lib/Enotify.php:368
#, php-format
msgid "%1$s posted to [zrl=%2$s]your wall[/zrl]"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:379
+#: ../../Zotlabs/Lib/Enotify.php:390
#, php-format
msgid "[$Projectname:Notify] %s tagged you"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:380
+#: ../../Zotlabs/Lib/Enotify.php:391
#, php-format
msgid "%1$s tagged you at %2$s"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:381
+#: ../../Zotlabs/Lib/Enotify.php:392
#, php-format
msgid "%1$s [zrl=%2$s]tagged you[/zrl]."
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:392
+#: ../../Zotlabs/Lib/Enotify.php:403
#, php-format
msgid "[$Projectname:Notify] %1$s poked you"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:393
+#: ../../Zotlabs/Lib/Enotify.php:404
#, php-format
msgid "%1$s poked you at %2$s"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:394
+#: ../../Zotlabs/Lib/Enotify.php:405
#, php-format
msgid "%1$s [zrl=%2$s]poked you[/zrl]."
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:409
+#: ../../Zotlabs/Lib/Enotify.php:421
#, php-format
msgid "[$Projectname:Notify] %s tagged your post"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:410
+#: ../../Zotlabs/Lib/Enotify.php:422
#, php-format
msgid "%1$s tagged your post at %2$s"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:411
+#: ../../Zotlabs/Lib/Enotify.php:423
#, php-format
msgid "%1$s tagged [zrl=%2$s]your post[/zrl]"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:422
+#: ../../Zotlabs/Lib/Enotify.php:433
msgid "[$Projectname:Notify] Introduction received"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:423
+#: ../../Zotlabs/Lib/Enotify.php:434
#, php-format
msgid "You've received an new connection request from '%1$s' at %2$s"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:424
+#: ../../Zotlabs/Lib/Enotify.php:435
#, php-format
msgid "You've received [zrl=%1$s]a new connection request[/zrl] from %2$s."
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:427 ../../Zotlabs/Lib/Enotify.php:445
+#: ../../Zotlabs/Lib/Enotify.php:438 ../../Zotlabs/Lib/Enotify.php:457
#, php-format
msgid "You may visit their profile at %s"
msgstr "Du kan besøke profilen deres på %s"
-#: ../../Zotlabs/Lib/Enotify.php:429
+#: ../../Zotlabs/Lib/Enotify.php:440
#, php-format
msgid "Please visit %s to approve or reject the connection request."
msgstr ""
"Vennligst besøk %s for å godkjenne eller avslå forespørselen om forbindelse."
-#: ../../Zotlabs/Lib/Enotify.php:436
+#: ../../Zotlabs/Lib/Enotify.php:448
msgid "[$Projectname:Notify] Friend suggestion received"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:437
+#: ../../Zotlabs/Lib/Enotify.php:449
#, php-format
msgid "You've received a friend suggestion from '%1$s' at %2$s"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:438
+#: ../../Zotlabs/Lib/Enotify.php:450
#, php-format
msgid "You've received [zrl=%1$s]a friend suggestion[/zrl] for %2$s from %3$s."
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:443
+#: ../../Zotlabs/Lib/Enotify.php:455
msgid "Name:"
msgstr "Navn:"
-#: ../../Zotlabs/Lib/Enotify.php:444
+#: ../../Zotlabs/Lib/Enotify.php:456
msgid "Photo:"
msgstr "Bilde:"
-#: ../../Zotlabs/Lib/Enotify.php:447
+#: ../../Zotlabs/Lib/Enotify.php:459
#, php-format
msgid "Please visit %s to approve or reject the suggestion."
msgstr "Vennligst besøk %s for å godkjenne eller avslå dette forslaget."
-#: ../../Zotlabs/Lib/Enotify.php:677
+#: ../../Zotlabs/Lib/Enotify.php:694
msgid "[$Projectname:Notify]"
msgstr ""
-#: ../../Zotlabs/Lib/Enotify.php:843
-msgid "created a new poll"
+#: ../../Zotlabs/Lib/Enotify.php:860
+msgid "started a poll"
msgstr "opprettet spørreskjema"
-#: ../../Zotlabs/Lib/Enotify.php:843
-msgid "created a new post"
-msgstr "laget et nytt innlegg"
+#: ../../Zotlabs/Lib/Enotify.php:860
+msgid "started a conversation"
+msgstr "started en samtale"
-#: ../../Zotlabs/Lib/Enotify.php:844
+#: ../../Zotlabs/Lib/Enotify.php:861
#, php-format
msgid "voted on %s's poll"
msgstr "stemte på %s sitt spørreskjema"
-#: ../../Zotlabs/Lib/Enotify.php:844
+#: ../../Zotlabs/Lib/Enotify.php:861
#, php-format
-msgid "commented on %s's post"
-msgstr "kommenterte på %s sitt innlegg"
+msgid "posted in %s's conversation"
+msgstr "postet i %s sin samtale"
-#: ../../Zotlabs/Lib/Enotify.php:848 ../../Zotlabs/Lib/Enotify.php:948
+#: ../../Zotlabs/Lib/Enotify.php:865 ../../Zotlabs/Lib/Enotify.php:957
msgid "shared a file with you"
-msgstr ""
+msgstr "delte en fil med deg"
-#: ../../Zotlabs/Lib/Enotify.php:857
+#: ../../Zotlabs/Lib/Enotify.php:873
#, php-format
-msgid "edited a post dated %s"
-msgstr ""
+msgid "edited a message dated %s"
+msgstr "redigerte en melding datert %s"
-#: ../../Zotlabs/Lib/Enotify.php:860
-#, php-format
-msgid "edited a comment dated %s"
-msgstr ""
-
-#: ../../Zotlabs/Lib/Enotify.php:933
+#: ../../Zotlabs/Lib/Enotify.php:942
msgid "added your channel"
msgstr "la til din kanal"
-#: ../../Zotlabs/Lib/Enotify.php:963
+#: ../../Zotlabs/Lib/Enotify.php:972
msgid "sent you a direct message"
msgstr "sendte deg en direktemelding"
-#: ../../Zotlabs/Lib/Enotify.php:970
+#: ../../Zotlabs/Lib/Enotify.php:979
msgid "g A l F d"
msgstr "g A l F d"
-#: ../../Zotlabs/Lib/Enotify.php:973
+#: ../../Zotlabs/Lib/Enotify.php:982
msgid "[today]"
msgstr "[idag]"
-#: ../../Zotlabs/Lib/Enotify.php:983
+#: ../../Zotlabs/Lib/Enotify.php:992
msgid "created an event"
-msgstr ""
+msgstr "opprettet et arrangement"
-#: ../../Zotlabs/Lib/Enotify.php:998
+#: ../../Zotlabs/Lib/Enotify.php:1007
msgid "status verified"
-msgstr ""
+msgstr "status verifisert"
-#: ../../Zotlabs/Lib/DB_Upgrade.php:68
-msgid "Source code of failed update: "
+#: ../../Zotlabs/Lib/Apps.php:330
+msgid "Affinity Tool"
+msgstr "Nærhetsverktøy"
+
+#: ../../Zotlabs/Lib/Apps.php:333
+msgid "Site Admin"
+msgstr "Nettstedsadministrator"
+
+#: ../../Zotlabs/Lib/Apps.php:337
+msgid "Content Filter"
+msgstr "Innholdsfilter"
+
+#: ../../Zotlabs/Lib/Apps.php:340
+msgid "Remote Diagnostics"
+msgstr "Fjerndiagnostikk"
+
+#: ../../Zotlabs/Lib/Apps.php:341
+msgid "Suggest Channels"
+msgstr "Foreslå kanaler"
+
+#: ../../Zotlabs/Lib/Apps.php:343
+msgid "Channel Manager"
+msgstr "Kanalstyring"
+
+#: ../../Zotlabs/Lib/Apps.php:348
+msgid "Wiki"
msgstr ""
-#: ../../Zotlabs/Lib/DB_Upgrade.php:89
-#, php-format
-msgid "Update Error at %s"
-msgstr "Oppdateringsfeil ved %s"
+#: ../../Zotlabs/Lib/Apps.php:355
+msgid "Mail"
+msgstr "Melding"
-#: ../../Zotlabs/Lib/DB_Upgrade.php:95
-#, php-format
-msgid "Update %s failed. See error logs."
-msgstr "Oppdatering %s mislyktes. Se feilloggen."
+#: ../../Zotlabs/Lib/Apps.php:356
+msgid "Chat"
+msgstr "Chat"
-#: ../../Zotlabs/Lib/Connect.php:45 ../../Zotlabs/Lib/Connect.php:146
-msgid "Channel is blocked on this site."
-msgstr "Kanalen er blokkert på dette nettstedet."
+#: ../../Zotlabs/Lib/Apps.php:358
+msgid "Probe"
+msgstr "Undersøk"
-#: ../../Zotlabs/Lib/Connect.php:50
-msgid "Channel location missing."
-msgstr "Kanalplassering mangler."
+#: ../../Zotlabs/Lib/Apps.php:359
+msgid "Suggest"
+msgstr "Forreslå"
-#: ../../Zotlabs/Lib/Connect.php:104
-msgid "Remote channel or protocol unavailable."
+#: ../../Zotlabs/Lib/Apps.php:360
+msgid "Random Channel"
+msgstr "Tilfeldig kanal"
+
+#: ../../Zotlabs/Lib/Apps.php:361
+msgid "Invite"
+msgstr "Inviter"
+
+#: ../../Zotlabs/Lib/Apps.php:362 ../../Zotlabs/Widget/Admin.php:31
+msgid "Features"
+msgstr "Funksjoner"
+
+#: ../../Zotlabs/Lib/Apps.php:364 ../../Zotlabs/Storage/Browser.php:414
+msgid "Post"
+msgstr "Innlegg"
+
+#: ../../Zotlabs/Lib/Apps.php:369 ../../Zotlabs/Widget/Notifications.php:116
+#: ../../Zotlabs/Widget/Messages.php:51
+msgid "Notifications"
+msgstr "Varsler"
+
+#: ../../Zotlabs/Lib/Apps.php:370
+msgid "Order Apps"
+msgstr "Ordne apper"
+
+#: ../../Zotlabs/Lib/Apps.php:371
+msgid "CardDAV"
msgstr ""
-#: ../../Zotlabs/Lib/Connect.php:140
-msgid "Channel discovery failed."
-msgstr "Kanaloppdagelse mislyktes."
+#: ../../Zotlabs/Lib/Apps.php:372 ../../Zotlabs/Module/Sources.php:105
+msgid "Channel Sources"
+msgstr "Kanalkilder"
-#: ../../Zotlabs/Lib/Connect.php:158
-msgid "Protocol disabled."
-msgstr "Protokollen er avskrudd."
+#: ../../Zotlabs/Lib/Apps.php:373 ../../Zotlabs/Module/Tokens.php:288
+msgid "Guest Access"
+msgstr "Gjestetilgang"
-#: ../../Zotlabs/Lib/Connect.php:170
-msgid "Cannot connect to yourself."
-msgstr "Kan ikke lage forbindelse med deg selv."
+#: ../../Zotlabs/Lib/Apps.php:375
+msgid "OAuth Apps Manager"
+msgstr ""
-#: ../../Zotlabs/Lib/Connect.php:275
-msgid "error saving data"
+#: ../../Zotlabs/Lib/Apps.php:376
+msgid "OAuth2 Apps Manager"
msgstr ""
-#: ../../Zotlabs/Lib/Libzot.php:688
-msgid "Unable to verify channel signature"
-msgstr "Ikke i stand til å sjekke kanalsignaturen"
+#: ../../Zotlabs/Lib/Apps.php:377
+msgid "PDL Editor"
+msgstr "PDL Redigering"
+
+#: ../../Zotlabs/Lib/Apps.php:378 ../../Zotlabs/Module/Permcats.php:248
+msgid "Contact Roles"
+msgstr "Kontaktroller"
+
+#: ../../Zotlabs/Lib/Apps.php:379 ../../Zotlabs/Widget/Notifications.php:154
+#: ../../Zotlabs/Module/Pubstream.php:109
+msgid "Public Stream"
+msgstr "Offentlig strøm"
+
+#: ../../Zotlabs/Lib/Apps.php:380
+msgid "My Chatrooms"
+msgstr "Mine chattekanaler"
+
+#: ../../Zotlabs/Lib/Apps.php:381
+msgid "Channel Export"
+msgstr "Kanaleksport"
+
+#: ../../Zotlabs/Lib/Apps.php:562 ../../Zotlabs/Module/Oauth.php:53
+#: ../../Zotlabs/Module/Oauth.php:135 ../../Zotlabs/Module/Connedit.php:748
+#: ../../Zotlabs/Module/Cdav.php:1044 ../../Zotlabs/Module/Cdav.php:1384
+#: ../../Zotlabs/Module/Oauth2.php:58 ../../Zotlabs/Module/Oauth2.php:142
+msgid "Update"
+msgstr "Oppdater"
+
+#: ../../Zotlabs/Lib/Apps.php:562
+msgid "Install"
+msgstr "Installer"
+
+#: ../../Zotlabs/Lib/Apps.php:592 ../../Zotlabs/Lib/Apps.php:614
+msgid "Purchase"
+msgstr "Kjøp"
+
+#: ../../Zotlabs/Lib/Apps.php:619
+msgid "Undelete"
+msgstr "Hent tilbake"
+
+#: ../../Zotlabs/Lib/Apps.php:627
+msgid "Add to app-tray"
+msgstr "Legg til i meny"
+
+#: ../../Zotlabs/Lib/Apps.php:628
+msgid "Remove from app-tray"
+msgstr "Fjern fra meny"
+
+#: ../../Zotlabs/Lib/Apps.php:629
+msgid "Pin to navbar"
+msgstr "Fest til navigasjonslinjen"
+
+#: ../../Zotlabs/Lib/Apps.php:630
+msgid "Unpin from navbar"
+msgstr "Fjern fra navigasjonslinjen"
#: ../../Zotlabs/Lib/PermissionDescription.php:108
#: ../../Zotlabs/Access/PermissionRoles.php:386
@@ -4926,51 +7170,332 @@ msgstr "Offentlig"
#: ../../Zotlabs/Lib/PermissionDescription.php:109
msgid "Anybody in the $Projectname network"
-msgstr ""
+msgstr "Alle i $Projectname-nettverket"
#: ../../Zotlabs/Lib/PermissionDescription.php:110
#, php-format
msgid "Any account on %s"
-msgstr ""
+msgstr "Brukerkontoer på %s"
#: ../../Zotlabs/Lib/PermissionDescription.php:111
msgid "Any of my connections"
-msgstr ""
+msgstr "Alle mine forbindelser"
#: ../../Zotlabs/Lib/PermissionDescription.php:112
msgid "Only connections I specifically allow"
-msgstr ""
+msgstr "Kun forbindelser jeg gir tilgang"
#: ../../Zotlabs/Lib/PermissionDescription.php:113
msgid "Anybody authenticated (could include visitors from other networks)"
msgstr ""
+"Alle som er autentisert (det kan inkludere besøkende fra andre nettverk)"
#: ../../Zotlabs/Lib/PermissionDescription.php:114
msgid "Any connections including those who haven't yet been approved"
msgstr ""
+"Alle forbindelser inkludert forbindelser som ikke enda har blitt godkjent"
#: ../../Zotlabs/Lib/PermissionDescription.php:150
msgid ""
"This is your default setting for the audience of your normal stream, and "
"posts."
msgstr ""
+"Dette er standardinnstillingene for hvem som vil motta din vanlige strøm og "
+"innlegg."
#: ../../Zotlabs/Lib/PermissionDescription.php:151
msgid ""
"This is your default setting for who can view your default channel profile"
msgstr ""
+"Dette er standardinnstillingene for hvem som kan se kanalens standardprofil"
#: ../../Zotlabs/Lib/PermissionDescription.php:152
msgid "This is your default setting for who can view your connections"
-msgstr ""
+msgstr "Dette er standardinnstillingene for hvem som kan se dine forbindelser"
#: ../../Zotlabs/Lib/PermissionDescription.php:153
msgid ""
"This is your default setting for who can view your file storage and photos"
msgstr ""
+"Dette er standardinnstillingene for hvem som kan se dine filer og bilder"
#: ../../Zotlabs/Lib/PermissionDescription.php:154
msgid "This is your default setting for the audience of your webpages"
+msgstr "Dette er standardinnstillingene for hvem som kan se dine websider"
+
+#: ../../Zotlabs/Lib/Permcat.php:83
+msgctxt "permcat"
+msgid "Default"
+msgstr "Standard"
+
+#: ../../Zotlabs/Lib/DB_Upgrade.php:85
+msgid "Source code of failed update: "
+msgstr "Kildekoden til oppdateringen som mislyktes:"
+
+#: ../../Zotlabs/Lib/DB_Upgrade.php:106
+#, php-format
+msgid "Update Error at %s"
+msgstr "Oppdateringsfeil ved %s"
+
+#: ../../Zotlabs/Lib/DB_Upgrade.php:112
+#, php-format
+msgid "Update %s failed. See error logs."
+msgstr "Oppdatering %s mislyktes. Se feilloggen."
+
+#: ../../Zotlabs/Lib/Connect.php:51 ../../Zotlabs/Lib/Connect.php:152
+msgid "Channel is blocked on this site."
+msgstr "Kanalen er blokkert på dette nettstedet."
+
+#: ../../Zotlabs/Lib/Connect.php:56
+msgid "Channel location missing."
+msgstr "Kanalplassering mangler."
+
+#: ../../Zotlabs/Lib/Connect.php:110
+msgid "Remote channel or protocol unavailable."
+msgstr "Kanalen eller protokollen er ikke tilgjengelig på den andre serveren."
+
+#: ../../Zotlabs/Lib/Connect.php:146
+msgid "Channel discovery failed."
+msgstr "Kanaloppdagelse mislyktes."
+
+#: ../../Zotlabs/Lib/Connect.php:164
+msgid "Protocol disabled."
+msgstr "Protokollen er avskrudd."
+
+#: ../../Zotlabs/Lib/Connect.php:176
+msgid "Cannot connect to yourself."
+msgstr "Kan ikke lage forbindelse med deg selv."
+
+#: ../../Zotlabs/Lib/Connect.php:281
+msgid "error saving data"
+msgstr "feil ved lagring av data"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:107
+msgid "Restricted message"
+msgstr "Begrenset melding"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:114
+msgid "Private message"
+msgstr "Privat melding"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:119
+msgid "Public Policy"
+msgstr ""
+
+#: ../../Zotlabs/Lib/ThreadItem.php:153
+msgid "Privacy conflict. Discretion advised."
+msgstr ""
+
+#: ../../Zotlabs/Lib/ThreadItem.php:184 ../../Zotlabs/Storage/Browser.php:377
+msgid "Admin Delete"
+msgstr ""
+
+#: ../../Zotlabs/Lib/ThreadItem.php:187 ../../Zotlabs/Module/Filer.php:66
+msgid "Save to Folder"
+msgstr "Lagre i mappe"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:214
+msgid "I will attend"
+msgstr "Jeg vil delta"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:214
+msgid "I will not attend"
+msgstr "Jeg deltar ikke"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:214
+msgid "I might attend"
+msgstr "Jeg vil kanskje delta"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:287
+msgid "Reply to this message"
+msgstr "Svar på denne meldingen"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:287
+msgid "reply"
+msgstr ""
+
+#: ../../Zotlabs/Lib/ThreadItem.php:287
+msgid "Reply to"
+msgstr ""
+
+#: ../../Zotlabs/Lib/ThreadItem.php:294 ../../Zotlabs/Widget/Pinned.php:95
+msgid "share"
+msgstr "del"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:298
+msgid "Repeat"
+msgstr ""
+
+#: ../../Zotlabs/Lib/ThreadItem.php:298
+msgid "repeat"
+msgstr ""
+
+#: ../../Zotlabs/Lib/ThreadItem.php:309
+msgid "Delivery Report"
+msgstr "Leveringsrapport"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:330
+#, php-format
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] "%d kommentar"
+msgstr[1] "%d kommentarer"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:332
+#, php-format
+msgid "%d unseen"
+msgstr "%d uleste"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:363
+#, php-format
+msgid "Load the next few of total %d comments"
+msgstr "Last inn noen flere av ialt %d kommentarer"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:369
+msgid "Expand Replies"
+msgstr ""
+
+#: ../../Zotlabs/Lib/ThreadItem.php:388
+msgid "Forum"
+msgstr ""
+
+#: ../../Zotlabs/Lib/ThreadItem.php:395
+msgid "to"
+msgstr "til"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:396 ../../Zotlabs/Widget/Messages.php:179
+#: ../../Zotlabs/Widget/Messages.php:182 ../../Zotlabs/Widget/Pinned.php:125
+msgid "via"
+msgstr "via"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:397
+msgid "Wall-to-Wall"
+msgstr "Vegg-til-vegg"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:398
+msgid "via Wall-To-Wall:"
+msgstr "via vegg-til-vegg:"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:414
+#, php-format
+msgid "Last edited %s"
+msgstr "Sist endret: %s"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:415
+#, php-format
+msgid "Expires %s"
+msgstr "Utløper %s"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:418
+#, php-format
+msgid "Published %s"
+msgstr "Publisert %s"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:425
+msgid "Attend"
+msgstr "Delta"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:426 ../../Zotlabs/Widget/Pinned.php:139
+msgid "Attendance Options"
+msgstr "Valg for deltagelse"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:428 ../../Zotlabs/Widget/Pinned.php:140
+msgid "Voting Options"
+msgstr "Valg for avstemning"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:442
+msgid "Go to previous comment"
+msgstr "Gå til forrige kommentar"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:452 ../../Zotlabs/Widget/Pinned.php:152
+msgid "Pinned post"
+msgstr "Festet innlegg"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:456
+msgid "Add to Calendar"
+msgstr "Legg til i kalender"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:464
+msgid "Mark all comments seen"
+msgstr "Merk alle kommentarer som sett"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:483
+msgid "Add yours"
+msgstr "Legg til din"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:483
+msgid "Remove yours"
+msgstr "Fjern din"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:496
+msgid "show less"
+msgstr "vis færre"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:497
+msgid "show more"
+msgstr "vis flere"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:497
+msgid "show all"
+msgstr "vis alle"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:806 ../../Zotlabs/Module/Photos.php:1093
+#: ../../Zotlabs/Module/Photos.php:1194
+msgid "This is you"
+msgstr "Dette er deg"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:818
+msgid "Insert Link"
+msgstr "Sett inn lenke"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:819
+msgid "Video"
+msgstr "Video"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:828
+msgid "Your full name (required)"
+msgstr "Fullt navn (påkrevd)"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:829
+msgid "Your email address (required)"
+msgstr "Epostaddresse (påkrevd)"
+
+#: ../../Zotlabs/Lib/ThreadItem.php:830
+msgid "Your website URL (optional)"
+msgstr "Webside (valgfritt)"
+
+#: ../../Zotlabs/Lib/Libzot.php:693
+msgid "Unable to verify channel signature"
+msgstr "Ikke i stand til å sjekke kanalsignaturen"
+
+#: ../../Zotlabs/Lib/Activity.php:2202
+#, php-format
+msgid "Likes %1$s's %2$s"
+msgstr ""
+
+#: ../../Zotlabs/Lib/Activity.php:2205
+#, php-format
+msgid "Doesn't like %1$s's %2$s"
+msgstr ""
+
+#: ../../Zotlabs/Lib/Activity.php:2211
+#, php-format
+msgid "Will attend %s's event"
+msgstr ""
+
+#: ../../Zotlabs/Lib/Activity.php:2214
+#, php-format
+msgid "Will not attend %s's event"
+msgstr ""
+
+#: ../../Zotlabs/Lib/Activity.php:2217
+#, php-format
+msgid "May attend %s's event"
+msgstr ""
+
+#: ../../Zotlabs/Lib/Activity.php:2220
+#, php-format
+msgid "May not attend %s's event"
msgstr ""
#: ../../Zotlabs/Lib/Libsync.php:824
@@ -4978,10 +7503,77 @@ msgstr ""
msgid "Unable to verify site signature for %s"
msgstr "Ikke i stand til å bekrefte signaturen til %s"
-#: ../../Zotlabs/Lib/Permcat.php:83
-msgctxt "permcat"
-msgid "Default"
-msgstr "Standard"
+#: ../../Zotlabs/Lib/Libzotdir.php:164
+msgid "Directory Options"
+msgstr "Kataloginnstillinger"
+
+#: ../../Zotlabs/Lib/Libzotdir.php:166
+msgid "Safe Mode"
+msgstr "Trygg modus"
+
+#: ../../Zotlabs/Lib/Libzotdir.php:167
+msgid "Public Forums Only"
+msgstr "Bare offentlige forum"
+
+#: ../../Zotlabs/Lib/Libzotdir.php:169
+msgid "This Website Only"
+msgstr "Kun dette nettstedet"
+
+#: ../../Zotlabs/Lib/Chatroom.php:25
+msgid "Missing room name"
+msgstr "Mangler romnavn"
+
+#: ../../Zotlabs/Lib/Chatroom.php:34
+msgid "Duplicate room name"
+msgstr "Duplikat romnavn"
+
+#: ../../Zotlabs/Lib/Chatroom.php:84 ../../Zotlabs/Lib/Chatroom.php:92
+msgid "Invalid room specifier."
+msgstr "Ugyldig rom-spesifisering."
+
+#: ../../Zotlabs/Lib/Chatroom.php:124
+msgid "Room not found."
+msgstr "Rommet ble ikke funnet."
+
+#: ../../Zotlabs/Lib/Chatroom.php:145
+msgid "Room is full"
+msgstr "Rommet er fullt"
+
+#: ../../Zotlabs/Lib/Techlevels.php:10
+msgid "0. Beginner/Basic"
+msgstr ""
+
+#: ../../Zotlabs/Lib/Techlevels.php:11
+msgid "1. Novice - not skilled but willing to learn"
+msgstr ""
+
+#: ../../Zotlabs/Lib/Techlevels.php:12
+msgid "2. Intermediate - somewhat comfortable"
+msgstr ""
+
+#: ../../Zotlabs/Lib/Techlevels.php:13
+msgid "3. Advanced - very comfortable"
+msgstr ""
+
+#: ../../Zotlabs/Lib/Techlevels.php:14
+msgid "4. Expert - I can write computer code"
+msgstr ""
+
+#: ../../Zotlabs/Lib/Techlevels.php:15
+msgid "5. Wizard - I probably know more than you do"
+msgstr ""
+
+#: ../../Zotlabs/Lib/AccessList.php:26
+msgid ""
+"A deleted privacy group with this name was revived. Existing item "
+"permissions <strong>may</strong> apply to this privacy group and any future "
+"members. If this is not what you intended, please create another privacy "
+"group with a different name."
+msgstr ""
+
+#: ../../Zotlabs/Lib/AccessList.php:268
+msgid "Select a privacy group"
+msgstr ""
#: ../../Zotlabs/Access/PermissionRoles.php:339
msgid "Social Networking"
@@ -4989,7 +7581,7 @@ msgstr "Sosialt nettverk"
#: ../../Zotlabs/Access/PermissionRoles.php:340
msgid "Social - Federation"
-msgstr ""
+msgstr "Sosial - føderert"
#: ../../Zotlabs/Access/PermissionRoles.php:341
msgid "Social - Mostly Public"
@@ -5052,9 +7644,8 @@ msgid "Personal"
msgstr "Personlig"
#: ../../Zotlabs/Access/PermissionRoles.php:388
-#, fuzzy
msgid "Community forum"
-msgstr "Forum for fellesskap"
+msgstr "Forum"
#: ../../Zotlabs/Access/PermissionRoles.php:389
msgid "Custom"
@@ -5062,7 +7653,7 @@ msgstr ""
#: ../../Zotlabs/Access/Permissions.php:56
msgid "Can view my channel stream and posts"
-msgstr ""
+msgstr "Kan se min kanalstrøm og innlegg"
#: ../../Zotlabs/Access/Permissions.php:57
msgid "Can send me their channel stream and posts"
@@ -5082,27 +7673,27 @@ msgstr "Kan se mine filer og bilder"
#: ../../Zotlabs/Access/Permissions.php:61
msgid "Can upload/modify my file storage and photos"
-msgstr ""
+msgstr "Kan laste opp/redigere mine filer og bilder"
#: ../../Zotlabs/Access/Permissions.php:62
msgid "Can view my channel webpages"
-msgstr ""
+msgstr "Kan se min kanal sine websider"
#: ../../Zotlabs/Access/Permissions.php:63
msgid "Can view my wiki pages"
-msgstr ""
+msgstr "Kan se mine wiki-sider"
#: ../../Zotlabs/Access/Permissions.php:64
msgid "Can create/edit my channel webpages"
-msgstr ""
+msgstr "Kan opprette/redigere websider på min kanal"
#: ../../Zotlabs/Access/Permissions.php:65
msgid "Can write to my wiki pages"
-msgstr ""
+msgstr "Kan skrive på mine wiki-sider"
#: ../../Zotlabs/Access/Permissions.php:66
msgid "Can post on my channel (wall) page"
-msgstr ""
+msgstr "Kan poste på min kanal (vegg)"
#: ../../Zotlabs/Access/Permissions.php:67
msgid "Can comment on or like my posts"
@@ -5114,1540 +7705,1170 @@ msgstr "Kan sende meg direktemeldinger"
#: ../../Zotlabs/Access/Permissions.php:69
msgid "Can like/dislike profiles and profile things"
-msgstr ""
+msgstr "Kan like/ikke like profiler og profilting"
#: ../../Zotlabs/Access/Permissions.php:70
msgid "Can chat with me"
-msgstr ""
+msgstr "Kan chatte med meg"
#: ../../Zotlabs/Access/Permissions.php:71
msgid "Can source/mirror my public posts in derived channels"
-msgstr ""
+msgstr "Kan speile eller bruke mine offentlige innlegg i avledede kanaler"
#: ../../Zotlabs/Access/Permissions.php:73
msgid "Can administer my channel"
-msgstr ""
+msgstr "Kan administrere min kanal"
-#: ../../Zotlabs/Web/Router.php:188 ../../Zotlabs/Module/Help.php:172
-#: ../../Zotlabs/Module/Block.php:77 ../../Zotlabs/Module/Page.php:136
-#: ../../Zotlabs/Module/Display.php:155
-#: ../../extend/addon/hzaddons/articles/articles.php:105
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWikiPage.php:545
-#: ../../extend/addon/hzaddons/cards/cards.php:104
-msgid "Page not found."
-msgstr "Siden ikke funnet."
-
-#: ../../Zotlabs/Storage/Browser.php:292
-msgid "Change filename to"
-msgstr ""
+#: ../../Zotlabs/Widget/Tasklist.php:31
+msgid "Tasks"
+msgstr "Oppgaver"
-#: ../../Zotlabs/Storage/Browser.php:309 ../../Zotlabs/Storage/Browser.php:394
-msgid "Select a target location"
+#: ../../Zotlabs/Widget/Cover_photo.php:74
+msgid "Click to show more"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:310 ../../Zotlabs/Storage/Browser.php:395
-msgid "Copy to target location"
-msgstr ""
+#: ../../Zotlabs/Widget/Appstore.php:16
+msgid "App Collections"
+msgstr "Appsamlinger"
-#: ../../Zotlabs/Storage/Browser.php:311 ../../Zotlabs/Storage/Browser.php:393
-msgid "Set permissions for all files and sub folders"
-msgstr ""
+#: ../../Zotlabs/Widget/Appstore.php:18
+msgid "Installed apps"
+msgstr "Installerte apper"
-#: ../../Zotlabs/Storage/Browser.php:312
-msgid "Notify your contacts about this file"
-msgstr "Varsle dine kontakter om denne filen"
+#: ../../Zotlabs/Widget/Appstore.php:19 ../../Zotlabs/Module/Apps.php:51
+msgid "Available Apps"
+msgstr "Tilgjengelige apper"
-#: ../../Zotlabs/Storage/Browser.php:351
-msgid "File category"
-msgstr ""
+#: ../../Zotlabs/Widget/Rating.php:59
+msgid "Rating Tools"
+msgstr "Vurderingsverktøy"
-#: ../../Zotlabs/Storage/Browser.php:365
-msgid "Total"
-msgstr "Totalt"
+#: ../../Zotlabs/Widget/Rating.php:63 ../../Zotlabs/Widget/Rating.php:65
+msgid "Rate Me"
+msgstr "Vurder meg"
-#: ../../Zotlabs/Storage/Browser.php:367
-msgid "Shared"
-msgstr "Delt"
+#: ../../Zotlabs/Widget/Rating.php:68
+msgid "View Ratings"
+msgstr "Vis vurderinger"
-#: ../../Zotlabs/Storage/Browser.php:368 ../../Zotlabs/Storage/Browser.php:545
-#: ../../Zotlabs/Module/Menu.php:182 ../../Zotlabs/Module/New_channel.php:190
-#: ../../Zotlabs/Module/Layouts.php:183 ../../Zotlabs/Module/Connedit.php:747
-#: ../../Zotlabs/Module/Blocks.php:157 ../../Zotlabs/Module/Webpages.php:249
-#: ../../Zotlabs/Module/Cdav.php:1050 ../../Zotlabs/Module/Cdav.php:1383
-#: ../../Zotlabs/Widget/Cdav.php:146 ../../Zotlabs/Widget/Cdav.php:184
-msgid "Create"
-msgstr "Lag"
+#: ../../Zotlabs/Widget/Notifications.php:24
+#, fuzzy
+#| msgid "Unseen stream activity"
+msgid "Unseen network activity"
+msgstr "Ny aktivitet i nettverksstrømmen"
-#: ../../Zotlabs/Storage/Browser.php:369
-msgid "Add Files"
+#: ../../Zotlabs/Widget/Notifications.php:27
+msgid "Network stream"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:381 ../../Zotlabs/Module/Oauth.php:111
-#: ../../Zotlabs/Module/Oauth.php:137 ../../Zotlabs/Module/Oauth2.php:116
-#: ../../Zotlabs/Module/Oauth2.php:144 ../../Zotlabs/Module/Chat.php:256
-#: ../../Zotlabs/Module/Connedit.php:732
-#: ../../Zotlabs/Module/Admin/Channels.php:181
-#: ../../Zotlabs/Module/Sharedwithme.php:107 ../../Zotlabs/Module/Cdav.php:1368
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:172
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWikiPage.php:588
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:221
-#: ../../extend/addon/hzaddons/wiki/Widget/Wiki_page_history.php:28
-msgid "Name"
-msgstr "Navn"
-
-#: ../../Zotlabs/Storage/Browser.php:382
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:222
-msgid "Type"
-msgstr "Type"
-
-#: ../../Zotlabs/Storage/Browser.php:384
-#: ../../Zotlabs/Module/Sharedwithme.php:110
-msgid "Last Modified"
-msgstr "Sist endret"
+#: ../../Zotlabs/Widget/Notifications.php:30
+#: ../../Zotlabs/Widget/Notifications.php:69
+#, fuzzy
+#| msgid "Mark all seen"
+msgid "Mark all read"
+msgstr "Merk alle som sett"
-#: ../../Zotlabs/Storage/Browser.php:385
-msgid "parent"
-msgstr "opp et nivå"
+#: ../../Zotlabs/Widget/Notifications.php:33
+#: ../../Zotlabs/Widget/Notifications.php:53
+#: ../../Zotlabs/Widget/Notifications.php:72
+#: ../../Zotlabs/Widget/Notifications.php:166
+#, fuzzy
+#| msgid "Conversation Features"
+msgid "Conversation starters"
+msgstr "Samtaleinnstillinger"
-#: ../../Zotlabs/Storage/Browser.php:390
-#: ../../Zotlabs/Module/Filestorage.php:206
-msgid "Copy/paste this code to attach file to a post"
-msgstr "Kopier og lim inn denne koden for å legge til filen i et innlegg"
+#: ../../Zotlabs/Widget/Notifications.php:34
+#: ../../Zotlabs/Widget/Notifications.php:54
+#: ../../Zotlabs/Widget/Notifications.php:73
+#: ../../Zotlabs/Widget/Notifications.php:134
+#: ../../Zotlabs/Widget/Notifications.php:167
+#: ../../Zotlabs/Widget/Messages.php:55
+msgid "Filter by name or address"
+msgstr "Filtrer etter navn eller adresse"
-#: ../../Zotlabs/Storage/Browser.php:391
-#: ../../Zotlabs/Module/Filestorage.php:207
-msgid "Copy/paste this URL to link file from a web page"
-msgstr "Kopier og lim inn denne URL-en for å lenke til filen fra en webside"
+#: ../../Zotlabs/Widget/Notifications.php:44
+#: ../../Zotlabs/Module/Settings/Channel.php:259
+msgid "Unseen channel activity"
+msgstr "Ny kanalaktivitet"
-#: ../../Zotlabs/Storage/Browser.php:403
+#: ../../Zotlabs/Widget/Notifications.php:47
#, fuzzy
-msgid "Select All"
-msgstr "velg alle"
+msgid "Channel stream"
+msgstr "Kanalnavn"
-#: ../../Zotlabs/Storage/Browser.php:404
-msgid "Bulk Actions"
-msgstr ""
+#: ../../Zotlabs/Widget/Notifications.php:50
+#: ../../Zotlabs/Widget/Notifications.php:88
+#: ../../Zotlabs/Widget/Notifications.php:123
+#: ../../Zotlabs/Module/Notifications.php:111
+msgid "Mark all seen"
+msgstr "Merk alle som sett"
-#: ../../Zotlabs/Storage/Browser.php:405
-msgid "Adjust Permissions"
-msgstr ""
+#: ../../Zotlabs/Widget/Notifications.php:62
+#, fuzzy
+#| msgid "Private Forum"
+msgid "Private"
+msgstr "Privat forum"
-#: ../../Zotlabs/Storage/Browser.php:406
-msgid "Move or Copy"
-msgstr ""
+#: ../../Zotlabs/Widget/Notifications.php:63
+#, fuzzy
+#| msgid "Unseen stream activity"
+msgid "Unseen private activity"
+msgstr "Ny aktivitet i nettverksstrømmen"
-#: ../../Zotlabs/Storage/Browser.php:408
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:215
-msgid "Download"
-msgstr ""
+#: ../../Zotlabs/Widget/Notifications.php:66
+#, fuzzy
+#| msgid "Private Forum"
+msgid "Private stream"
+msgstr "Privat forum"
-#: ../../Zotlabs/Storage/Browser.php:409
-msgid "Info"
-msgstr ""
+#: ../../Zotlabs/Widget/Notifications.php:81
+#: ../../Zotlabs/Widget/Activity_filter.php:53
+msgid "Events"
+msgstr "Hendelser"
-#: ../../Zotlabs/Storage/Browser.php:410
-msgid "Rename"
-msgstr ""
+#: ../../Zotlabs/Widget/Notifications.php:82
+#, fuzzy
+#| msgid "Unseen stream activity"
+msgid "Unseen events activity"
+msgstr "Ny aktivitet i nettverksstrømmen"
-#: ../../Zotlabs/Storage/Browser.php:412
-msgid "Attachment BBcode"
+#: ../../Zotlabs/Widget/Notifications.php:85
+msgid "View events"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:413
-msgid "Embed BBcode"
-msgstr ""
+#: ../../Zotlabs/Widget/Notifications.php:96
+#: ../../Zotlabs/Module/Connections.php:168
+msgid "New Connections"
+msgstr "Nye forbindelser"
-#: ../../Zotlabs/Storage/Browser.php:414
-msgid "Link BBcode"
-msgstr ""
+#: ../../Zotlabs/Widget/Notifications.php:97
+#: ../../Zotlabs/Module/Settings/Channel.php:267
+msgid "New connections"
+msgstr "Nye forbindelser"
-#: ../../Zotlabs/Storage/Browser.php:487
-#, php-format
-msgid "You are using %1$s of your available file storage."
-msgstr "Du bruker %1$s av din tilgjengelige lagringsplass."
+#: ../../Zotlabs/Widget/Notifications.php:100
+#: ../../Zotlabs/Widget/Notifications.php:120
+#: ../../Zotlabs/Module/Photos.php:1114 ../../Zotlabs/Module/Photos.php:1126
+msgid "View all"
+msgstr "Vis alle"
-#: ../../Zotlabs/Storage/Browser.php:492
-#, php-format
-msgid "You are using %1$s of %2$s available file storage. (%3$s&#37;)"
-msgstr "Du bruker %1$s av %2$s tilgjengelig lagringsplass (%3$s&#37;)"
+#: ../../Zotlabs/Widget/Notifications.php:109
+#, fuzzy
+#| msgid "Unseen stream activity"
+msgid "Useen files activity"
+msgstr "Ny aktivitet i nettverksstrømmen"
-#: ../../Zotlabs/Storage/Browser.php:503
-msgid "WARNING:"
-msgstr "ADVARSEL:"
+#: ../../Zotlabs/Widget/Notifications.php:117
+#, fuzzy
+#| msgid "System Notifications"
+msgid "Unseen notifications"
+msgstr "Systemvarsler"
-#: ../../Zotlabs/Storage/Browser.php:544
-msgid "Create new folder"
-msgstr "Lag ny mappe"
+#: ../../Zotlabs/Widget/Notifications.php:132
+#, fuzzy
+#| msgid "Unseen stream activity"
+msgid "Unseen forums activity"
+msgstr "Ny aktivitet i nettverksstrømmen"
-#: ../../Zotlabs/Storage/Browser.php:546
-msgid "Upload file"
-msgstr "Last opp fil"
+#: ../../Zotlabs/Widget/Notifications.php:144
+msgid "Registrations"
+msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:547 ../../Zotlabs/Module/Photos.php:681
-#: ../../Zotlabs/Module/Cover_photo.php:381
-#: ../../Zotlabs/Module/Profile_photo.php:547
-#: ../../Zotlabs/Module/Embedphotos.php:189 ../../Zotlabs/Widget/Album.php:103
-#: ../../Zotlabs/Widget/Portfolio.php:114 ../../Zotlabs/Widget/Cdav.php:152
-#: ../../Zotlabs/Widget/Cdav.php:188
-msgid "Upload"
-msgstr "Last opp"
+#: ../../Zotlabs/Widget/Notifications.php:145
+#, fuzzy
+#| msgid "Unseen stream activity"
+msgid "Unseen registration activity"
+msgstr "Ny aktivitet i nettverksstrømmen"
-#: ../../Zotlabs/Storage/Browser.php:558
-msgid "Drop files here to immediately upload"
-msgstr ""
+#: ../../Zotlabs/Widget/Notifications.php:155
+#: ../../Zotlabs/Module/Settings/Channel.php:270
+msgid "Unseen public stream activity"
+msgstr "Ny aktivitet i den offentlige strømmen"
-#: ../../Zotlabs/Storage/Browser.php:559
-#: ../../Zotlabs/Module/Filestorage.php:211
-msgid "Show in your contacts shared folder"
+#: ../../Zotlabs/Widget/Notifications.php:158
+msgid "Public stream"
msgstr ""
-#: ../../Zotlabs/Storage/Browser.php:561
-msgid ""
-"You can select files via the upload button or drop them right here or into "
-"an existing folder."
+#: ../../Zotlabs/Widget/Notifications.php:174
+msgid "Sorry, you have got no notifications at the moment"
msgstr ""
-#: ../../Zotlabs/Module/Menu.php:68
-msgid "Unable to update menu."
-msgstr "Ikke i stand til å oppdatere meny."
-
-#: ../../Zotlabs/Module/Menu.php:79
-msgid "Unable to create menu."
-msgstr "Ikke i stand til å lage meny."
-
-#: ../../Zotlabs/Module/Menu.php:161 ../../Zotlabs/Module/Menu.php:174
-msgid "Menu Name"
-msgstr "Menynavn"
+#: ../../Zotlabs/Widget/Bookmarkedchats.php:25
+msgid "Bookmarked Chatrooms"
+msgstr "Bokmerkede chatrom"
-#: ../../Zotlabs/Module/Menu.php:161
-msgid "Unique name (not visible on webpage) - required"
-msgstr "Unikt navn (ikke synlig på websiden) - påkrevet"
+#: ../../Zotlabs/Widget/Chatroom_list.php:26
+msgid "Overview"
+msgstr ""
-#: ../../Zotlabs/Module/Menu.php:162 ../../Zotlabs/Module/Menu.php:175
-msgid "Menu Title"
-msgstr "Menytittel"
+#: ../../Zotlabs/Widget/Tokens.php:41
+msgid "Add new guest"
+msgstr ""
-#: ../../Zotlabs/Module/Menu.php:162
-msgid "Visible on webpage - leave empty for no title"
-msgstr "Synlig på websiden - la stå tomt for ingen tittel"
+#: ../../Zotlabs/Widget/Tokens.php:49 ../../Zotlabs/Module/Lockview.php:236
+msgid "Guest access"
+msgstr ""
-#: ../../Zotlabs/Module/Menu.php:163
-msgid "Allow Bookmarks"
-msgstr "Tillat bokmerker"
+#: ../../Zotlabs/Widget/Newmember.php:38
+msgid "Profile Creation"
+msgstr "Oppretting av profil"
-#: ../../Zotlabs/Module/Menu.php:163 ../../Zotlabs/Module/Menu.php:222
-msgid "Menu may be used to store saved bookmarks"
-msgstr "Menyen kan brukes til å lagre lagrede bokmerker"
+#: ../../Zotlabs/Widget/Newmember.php:40
+msgid "Upload profile photo"
+msgstr "Last opp profilbilde"
-#: ../../Zotlabs/Module/Menu.php:164 ../../Zotlabs/Module/Menu.php:225
-msgid "Submit and proceed"
-msgstr "Send inn og fortsett"
+#: ../../Zotlabs/Widget/Newmember.php:41
+msgid "Upload cover photo"
+msgstr "Last opp omslagsbilde"
-#: ../../Zotlabs/Module/Menu.php:177 ../../Zotlabs/Module/Locs.php:124
-msgid "Drop"
-msgstr "Slett"
+#: ../../Zotlabs/Widget/Newmember.php:45
+msgid "Find and Connect with others"
+msgstr "Finn andre"
-#: ../../Zotlabs/Module/Menu.php:178 ../../Zotlabs/Module/Layouts.php:189
-#: ../../Zotlabs/Module/Blocks.php:155 ../../Zotlabs/Module/Webpages.php:261
-msgid "Created"
-msgstr "Laget"
+#: ../../Zotlabs/Widget/Newmember.php:47
+msgid "View the directory"
+msgstr "Se i katalogen"
-#: ../../Zotlabs/Module/Menu.php:179 ../../Zotlabs/Module/Layouts.php:190
-#: ../../Zotlabs/Module/Blocks.php:156 ../../Zotlabs/Module/Webpages.php:262
-msgid "Edited"
-msgstr "Endret"
+#: ../../Zotlabs/Widget/Newmember.php:48 ../../Zotlabs/Module/Go.php:39
+msgid "View friend suggestions"
+msgstr "Vis venneforslag"
-#: ../../Zotlabs/Module/Menu.php:180 ../../Zotlabs/Module/Connections.php:83
-#: ../../Zotlabs/Module/Connections.php:92
-#: ../../Zotlabs/Module/Notifications.php:101
-msgid "New"
-msgstr "Nye"
+#: ../../Zotlabs/Widget/Newmember.php:49
+msgid "Manage your connections"
+msgstr "Behandle forbindelser"
-#: ../../Zotlabs/Module/Menu.php:181
-msgid "Bookmarks allowed"
-msgstr "Bokmerker tillatt"
+#: ../../Zotlabs/Widget/Newmember.php:52
+msgid "Communicate"
+msgstr "Kommuniser"
-#: ../../Zotlabs/Module/Menu.php:183
-msgid "Delete this menu"
-msgstr "Slett denne menyen"
+#: ../../Zotlabs/Widget/Newmember.php:54
+msgid "View your channel homepage"
+msgstr "Vis kanalens hjemmeside"
-#: ../../Zotlabs/Module/Menu.php:184 ../../Zotlabs/Module/Menu.php:219
-msgid "Edit menu contents"
-msgstr "Endre menyinnholdet"
+#: ../../Zotlabs/Widget/Newmember.php:55
+msgid "View your network stream"
+msgstr "Vis nettverksstrømmen"
-#: ../../Zotlabs/Module/Menu.php:185
-msgid "Edit this menu"
-msgstr "Endre denne menyen"
+#: ../../Zotlabs/Widget/Newmember.php:61
+msgid "Documentation"
+msgstr "Dokumentasjon"
-#: ../../Zotlabs/Module/Menu.php:201
-msgid "Menu could not be deleted."
-msgstr "Menyen kunne ikke bli slettet."
+#: ../../Zotlabs/Widget/Newmember.php:64
+msgid "Missing Features?"
+msgstr "Noe som mangler?"
-#: ../../Zotlabs/Module/Menu.php:209 ../../Zotlabs/Module/Mitem.php:31
-msgid "Menu not found."
-msgstr "Menyen ble ikke funnet."
+#: ../../Zotlabs/Widget/Newmember.php:66
+msgid "Pin apps to navigation bar"
+msgstr "Fest apper til navigasjonslinjen"
-#: ../../Zotlabs/Module/Menu.php:214
-msgid "Edit Menu"
-msgstr "Endre meny"
+#: ../../Zotlabs/Widget/Newmember.php:67
+msgid "Install more apps"
+msgstr "Legg til flere apper"
-#: ../../Zotlabs/Module/Menu.php:218
-msgid "Add or remove entries to this menu"
-msgstr "Legg til eller fjern punkter i denne menyen"
+#: ../../Zotlabs/Widget/Newmember.php:78
+msgid "View public stream"
+msgstr ""
-#: ../../Zotlabs/Module/Menu.php:220
-msgid "Menu name"
-msgstr "Menynavn"
+#: ../../Zotlabs/Widget/Newmember.php:82
+#: ../../Zotlabs/Module/Settings/Display.php:201
+msgid "New Member Links"
+msgstr "Lenker for nye medlemmer"
-#: ../../Zotlabs/Module/Menu.php:220
-msgid "Must be unique, only seen by you"
-msgstr "Må være unik, ses bare av deg"
+#: ../../Zotlabs/Widget/Suggestions.php:53 ../../Zotlabs/Module/Suggest.php:71
+msgid "Ignore/Hide"
+msgstr "Ignorer/Skjul"
-#: ../../Zotlabs/Module/Menu.php:221
-msgid "Menu title"
-msgstr "Menytittel"
+#: ../../Zotlabs/Widget/Suggestions.php:58
+msgid "Suggestions"
+msgstr "Forslag"
-#: ../../Zotlabs/Module/Menu.php:221
-msgid "Menu title as seen by others"
-msgstr "Menytittelen andre ser"
+#: ../../Zotlabs/Widget/Suggestions.php:59
+msgid "See more..."
+msgstr "Se mer..."
-#: ../../Zotlabs/Module/Menu.php:222
-msgid "Allow bookmarks"
-msgstr "Tillat bokmerker"
+#: ../../Zotlabs/Widget/Messages.php:47
+msgid "Public and restricted conversations"
+msgstr "Offentlige og begrensede samtaler"
-#: ../../Zotlabs/Module/Menu.php:232 ../../Zotlabs/Module/Xchan.php:41
-#: ../../Zotlabs/Module/Mitem.php:134
-msgid "Not found."
-msgstr "Ikke funnet."
+#: ../../Zotlabs/Widget/Messages.php:48
+msgid "Private conversations"
+msgstr "Private samtaler"
-#: ../../Zotlabs/Module/Help.php:61
-msgid "Documentation Search"
-msgstr "Søk i dokumentasjon"
+#: ../../Zotlabs/Widget/Messages.php:49
+msgid "Starred conversations"
+msgstr "Stjernemerkede samtaler"
-#: ../../Zotlabs/Module/Help.php:169
-msgid "Not Found"
-msgstr "Ikke funnet"
+#: ../../Zotlabs/Widget/Messages.php:50
+msgid "Filed messages"
+msgstr "Lagrede meldinger"
-#: ../../Zotlabs/Module/Help.php:222
-msgid "$Projectname Documentation"
-msgstr "$Projectname dokumentasjon"
+#: ../../Zotlabs/Widget/Messages.php:53
+msgid "No conversations"
+msgstr "Ingen samtaler"
-#: ../../Zotlabs/Module/Help.php:233
-msgid "Contents"
-msgstr ""
+#: ../../Zotlabs/Widget/Messages.php:54
+msgid "Unseen reactions"
+msgstr "Usette reaksjoner"
-#: ../../Zotlabs/Module/Help.php:240
-msgid "Members"
-msgstr "Medlemmer"
+#: ../../Zotlabs/Widget/Messages.php:56
+msgid "Filter by file name"
+msgstr "Filtrer etter navn"
-#: ../../Zotlabs/Module/Help.php:241
-msgid "Administrators"
-msgstr ""
+#: ../../Zotlabs/Widget/Hq_controls.php:23
+msgid "Toggle post editor"
+msgstr "Vis redigering av innlegg"
-#: ../../Zotlabs/Module/Help.php:242
-msgid "Developers"
+#: ../../Zotlabs/Widget/Hq_controls.php:33
+msgid "Toggle personal notes"
msgstr ""
-#: ../../Zotlabs/Module/Help.php:243
-msgid "Tutorials"
+#: ../../Zotlabs/Widget/Hq_controls.php:43
+msgid "Channel activities"
msgstr ""
-#: ../../Zotlabs/Module/Help.php:261
-msgid "Help:"
-msgstr "Hjelp:"
+#: ../../Zotlabs/Widget/Album.php:87 ../../Zotlabs/Widget/Portfolio.php:95
+#: ../../Zotlabs/Module/Embedphotos.php:173 ../../Zotlabs/Module/Photos.php:784
+#: ../../Zotlabs/Module/Photos.php:1318
+msgid "View Photo"
+msgstr "Vis foto"
-#: ../../Zotlabs/Module/Editblock.php:79 ../../Zotlabs/Module/Editblock.php:95
-#: ../../Zotlabs/Module/Editlayout.php:79 ../../Zotlabs/Module/Editpost.php:24
-#: ../../Zotlabs/Module/Editwebpage.php:80
-#: ../../extend/addon/hzaddons/articles/Mod_Article_edit.php:17
-#: ../../extend/addon/hzaddons/articles/Mod_Article_edit.php:33
-#: ../../extend/addon/hzaddons/cards/Mod_Card_edit.php:17
-#: ../../extend/addon/hzaddons/cards/Mod_Card_edit.php:33
-msgid "Item not found"
-msgstr "Elementet ble ikke funnet."
+#: ../../Zotlabs/Widget/Album.php:104 ../../Zotlabs/Widget/Portfolio.php:116
+#: ../../Zotlabs/Module/Embedphotos.php:189 ../../Zotlabs/Module/Photos.php:815
+msgid "Edit Album"
+msgstr "Endre album"
-#: ../../Zotlabs/Module/Editblock.php:113 ../../Zotlabs/Module/Blocks.php:97
-#: ../../Zotlabs/Module/Blocks.php:153
-msgid "Block Name"
-msgstr "Byggeklossens navn"
+#: ../../Zotlabs/Widget/Album.php:106 ../../Zotlabs/Widget/Portfolio.php:118
+#: ../../Zotlabs/Widget/Cdav.php:152 ../../Zotlabs/Widget/Cdav.php:188
+#: ../../Zotlabs/Storage/Browser.php:550
+#: ../../Zotlabs/Module/Cover_photo.php:381
+#: ../../Zotlabs/Module/Embedphotos.php:191
+#: ../../Zotlabs/Module/Profile_photo.php:547
+#: ../../Zotlabs/Module/Photos.php:683
+msgid "Upload"
+msgstr "Last opp"
-#: ../../Zotlabs/Module/Editblock.php:138
-msgid "Edit Block"
-msgstr "Endre byggekloss"
+#: ../../Zotlabs/Widget/Pinned.php:95
+msgid "Share This"
+msgstr "Del dette"
-#: ../../Zotlabs/Module/Channel.php:150 ../../Zotlabs/Module/Hcard.php:37
-#: ../../Zotlabs/Module/Profile.php:62
-msgid "Posts and comments"
-msgstr "Innlegg og kommentarer"
+#: ../../Zotlabs/Widget/Pinned.php:120 ../../Zotlabs/Widget/Pinned.php:121
+#, php-format
+msgid "View %s's profile - %s"
+msgstr "Vis %s sin profil - %s"
-#: ../../Zotlabs/Module/Channel.php:157 ../../Zotlabs/Module/Hcard.php:44
-#: ../../Zotlabs/Module/Profile.php:69
-msgid "Only posts"
-msgstr "Kun innlegg"
+#: ../../Zotlabs/Widget/Pinned.php:154
+msgid "Don't show"
+msgstr "Ikke vis"
-#: ../../Zotlabs/Module/Channel.php:195 ../../Zotlabs/Module/Oep.php:82
-#: ../../Zotlabs/Module/Pubstream.php:55 ../../Zotlabs/Module/Display.php:53
-msgid "Malformed message id."
-msgstr ""
+#: ../../Zotlabs/Widget/Follow.php:27 ../../Zotlabs/Module/Connections.php:377
+#, php-format
+msgid "You have %1$.0f of %2$.0f allowed connections."
+msgstr "Du har %1$.0f av %2$.0f tillate forbindelser."
-#: ../../Zotlabs/Module/Channel.php:231
-msgid "Insufficient permissions. Request redirected to profile page."
-msgstr "Utilstrekkelig tillatelse. Forespørsel omdirigert til profilsiden."
+#: ../../Zotlabs/Widget/Follow.php:34
+msgid "Add New Connection"
+msgstr "Legg til ny forbindelse"
-#: ../../Zotlabs/Module/Channel.php:246 ../../Zotlabs/Module/Network.php:172
-msgid "Search Results For:"
-msgstr "Søkeresultat for:"
+#: ../../Zotlabs/Widget/Follow.php:35
+msgid "Enter channel address"
+msgstr "Skriv kanaladressen"
-#: ../../Zotlabs/Module/Channel.php:279 ../../Zotlabs/Module/Rpost.php:111
-#: ../../Zotlabs/Module/Network.php:213 ../../Zotlabs/Module/Hq.php:99
-#: ../../Zotlabs/Module/Pubstream.php:98 ../../Zotlabs/Module/Display.php:95
-msgid "Reset form"
-msgstr "Nullstill skjema"
+#: ../../Zotlabs/Widget/Follow.php:36
+msgid "Examples: bob@example.com, https://example.com/barbara"
+msgstr "Eksempel: ola@eksempel.no, https://eksempel.no/kari"
-#: ../../Zotlabs/Module/Channel.php:501 ../../Zotlabs/Module/Display.php:320
-msgid ""
-"You must enable javascript for your browser to be able to view this content."
+#: ../../Zotlabs/Widget/Notes.php:42
+msgid "Read mode"
msgstr ""
-#: ../../Zotlabs/Module/New_channel.php:148 ../../Zotlabs/Module/Manage.php:130
-#, php-format
-msgid "You have created %1$.0f of %2$.0f allowed channels."
-msgstr "Du har laget %1$.0f av %2$.0f tillatte kanaler."
-
-#: ../../Zotlabs/Module/New_channel.php:160
-msgid "Your real name is recommended."
+#: ../../Zotlabs/Widget/Notes.php:43
+msgid "Edit mode"
msgstr ""
-#: ../../Zotlabs/Module/New_channel.php:161
-msgid ""
-"Examples: \"Bob Jameson\", \"Lisa and her Horses\", \"Soccer\", \"Aviation "
-"Group\""
+#: ../../Zotlabs/Widget/Notes.php:44
+msgid "Editing"
msgstr ""
-"Eksempel: \"Ola Nordmann\", \"Lisa og hestene hennes\", \"Fotball\", "
-"\"Sykkelgruppa\""
-#: ../../Zotlabs/Module/New_channel.php:166
-msgid ""
-"This will be used to create a unique network address (like an email address)."
+#: ../../Zotlabs/Widget/Notes.php:45
+msgid "Saving"
msgstr ""
-#: ../../Zotlabs/Module/New_channel.php:168
-msgid "Allowed characters are a-z 0-9, - and _"
+#: ../../Zotlabs/Widget/Notes.php:46
+msgid "Saved"
msgstr ""
-#: ../../Zotlabs/Module/New_channel.php:176
-#, fuzzy
-msgid "Channel name"
-msgstr "Kanalnavn"
-
-#: ../../Zotlabs/Module/New_channel.php:178
-#: ../../Zotlabs/Module/Register.php:513
-msgid "Choose a short nickname"
-msgstr "Velg et kort kallenavn"
-
-#: ../../Zotlabs/Module/New_channel.php:179
-#: ../../Zotlabs/Module/Settings/Channel.php:234
-msgid "Channel role"
-msgstr "Kanalrolle"
+#: ../../Zotlabs/Widget/Activity_order.php:94
+msgid "Posted Date"
+msgstr "Innleggsdato"
-#: ../../Zotlabs/Module/New_channel.php:182
-msgid "Create a Channel"
-msgstr ""
+#: ../../Zotlabs/Widget/Activity_order.php:98
+msgid "Order by last posted date"
+msgstr "Sorter etter dato innlegg ble postet"
-#: ../../Zotlabs/Module/New_channel.php:183
-msgid ""
-"A channel is a unique network identity. It can represent a person (social "
-"network profile), a forum (group), a business or celebrity page, a newsfeed, "
-"and many other things."
-msgstr ""
+#: ../../Zotlabs/Widget/Activity_order.php:102
+msgid "Commented Date"
+msgstr "Sist kommentert"
-#: ../../Zotlabs/Module/New_channel.php:184
-msgid ""
-"or <a href=\"import\">import an existing channel</a> from another location."
-msgstr ""
-"eller <a href=\"import\">importer en eksisterende kanal</a> fra et annet "
-"sted."
+#: ../../Zotlabs/Widget/Activity_order.php:106
+msgid "Order by last commented date"
+msgstr "Sorter etter dato for siste kommentar"
-#: ../../Zotlabs/Module/New_channel.php:189
-msgid "Validate"
-msgstr ""
+#: ../../Zotlabs/Widget/Activity_order.php:110
+msgid "Date Unthreaded"
+msgstr "Utrådet"
-#: ../../Zotlabs/Module/Oexchange.php:27
-msgid "Unable to find your hub."
-msgstr "Ikke i stand til å finne hubben din."
+#: ../../Zotlabs/Widget/Activity_order.php:114
+msgid "Order unthreaded by date"
+msgstr "Sorter innlegg og kommentarer uavhengig av hverandre etter dato"
-#: ../../Zotlabs/Module/Oexchange.php:41
-msgid "Post successful."
-msgstr "Innlegg vellykket."
+#: ../../Zotlabs/Widget/Activity_order.php:129
+msgid "Stream Order"
+msgstr "Sortering av innlegg"
-#: ../../Zotlabs/Module/Import_items.php:50
-msgid "Not a zip file or zip file corrupted."
-msgstr ""
+#: ../../Zotlabs/Widget/Activity_filter.php:44
+msgid "Direct Messages"
+msgstr "Direktemeldinger"
-#: ../../Zotlabs/Module/Import_items.php:121
-msgid "Import Items"
-msgstr "Importer elementer"
+#: ../../Zotlabs/Widget/Activity_filter.php:48
+msgid "Show direct (private) messages"
+msgstr "Vis direktemeldinger (private meldinger)"
-#: ../../Zotlabs/Module/Import_items.php:122
-msgid "Use this form to import existing posts and content from an export file."
+#: ../../Zotlabs/Widget/Activity_filter.php:57
+msgid "Show posts that include events"
msgstr ""
-"Bruk dette skjemaet for å importere eksisterende innlegg og innhold fra en "
-"eksportfil."
-#: ../../Zotlabs/Module/Import_items.php:123
-#: ../../Zotlabs/Module/Import.php:605
-msgid "File to Upload"
-msgstr "Fil som skal lastes opp"
+#: ../../Zotlabs/Widget/Activity_filter.php:63
+msgid "Polls"
+msgstr "Spørreskjema"
-#: ../../Zotlabs/Module/Import_items.php:136
-#: ../../Zotlabs/Module/Import.php:108
-msgid "Imported file is empty."
-msgstr "Importert fil er tom."
+#: ../../Zotlabs/Widget/Activity_filter.php:67
+msgid "Show posts that include polls"
+msgstr "Vis innlegg som inneholder spørreskjema"
-#: ../../Zotlabs/Module/Import_items.php:159
-msgid "Content import completed"
+#: ../../Zotlabs/Widget/Activity_filter.php:90
+#, php-format
+msgid "Show posts related to the %s privacy group"
msgstr ""
-#: ../../Zotlabs/Module/Import_items.php:164
-msgid "Chatroom import completed"
+#: ../../Zotlabs/Widget/Activity_filter.php:99
+msgid "Show my privacy groups"
msgstr ""
-#: ../../Zotlabs/Module/Import_items.php:170
-msgid "Channel calendar import 1/2 completed"
+#: ../../Zotlabs/Widget/Activity_filter.php:123
+msgid "Show posts to this forum"
msgstr ""
-#: ../../Zotlabs/Module/Import_items.php:176
-msgid "Channel calendar import 2/2 completed"
+#: ../../Zotlabs/Widget/Activity_filter.php:134
+msgid "Show forums"
msgstr ""
-#: ../../Zotlabs/Module/Import_items.php:181
-msgid "Menu import completed"
+#: ../../Zotlabs/Widget/Activity_filter.php:148
+msgid "Starred Posts"
msgstr ""
-#: ../../Zotlabs/Module/Import_items.php:186
-msgid "Wiki import completed"
+#: ../../Zotlabs/Widget/Activity_filter.php:152
+msgid "Show posts that I have starred"
msgstr ""
-#: ../../Zotlabs/Module/Import_items.php:191
-msgid "Webpages import completed"
+#: ../../Zotlabs/Widget/Activity_filter.php:163
+msgid "Personal Posts"
msgstr ""
-#: ../../Zotlabs/Module/Rpost.php:117 ../../Zotlabs/Module/Editpost.php:113
-msgid "Edit post"
-msgstr "Endre innlegg"
-
-#: ../../Zotlabs/Module/Block.php:29 ../../Zotlabs/Module/Page.php:39
-msgid "Invalid item."
-msgstr "Ugyldig element."
-
-#: ../../Zotlabs/Module/Block.php:41 ../../Zotlabs/Module/Chanview.php:95
-#: ../../Zotlabs/Module/Cal.php:31 ../../Zotlabs/Module/Page.php:75
-#: ../../Zotlabs/Module/Wall_upload.php:30
-#: ../../Zotlabs/Module/Attach_edit.php:52 ../../Zotlabs/Module/Attach.php:22
-#: ../../extend/addon/hzaddons/articles/Mod_Article_edit.php:44
-#: ../../extend/addon/hzaddons/cards/Mod_Card_edit.php:44
-msgid "Channel not found."
-msgstr "Kanalen ble ikke funnet."
-
-#: ../../Zotlabs/Module/Lang.php:20
-msgid "Language App"
+#: ../../Zotlabs/Widget/Activity_filter.php:167
+msgid "Show posts that mention or involve me"
msgstr ""
-#: ../../Zotlabs/Module/Lang.php:20 ../../Zotlabs/Module/Invite.php:70
-#: ../../extend/addon/hzaddons/xmpp/Mod_Xmpp.php:35
-#: ../../extend/addon/hzaddons/ijpost/Mod_Ijpost.php:35
-msgid "Not Installed"
+#: ../../Zotlabs/Widget/Activity_filter.php:190
+#, php-format
+msgid "Show posts that I have filed to %s"
msgstr ""
-#: ../../Zotlabs/Module/Email_resend.php:12
-#: ../../Zotlabs/Module/Email_validation.php:25
-msgid "Token verification failed."
+#: ../../Zotlabs/Widget/Activity_filter.php:200
+msgid "Show filed post categories"
msgstr ""
-#: ../../Zotlabs/Module/Email_resend.php:30
-msgid "Email verification resent"
+#: ../../Zotlabs/Widget/Activity_filter.php:214
+msgid "Panel search"
msgstr ""
-#: ../../Zotlabs/Module/Email_resend.php:33
-msgid "Unable to resend email verification message."
-msgstr ""
+#: ../../Zotlabs/Widget/Activity_filter.php:224
+msgid "Filter by name"
+msgstr "Filtrer etter navn"
-#: ../../Zotlabs/Module/Setup.php:180
-msgid "$Projectname Server - Setup"
-msgstr "$Projectname-tjener - oppsett"
+#: ../../Zotlabs/Widget/Activity_filter.php:239
+msgid "Remove active filter"
+msgstr ""
-#: ../../Zotlabs/Module/Setup.php:184
-msgid "Could not connect to database."
-msgstr "Fikk ikke kontakt med databasen."
+#: ../../Zotlabs/Widget/Activity_filter.php:255
+msgid "Stream Filters"
+msgstr "Filtere for tidslinjen"
-#: ../../Zotlabs/Module/Setup.php:188
-msgid ""
-"Could not connect to specified site URL. Possible SSL certificate or DNS "
-"issue."
-msgstr ""
-"Fikk ikke kontakt med det angitte nettstedets URL. Problemet kan muligens "
-"skyldes SSL-sertifikatet eller DNS."
+#: ../../Zotlabs/Widget/Channel_activities.php:29
+#: ../../Zotlabs/Module/Go.php:28
+msgid "Welcome"
+msgstr "Velkommen"
-#: ../../Zotlabs/Module/Setup.php:195
-msgid "Could not create table."
-msgstr "Kunne ikke lage tabellen."
+#: ../../Zotlabs/Widget/Channel_activities.php:46
+#, fuzzy
+#| msgid "Recent activity"
+msgid "No recent activities"
+msgstr "Nylig aktivitet"
-#: ../../Zotlabs/Module/Setup.php:201
-msgid "Your site database has been installed."
-msgstr "Databasen til ditt nettsted har blitt installert."
+#: ../../Zotlabs/Widget/Channel_activities.php:214
+msgctxt "noun"
+msgid "new connection"
+msgid_plural "new connections"
+msgstr[0] "ny forbindelse"
+msgstr[1] "nye forbindelser"
-#: ../../Zotlabs/Module/Setup.php:207
-msgid ""
-"You may need to import the file \"install/schema_xxx.sql\" manually using a "
-"database client."
-msgstr ""
-"Du må kanskje importere filen \"install/schmea_xxx.sql\" manuelt ved å bruke "
-"en databaseklient."
+#: ../../Zotlabs/Widget/Channel_activities.php:220
+msgctxt "noun"
+msgid "notice"
+msgid_plural "notices"
+msgstr[0] "varsel"
+msgstr[1] "varsel"
-#: ../../Zotlabs/Module/Setup.php:208 ../../Zotlabs/Module/Setup.php:274
-#: ../../Zotlabs/Module/Setup.php:799
-msgid "Please see the file \"install/INSTALL.txt\"."
-msgstr "Vennligst les filen \"install/INSTALL.txt\"."
+#: ../../Zotlabs/Widget/Affinity.php:36
+#: ../../Zotlabs/Module/Contactedit.php:281
+#: ../../Zotlabs/Module/Connedit.php:579
+msgid "Me"
+msgstr "Meg"
-#: ../../Zotlabs/Module/Setup.php:271
-msgid "System check"
-msgstr "Systemsjekk"
+#: ../../Zotlabs/Widget/Affinity.php:37
+#: ../../Zotlabs/Module/Contactedit.php:282
+#: ../../Zotlabs/Module/Connedit.php:580
+msgid "Family"
+msgstr "Familie"
-#: ../../Zotlabs/Module/Setup.php:275 ../../Zotlabs/Module/Photos.php:955
-#: ../../Zotlabs/Module/Cal.php:200 ../../Zotlabs/Module/Cdav.php:1027
-msgid "Next"
-msgstr "Neste"
+#: ../../Zotlabs/Widget/Affinity.php:39
+#: ../../Zotlabs/Module/Contactedit.php:284
+#: ../../Zotlabs/Module/Connedit.php:582
+msgid "Acquaintances"
+msgstr "Bekjente"
-#: ../../Zotlabs/Module/Setup.php:276
-msgid "Check again"
-msgstr "Sjekk igjen"
+#: ../../Zotlabs/Widget/Affinity.php:40
+#: ../../Zotlabs/Module/Contactedit.php:285
+#: ../../Zotlabs/Module/Connedit.php:583
+#: ../../Zotlabs/Module/Connections.php:97
+#: ../../Zotlabs/Module/Connections.php:111
+msgid "All"
+msgstr "Alle"
-#: ../../Zotlabs/Module/Setup.php:297
-msgid "Database connection"
-msgstr "Databaseforbindelse"
+#: ../../Zotlabs/Widget/Affinity.php:60
+#: ../../Zotlabs/Module/Contactedit.php:618
+msgid "Refresh"
+msgstr "Forny"
-#: ../../Zotlabs/Module/Setup.php:298
-msgid ""
-"In order to install $Projectname we need to know how to connect to your "
-"database."
-msgstr ""
-"For å installere $Projectname må du oppgi hvordan din database kan kontaktes."
+#: ../../Zotlabs/Widget/Admin.php:27 ../../Zotlabs/Module/Admin/Site.php:402
+msgid "Site"
+msgstr "Nettsted"
-#: ../../Zotlabs/Module/Setup.php:299
-msgid ""
-"Please contact your hosting provider or site administrator if you have "
-"questions about these settings."
-msgstr ""
-"Vennligst kontakt din nettstedstilbyder eller nettstedsadministrator hvis du "
-"har spørsmål om disse innstillingene."
+#: ../../Zotlabs/Widget/Admin.php:28
+#: ../../Zotlabs/Module/Admin/Accounts.php:208
+#: ../../Zotlabs/Module/Admin/Accounts.php:227
+#: ../../Zotlabs/Module/Admin.php:97
+msgid "Accounts"
+msgstr "Kontoer"
-#: ../../Zotlabs/Module/Setup.php:300
-msgid ""
-"The database you specify below should already exist. If it does not, please "
-"create it before continuing."
+#: ../../Zotlabs/Widget/Admin.php:28 ../../Zotlabs/Widget/Admin.php:66
+msgid "Member registrations waiting for confirmation"
msgstr ""
-"Databasen du oppgir nedenfor må finnes på forhånd. Hvis den ikke finnes, "
-"vennligst lag den før du fortsetter."
-
-#: ../../Zotlabs/Module/Setup.php:304
-msgid "Database Server Name"
-msgstr "Navn på databasetjener"
-#: ../../Zotlabs/Module/Setup.php:304
-msgid "Default is 127.0.0.1"
-msgstr "Standard er 127.0.0.1"
+#: ../../Zotlabs/Widget/Admin.php:30
+#: ../../Zotlabs/Module/Admin/Security.php:107
+msgid "Security"
+msgstr "Sikkerhet"
-#: ../../Zotlabs/Module/Setup.php:305
-msgid "Database Port"
-msgstr "Databaseport"
+#: ../../Zotlabs/Widget/Admin.php:32 ../../Zotlabs/Module/Admin/Addons.php:125
+#: ../../Zotlabs/Module/Admin/Addons.php:192
+msgid "Addons"
+msgstr "Tillegg"
-#: ../../Zotlabs/Module/Setup.php:305
-msgid "Communication port number - use 0 for default"
-msgstr "Kommunikasjonsportnummer - bruk 0 for standard"
+#: ../../Zotlabs/Widget/Admin.php:33 ../../Zotlabs/Module/Admin/Themes.php:139
+#: ../../Zotlabs/Module/Admin/Themes.php:173
+msgid "Themes"
+msgstr "Utseende"
-#: ../../Zotlabs/Module/Setup.php:306
-msgid "Database Login Name"
-msgstr "Database innloggingsnavn"
+#: ../../Zotlabs/Widget/Admin.php:34
+msgid "Inspect queue"
+msgstr "Inspiser kø"
-#: ../../Zotlabs/Module/Setup.php:307
-msgid "Database Login Password"
-msgstr "Database innloggingspassord"
+#: ../../Zotlabs/Widget/Admin.php:35
+msgid "Queueworker"
+msgstr ""
-#: ../../Zotlabs/Module/Setup.php:308
-msgid "Database Name"
-msgstr "Databasenavn"
+#: ../../Zotlabs/Widget/Admin.php:36 ../../Zotlabs/Module/Admin/Profs.php:169
+msgid "Profile Fields"
+msgstr "Profilfelter"
-#: ../../Zotlabs/Module/Setup.php:309
-msgid "Database Type"
-msgstr "Databasetype"
+#: ../../Zotlabs/Widget/Admin.php:37
+msgid "DB updates"
+msgstr "Databaseoppdateringer"
-#: ../../Zotlabs/Module/Setup.php:311 ../../Zotlabs/Module/Setup.php:351
-msgid "Site administrator email address"
-msgstr "E-postadressen til administrator ved nettstedet"
+#: ../../Zotlabs/Widget/Admin.php:54 ../../Zotlabs/Widget/Admin.php:64
+#: ../../Zotlabs/Module/Admin/Logs.php:84
+msgid "Logs"
+msgstr "Logger"
-#: ../../Zotlabs/Module/Setup.php:311 ../../Zotlabs/Module/Setup.php:351
-msgid ""
-"Your account email address must match this in order to use the web admin "
-"panel."
+#: ../../Zotlabs/Widget/Admin.php:62
+msgid "Addon Features"
msgstr ""
-"Din konto sin e-postadresse må være lik denne for å kunne bruke web-"
-"administrasjonspanelet."
-#: ../../Zotlabs/Module/Setup.php:312 ../../Zotlabs/Module/Setup.php:353
-msgid "Website URL"
-msgstr "Nettstedets URL"
+#: ../../Zotlabs/Widget/Permcats.php:43
+msgid "Add new role"
+msgstr "Ny rolle"
-#: ../../Zotlabs/Module/Setup.php:312 ../../Zotlabs/Module/Setup.php:353
-msgid "Please use SSL (https) URL if available."
-msgstr "Vennligst bruk SSL (https) URL hvis tilgjengelig."
+#: ../../Zotlabs/Widget/Permcats.php:92
+#: ../../Zotlabs/Module/Contactedit.php:395
+msgid "Contact roles"
+msgstr "Kontaktroller"
-#: ../../Zotlabs/Module/Setup.php:313 ../../Zotlabs/Module/Setup.php:355
-msgid "Please select a default timezone for your website"
-msgstr "Vennligst velg en standard tidssone for ditt nettsted"
+#: ../../Zotlabs/Widget/Permcats.php:93
+msgid "Role members"
+msgstr "Medlemmer"
-#: ../../Zotlabs/Module/Setup.php:340
-msgid "Site settings"
-msgstr "Nettstedets innstillinger"
+#: ../../Zotlabs/Widget/Activity.php:55
+msgctxt "widget"
+msgid "Activity"
+msgstr "Aktivitet"
-#: ../../Zotlabs/Module/Setup.php:394
-msgid "PHP version 8.0 or greater is required."
-msgstr ""
+#: ../../Zotlabs/Widget/Photo.php:54 ../../Zotlabs/Widget/Photo_rand.php:63
+msgid "photo/image"
+msgstr "foto/bilde"
-#: ../../Zotlabs/Module/Setup.php:395
-msgid "PHP version"
-msgstr ""
+#: ../../Zotlabs/Widget/Savedsearch.php:81
+msgid "Remove term"
+msgstr "Fjern begrep"
-#: ../../Zotlabs/Module/Setup.php:411
-msgid "Could not find a command line version of PHP in the web server PATH."
-msgstr "Fant ikke en kommandolinjeversjon av PHP i webtjenerens sti (PATH)."
+#: ../../Zotlabs/Widget/Chatroom_members.php:17
+msgid "Chat Members"
+msgstr "Medlemmer"
-#: ../../Zotlabs/Module/Setup.php:412
-msgid ""
-"If you don't have a command line version of PHP installed on server, you "
-"will not be able to run background polling via cron."
-msgstr ""
-"Hvis du ikke har en kommandolinjeversjon av PHP installert på tjeneren, så "
-"vil du ikke kunne kjøre bakgrunnshenting via cron."
+#: ../../Zotlabs/Widget/Suggestedchats.php:36
+msgid "Suggested Chatrooms"
+msgstr "Foreslåtte chatrom"
-#: ../../Zotlabs/Module/Setup.php:416
-msgid "PHP executable path"
-msgstr "PHP-kjørefilens sti"
+#: ../../Zotlabs/Widget/Appcategories.php:49
+msgid "App Categories"
+msgstr "Appkategorier"
-#: ../../Zotlabs/Module/Setup.php:416
-msgid ""
-"Enter full path to php executable. You can leave this blank to continue the "
-"installation."
-msgstr ""
-"Skriv full sti til kjørefilen for PHP. Du kan la denne stå blank for å "
-"fortsette installasjonen."
+#: ../../Zotlabs/Widget/Settings_menu.php:37
+msgid "Account settings"
+msgstr "Kontoinnstillinger"
-#: ../../Zotlabs/Module/Setup.php:421
-msgid "Command line PHP"
-msgstr "Kommandolinje PHP"
+#: ../../Zotlabs/Widget/Settings_menu.php:43
+msgid "Channel settings"
+msgstr "Kanalinnstillinger"
-#: ../../Zotlabs/Module/Setup.php:431
-msgid ""
-"Unable to check command line PHP, as shell_exec() is disabled. This is "
-"required."
-msgstr ""
+#: ../../Zotlabs/Widget/Settings_menu.php:49
+msgid "Privacy settings"
+msgstr "Personverninnstillinger"
-#: ../../Zotlabs/Module/Setup.php:435
-msgid ""
-"The command line version of PHP on your system does not have "
-"\"register_argc_argv\" enabled."
-msgstr ""
-"Kommandolinjeversjonen av PHP på ditt system har ikke \"register_argc_argv\" "
-"påskrudd."
+#: ../../Zotlabs/Widget/Settings_menu.php:56
+msgid "Display settings"
+msgstr "Visningsinnstillinger"
-#: ../../Zotlabs/Module/Setup.php:436
-msgid "This is required for message delivery to work."
-msgstr "Dette er påkrevd for at meldingslevering skal virke."
+#: ../../Zotlabs/Widget/Settings_menu.php:63
+msgid "Manage locations"
+msgstr ""
-#: ../../Zotlabs/Module/Setup.php:439
-msgid "PHP register_argc_argv"
-msgstr "PHP register_argc_argv"
+#: ../../Zotlabs/Widget/Archive.php:49
+msgid "Archives"
+msgstr "Arkiv"
-#: ../../Zotlabs/Module/Setup.php:459
-msgid ""
-"This is not sufficient to upload larger images or files. You should be able "
-"to upload at least 4 MB at once."
+#: ../../Zotlabs/Widget/Cdav.php:41
+msgid "Select Channel"
msgstr ""
-#: ../../Zotlabs/Module/Setup.php:461
-#, php-format
-msgid ""
-"Your max allowed total upload size is set to %s. Maximum size of one file to "
-"upload is set to %s. You are allowed to upload up to %d files at once."
+#: ../../Zotlabs/Widget/Cdav.php:46
+msgid "Read-write"
msgstr ""
-"Den største totale opplastingsstørrelsen du er tillatt er satt til %s. "
-"Filstørrelsen på en enkelt fil er satt til å maksimalt være %s. Du har lov "
-"til å laste opp inntil %d filer samtidig."
-#: ../../Zotlabs/Module/Setup.php:467
-msgid "You can adjust these settings in the server php.ini file."
+#: ../../Zotlabs/Widget/Cdav.php:47
+msgid "Read-only"
msgstr ""
-#: ../../Zotlabs/Module/Setup.php:469
-msgid "PHP upload limits"
-msgstr "PHP opplastingsgrenser"
-
-#: ../../Zotlabs/Module/Setup.php:492
-msgid ""
-"Error: the \"openssl_pkey_new\" function on this system is not able to "
-"generate encryption keys"
+#: ../../Zotlabs/Widget/Cdav.php:133
+msgid "Channel Calendar"
msgstr ""
-"Feil: \"openssl_pkey_new\"-funksjonen på dette systemet er ikke i stand til "
-"å lage krypteringsnøkler"
-#: ../../Zotlabs/Module/Setup.php:493
-msgid ""
-"If running under Windows, please see \"http://www.php.net/manual/en/openssl."
-"installation.php\"."
+#: ../../Zotlabs/Widget/Cdav.php:135 ../../Zotlabs/Widget/Cdav.php:149
+#: ../../Zotlabs/Module/Cdav.php:1046
+msgid "CalDAV Calendars"
msgstr ""
-"Ved kjøring på Windows, vennligst se \"http://www.php.net/manual/en/openssl."
-"installation.php\"."
-
-#: ../../Zotlabs/Module/Setup.php:496
-msgid "Generate encryption keys"
-msgstr "Lag krypteringsnøkler"
-#: ../../Zotlabs/Module/Setup.php:500
-msgid "Error: the sodium encryption library is not installed."
+#: ../../Zotlabs/Widget/Cdav.php:137
+msgid "Shared CalDAV Calendars"
msgstr ""
-#: ../../Zotlabs/Module/Setup.php:502
-#, fuzzy
-#| msgid "Generate encryption keys"
-msgid "Generate ed25519 encryption keys"
-msgstr "Lag krypteringsnøkler"
-
-#: ../../Zotlabs/Module/Setup.php:507
-msgid ""
-"Error: one of \"bcmath\" or \"gmp\" (bigmath library) extensions are "
-"required."
+#: ../../Zotlabs/Widget/Cdav.php:141
+msgid "Share this calendar"
msgstr ""
-#: ../../Zotlabs/Module/Setup.php:509
-msgid "Bigmath library (either bcmath or gmp)"
+#: ../../Zotlabs/Widget/Cdav.php:143
+msgid "Calendar name and color"
msgstr ""
-#: ../../Zotlabs/Module/Setup.php:526
-msgid "libCurl PHP module"
-msgstr "libCurl PHP-modul"
-
-#: ../../Zotlabs/Module/Setup.php:527
-msgid "GD graphics PHP module"
-msgstr "GD graphics PHP-modul"
-
-#: ../../Zotlabs/Module/Setup.php:528
-msgid "OpenSSL PHP module"
-msgstr "OpenSSL PHP-modul"
-
-#: ../../Zotlabs/Module/Setup.php:529
-msgid "PDO database PHP module"
+#: ../../Zotlabs/Widget/Cdav.php:145
+msgid "Create new CalDAV calendar"
msgstr ""
-#: ../../Zotlabs/Module/Setup.php:530
-msgid "mb_string PHP module"
-msgstr "mb_string PHP-modul"
-
-#: ../../Zotlabs/Module/Setup.php:531
-msgid "xml PHP module"
-msgstr "xml PHP modul"
+#: ../../Zotlabs/Widget/Cdav.php:146 ../../Zotlabs/Widget/Cdav.php:184
+#: ../../Zotlabs/Storage/Browser.php:372 ../../Zotlabs/Storage/Browser.php:548
+#: ../../Zotlabs/Module/Webpages.php:248 ../../Zotlabs/Module/Connedit.php:747
+#: ../../Zotlabs/Module/Layouts.php:183 ../../Zotlabs/Module/Menu.php:182
+#: ../../Zotlabs/Module/Blocks.php:157 ../../Zotlabs/Module/New_channel.php:190
+#: ../../Zotlabs/Module/Cdav.php:1050 ../../Zotlabs/Module/Cdav.php:1383
+msgid "Create"
+msgstr "Lag"
-#: ../../Zotlabs/Module/Setup.php:532
-msgid "zip PHP module"
+#: ../../Zotlabs/Widget/Cdav.php:147
+msgid "Calendar Name"
msgstr ""
-#: ../../Zotlabs/Module/Setup.php:533
-#, fuzzy
-#| msgid "xml PHP module"
-msgid "intl PHP module"
-msgstr "xml PHP modul"
-
-#: ../../Zotlabs/Module/Setup.php:537 ../../Zotlabs/Module/Setup.php:539
-msgid "Apache mod_rewrite module"
-msgstr "Apache mod_rewrite-modul"
-
-#: ../../Zotlabs/Module/Setup.php:537
-msgid ""
-"Error: Apache webserver mod-rewrite module is required but not installed."
+#: ../../Zotlabs/Widget/Cdav.php:148
+msgid "Calendar Tools"
msgstr ""
-"Feil: Apache web-tjenerens mod-rewrite-modul er påkrevd, men ikke installert."
-#: ../../Zotlabs/Module/Setup.php:543 ../../Zotlabs/Module/Setup.php:546
-msgid "exec"
+#: ../../Zotlabs/Widget/Cdav.php:149 ../../Zotlabs/Module/Cdav.php:1046
+msgid "Channel Calendars"
msgstr ""
-#: ../../Zotlabs/Module/Setup.php:543
-msgid ""
-"Error: exec is required but is either not installed or has been disabled in "
-"php.ini"
+#: ../../Zotlabs/Widget/Cdav.php:150
+msgid "Import calendar"
msgstr ""
-#: ../../Zotlabs/Module/Setup.php:549 ../../Zotlabs/Module/Setup.php:552
-msgid "shell_exec"
+#: ../../Zotlabs/Widget/Cdav.php:151
+msgid "Select a calendar to import to"
msgstr ""
-#: ../../Zotlabs/Module/Setup.php:549
-msgid ""
-"Error: shell_exec is required but is either not installed or has been "
-"disabled in php.ini"
+#: ../../Zotlabs/Widget/Cdav.php:178
+msgid "Addressbooks"
msgstr ""
-#: ../../Zotlabs/Module/Setup.php:557
-msgid "Error: libCURL PHP module required but not installed."
-msgstr "Feil: libCURL PHP-modul er påkrevd, men er ikke installert."
-
-#: ../../Zotlabs/Module/Setup.php:561
-msgid ""
-"Error: GD PHP module with JPEG support or ImageMagick graphics library "
-"required but not installed."
+#: ../../Zotlabs/Widget/Cdav.php:180
+msgid "Addressbook name"
msgstr ""
-#: ../../Zotlabs/Module/Setup.php:565
-msgid "Error: openssl PHP module required but not installed."
-msgstr "Feil: openssl PHP-modul er påkrevd, men er ikke installert."
-
-#: ../../Zotlabs/Module/Setup.php:571
-msgid ""
-"Error: PDO database PHP module missing a driver for either mysql or pgsql."
+#: ../../Zotlabs/Widget/Cdav.php:182
+msgid "Create new addressbook"
msgstr ""
-#: ../../Zotlabs/Module/Setup.php:576
-msgid "Error: PDO database PHP module required but not installed."
+#: ../../Zotlabs/Widget/Cdav.php:183
+msgid "Addressbook Name"
msgstr ""
-#: ../../Zotlabs/Module/Setup.php:580
-msgid "Error: mb_string PHP module required but not installed."
-msgstr "Feil: mb_string PHP-modul er påkrevd, men er ikke installert."
-
-#: ../../Zotlabs/Module/Setup.php:584
-msgid "Error: xml PHP module required for DAV but not installed."
-msgstr "Feil: XML PHP modul er påkrevet for DAV, men den er ikke installert."
-
-#: ../../Zotlabs/Module/Setup.php:588
-msgid "Error: zip PHP module required but not installed."
+#: ../../Zotlabs/Widget/Cdav.php:185
+msgid "Addressbook Tools"
msgstr ""
-#: ../../Zotlabs/Module/Setup.php:592
-#, fuzzy
-#| msgid "Error: openssl PHP module required but not installed."
-msgid "Error: intl PHP module required but not installed."
-msgstr "Feil: openssl PHP-modul er påkrevd, men er ikke installert."
-
-#: ../../Zotlabs/Module/Setup.php:611 ../../Zotlabs/Module/Setup.php:620
-msgid ".htconfig.php is writable"
-msgstr ".htconfig.php kan skrives til"
-
-#: ../../Zotlabs/Module/Setup.php:616
-msgid ""
-"The web installer needs to be able to create a file called \".htconfig.php\" "
-"in the top folder of your web server and it is unable to do so."
+#: ../../Zotlabs/Widget/Cdav.php:186
+msgid "Import addressbook"
msgstr ""
-"Web-installasjonen må kunne lage en fil kalt \".htconfig.php\" i "
-"toppkatalogen til web-tjeneren din, men dette får den ikke til."
-#: ../../Zotlabs/Module/Setup.php:617
-msgid ""
-"This is most often a permission setting, as the web server may not be able "
-"to write files in your folder - even if you can."
+#: ../../Zotlabs/Widget/Cdav.php:187
+msgid "Select an addressbook to import to"
msgstr ""
-"Dette er oftest tillatelsesinnstilling, ettersom webtjeneren kanskje kan "
-"skrive til filer i din mappe - selv om du kan."
-#: ../../Zotlabs/Module/Setup.php:618
-msgid "Please see install/INSTALL.txt for additional information."
+#: ../../Zotlabs/Widget/Privacygroups.php:45
+msgid "Add new group"
msgstr ""
-#: ../../Zotlabs/Module/Setup.php:634
-msgid ""
-"This software uses the Smarty3 template engine to render its web views. "
-"Smarty3 compiles templates to PHP to speed up rendering."
-msgstr ""
+#: ../../Zotlabs/Widget/Privacygroups.php:54
+#: ../../Zotlabs/Module/Contactedit.php:429
+msgid "Privacy groups"
+msgstr "Personverngrupper"
-#: ../../Zotlabs/Module/Setup.php:635
-#, php-format
-msgid ""
-"In order to store these compiled templates, the web server needs to have "
-"write access to the directory %s under the top level web folder."
+#: ../../Zotlabs/Storage/Browser.php:296
+msgid "Change filename to"
msgstr ""
-#: ../../Zotlabs/Module/Setup.php:636 ../../Zotlabs/Module/Setup.php:657
-msgid ""
-"Please ensure that the user that your web server runs as (e.g. www-data) has "
-"write access to this folder."
+#: ../../Zotlabs/Storage/Browser.php:313 ../../Zotlabs/Storage/Browser.php:398
+msgid "Select a target location"
msgstr ""
-"Vennligst sikre at brukeren som din web-tjeneste kjører som (for eksempel "
-"www-data) har skrivetilgang til denne katalogen."
-#: ../../Zotlabs/Module/Setup.php:637
-#, php-format
-msgid ""
-"Note: as a security measure, you should give the web server write access to "
-"%s only--not the template files (.tpl) that it contains."
+#: ../../Zotlabs/Storage/Browser.php:314 ../../Zotlabs/Storage/Browser.php:399
+msgid "Copy to target location"
msgstr ""
-"Merknad: som et sikkerhetstiltak bør du bare gi webtjerenn skrivetilgang til "
-"%s - ikke til malfilene (.tpl) som den inneholder."
-
-#: ../../Zotlabs/Module/Setup.php:640
-#, php-format
-msgid "%s is writable"
-msgstr "%s kan skrives til"
-#: ../../Zotlabs/Module/Setup.php:656
-msgid ""
-"This software uses the store directory to save uploaded files. The web "
-"server needs to have write access to the store directory under the top level "
-"web folder"
+#: ../../Zotlabs/Storage/Browser.php:315 ../../Zotlabs/Storage/Browser.php:397
+msgid "Set permissions for all files and sub folders"
msgstr ""
-#: ../../Zotlabs/Module/Setup.php:660
-msgid "store is writable"
-msgstr "lageret kan skrives til"
-
-#: ../../Zotlabs/Module/Setup.php:692
-msgid ""
-"SSL certificate cannot be validated. Fix certificate or disable https access "
-"to this site."
-msgstr ""
-"SSL-sertifikatet kan ikke kontrolleres. Fiks sertifikatet eller skru av "
-"https tilgang til dette nettstedet."
+#: ../../Zotlabs/Storage/Browser.php:316
+msgid "Notify your contacts about this file"
+msgstr "Varsle dine kontakter om denne filen"
-#: ../../Zotlabs/Module/Setup.php:693
-msgid ""
-"If you have https access to your website or allow connections to TCP port "
-"443 (the https: port), you MUST use a browser-valid certificate. You MUST "
-"NOT use self-signed certificates!"
+#: ../../Zotlabs/Storage/Browser.php:355
+msgid "File category"
msgstr ""
-"Hvis du har HTTPS-tilgang til ditt nettsted eller tillater forbindelser til "
-"TCP port 443 (HTTPS-porten), så MÅ du bruke nettlesergodkjent sertifkater. "
-"Du MÅ IKKE bruke egensignert sertifikater!"
-#: ../../Zotlabs/Module/Setup.php:694
-msgid ""
-"This restriction is incorporated because public posts from you may for "
-"example contain references to images on your own hub."
-msgstr ""
-"Denne begrensningen er tatt inn fordi offentlige innlegg fra deg kan for "
-"eksempel inneholde referanser til bilder på din egen hub."
+#: ../../Zotlabs/Storage/Browser.php:369
+msgid "Total"
+msgstr "Totalt"
-#: ../../Zotlabs/Module/Setup.php:695
-msgid ""
-"If your certificate is not recognized, members of other sites (who may "
-"themselves have valid certificates) will get a warning message on their own "
-"site complaining about security issues."
-msgstr ""
-"Hvis sertifikatet ditt ikke gjenkjennes, så vil medlemmer på andre "
-"nettsteder (som selv kan ha godkjente sertifikater) få en beskjed med en "
-"advarsel på deres eget nettsted som klager over sikkerhetsproblemer."
+#: ../../Zotlabs/Storage/Browser.php:371
+msgid "Shared"
+msgstr "Delt"
-#: ../../Zotlabs/Module/Setup.php:696
-msgid ""
-"This can cause usability issues elsewhere (not just on your own site) so we "
-"must insist on this requirement."
+#: ../../Zotlabs/Storage/Browser.php:373
+msgid "Add Files"
msgstr ""
-"Dette kan gi problemer med brukervennlighet (ikke bare på ditt eget "
-"nettsted), så vi må insistere på dette kravet."
-#: ../../Zotlabs/Module/Setup.php:697
-msgid ""
-"Providers are available that issue free certificates which are browser-valid."
-msgstr ""
-"Det finnes tilbydere som utsteder gratis sertifikater som er gyldige i "
-"nettlesere."
+#: ../../Zotlabs/Storage/Browser.php:388
+#: ../../Zotlabs/Module/Sharedwithme.php:110
+msgid "Last Modified"
+msgstr "Sist endret"
-#: ../../Zotlabs/Module/Setup.php:698
-msgid ""
-"If you are confident that the certificate is valid and signed by a trusted "
-"authority, check to see if you have failed to install an intermediate cert. "
-"These are not normally required by browsers, but are required for server-to-"
-"server communications."
-msgstr ""
+#: ../../Zotlabs/Storage/Browser.php:389
+msgid "parent"
+msgstr "opp et nivå"
-#: ../../Zotlabs/Module/Setup.php:700
-msgid "SSL certificate validation"
-msgstr "SSL sertifikat-kontroll"
+#: ../../Zotlabs/Storage/Browser.php:394
+#: ../../Zotlabs/Module/Filestorage.php:206
+msgid "Copy/paste this code to attach file to a post"
+msgstr "Kopier og lim inn denne koden for å legge til filen i et innlegg"
-#: ../../Zotlabs/Module/Setup.php:706
-msgid ""
-"Url rewrite in .htaccess is not working. Check your server configuration."
-"Test: "
-msgstr ""
-"URL omskriving (rewrite) i .htaccess virker ikke. Sjekk konfigurasjonen til "
-"tjeneren din. Test: "
+#: ../../Zotlabs/Storage/Browser.php:395
+#: ../../Zotlabs/Module/Filestorage.php:207
+msgid "Copy/paste this URL to link file from a web page"
+msgstr "Kopier og lim inn denne URL-en for å lenke til filen fra en webside"
-#: ../../Zotlabs/Module/Setup.php:709
-msgid "Url rewrite is working"
-msgstr "URL rewrite virker"
+#: ../../Zotlabs/Storage/Browser.php:406
+msgid "Select All"
+msgstr "Velg alle"
-#: ../../Zotlabs/Module/Setup.php:722
-msgid ""
-"The database configuration file \".htconfig.php\" could not be written. "
-"Please use the enclosed text to create a configuration file in your web "
-"server root."
+#: ../../Zotlabs/Storage/Browser.php:407
+msgid "Bulk Actions"
msgstr ""
-"Databasekonfigurasjonsfilen \".htconfig.php\" kunne ikke skrives. Vennligst "
-"bruk den medfølgende teksten for å lage en konfigurasjonsfil i toppkatalogen "
-"av din web-tjener."
-#: ../../Zotlabs/Module/Setup.php:751
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:401
-msgid "Errors encountered creating database tables."
-msgstr "Feil oppstod under opprettelsen av databasetabeller."
-
-#: ../../Zotlabs/Module/Setup.php:797
-msgid "<h1>What next?</h1>"
+#: ../../Zotlabs/Storage/Browser.php:408
+msgid "Adjust Permissions"
msgstr ""
-#: ../../Zotlabs/Module/Setup.php:798
-msgid ""
-"IMPORTANT: You will need to [manually] setup a scheduled task for the poller."
+#: ../../Zotlabs/Storage/Browser.php:409
+msgid "Move or Copy"
msgstr ""
-"VIKTIG: Du må [manuelt] sette opp en automatisert tidfestet oppgave til "
-"bakgrunnshenteren."
-
-#: ../../Zotlabs/Module/Bookmarks.php:62
-msgid "Bookmark added"
-msgstr "Bokmerke lagt til"
-
-#: ../../Zotlabs/Module/Bookmarks.php:101
-msgid "My Connections Bookmarks"
-msgstr "Mine forbindelsers bokmerker"
-#: ../../Zotlabs/Module/Z6trans.php:19
-msgid "Update to Hubzilla 5.0 step 2"
+#: ../../Zotlabs/Storage/Browser.php:412
+msgid "Info"
msgstr ""
-#: ../../Zotlabs/Module/Z6trans.php:21
-msgid "To complete the update please run"
+#: ../../Zotlabs/Storage/Browser.php:413
+msgid "Rename"
msgstr ""
-#: ../../Zotlabs/Module/Z6trans.php:23
-msgid "php util/z6convert.php"
+#: ../../Zotlabs/Storage/Browser.php:415
+msgid "Attachment BBcode"
msgstr ""
-#: ../../Zotlabs/Module/Z6trans.php:25
-msgid "from the terminal."
+#: ../../Zotlabs/Storage/Browser.php:416
+msgid "Embed BBcode"
msgstr ""
-#: ../../Zotlabs/Module/Regate.php:85
-msgid "Email resent"
+#: ../../Zotlabs/Storage/Browser.php:417
+msgid "Link BBcode"
msgstr ""
-#: ../../Zotlabs/Module/Regate.php:85
-msgid "Email resend failed"
-msgstr ""
+#: ../../Zotlabs/Storage/Browser.php:490
+#, php-format
+msgid "You are using %1$s of your available file storage."
+msgstr "Du bruker %1$s av din tilgjengelige lagringsplass."
-#: ../../Zotlabs/Module/Regate.php:110
-msgid "Verification successful"
-msgstr ""
+#: ../../Zotlabs/Storage/Browser.php:495
+#, php-format
+msgid "You are using %1$s of %2$s available file storage. (%3$s&#37;)"
+msgstr "Du bruker %1$s av %2$s tilgjengelig lagringsplass (%3$s&#37;)"
-#: ../../Zotlabs/Module/Regate.php:154
-msgid "Account successfull created"
-msgstr ""
+#: ../../Zotlabs/Storage/Browser.php:506
+msgid "WARNING:"
+msgstr "ADVARSEL:"
-#: ../../Zotlabs/Module/Regate.php:212
-msgid "Channel successfull created"
-msgstr ""
+#: ../../Zotlabs/Storage/Browser.php:547
+msgid "Create new folder"
+msgstr "Lag ny mappe"
-#: ../../Zotlabs/Module/Regate.php:218
-msgid "Automatic channel creation failed. Please create a channel."
-msgstr ""
+#: ../../Zotlabs/Storage/Browser.php:549
+msgid "Upload file"
+msgstr "Last opp fil"
-#: ../../Zotlabs/Module/Regate.php:230
-msgid "Account creation error"
+#: ../../Zotlabs/Storage/Browser.php:561
+msgid "Drop files here to immediately upload"
msgstr ""
-#: ../../Zotlabs/Module/Regate.php:242
-msgid "Verify failed"
+#: ../../Zotlabs/Storage/Browser.php:562
+#: ../../Zotlabs/Module/Filestorage.php:211
+msgid "Show in your contacts shared folder"
msgstr ""
-#: ../../Zotlabs/Module/Regate.php:247
-msgid "Token verification failed"
+#: ../../Zotlabs/Storage/Browser.php:564
+msgid ""
+"You can select files via the upload button or drop them right here or into "
+"an existing folder."
msgstr ""
-#: ../../Zotlabs/Module/Regate.php:252
-msgid "Request not inside time frame"
-msgstr ""
+#: ../../Zotlabs/Module/Page.php:39 ../../Zotlabs/Module/Block.php:29
+msgid "Invalid item."
+msgstr "Ugyldig element."
-#: ../../Zotlabs/Module/Regate.php:258 ../../Zotlabs/Module/Regate.php:288
-msgid "Identity unknown"
+#: ../../Zotlabs/Module/Page.php:174
+msgid ""
+"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
+"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, "
+"quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo "
+"consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse "
+"cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat "
+"non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
msgstr ""
+"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
+"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, "
+"quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo "
+"consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse "
+"cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat "
+"non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
-#: ../../Zotlabs/Module/Regate.php:264
-msgid "dId2 mistaken"
+#: ../../Zotlabs/Module/Import_progress.php:40
+msgid "Item sync completed!"
msgstr ""
-#: ../../Zotlabs/Module/Regate.php:292
-msgid "Your Registration ID"
+#: ../../Zotlabs/Module/Import_progress.php:63
+#: ../../Zotlabs/Module/Import_progress.php:112
+msgid "Import host does not seem to be online or compatible"
msgstr ""
-#: ../../Zotlabs/Module/Regate.php:305 ../../Zotlabs/Module/Regate.php:397
-#: ../../Zotlabs/Module/Regate.php:429
-msgid "Registration verification"
+#: ../../Zotlabs/Module/Import_progress.php:74
+msgid "Item sync completed but no items were found!"
msgstr ""
-#: ../../Zotlabs/Module/Regate.php:312 ../../Zotlabs/Module/Regate.php:434
-msgid "Hold on, you can start verification in"
+#: ../../Zotlabs/Module/Import_progress.php:91
+msgid "File sync completed!"
msgstr ""
-#: ../../Zotlabs/Module/Regate.php:313
-msgid "Please remember your verification token for ID"
+#: ../../Zotlabs/Module/Import_progress.php:123
+msgid "File sync completed but no files were found!"
msgstr ""
-#: ../../Zotlabs/Module/Regate.php:315
-msgid "Token validity"
+#: ../../Zotlabs/Module/Import_progress.php:141
+msgid "Channel clone status"
msgstr ""
-#: ../../Zotlabs/Module/Regate.php:351
-msgid "Resend email"
+#: ../../Zotlabs/Module/Import_progress.php:142
+msgid "Item sync status"
msgstr ""
-#: ../../Zotlabs/Module/Regate.php:356
-msgid "Registration status"
+#: ../../Zotlabs/Module/Import_progress.php:143
+msgid "File sync status"
msgstr ""
-#: ../../Zotlabs/Module/Regate.php:359
-msgid "Verification successful!"
+#: ../../Zotlabs/Module/Import_progress.php:150
+msgid "Channel cloning completed!"
msgstr ""
-#: ../../Zotlabs/Module/Regate.php:360
-msgid "Your login ID is"
+#: ../../Zotlabs/Module/Import_progress.php:151
+msgid "Resume"
msgstr ""
-#: ../../Zotlabs/Module/Regate.php:361
-msgid ""
-"After your account has been approved by our administrator you will be able "
-"to login with your login ID and your provided password."
+#: ../../Zotlabs/Module/Import_progress.php:152
+msgid "Only resume if sync stalled!"
msgstr ""
-#: ../../Zotlabs/Module/Regate.php:373
-msgid "Registration request revoked"
-msgstr ""
+#: ../../Zotlabs/Module/Display.php:27 ../../Zotlabs/Module/Search.php:24
+#: ../../Zotlabs/Module/Directory.php:72 ../../Zotlabs/Module/Directory.php:77
+#: ../../Zotlabs/Module/Viewconnections.php:23
+#: ../../Zotlabs/Module/Photos.php:511
+msgid "Public access denied."
+msgstr "Offentlig tilgang avvist."
-#: ../../Zotlabs/Module/Regate.php:374
-msgid "Sorry for any inconvience. Thank you for your response."
+#: ../../Zotlabs/Module/Display.php:53 ../../Zotlabs/Module/Oep.php:83
+#: ../../Zotlabs/Module/Pubstream.php:55 ../../Zotlabs/Module/Channel.php:195
+msgid "Malformed message id."
msgstr ""
-#: ../../Zotlabs/Module/Regate.php:398
-msgid "Please enter your verification token for ID"
-msgstr ""
+#: ../../Zotlabs/Module/Display.php:59 ../../Zotlabs/Module/Display.php:120
+#: ../../Zotlabs/Module/Display.php:391 ../../Zotlabs/Module/Thing.php:120
+#: ../../Zotlabs/Module/Filestorage.php:29 ../../Zotlabs/Module/Viewsrc.php:25
+#: ../../Zotlabs/Module/Admin/Themes.php:73
+#: ../../Zotlabs/Module/Admin/Addons.php:42 ../../Zotlabs/Module/Admin.php:63
+msgid "Item not found."
+msgstr "Elementet ble ikke funnet."
-#: ../../Zotlabs/Module/Regate.php:399 ../../Zotlabs/Module/Regate.php:426
-msgid "Please check your email!"
-msgstr ""
+#: ../../Zotlabs/Module/Display.php:95 ../../Zotlabs/Module/Network.php:215
+#: ../../Zotlabs/Module/Hq.php:101 ../../Zotlabs/Module/Pubstream.php:98
+#: ../../Zotlabs/Module/Channel.php:279 ../../Zotlabs/Module/Rpost.php:111
+msgid "Reset form"
+msgstr "Nullstill skjema"
-#: ../../Zotlabs/Module/Regate.php:409
-msgid "Verification token"
+#: ../../Zotlabs/Module/Display.php:315 ../../Zotlabs/Module/Channel.php:503
+msgid ""
+"You must enable javascript for your browser to be able to view this content."
msgstr ""
-#: ../../Zotlabs/Module/Regate.php:420
-msgid "ID expired"
+#: ../../Zotlabs/Module/Display.php:335
+msgid "Article"
msgstr ""
-#: ../../Zotlabs/Module/Regate.php:435
-msgid "You will require the verification token for ID"
+#: ../../Zotlabs/Module/Display.php:384
+msgid "Item has been removed."
msgstr ""
-#: ../../Zotlabs/Module/Regate.php:444
-msgid "Unknown or expired ID"
-msgstr ""
+#: ../../Zotlabs/Module/Cover_photo.php:86
+#: ../../Zotlabs/Module/Profile_photo.php:129
+msgid "Image uploaded but image cropping failed."
+msgstr "Bildet ble lastet opp, men beskjæring av bildet mislyktes."
-#: ../../Zotlabs/Module/Regate.php:455
-msgid "dId2 malformed"
-msgstr ""
+#: ../../Zotlabs/Module/Cover_photo.php:195
+#: ../../Zotlabs/Module/Cover_photo.php:252
+msgid "Cover Photos"
+msgstr "Forsidebilder"
-#: ../../Zotlabs/Module/Api.php:76 ../../Zotlabs/Module/Api.php:97
-msgid "Authorize application connection"
-msgstr "Tillat programforbindelse"
+#: ../../Zotlabs/Module/Cover_photo.php:211
+#: ../../Zotlabs/Module/Profile_photo.php:185
+msgid "Image resize failed."
+msgstr "Endring av bildestørrelse mislyktes."
-#: ../../Zotlabs/Module/Api.php:77
-msgid "Return to your app and insert this Security Code:"
-msgstr ""
+#: ../../Zotlabs/Module/Cover_photo.php:263
+#: ../../Zotlabs/Module/Profile_photo.php:350
+msgid "Image upload failed."
+msgstr "Opplasting av bildet mislyktes."
-#: ../../Zotlabs/Module/Api.php:87
-msgid "Please login to continue."
-msgstr "Vennligst logg inn for å fortsette."
+#: ../../Zotlabs/Module/Cover_photo.php:280
+#: ../../Zotlabs/Module/Profile_photo.php:370
+msgid "Unable to process image."
+msgstr "Kan ikke behandle bildet."
-#: ../../Zotlabs/Module/Api.php:99
-msgid ""
-"Do you want to authorize this application to access your posts and contacts, "
-"and/or create new posts for you?"
-msgstr ""
-"Vil du tillate dette programmet å få tilgang til dine innlegg og kontakter, "
-"og/eller lage nye innlegg for deg?"
+#: ../../Zotlabs/Module/Cover_photo.php:325
+#: ../../Zotlabs/Module/Cover_photo.php:340
+#: ../../Zotlabs/Module/Profile_photo.php:432
+#: ../../Zotlabs/Module/Profile_photo.php:497
+msgid "Photo not available."
+msgstr "Bildet er ikke tilgjengelig."
-#: ../../Zotlabs/Module/Channel_calendar.php:62
-msgid "Event can not end before it has started."
-msgstr "Hendelsen kan ikke slutte før den starter."
+#: ../../Zotlabs/Module/Cover_photo.php:376
+msgid "Your cover photo may be visible to anybody on the internet"
+msgstr "Omslagsbildet ditt vil være synlig for alle på internett"
-#: ../../Zotlabs/Module/Channel_calendar.php:64
-#: ../../Zotlabs/Module/Channel_calendar.php:72
-#: ../../Zotlabs/Module/Channel_calendar.php:87
-msgid "Unable to generate preview."
-msgstr "Klarer ikke å lage forhåndsvisning."
+#: ../../Zotlabs/Module/Cover_photo.php:378
+msgid "Upload File:"
+msgstr "Last opp fil:"
-#: ../../Zotlabs/Module/Channel_calendar.php:70
-msgid "Event title and start time are required."
-msgstr "Hendelsestittel og starttidspunkt er påkrevd."
+#: ../../Zotlabs/Module/Cover_photo.php:379
+msgid "Select a profile:"
+msgstr "Velg en profil:"
-#: ../../Zotlabs/Module/Channel_calendar.php:85
-#: ../../Zotlabs/Module/Channel_calendar.php:226
-msgid "Event not found."
-msgstr "Hendelsen ble ikke funnet."
+#: ../../Zotlabs/Module/Cover_photo.php:380
+#, fuzzy
+msgid "Change Cover Photo"
+msgstr "Endre omslagsbilde"
-#: ../../Zotlabs/Module/Channel_calendar.php:370
-msgid "Edit event"
-msgstr "Endre hendelse"
+#: ../../Zotlabs/Module/Cover_photo.php:384
+#: ../../Zotlabs/Module/Cover_photo.php:385
+#: ../../Zotlabs/Module/Profile_photo.php:551
+#: ../../Zotlabs/Module/Profile_photo.php:552
+msgid "Use a photo from your albums"
+msgstr ""
-#: ../../Zotlabs/Module/Channel_calendar.php:372
-msgid "Delete event"
-msgstr "Slett hendelse"
+#: ../../Zotlabs/Module/Cover_photo.php:396
+msgid "Select existing photo"
+msgstr "Velg eksisterende bilde"
-#: ../../Zotlabs/Module/Channel_calendar.php:392
-#: ../../Zotlabs/Module/Cal.php:161 ../../Zotlabs/Module/Cdav.php:935
-#, fuzzy
-msgid "Link to source"
-msgstr "Lenke til kilde"
+#: ../../Zotlabs/Module/Cover_photo.php:413
+#: ../../Zotlabs/Module/Profile_photo.php:581
+msgid "Crop Image"
+msgstr "Beskjær bildet"
-#: ../../Zotlabs/Module/Channel_calendar.php:406
-msgid "calendar"
-msgstr "kalender"
+#: ../../Zotlabs/Module/Cover_photo.php:414
+#: ../../Zotlabs/Module/Profile_photo.php:582
+msgid "Please adjust the image cropping for optimum viewing."
+msgstr "Vennligst juster bildebeskjæringen for optimal visning."
-#: ../../Zotlabs/Module/Channel_calendar.php:493
-msgid "Failed to remove event"
-msgstr "Mislyktes med å slette hendelse"
+#: ../../Zotlabs/Module/Cover_photo.php:416
+msgid "Done Editing"
+msgstr "Lagre endringer"
-#: ../../Zotlabs/Module/Lockview.php:101
-msgid "Remote privacy information not available"
-msgstr ""
+#: ../../Zotlabs/Module/Editwebpage.php:139
+msgid "Page link"
+msgstr "Sidelenke"
-#: ../../Zotlabs/Module/Lockview.php:144 ../../Zotlabs/Module/Lockview.php:203
-#: ../../Zotlabs/Module/Acl.php:124
-msgctxt "acl"
-msgid "Profile"
-msgstr "Profil"
+#: ../../Zotlabs/Module/Editwebpage.php:166
+msgid "Edit Webpage"
+msgstr "Endre webside"
-#: ../../Zotlabs/Module/Lockview.php:155 ../../Zotlabs/Module/Lockview.php:212
+#: ../../Zotlabs/Module/Sse_bs.php:657
+msgid "Private forum"
+msgstr ""
+
+#: ../../Zotlabs/Module/Sse_bs.php:657
#, fuzzy
-msgid "Privacy group"
-msgstr "Personverngruppe:"
+msgid "Public forum"
+msgstr "Offentlig forum:"
-#: ../../Zotlabs/Module/Lockview.php:183
-msgid "Item"
+#: ../../Zotlabs/Module/Webpages.php:67
+msgid "Import Webpage Elements"
msgstr ""
-#: ../../Zotlabs/Module/Lockview.php:230
-#, php-format
-msgid "Click to copy link to this ressource for guest %s to clipboard"
+#: ../../Zotlabs/Module/Webpages.php:68
+msgid "Import selected"
msgstr ""
-#: ../../Zotlabs/Module/Lockview.php:230
-msgid "Link copied"
+#: ../../Zotlabs/Module/Webpages.php:90
+msgid "Export Webpage Elements"
msgstr ""
-#: ../../Zotlabs/Module/Lockview.php:235
-msgid "Access"
-msgstr "Tilgang"
-
-#: ../../Zotlabs/Module/Lockview.php:236 ../../Zotlabs/Widget/Tokens.php:49
-msgid "Guest access"
+#: ../../Zotlabs/Module/Webpages.php:91
+msgid "Export selected"
msgstr ""
-#: ../../Zotlabs/Module/Lockview.php:237
-msgid "OCAP access"
-msgstr ""
+#: ../../Zotlabs/Module/Webpages.php:257
+msgid "Actions"
+msgstr "Handlinger"
-#: ../../Zotlabs/Module/Editlayout.php:128 ../../Zotlabs/Module/Layouts.php:129
-#: ../../Zotlabs/Module/Layouts.php:187
-msgid "Layout Name"
-msgstr "Layout-navn"
+#: ../../Zotlabs/Module/Webpages.php:258
+msgid "Page Link"
+msgstr "Sidelenke"
-#: ../../Zotlabs/Module/Editlayout.php:129 ../../Zotlabs/Module/Layouts.php:132
-msgid "Layout Description (Optional)"
-msgstr "Layoutens beskrivelse (valgfritt)"
+#: ../../Zotlabs/Module/Webpages.php:259
+msgid "Page Title"
+msgstr "Sidetittel"
-#: ../../Zotlabs/Module/Editlayout.php:137
-msgid "Edit Layout"
-msgstr "Endre layout"
+#: ../../Zotlabs/Module/Webpages.php:260 ../../Zotlabs/Module/Layouts.php:189
+#: ../../Zotlabs/Module/Menu.php:178 ../../Zotlabs/Module/Blocks.php:155
+msgid "Created"
+msgstr "Laget"
-#: ../../Zotlabs/Module/Photos.php:84 ../../Zotlabs/Module/Photos.php:103
-msgid "Album not found."
-msgstr "Albumet ble ikke funnet."
+#: ../../Zotlabs/Module/Webpages.php:261 ../../Zotlabs/Module/Layouts.php:190
+#: ../../Zotlabs/Module/Menu.php:179 ../../Zotlabs/Module/Blocks.php:156
+msgid "Edited"
+msgstr "Endret"
-#: ../../Zotlabs/Module/Photos.php:93
-msgid "Delete Album"
-msgstr "Slett album"
+#: ../../Zotlabs/Module/Webpages.php:289
+msgid "Invalid file type."
+msgstr ""
-#: ../../Zotlabs/Module/Photos.php:165 ../../Zotlabs/Module/Photos.php:1057
-msgid "Delete Photo"
-msgstr "Slett bilde"
+#: ../../Zotlabs/Module/Webpages.php:301
+msgid "Error opening zip file"
+msgstr ""
-#: ../../Zotlabs/Module/Photos.php:511
-#: ../../Zotlabs/Module/Viewconnections.php:23
-#: ../../Zotlabs/Module/Directory.php:72 ../../Zotlabs/Module/Directory.php:77
-#: ../../Zotlabs/Module/Display.php:27 ../../Zotlabs/Module/Search.php:24
-msgid "Public access denied."
-msgstr "Offentlig tilgang avvist."
+#: ../../Zotlabs/Module/Webpages.php:312
+msgid "Invalid folder path."
+msgstr ""
-#: ../../Zotlabs/Module/Photos.php:522
-msgid "No photos selected"
-msgstr "Ingen bilder valgt"
+#: ../../Zotlabs/Module/Webpages.php:339
+msgid "No webpage elements detected."
+msgstr ""
-#: ../../Zotlabs/Module/Photos.php:571
-msgid "Access to this item is restricted."
-msgstr "Tilgang til dette elementet er begrenset."
+#: ../../Zotlabs/Module/Webpages.php:414
+msgid "Import complete."
+msgstr ""
-#: ../../Zotlabs/Module/Photos.php:614
+#: ../../Zotlabs/Module/Impel.php:188
#, php-format
-msgid "%1$.2f MB photo storage used."
-msgstr "%1$.2f MB lagringsplass til bilder er brukt."
+msgid "%s element installed"
+msgstr "%s element installert"
-#: ../../Zotlabs/Module/Photos.php:618
+#: ../../Zotlabs/Module/Impel.php:191
#, php-format
-msgid "%1$.2f MB of %2$.2f MB photo storage used."
-msgstr "%1$.2f MB av %2$.2f MB lagringsplass til bilder er brukt."
-
-#: ../../Zotlabs/Module/Photos.php:660
-msgid "Upload Photos"
-msgstr "Last opp bilder"
-
-#: ../../Zotlabs/Module/Photos.php:664
-msgid "Enter an album name"
-msgstr "Skriv et albumnavn"
-
-#: ../../Zotlabs/Module/Photos.php:665
-msgid "or select an existing album (doubleclick)"
-msgstr "eller velg et eksisterende album (dobbeltklikk)"
-
-#: ../../Zotlabs/Module/Photos.php:666
-msgid "Create a status post for this upload"
-msgstr "Lag et statusinnlegg for denne opplastingen"
-
-#: ../../Zotlabs/Module/Photos.php:668
-msgid "Description (optional)"
-msgstr "Beskrivelse (valgritt)"
-
-#: ../../Zotlabs/Module/Photos.php:756
-msgid "Show Newest First"
-msgstr "Vis nyeste først"
-
-#: ../../Zotlabs/Module/Photos.php:758
-msgid "Show Oldest First"
-msgstr "Vis eldste først"
-
-#: ../../Zotlabs/Module/Photos.php:782 ../../Zotlabs/Module/Photos.php:1324
-#: ../../Zotlabs/Module/Embedphotos.php:171 ../../Zotlabs/Widget/Album.php:84
-#: ../../Zotlabs/Widget/Portfolio.php:91
-msgid "View Photo"
-msgstr "Vis foto"
-
-#: ../../Zotlabs/Module/Photos.php:813 ../../Zotlabs/Module/Embedphotos.php:187
-#: ../../Zotlabs/Widget/Album.php:101 ../../Zotlabs/Widget/Portfolio.php:112
-msgid "Edit Album"
-msgstr "Endre album"
-
-#: ../../Zotlabs/Module/Photos.php:815 ../../Zotlabs/Module/Photos.php:1355
-msgid "Add Photos"
-msgstr "Legg til bilder"
-
-#: ../../Zotlabs/Module/Photos.php:867
-msgid "Permission denied. Access to this item may be restricted."
-msgstr "Tillatelse avvist. Tilgang til dette elementet kan være begrenset."
-
-#: ../../Zotlabs/Module/Photos.php:869
-msgid "Photo not available"
-msgstr "Bilde er utilgjengelig"
-
-#: ../../Zotlabs/Module/Photos.php:927
-msgid "Use as profile photo"
-msgstr "Bruk som profilbilde"
-
-#: ../../Zotlabs/Module/Photos.php:928
-msgid "Use as cover photo"
-msgstr "Bruk som omslagsbilde"
-
-#: ../../Zotlabs/Module/Photos.php:935
-msgid "Private Photo"
-msgstr "Privat bilde"
-
-#: ../../Zotlabs/Module/Photos.php:946 ../../Zotlabs/Module/Cal.php:199
-#: ../../Zotlabs/Module/Cdav.php:1026
-msgid "Previous"
-msgstr "Forrige"
-
-#: ../../Zotlabs/Module/Photos.php:950
-msgid "View Full Size"
-msgstr "Vis i full størrelse"
-
-#: ../../Zotlabs/Module/Photos.php:994 ../../Zotlabs/Module/Cover_photo.php:382
-#: ../../Zotlabs/Module/Tagrm.php:137 ../../Zotlabs/Module/Admin/Addons.php:463
-#: ../../extend/addon/hzaddons/superblock/Mod_Superblock.php:90
-msgid "Remove"
-msgstr "Fjern"
-
-#: ../../Zotlabs/Module/Photos.php:1031
-msgid "Edit photo"
-msgstr "Endre bilde"
-
-#: ../../Zotlabs/Module/Photos.php:1033
-msgid "Rotate CW (right)"
-msgstr "Roter med klokka (mot høyre)"
-
-#: ../../Zotlabs/Module/Photos.php:1034
-msgid "Rotate CCW (left)"
-msgstr "Roter mot klokka (venstre)"
-
-#: ../../Zotlabs/Module/Photos.php:1037
-msgid "Move photo to album"
-msgstr ""
-
-#: ../../Zotlabs/Module/Photos.php:1038
-msgid "Enter a new album name"
-msgstr "Skriv et nytt albumnavn"
-
-#: ../../Zotlabs/Module/Photos.php:1039
-msgid "or select an existing one (doubleclick)"
-msgstr "eller velg et eksisterende album (dobbeltklikk)"
-
-#: ../../Zotlabs/Module/Photos.php:1044
-msgid "Add a Tag"
-msgstr "Legg til merkelapp"
-
-#: ../../Zotlabs/Module/Photos.php:1052
-msgid "Example: @bob, @Barbara_Jensen, @jim@example.com"
-msgstr "Eksempel: @bob, @Barbara_Jensen, @jim@example.com"
-
-#: ../../Zotlabs/Module/Photos.php:1055
-msgid "Flag as adult in album view"
-msgstr "Flag som voksent i albumvisning"
-
-#: ../../Zotlabs/Module/Photos.php:1125 ../../Zotlabs/Module/Photos.php:1137
-msgid "View all"
-msgstr "Vis alle"
-
-#: ../../Zotlabs/Module/Photos.php:1238
-msgid "Photo Tools"
-msgstr "Fotoverktøy"
-
-#: ../../Zotlabs/Module/Photos.php:1247
-msgid "In This Photo:"
-msgstr "I dette bildet:"
-
-#: ../../Zotlabs/Module/Photos.php:1252
-msgid "Map"
-msgstr "Kart"
-
-#: ../../Zotlabs/Module/Photos.php:1260
-msgctxt "noun"
-msgid "Likes"
-msgstr "Liker"
-
-#: ../../Zotlabs/Module/Photos.php:1261
-msgctxt "noun"
-msgid "Dislikes"
-msgstr "Liker ikke"
+msgid "%s element installation failed"
+msgstr "Installasjon av %s-element mislyktes"
#: ../../Zotlabs/Module/Oauth.php:45
msgid "Name is required"
@@ -6657,53 +8878,40 @@ msgstr "Navn er påkrevd"
msgid "Key and Secret are required"
msgstr "Nøkkel og hemmelighet er påkrevd"
-#: ../../Zotlabs/Module/Oauth.php:108 ../../Zotlabs/Module/Oauth.php:134
+#: ../../Zotlabs/Module/Oauth.php:109 ../../Zotlabs/Module/Oauth.php:134
#: ../../Zotlabs/Module/Oauth.php:170 ../../Zotlabs/Module/Oauth2.php:141
#: ../../Zotlabs/Module/Oauth2.php:191
msgid "Add application"
msgstr "Legg til program"
-#: ../../Zotlabs/Module/Oauth.php:111 ../../Zotlabs/Module/Oauth2.php:116
+#: ../../Zotlabs/Module/Oauth.php:112 ../../Zotlabs/Module/Oauth2.php:117
#: ../../Zotlabs/Module/Oauth2.php:144
msgid "Name of application"
msgstr "Navn på program"
-#: ../../Zotlabs/Module/Oauth.php:112 ../../Zotlabs/Module/Oauth.php:138
-#: ../../extend/addon/hzaddons/statusnet/statusnet.php:596
-#: ../../extend/addon/hzaddons/twitter/twitter.php:504
-msgid "Consumer Key"
-msgstr "Consumer Key"
-
-#: ../../Zotlabs/Module/Oauth.php:112 ../../Zotlabs/Module/Oauth.php:113
-#: ../../Zotlabs/Module/Oauth2.php:117 ../../Zotlabs/Module/Oauth2.php:145
+#: ../../Zotlabs/Module/Oauth.php:113 ../../Zotlabs/Module/Oauth.php:114
+#: ../../Zotlabs/Module/Oauth2.php:118 ../../Zotlabs/Module/Oauth2.php:145
msgid "Automatically generated - change if desired. Max length 20"
msgstr "Automatisk laget - kan endres om du vil. Største lengde 20"
-#: ../../Zotlabs/Module/Oauth.php:113 ../../Zotlabs/Module/Oauth.php:139
-#: ../../Zotlabs/Module/Oauth2.php:117 ../../Zotlabs/Module/Oauth2.php:145
-#: ../../extend/addon/hzaddons/statusnet/statusnet.php:595
-#: ../../extend/addon/hzaddons/twitter/twitter.php:505
-msgid "Consumer Secret"
-msgstr "Consumer Secret"
-
-#: ../../Zotlabs/Module/Oauth.php:114 ../../Zotlabs/Module/Oauth.php:140
-#: ../../Zotlabs/Module/Oauth2.php:118 ../../Zotlabs/Module/Oauth2.php:146
+#: ../../Zotlabs/Module/Oauth.php:115 ../../Zotlabs/Module/Oauth.php:140
+#: ../../Zotlabs/Module/Oauth2.php:119 ../../Zotlabs/Module/Oauth2.php:146
msgid "Redirect"
msgstr "Omdirigering"
-#: ../../Zotlabs/Module/Oauth.php:114 ../../Zotlabs/Module/Oauth2.php:118
+#: ../../Zotlabs/Module/Oauth.php:115 ../../Zotlabs/Module/Oauth2.php:119
#: ../../Zotlabs/Module/Oauth2.php:146
msgid ""
"Redirect URI - leave blank unless your application specifically requires this"
msgstr ""
"Omdirigerings-URI - la stå tomt hvis ikke ditt program spesifikt krever dette"
-#: ../../Zotlabs/Module/Oauth.php:115 ../../Zotlabs/Module/Oauth.php:141
+#: ../../Zotlabs/Module/Oauth.php:116 ../../Zotlabs/Module/Oauth.php:141
msgid "Icon url"
msgstr "Ikon-URL"
-#: ../../Zotlabs/Module/Oauth.php:115 ../../Zotlabs/Module/Sources.php:121
-#: ../../Zotlabs/Module/Sources.php:156 ../../Zotlabs/Module/Register.php:501
+#: ../../Zotlabs/Module/Oauth.php:116 ../../Zotlabs/Module/Register.php:501
+#: ../../Zotlabs/Module/Sources.php:121 ../../Zotlabs/Module/Sources.php:156
msgid "Optional"
msgstr "Valgfritt"
@@ -6727,9 +8935,25 @@ msgstr "Ikke noe navn"
msgid "Remove authorization"
msgstr "Fjern tillatelse"
-#: ../../Zotlabs/Module/Apps.php:51 ../../Zotlabs/Widget/Appstore.php:19
-msgid "Available Apps"
-msgstr "Tilgjengelige apper"
+#: ../../Zotlabs/Module/Network.php:110
+msgid "No such group"
+msgstr "Gruppen finnes ikke"
+
+#: ../../Zotlabs/Module/Network.php:162
+msgid "No such channel"
+msgstr "Ingen slik kanal"
+
+#: ../../Zotlabs/Module/Network.php:174 ../../Zotlabs/Module/Channel.php:246
+msgid "Search Results For:"
+msgstr "Søkeresultat for:"
+
+#: ../../Zotlabs/Module/Network.php:250
+msgid "Privacy group is empty"
+msgstr "Personverngruppen er tom"
+
+#: ../../Zotlabs/Module/Network.php:260
+msgid "Privacy group: "
+msgstr "Personverngruppe: "
#: ../../Zotlabs/Module/Apps.php:51
msgid "Installed Apps"
@@ -6743,941 +8967,928 @@ msgstr "Behandle apper"
msgid "Create Custom App"
msgstr ""
-#: ../../Zotlabs/Module/Viewconnections.php:65
-msgid "No connections."
-msgstr "Ingen forbindelser."
-
-#: ../../Zotlabs/Module/Viewconnections.php:105
-#, php-format
-msgid "Visit %s's profile [%s]"
-msgstr "Besøk %s sin profil [%s]"
-
-#: ../../Zotlabs/Module/Viewconnections.php:135
-msgid "View Connections"
-msgstr "Vis forbindelser"
-
-#: ../../Zotlabs/Module/Chanview.php:132
-msgid "toggle full screen mode"
-msgstr ""
+#: ../../Zotlabs/Module/Service_limits.php:23
+msgid "No service class restrictions found."
+msgstr "Ingen restriksjoner er funnet i tjenesteklasse."
-#: ../../Zotlabs/Module/Editpost.php:38 ../../Zotlabs/Module/Editpost.php:43
-msgid "Item is not editable"
-msgstr "Elementet kan ikke endres"
+#: ../../Zotlabs/Module/Lostpass.php:19
+msgid "No valid account found."
+msgstr "Ingen gyldig konto funnet."
-#: ../../Zotlabs/Module/Tagger.php:50
-msgid "Post not found."
-msgstr ""
+#: ../../Zotlabs/Module/Lostpass.php:33
+msgid "Password reset request issued. Check your email."
+msgstr "Forespørsel om å tilbakestille passord er mottatt. Sjekk e-posten din."
-#: ../../Zotlabs/Module/Tagger.php:121
+#: ../../Zotlabs/Module/Lostpass.php:39 ../../Zotlabs/Module/Lostpass.php:108
#, php-format
-msgid "%1$s tagged %2$s's %3$s with %4$s"
-msgstr "%1$s merket %3$s til %2$s med %4$s"
-
-#: ../../Zotlabs/Module/Common.php:14
-msgid "No channel."
-msgstr "Ingen kanal."
+msgid "Site Member (%s)"
+msgstr "Nettstedsmedlem (%s)"
-#: ../../Zotlabs/Module/Common.php:45
-msgid "No connections in common."
-msgstr "Ingen forbindelser felles."
+#: ../../Zotlabs/Module/Lostpass.php:44 ../../Zotlabs/Module/Lostpass.php:49
+#, php-format
+msgid "Password reset requested at %s"
+msgstr "Forespurt om å tilbakestille passord hos %s"
-#: ../../Zotlabs/Module/Common.php:65
-msgid "View Common Connections"
+#: ../../Zotlabs/Module/Lostpass.php:68
+msgid ""
+"Request could not be verified. (You may have previously submitted it.) "
+"Password reset failed."
msgstr ""
+"Forespørsel kunne ikke bekreftes. (Du kan ha sendt den inn tidligere.) "
+"Tilbakestilling av passord mislyktes."
-#: ../../Zotlabs/Module/Profiles.php:23 ../../Zotlabs/Module/Profiles.php:211
-#: ../../Zotlabs/Module/Profiles.php:641
-msgid "Profile not found."
-msgstr "Profilen ble ikke funnet."
-
-#: ../../Zotlabs/Module/Profiles.php:43
-msgid "Profile deleted."
-msgstr "Profilen er slettet."
-
-#: ../../Zotlabs/Module/Profiles.php:67 ../../Zotlabs/Module/Profiles.php:104
-msgid "Profile-"
-msgstr "Profil-"
-
-#: ../../Zotlabs/Module/Profiles.php:89 ../../Zotlabs/Module/Profiles.php:126
-msgid "New profile created."
-msgstr "Ny profil opprettet."
-
-#: ../../Zotlabs/Module/Profiles.php:110
-msgid "Profile unavailable to clone."
-msgstr "Profilen er utilgjengelig for klonen."
-
-#: ../../Zotlabs/Module/Profiles.php:145
-msgid "Profile unavailable to export."
-msgstr "Profilen er utilgjengelig for eksport."
-
-#: ../../Zotlabs/Module/Profiles.php:221
-msgid "Profile Name is required."
-msgstr "Profilnavn er påkrevd."
-
-#: ../../Zotlabs/Module/Profiles.php:426
-msgid "Marital Status"
-msgstr "Sivilstand"
-
-#: ../../Zotlabs/Module/Profiles.php:430
-msgid "Romantic Partner"
-msgstr "Romantisk partner"
-
-#: ../../Zotlabs/Module/Profiles.php:434 ../../Zotlabs/Module/Profiles.php:787
-msgid "Likes"
-msgstr "Liker"
-
-#: ../../Zotlabs/Module/Profiles.php:438 ../../Zotlabs/Module/Profiles.php:788
-msgid "Dislikes"
-msgstr "Liker ikke"
-
-#: ../../Zotlabs/Module/Profiles.php:442 ../../Zotlabs/Module/Profiles.php:795
-msgid "Work/Employment"
-msgstr "Arbeid/sysselsetting"
-
-#: ../../Zotlabs/Module/Profiles.php:445
-msgid "Religion"
-msgstr "Religion"
-
-#: ../../Zotlabs/Module/Profiles.php:449
-msgid "Political Views"
-msgstr "Politiske synspunkter"
+#: ../../Zotlabs/Module/Lostpass.php:92
+msgid "Your password has been reset as requested."
+msgstr "Ditt passord har blitt tilbakestilt som forespurt."
-#: ../../Zotlabs/Module/Profiles.php:453
-#: ../../extend/addon/hzaddons/openid/MysqlProvider.php:74
-msgid "Gender"
-msgstr "Kjønn"
+#: ../../Zotlabs/Module/Lostpass.php:93
+msgid "Your new password is"
+msgstr "Ditt nye passord er"
-#: ../../Zotlabs/Module/Profiles.php:457
-msgid "Sexual Preference"
-msgstr "Seksuelle preferanser"
+#: ../../Zotlabs/Module/Lostpass.php:94
+msgid "Save or copy your new password - and then"
+msgstr "Lagre eller kopier ditt nye passord, og deretter kan du"
-#: ../../Zotlabs/Module/Profiles.php:461
-msgid "Homepage"
-msgstr "Hjemmeside"
+#: ../../Zotlabs/Module/Lostpass.php:95
+msgid "click here to login"
+msgstr "klikke her for å logge inn"
-#: ../../Zotlabs/Module/Profiles.php:465
-msgid "Interests"
-msgstr "Interesser"
+#: ../../Zotlabs/Module/Lostpass.php:96
+msgid ""
+"Your password may be changed from the <em>Settings</em> page after "
+"successful login."
+msgstr ""
+"Ditt passord kan endres på siden <em>Innstillinger</em> etter vellykket "
+"innlogging."
-#: ../../Zotlabs/Module/Profiles.php:469 ../../Zotlabs/Module/Connedit.php:739
-#: ../../Zotlabs/Module/Locs.php:122
-#: ../../Zotlabs/Module/Admin/Channels.php:182
-#: ../../Zotlabs/Module/Cdav.php:1375
-msgid "Address"
-msgstr "Adresse"
+#: ../../Zotlabs/Module/Lostpass.php:117
+#, php-format
+msgid "Your password has changed at %s"
+msgstr "Ditt passord er endret hos %s"
-#: ../../Zotlabs/Module/Profiles.php:573
-msgid "Profile updated."
-msgstr "Profilen er oppdatert."
+#: ../../Zotlabs/Module/Lostpass.php:130
+msgid "Forgot your Password?"
+msgstr "Glemt passord ditt?"
-#: ../../Zotlabs/Module/Profiles.php:671
-msgid "Hide my connections from viewers of this profile"
-msgstr "Skjul mine forbindelser fra besøkende som ser denne profilen"
+#: ../../Zotlabs/Module/Lostpass.php:131
+msgid ""
+"Enter your email address and submit to have your password reset. Then check "
+"your email for further instructions."
+msgstr ""
+"Skriv e-postadressen din og send inn for å tilbakestille passordet ditt. "
+"Sjekk deretter din e-post for videre instruksjoner."
-#: ../../Zotlabs/Module/Profiles.php:684
-msgid "Publish my default profile in the network directory"
-msgstr "La standardprofilen min vises i nettverkskatalonen"
+#: ../../Zotlabs/Module/Lostpass.php:132
+msgid "Email Address"
+msgstr "E-postadresse"
-#: ../../Zotlabs/Module/Profiles.php:692
-msgid "Suggest me as a potential contact to new members"
-msgstr "Foreslå meg som mulig kontakt til nye medlemmer"
+#: ../../Zotlabs/Module/Lostpass.php:133 ../../Zotlabs/Module/Pdledit.php:76
+#: ../../Zotlabs/Module/Pdledit.php:93
+msgid "Reset"
+msgstr "Tilbakestill"
-#: ../../Zotlabs/Module/Profiles.php:696
-msgid "Reveal my online status"
-msgstr "La andre se om jeg er pålogget eller ikke"
+#: ../../Zotlabs/Module/Removeme.php:35
+msgid ""
+"Channel removals are not allowed within 48 hours of changing the account "
+"password."
+msgstr ""
+"Fjerning av kanaler er ikke tillatt innen 48 timer etter endring av "
+"kontopassordet."
-#: ../../Zotlabs/Module/Profiles.php:737
-msgid "Edit Profile Details"
-msgstr "Endre profildetaljer"
+#: ../../Zotlabs/Module/Removeme.php:60 ../../Zotlabs/Module/Removeme.php:64
+#: ../../Zotlabs/Module/Settings/Channel.php:283
+msgid "Remove Channel"
+msgstr "Fjern kanal"
-#: ../../Zotlabs/Module/Profiles.php:739
-msgid "View this profile"
-msgstr "Vis denne profilen"
+#: ../../Zotlabs/Module/Removeme.php:61
+#: ../../Zotlabs/Module/Removeaccount.php:58
+#: ../../Zotlabs/Module/Changeaddr.php:78
+msgid "WARNING: "
+msgstr "ADVARSEL: "
-#: ../../Zotlabs/Module/Profiles.php:741
-msgid "Profile Tools"
-msgstr "Profilverktøy"
+#: ../../Zotlabs/Module/Removeme.php:61
+msgid "This channel will be permanently removed. "
+msgstr ""
-#: ../../Zotlabs/Module/Profiles.php:742
-msgid "Change cover photo"
-msgstr "Endre omslagsbilde"
+#: ../../Zotlabs/Module/Removeme.php:61
+msgid "This action can not be undone!"
+msgstr ""
-#: ../../Zotlabs/Module/Profiles.php:744
-msgid "Create a new profile using these settings"
-msgstr "Lag en ny profil ved å bruke disse innstillingene"
+#: ../../Zotlabs/Module/Removeme.php:62
+#: ../../Zotlabs/Module/Removeaccount.php:59
+#: ../../Zotlabs/Module/Changeaddr.php:79
+msgid "Please enter your password for verification:"
+msgstr "Vennligst skriv ditt passord for å få bekreftelse:"
-#: ../../Zotlabs/Module/Profiles.php:745
-msgid "Clone this profile"
-msgstr "Klon denne profilen"
+#: ../../Zotlabs/Module/Import_items.php:50
+msgid "Not a zip file or zip file corrupted."
+msgstr ""
-#: ../../Zotlabs/Module/Profiles.php:746
-msgid "Delete this profile"
-msgstr "Slett denne profilen"
+#: ../../Zotlabs/Module/Import_items.php:121
+msgid "Import Items"
+msgstr "Importer elementer"
-#: ../../Zotlabs/Module/Profiles.php:747
-msgid "Add profile things"
-msgstr "Legg til profilting"
+#: ../../Zotlabs/Module/Import_items.php:122
+msgid "Use this form to import existing posts and content from an export file."
+msgstr ""
+"Bruk dette skjemaet for å importere eksisterende innlegg og innhold fra en "
+"eksportfil."
-#: ../../Zotlabs/Module/Profiles.php:748
-msgid "Basic"
-msgstr "Grunnleggende"
+#: ../../Zotlabs/Module/Import_items.php:123
+#: ../../Zotlabs/Module/Import.php:606
+msgid "File to Upload"
+msgstr "Fil som skal lastes opp"
-#: ../../Zotlabs/Module/Profiles.php:750
-msgid "Relationship"
-msgstr "Forhold"
+#: ../../Zotlabs/Module/Import_items.php:136
+#: ../../Zotlabs/Module/Import.php:108
+msgid "Imported file is empty."
+msgstr "Importert fil er tom."
-#: ../../Zotlabs/Module/Profiles.php:753
-msgid "Import profile from file"
-msgstr "Importer profil fra fil"
+#: ../../Zotlabs/Module/Import_items.php:159
+msgid "Content import completed"
+msgstr ""
-#: ../../Zotlabs/Module/Profiles.php:754
-msgid "Export profile to file"
-msgstr "Eksporter profil til fil"
+#: ../../Zotlabs/Module/Import_items.php:164
+msgid "Chatroom import completed"
+msgstr ""
-#: ../../Zotlabs/Module/Profiles.php:755
-msgid "Your gender"
-msgstr "Kjønn"
+#: ../../Zotlabs/Module/Import_items.php:170
+msgid "Channel calendar import 1/2 completed"
+msgstr ""
-#: ../../Zotlabs/Module/Profiles.php:756
-msgid "Marital status"
-msgstr "Sivilstatus"
+#: ../../Zotlabs/Module/Import_items.php:176
+msgid "Channel calendar import 2/2 completed"
+msgstr ""
-#: ../../Zotlabs/Module/Profiles.php:757
-msgid "Sexual preference"
-msgstr "Legning"
+#: ../../Zotlabs/Module/Import_items.php:181
+msgid "Menu import completed"
+msgstr ""
-#: ../../Zotlabs/Module/Profiles.php:760
-msgid "Profile name"
-msgstr "Profilnavn"
+#: ../../Zotlabs/Module/Import_items.php:186
+msgid "Wiki import completed"
+msgstr ""
-#: ../../Zotlabs/Module/Profiles.php:762
-msgid "This is your default profile."
-msgstr "Dette er din standardprofil."
+#: ../../Zotlabs/Module/Import_items.php:191
+msgid "Webpages import completed"
+msgstr ""
-#: ../../Zotlabs/Module/Profiles.php:764
-msgid "Your full name"
-msgstr "Fullt navn"
+#: ../../Zotlabs/Module/Regdir.php:53 ../../Zotlabs/Module/Dirsearch.php:24
+msgid "This site is not a directory server"
+msgstr "Dette nettstedet er ikke en katalogtjener"
-#: ../../Zotlabs/Module/Profiles.php:765
-msgid "Short title/description"
-msgstr "Kort tittel/beskrivelse"
+#: ../../Zotlabs/Module/Notifications.php:101 ../../Zotlabs/Module/Menu.php:180
+#: ../../Zotlabs/Module/Connections.php:83
+#: ../../Zotlabs/Module/Connections.php:92
+msgid "New"
+msgstr "Nye"
-#: ../../Zotlabs/Module/Profiles.php:765
-msgid "Maximal 190 characters"
-msgstr "Maksimum 190 tegn"
+#: ../../Zotlabs/Module/Notifications.php:106
+#: ../../Zotlabs/Module/Notify.php:85
+msgid "No more system notifications."
+msgstr "Ingen flere systemvarsler."
-#: ../../Zotlabs/Module/Profiles.php:768
-msgid "Street address"
-msgstr "Gateadresse"
+#: ../../Zotlabs/Module/Notifications.php:110
+#: ../../Zotlabs/Module/Notify.php:89
+msgid "System Notifications"
+msgstr "Systemvarsler"
-#: ../../Zotlabs/Module/Profiles.php:769
-msgid "Locality/City"
-msgstr "Sted/by"
+#: ../../Zotlabs/Module/Z6trans.php:19
+msgid "Update to Hubzilla 5.0 step 2"
+msgstr ""
-#: ../../Zotlabs/Module/Profiles.php:770
-msgid "Region/State"
-msgstr "Region"
+#: ../../Zotlabs/Module/Z6trans.php:21
+msgid "To complete the update please run"
+msgstr ""
-#: ../../Zotlabs/Module/Profiles.php:771
-msgid "Postal/Zip code"
-msgstr "Postnummer"
+#: ../../Zotlabs/Module/Z6trans.php:23
+msgid "php util/z6convert.php"
+msgstr ""
-#: ../../Zotlabs/Module/Profiles.php:772 ../../Zotlabs/Module/Connedit.php:757
-#: ../../Zotlabs/Module/Cdav.php:1393
-msgid "Country"
-msgstr "Land"
+#: ../../Zotlabs/Module/Z6trans.php:25
+msgid "from the terminal."
+msgstr ""
-#: ../../Zotlabs/Module/Profiles.php:777
-msgid "Who (if applicable)"
-msgstr "Hvem (om relevant)"
+#: ../../Zotlabs/Module/Request.php:80
+msgid "+ Repeat again"
+msgstr ""
-#: ../../Zotlabs/Module/Profiles.php:777
-msgid "Examples: cathy123, Cathy Williams, cathy@example.com"
-msgstr "Eksempler: kari123, Kari Villiamsen, kari@example.com"
+#: ../../Zotlabs/Module/Request.php:80
+#, fuzzy
+#| msgid "Remove term"
+msgid "- Remove yours"
+msgstr "Fjern begrep"
-#: ../../Zotlabs/Module/Profiles.php:778
-msgid "Since (date)"
-msgstr "Fra (dato)"
+#: ../../Zotlabs/Module/Request.php:80
+msgid "+ Add yours"
+msgstr "+ Legg til"
-#: ../../Zotlabs/Module/Profiles.php:781
-msgid "Tell us about yourself"
-msgstr "Fortell oss om deg selv"
+#: ../../Zotlabs/Module/Thing.php:146
+msgid "Thing updated"
+msgstr "Tingen er oppdatert"
-#: ../../Zotlabs/Module/Profiles.php:782
-#: ../../extend/addon/hzaddons/openid/MysqlProvider.php:68
-msgid "Homepage URL"
-msgstr "Hjemmeside URL"
+#: ../../Zotlabs/Module/Thing.php:198
+msgid "Object store: failed"
+msgstr "Objektlagring: mislyktes"
-#: ../../Zotlabs/Module/Profiles.php:783
-msgid "Hometown"
-msgstr "Hjemsted"
+#: ../../Zotlabs/Module/Thing.php:202
+msgid "Thing added"
+msgstr "Ting lagt til"
-#: ../../Zotlabs/Module/Profiles.php:784
-msgid "Political views"
-msgstr "Politiske holdninger"
+#: ../../Zotlabs/Module/Thing.php:217
+#, php-format
+msgid "OBJ: %1$s %2$s %3$s"
+msgstr "OBJ: %1$s %2$s %3$s"
-#: ../../Zotlabs/Module/Profiles.php:785
-msgid "Religious views"
-msgstr "Religiøse holdninger"
+#: ../../Zotlabs/Module/Thing.php:284 ../../Zotlabs/Module/Thing.php:308
+msgid "item not found."
+msgstr "element ble ikke funnet."
-#: ../../Zotlabs/Module/Profiles.php:786
-msgid "Keywords used in directory listings"
-msgstr "Nøkkelord for bruk i katalogoppføringen"
+#: ../../Zotlabs/Module/Thing.php:343
+msgid "Edit Thing"
+msgstr "Endre ting"
-#: ../../Zotlabs/Module/Profiles.php:786
-msgid "Example: fishing photography software"
-msgstr "Eksempel: fisking fotografering programvare"
+#: ../../Zotlabs/Module/Thing.php:345 ../../Zotlabs/Module/Thing.php:399
+msgid "Select a profile"
+msgstr "Velg en profil"
-#: ../../Zotlabs/Module/Profiles.php:789
-msgid "Musical interests"
-msgstr "Musikkinteresser"
+#: ../../Zotlabs/Module/Thing.php:349 ../../Zotlabs/Module/Thing.php:402
+msgid "Post an activity"
+msgstr "Legg inn en aktivitet"
-#: ../../Zotlabs/Module/Profiles.php:790
-msgid "Books, literature"
-msgstr "Bøker, litteratur"
+#: ../../Zotlabs/Module/Thing.php:349 ../../Zotlabs/Module/Thing.php:402
+msgid "Only sends to viewers of the applicable profile"
+msgstr "Sender bare til seere av den aktuelle profilen"
-#: ../../Zotlabs/Module/Profiles.php:791
-msgid "Television"
-msgstr "TV/fjernsyn"
+#: ../../Zotlabs/Module/Thing.php:351 ../../Zotlabs/Module/Thing.php:404
+msgid "Name of thing e.g. something"
+msgstr "Navn på ting for eksempel noe"
-#: ../../Zotlabs/Module/Profiles.php:792
-msgid "Film/Dance/Culture/Entertainment"
-msgstr "Film/dans/kultur/underholdning"
+#: ../../Zotlabs/Module/Thing.php:353 ../../Zotlabs/Module/Thing.php:405
+msgid "URL of thing (optional)"
+msgstr "URL til ting (valgfritt)"
-#: ../../Zotlabs/Module/Profiles.php:793
-msgid "Hobbies/Interests"
-msgstr "Hobbier/Interesser"
+#: ../../Zotlabs/Module/Thing.php:355 ../../Zotlabs/Module/Thing.php:406
+msgid "URL for photo of thing (optional)"
+msgstr "URL til bilde av ting (valgfritt)"
-#: ../../Zotlabs/Module/Profiles.php:794
-msgid "Love/Romance"
-msgstr "Kjærlighet/romantikk"
+#: ../../Zotlabs/Module/Thing.php:397
+msgid "Add Thing to your Profile"
+msgstr "Legg til ting i din profil"
-#: ../../Zotlabs/Module/Profiles.php:796
-msgid "School/Education"
-msgstr "Skolle/utdanning"
+#: ../../Zotlabs/Module/Removeaccount.php:35
+msgid ""
+"Account removals are not allowed within 48 hours of changing the account "
+"password."
+msgstr ""
+"Sletting av kontoer er ikke tillatt innen 48 timer etter endring av "
+"kontopassordet."
-#: ../../Zotlabs/Module/Profiles.php:797
-msgid "Contact information and social networks"
-msgstr "Kontaktinformasjon og andre sosiale nettverk"
+#: ../../Zotlabs/Module/Removeaccount.php:57
+msgid "Remove This Account"
+msgstr "Slett denne kontoen"
-#: ../../Zotlabs/Module/Profiles.php:798
-msgid "My other channels"
-msgstr "Mine andre kanaler"
+#: ../../Zotlabs/Module/Removeaccount.php:58
+msgid ""
+"This account and all its channels will be completely removed from the "
+"network. "
+msgstr ""
+"Denne kontoen og alle dens kanaler vil bli fullstendig fjernet fra "
+"nettverket. "
-#: ../../Zotlabs/Module/Profiles.php:852 ../../Zotlabs/Module/Manage.php:137
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:217
-msgid "Create New"
-msgstr "Lag ny profil"
+#: ../../Zotlabs/Module/Removeaccount.php:58
+msgid "This action is permanent and can not be undone!"
+msgstr "Denne handlingen er permanent og kan ikke angres!"
-#: ../../Zotlabs/Module/Network.php:108
-msgid "No such group"
-msgstr "Gruppen finnes ikke"
+#: ../../Zotlabs/Module/Removeaccount.php:61
+#: ../../Zotlabs/Module/Settings/Account.php:113
+msgid "Remove Account"
+msgstr "Slett konto"
-#: ../../Zotlabs/Module/Network.php:160
-msgid "No such channel"
-msgstr "Ingen slik kanal"
+#: ../../Zotlabs/Module/Tokens.php:94
+#, php-format
+msgid "This channel is limited to %d tokens"
+msgstr ""
-#: ../../Zotlabs/Module/Network.php:248
-msgid "Privacy group is empty"
-msgstr "Personverngruppen er tom"
+#: ../../Zotlabs/Module/Tokens.php:100
+msgid "Name and Password are required."
+msgstr ""
-#: ../../Zotlabs/Module/Network.php:258
-msgid "Privacy group: "
-msgstr "Personverngruppe: "
+#: ../../Zotlabs/Module/Tokens.php:215
+msgid "Token saved."
+msgstr ""
-#: ../../Zotlabs/Module/Network.php:333
-#: ../../extend/addon/hzaddons/redred/Mod_Redred.php:29
-#, fuzzy
-msgid "Invalid channel."
-msgstr "Ugyldig kanal"
+#: ../../Zotlabs/Module/Tokens.php:261
+msgid ""
+"Use this form to create temporary access identifiers to share things with "
+"non-members. These identities may be used in privacy groups and visitors may "
+"login using these credentials to access private content."
+msgstr ""
-#: ../../Zotlabs/Module/Invite.php:70
-msgid "Invite App"
+#: ../../Zotlabs/Module/Tokens.php:274
+msgid "Please select a role for this guest!"
msgstr ""
-#: ../../Zotlabs/Module/Invite.php:82
-msgid "Register is closed"
+#: ../../Zotlabs/Module/Tokens.php:287
+msgid "Select a role for this guest"
msgstr ""
-#: ../../Zotlabs/Module/Invite.php:116 ../../Zotlabs/Module/Invite.php:563
-msgid "Note, the invitation code is valid up to"
+#: ../../Zotlabs/Module/Tokens.php:291
+msgid "Login Name"
msgstr ""
-#: ../../Zotlabs/Module/Invite.php:129
-#, php-format
-msgid "Too many recipients for one invitation (max %d)"
+#: ../../Zotlabs/Module/Tokens.php:292
+msgid "Login Password"
msgstr ""
-#: ../../Zotlabs/Module/Invite.php:133
-msgid "No recipients for this invitation"
+#: ../../Zotlabs/Module/Tokens.php:293
+msgid "Expires (yyyy-mm-dd)"
msgstr ""
-#: ../../Zotlabs/Module/Invite.php:152
-#, php-format
-msgid "(%s) : Not a real email address"
-msgstr "(%s): Ikke en virkelig epostadresse"
+#: ../../Zotlabs/Module/Pdledit.php:27
+msgid "Layout updated."
+msgstr "Layout er oppdatert."
-#: ../../Zotlabs/Module/Invite.php:159
-#, php-format
-msgid "(%s) : Not allowed email address"
-msgstr "(%s): Ikke en tillatt epostadresse"
+#: ../../Zotlabs/Module/Pdledit.php:55 ../../Zotlabs/Module/Pdledit.php:129
+msgid "Edit System Page Description"
+msgstr "Endre beskrivelsen av systemsiden"
-#: ../../Zotlabs/Module/Invite.php:172
-#, php-format
-msgid "(%s) : email address already in use"
-msgstr "(%s): epostadressen er allerede i bruk"
+#: ../../Zotlabs/Module/Pdledit.php:76 ../../Zotlabs/Module/Pdledit.php:93
+msgid "(modified)"
+msgstr "(endret)"
-#: ../../Zotlabs/Module/Invite.php:179
-#, php-format
-msgid "(%s) : Accepted email address"
-msgstr "(%s): Godkjent epostadresse"
+#: ../../Zotlabs/Module/Pdledit.php:124
+msgid "Layout not found."
+msgstr "Layouten ble ikke funnet."
-#: ../../Zotlabs/Module/Invite.php:266
-#: ../../extend/addon/hzaddons/notifyadmin/notifyadmin.php:40
-#, php-format
-msgid "%s : Message delivery failed."
-msgstr "%s : meldingslevering feilet."
+#: ../../Zotlabs/Module/Pdledit.php:130
+msgid "Module Name:"
+msgstr "Modulnavn:"
-#: ../../Zotlabs/Module/Invite.php:271
-#, php-format
-msgid "To %s : Message delivery success."
-msgstr ""
+#: ../../Zotlabs/Module/Pdledit.php:131
+msgid "Layout Help"
+msgstr "Layout-hjelp"
-#: ../../Zotlabs/Module/Invite.php:303
-#, php-format
-msgid "%1$d mail(s) sent, %2$d mail error(s)"
+#: ../../Zotlabs/Module/Pdledit.php:132
+msgid "Edit another layout"
msgstr ""
-#: ../../Zotlabs/Module/Invite.php:328
-msgid "Invites not proposed by configuration"
+#: ../../Zotlabs/Module/Pdledit.php:133
+msgid "System layout"
msgstr ""
-#: ../../Zotlabs/Module/Invite.php:329
-msgid "Contact the site admin"
-msgstr ""
+#: ../../Zotlabs/Module/Connect.php:65 ../../Zotlabs/Module/Connect.php:118
+msgid "Continue"
+msgstr "Fortsett"
-#: ../../Zotlabs/Module/Invite.php:345
-msgid "Invites by users not enabled"
-msgstr ""
+#: ../../Zotlabs/Module/Connect.php:99
+msgid "Premium Channel Setup"
+msgstr "Premiumkanal-oppsett"
-#: ../../Zotlabs/Module/Invite.php:350
-msgid "You have no more invitations available"
-msgstr "Du har ikke flere invitasjoner tilgjengelig"
+#: ../../Zotlabs/Module/Connect.php:101
+msgid "Enable premium channel connection restrictions"
+msgstr "Slå på restriksjoner for forbindelse med premiumkanal"
-#: ../../Zotlabs/Module/Invite.php:366
-msgid "Not on xchan"
+#: ../../Zotlabs/Module/Connect.php:102
+msgid ""
+"Please enter your restrictions or conditions, such as paypal receipt, usage "
+"guidelines, etc."
msgstr ""
+"Vennligst skriv dine restriksjoner og betingelser, slik som PayPal-"
+"kvittering, retningslinjer for bruk, og så videre."
-#: ../../Zotlabs/Module/Invite.php:399
-msgid "All users invitation limit exceeded."
+#: ../../Zotlabs/Module/Connect.php:104 ../../Zotlabs/Module/Connect.php:124
+msgid ""
+"This channel may require additional steps or acknowledgement of the "
+"following conditions prior to connecting:"
msgstr ""
+"Denne kanalen kan kreve ytterligere steg og bekreftelse av følgende "
+"betingelser før tilkobling:"
-#: ../../Zotlabs/Module/Invite.php:411 ../../Zotlabs/Module/Admin/Site.php:343
-msgid "Minute(s)"
-msgstr ""
+#: ../../Zotlabs/Module/Connect.php:105
+msgid ""
+"Potential connections will then see the following text before proceeding:"
+msgstr "Potensielle forbindelser vil da se følgende tekst før de går videre:"
-#: ../../Zotlabs/Module/Invite.php:412 ../../Zotlabs/Module/Admin/Site.php:344
-msgid "Hour(s)"
+#: ../../Zotlabs/Module/Connect.php:106 ../../Zotlabs/Module/Connect.php:127
+msgid ""
+"By continuing, I certify that I have complied with any instructions provided "
+"on this page."
msgstr ""
+"Ved å fortsette bekrefter jeg at jeg har oppfylt alle instruksjoner gitt på "
+"denne siden."
-#: ../../Zotlabs/Module/Invite.php:413 ../../Zotlabs/Module/Admin/Site.php:345
-msgid "Day(s)"
-msgstr ""
+#: ../../Zotlabs/Module/Connect.php:115
+msgid "(No specific instructions have been provided by the channel owner.)"
+msgstr "(Ingen spesifikke instruksjoner er gitt av kanaleieren.)"
-#: ../../Zotlabs/Module/Invite.php:417
-msgid "Invitation expires after"
-msgstr ""
+#: ../../Zotlabs/Module/Connect.php:123
+msgid "Restricted or Premium Channel"
+msgstr "Begrenset kanal eller premiumkanal"
-#: ../../Zotlabs/Module/Invite.php:422 ../../Zotlabs/Module/Admin/Site.php:362
-#: ../../Zotlabs/Module/Admin/Site.php:384
-msgid "duration up from now"
+#: ../../Zotlabs/Module/Filestorage.php:14
+#: ../../Zotlabs/Module/Filestorage.php:53
+msgid "Deprecated!"
msgstr ""
-#: ../../Zotlabs/Module/Invite.php:518 ../../Zotlabs/Module/Invite.php:557
-msgid "Invitation"
-msgstr ""
+#: ../../Zotlabs/Module/Filestorage.php:109
+#: ../../Zotlabs/Module/Attach_edit.php:69
+msgid "File not found."
+msgstr "Filen ble ikke funnet."
-#: ../../Zotlabs/Module/Invite.php:548
-msgid "Send invitations"
-msgstr "Send invitasjoner"
+#: ../../Zotlabs/Module/Filestorage.php:157
+msgid "Permission Denied."
+msgstr "Tillatelse avvist."
-#: ../../Zotlabs/Module/Invite.php:549
-msgid "Invitations I am using"
-msgstr "Invitasjoner jeg bruker"
+#: ../../Zotlabs/Module/Filestorage.php:190
+msgid "Edit file permissions"
+msgstr "Endre filtillatelser"
-#: ../../Zotlabs/Module/Invite.php:550
-msgid "Invitations we are using"
-msgstr ""
+#: ../../Zotlabs/Module/Filestorage.php:202
+msgid "Set/edit permissions"
+msgstr "Angi/endre tillatelser"
-#: ../../Zotlabs/Module/Invite.php:551
-msgid "§ Note, the email(s) sent will be recorded in the system logs"
-msgstr ""
+#: ../../Zotlabs/Module/Filestorage.php:203
+msgid "Include all files and sub folders"
+msgstr "Inkluder alle filer og undermapper"
-#: ../../Zotlabs/Module/Invite.php:552
-msgid "Enter email addresses, one per line:"
-msgstr "Skriv e-postadresser, en per linje:"
+#: ../../Zotlabs/Module/Filestorage.php:204
+msgid "Return to file list"
+msgstr "Gå tilbake til filoversikten"
-#: ../../Zotlabs/Module/Invite.php:553
-msgid "Your message:"
-msgstr "Din melding:"
+#: ../../Zotlabs/Module/Filestorage.php:209
+msgid "Share this file"
+msgstr "Del denne filen"
-#: ../../Zotlabs/Module/Invite.php:554
-msgid "Invite template"
-msgstr ""
+#: ../../Zotlabs/Module/Filestorage.php:210
+msgid "Show URL to this file"
+msgstr "Vis URLen til denne filen"
-#: ../../Zotlabs/Module/Invite.php:556
-msgid "Subject:"
-msgstr "Emne:"
+#: ../../Zotlabs/Module/Totp_check.php:71
+#: ../../Zotlabs/Module/Admin/Account_edit.php:66
+msgid "Account not found."
+msgstr "Kontoen ble ikke funnet."
-#: ../../Zotlabs/Module/Invite.php:562
-msgid "Here you may enter personal notes to the recipient(s)"
+#: ../../Zotlabs/Module/Totp_check.php:78
+msgid "Multifactor Verification"
msgstr ""
-#: ../../Zotlabs/Module/Sources.php:41
-msgid "Failed to create source. No channel selected."
-msgstr "Mislyktes med å lage kilde. Ingen kanal er valgt."
-
-#: ../../Zotlabs/Module/Sources.php:57
-msgid "Source created."
-msgstr "Kilden er laget."
-
-#: ../../Zotlabs/Module/Sources.php:70
-msgid "Source updated."
-msgstr "Kilden er oppdatert."
-
-#: ../../Zotlabs/Module/Sources.php:99
-msgid "*"
-msgstr "*"
-
-#: ../../Zotlabs/Module/Sources.php:106
-msgid "Manage remote sources of content for your channel."
-msgstr "Håndtere eksterne innholdskilder til din kanal."
+#: ../../Zotlabs/Module/Totp_check.php:80
+msgid "Please enter the verification key from your authenticator app"
+msgstr ""
-#: ../../Zotlabs/Module/Sources.php:107 ../../Zotlabs/Module/Sources.php:117
-msgid "New Source"
-msgstr "Ny kilde"
+#: ../../Zotlabs/Module/Totp_check.php:81
+msgid "Verify"
+msgstr ""
-#: ../../Zotlabs/Module/Sources.php:118 ../../Zotlabs/Module/Sources.php:152
-msgid ""
-"Import all or selected content from the following channel into this channel "
-"and distribute it according to your channel settings."
+#: ../../Zotlabs/Module/Contactedit.php:50
+msgid "Invalid abook_id"
msgstr ""
-"Importer alt eller et utvalgt av innhold fra følgende kanal inn i denne "
-"kanalen og distribuer det i henhold til dine egne kanalinnstillinger."
-#: ../../Zotlabs/Module/Sources.php:119 ../../Zotlabs/Module/Sources.php:153
-msgid "Only import content with these words (one per line)"
-msgstr "Bare importer innhold med disse ordene (ett ord per linje)"
+#: ../../Zotlabs/Module/Contactedit.php:78 ../../Zotlabs/Module/Connedit.php:80
+#: ../../Zotlabs/Module/Defperms.php:67
+msgid "Could not access contact record."
+msgstr "Fikk ikke tilgang til kontaktinformasjonen."
-#: ../../Zotlabs/Module/Sources.php:119 ../../Zotlabs/Module/Sources.php:153
-msgid "Leave blank to import all public content"
-msgstr "La stå tomt for å importere alt offentlig innhold"
+#: ../../Zotlabs/Module/Contactedit.php:107
+#: ../../Zotlabs/Module/Connedit.php:101
+msgid "Could not locate selected profile."
+msgstr "Fant ikke valgt profil."
-#: ../../Zotlabs/Module/Sources.php:120 ../../Zotlabs/Module/Sources.php:159
-msgid "Channel Name"
-msgstr "Kanalnavn"
+#: ../../Zotlabs/Module/Contactedit.php:180
+#: ../../Zotlabs/Module/Connedit.php:215
+msgid "is now connected to"
+msgstr "er nå forbundet til"
-#: ../../Zotlabs/Module/Sources.php:121 ../../Zotlabs/Module/Sources.php:156
-msgid ""
-"Add the following categories to posts imported from this source (comma "
-"separated)"
-msgstr ""
+#: ../../Zotlabs/Module/Contactedit.php:238
+#: ../../Zotlabs/Module/Contactedit.php:387
+#: ../../Zotlabs/Module/Connedit.php:701
+msgid "Contact Tools"
+msgstr "Kontaktverktøy"
-#: ../../Zotlabs/Module/Sources.php:122 ../../Zotlabs/Module/Sources.php:157
-msgid "Resend posts with this channel as author"
+#: ../../Zotlabs/Module/Contactedit.php:315
+#: ../../Zotlabs/Module/Connedit.php:622
+msgid "Approve this contact"
msgstr ""
-#: ../../Zotlabs/Module/Sources.php:122 ../../Zotlabs/Module/Sources.php:157
-msgid "Copyrights may apply"
+#: ../../Zotlabs/Module/Contactedit.php:315
+#: ../../Zotlabs/Module/Connedit.php:622
+msgid "Accept contact to allow communication"
msgstr ""
-#: ../../Zotlabs/Module/Sources.php:142 ../../Zotlabs/Module/Sources.php:172
-msgid "Source not found."
-msgstr "Kilden ble ikke funnet."
-
-#: ../../Zotlabs/Module/Sources.php:149
-msgid "Edit Source"
-msgstr "Endre kilde"
-
-#: ../../Zotlabs/Module/Sources.php:150
-msgid "Delete Source"
-msgstr "Slett kilde"
+#: ../../Zotlabs/Module/Contactedit.php:348
+#: ../../Zotlabs/Module/Connedit.php:658
+msgid "Please select a role for this contact!"
+msgstr ""
-#: ../../Zotlabs/Module/Sources.php:180
-msgid "Source removed"
-msgstr "Kilden er fjernet"
+#: ../../Zotlabs/Module/Contactedit.php:368
+#: ../../Zotlabs/Module/Connedit.php:678
+msgid "This contact is unreachable from this location."
+msgstr ""
-#: ../../Zotlabs/Module/Sources.php:182
-msgid "Unable to remove source."
-msgstr "Ikke i stand til å fjerne kilde."
+#: ../../Zotlabs/Module/Contactedit.php:369
+#: ../../Zotlabs/Module/Connedit.php:679
+msgid "This contact may be unreachable from other channel locations."
+msgstr ""
-#: ../../Zotlabs/Module/Suggest.php:52
-msgid ""
-"No suggestions available. If this is a new site, please try again in 24 "
-"hours."
+#: ../../Zotlabs/Module/Contactedit.php:371
+#: ../../Zotlabs/Module/Connedit.php:681
+msgid "Location independence is not supported by their network."
msgstr ""
-"Ingen forslag tilgjengelige. Hvis dette er et nytt nettsted, vennligst prøv "
-"igjen om 24 timer."
-#: ../../Zotlabs/Module/Suggest.php:71 ../../Zotlabs/Widget/Suggestions.php:53
-msgid "Ignore/Hide"
-msgstr "Ignorer/Skjul"
+#: ../../Zotlabs/Module/Contactedit.php:381
+#, fuzzy
+msgid "View profile"
+msgstr "Vis profil"
-#: ../../Zotlabs/Module/Impel.php:188
-#, php-format
-msgid "%s element installed"
-msgstr "%s element installert"
+#: ../../Zotlabs/Module/Contactedit.php:383
+#: ../../Zotlabs/Module/Connections.php:413
+msgid "This is a group/forum channel"
+msgstr ""
-#: ../../Zotlabs/Module/Impel.php:191
-#, php-format
-msgid "%s element installation failed"
-msgstr "Installasjon av %s-element mislyktes"
+#: ../../Zotlabs/Module/Contactedit.php:394
+msgid "Select a role for this contact"
+msgstr "Velg en rolle for denne forbindelsen"
-#: ../../Zotlabs/Module/Group.php:48
-msgid "Privacy group created."
-msgstr "Personverngruppen er opprettet."
+#: ../../Zotlabs/Module/Contactedit.php:406
+#: ../../Zotlabs/Module/Connedit.php:703
+msgid "Slide to adjust your degree of friendship"
+msgstr "Flytt for å justere din grad av vennskap"
-#: ../../Zotlabs/Module/Group.php:51
-msgid "Could not create privacy group."
-msgstr "Kunne ikke opprette personverngruppen."
+#: ../../Zotlabs/Module/Contactedit.php:408
+#: ../../Zotlabs/Module/Connedit.php:705
+msgid "Custom Filter"
+msgstr "Tilpasset filter"
-#: ../../Zotlabs/Module/Group.php:83
-msgid "Privacy group updated."
-msgstr "Personverngruppen er oppdatert."
+#: ../../Zotlabs/Module/Contactedit.php:409
+#: ../../Zotlabs/Module/Connedit.php:706
+#: ../../Zotlabs/Module/Settings/Channel.php:287
+msgid "Only import posts with this text"
+msgstr "Bare importer innlegg med disse ordene"
-#: ../../Zotlabs/Module/Group.php:138 ../../Zotlabs/Module/Group.php:302
-msgid "Post to this group by default"
+#: ../../Zotlabs/Module/Contactedit.php:409
+#: ../../Zotlabs/Module/Contactedit.php:410
+#: ../../Zotlabs/Module/Connedit.php:706 ../../Zotlabs/Module/Connedit.php:707
+#: ../../Zotlabs/Module/Admin/Site.php:502
+#: ../../Zotlabs/Module/Admin/Site.php:503
+msgid ""
+"words one per line or #tags or /patterns/ or lang=xx, leave blank to import "
+"all posts"
msgstr ""
+"ord per linje eller #merkelapper eller /mønster/ eller språk lang=xx, la stå "
+"blankt for å importere alle innlegg"
-#: ../../Zotlabs/Module/Group.php:139 ../../Zotlabs/Module/Group.php:303
-msgid "Add new contacts to this group by default"
-msgstr ""
+#: ../../Zotlabs/Module/Contactedit.php:410
+#: ../../Zotlabs/Module/Connedit.php:707
+#: ../../Zotlabs/Module/Settings/Channel.php:286
+msgid "Do not import posts with this text"
+msgstr "Ikke importer innlegg med denne teksten"
-#: ../../Zotlabs/Module/Group.php:147
-#, fuzzy
-msgid "Privacy group name"
-msgstr "Personverngruppens navn:"
+#: ../../Zotlabs/Module/Contactedit.php:415
+#: ../../Zotlabs/Module/Connedit.php:714
+msgid "Approve contact"
+msgstr ""
-#: ../../Zotlabs/Module/Group.php:148 ../../Zotlabs/Module/Group.php:250
-msgid "Members are visible to other channels"
-msgstr "Medlemmer er synlig for andre kanaler"
+#: ../../Zotlabs/Module/Contactedit.php:417
+#: ../../Zotlabs/Module/Connedit.php:717
+msgid "Their"
+msgstr "Deres"
-#: ../../Zotlabs/Module/Group.php:176
-msgid "Privacy group removed."
-msgstr "Personverngruppen er fjernet."
+#: ../../Zotlabs/Module/Contactedit.php:418
+#: ../../Zotlabs/Module/Connedit.php:718
+msgid "My"
+msgstr "Mine"
-#: ../../Zotlabs/Module/Group.php:179
-msgid "Unable to remove privacy group."
-msgstr "Ikke i stand til å fjerne personverngruppen."
+#: ../../Zotlabs/Module/Contactedit.php:426
+msgid "Roles"
+msgstr "Roller"
-#: ../../Zotlabs/Module/Group.php:245
-#, fuzzy, php-format
-msgid "Privacy Group: %s"
-msgstr "Personverngruppe: %s"
+#: ../../Zotlabs/Module/Contactedit.php:427
+msgid "Compare permissions"
+msgstr "Sammenlign tillatelser"
-#: ../../Zotlabs/Module/Group.php:247
-msgid "Privacy group name: "
-msgstr "Personverngruppens navn: "
+#: ../../Zotlabs/Module/Contactedit.php:428
+msgid "Permission"
+msgstr "Tillatelse"
-#: ../../Zotlabs/Module/Group.php:263
-msgid "Group members"
+#: ../../Zotlabs/Module/Contactedit.php:432
+msgid "Content filter"
msgstr ""
-#: ../../Zotlabs/Module/Group.php:265
-msgid "Not in this group"
+#: ../../Zotlabs/Module/Contactedit.php:442
+msgid "Contact updated"
msgstr ""
-#: ../../Zotlabs/Module/Group.php:297
-msgid "Click a channel to toggle membership"
+#: ../../Zotlabs/Module/Contactedit.php:442
+msgid "Contact update failed"
msgstr ""
-#: ../../Zotlabs/Module/Layouts.php:184
-msgid "Comanche page description language help"
-msgstr "Hjelp med Comanche sidebeskrivelsesspråk"
-
-#: ../../Zotlabs/Module/Layouts.php:188
-msgid "Layout Description"
-msgstr "Layout-beskrivelse"
+#: ../../Zotlabs/Module/Contactedit.php:448
+#: ../../Zotlabs/Module/Connections.php:357
+msgid "Approve connection"
+msgstr "Godkjenn forbindelse"
-#: ../../Zotlabs/Module/Layouts.php:193
-msgid "Download PDL file"
-msgstr "Last ned PDL-fil"
+#: ../../Zotlabs/Module/Contactedit.php:520
+msgid "Block status updated"
+msgstr ""
-#: ../../Zotlabs/Module/Layouts.php:196 ../../Zotlabs/Module/Pubsites.php:63
-#: ../../Zotlabs/Module/Blocks.php:164 ../../Zotlabs/Module/Webpages.php:256
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:216
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:406
-msgid "View"
-msgstr "Vis"
+#: ../../Zotlabs/Module/Contactedit.php:524
+msgid "Block failed"
+msgstr ""
-#: ../../Zotlabs/Module/Oauth2.php:54
-msgid "Name and Secret are required"
+#: ../../Zotlabs/Module/Contactedit.php:535
+msgid "Ignore status updated"
msgstr ""
-#: ../../Zotlabs/Module/Oauth2.php:113
-msgid "Add OAuth2 application"
+#: ../../Zotlabs/Module/Contactedit.php:539
+msgid "Ignore failed"
msgstr ""
-#: ../../Zotlabs/Module/Oauth2.php:119 ../../Zotlabs/Module/Oauth2.php:147
-msgid "Grant Types"
+#: ../../Zotlabs/Module/Contactedit.php:550
+msgid "Archive status updated"
msgstr ""
-#: ../../Zotlabs/Module/Oauth2.php:119 ../../Zotlabs/Module/Oauth2.php:120
-msgid "leave blank unless your application sepcifically requires this"
+#: ../../Zotlabs/Module/Contactedit.php:554
+msgid "Archive failed"
msgstr ""
-#: ../../Zotlabs/Module/Oauth2.php:120 ../../Zotlabs/Module/Oauth2.php:148
-msgid "Authorization scope"
+#: ../../Zotlabs/Module/Contactedit.php:565
+msgid "Hide status updated"
msgstr ""
-#: ../../Zotlabs/Module/Oauth2.php:132
-msgid "OAuth2 Application not found."
+#: ../../Zotlabs/Module/Contactedit.php:569
+msgid "Hide failed"
msgstr ""
-#: ../../Zotlabs/Module/Oauth2.php:147 ../../Zotlabs/Module/Oauth2.php:148
-msgid "leave blank unless your application specifically requires this"
+#: ../../Zotlabs/Module/Contactedit.php:604
+msgid "Contact removed"
msgstr ""
-#: ../../Zotlabs/Module/Oauth2.php:190
-msgid "Connected OAuth2 Apps"
+#: ../../Zotlabs/Module/Contactedit.php:608
+msgid "Delete failed"
msgstr ""
-#: ../../Zotlabs/Module/Viewsrc.php:43
-msgid "item"
+#: ../../Zotlabs/Module/Contactedit.php:619
+msgid "Refetch contact info"
msgstr ""
-#: ../../Zotlabs/Module/Cover_photo.php:86
-#: ../../Zotlabs/Module/Profile_photo.php:129
-msgid "Image uploaded but image cropping failed."
-msgstr "Bildet ble lastet opp, men beskjæring av bildet mislyktes."
+#: ../../Zotlabs/Module/Contactedit.php:623
+#: ../../Zotlabs/Module/Connedit.php:508
+#: ../../Zotlabs/Module/Admin/Accounts.php:222
+msgid "Unblock"
+msgstr "Ikke blokker lenger"
-#: ../../Zotlabs/Module/Cover_photo.php:195
-#: ../../Zotlabs/Module/Cover_photo.php:252
-msgid "Cover Photos"
-msgstr "Forsidebilder"
+#: ../../Zotlabs/Module/Contactedit.php:623
+#: ../../Zotlabs/Module/Connedit.php:508
+#: ../../Zotlabs/Module/Admin/Accounts.php:221
+msgid "Block"
+msgstr "Blokker"
-#: ../../Zotlabs/Module/Cover_photo.php:211
-#: ../../Zotlabs/Module/Profile_photo.php:185
-msgid "Image resize failed."
-msgstr "Endring av bildestørrelse mislyktes."
+#: ../../Zotlabs/Module/Contactedit.php:625
+#: ../../Zotlabs/Module/Connedit.php:511
+msgid "Block (or Unblock) all communications with this connection"
+msgstr ""
+"Blokker eller fjern blokkering av all kommunikasjon med denne forbindelsen"
-#: ../../Zotlabs/Module/Cover_photo.php:263
-#: ../../Zotlabs/Module/Profile_photo.php:350
-msgid "Image upload failed."
-msgstr "Opplasting av bildet mislyktes."
+#: ../../Zotlabs/Module/Contactedit.php:626
+#: ../../Zotlabs/Module/Connedit.php:512
+msgid "This connection is blocked!"
+msgstr "Denne forbindelsen er blokkert!"
-#: ../../Zotlabs/Module/Cover_photo.php:280
-#: ../../Zotlabs/Module/Profile_photo.php:370
-msgid "Unable to process image."
-msgstr "Kan ikke behandle bildet."
+#: ../../Zotlabs/Module/Contactedit.php:630
+#: ../../Zotlabs/Module/Connedit.php:516
+msgid "Unignore"
+msgstr "Ikke ignorer lenger"
-#: ../../Zotlabs/Module/Cover_photo.php:325
-#: ../../Zotlabs/Module/Cover_photo.php:340
-#: ../../Zotlabs/Module/Profile_photo.php:432
-#: ../../Zotlabs/Module/Profile_photo.php:497
-msgid "Photo not available."
-msgstr "Bildet er ikke tilgjengelig."
+#: ../../Zotlabs/Module/Contactedit.php:630
+#: ../../Zotlabs/Module/Connedit.php:516
+#: ../../Zotlabs/Module/Connections.php:360
+msgid "Ignore"
+msgstr "Ignorer"
-#: ../../Zotlabs/Module/Cover_photo.php:376
-msgid "Your cover photo may be visible to anybody on the internet"
-msgstr "Omslagsbildet ditt vil være synlig for enhver på internett"
+#: ../../Zotlabs/Module/Contactedit.php:632
+#: ../../Zotlabs/Module/Connedit.php:519
+msgid "Ignore (or Unignore) all inbound communications from this connection"
+msgstr ""
+"Ignorer eller fjern ignorering av all inngående kommunikasjon fra denne "
+"forbindelsen"
-#: ../../Zotlabs/Module/Cover_photo.php:378
-msgid "Upload File:"
-msgstr "Last opp fil:"
+#: ../../Zotlabs/Module/Contactedit.php:633
+#: ../../Zotlabs/Module/Connedit.php:520
+msgid "This connection is ignored!"
+msgstr "Denne forbindelsen er ignorert!"
-#: ../../Zotlabs/Module/Cover_photo.php:379
-msgid "Select a profile:"
-msgstr "Velg en profil:"
+#: ../../Zotlabs/Module/Contactedit.php:637
+#: ../../Zotlabs/Module/Connedit.php:524
+msgid "Unarchive"
+msgstr "Ikke arkiver lenger"
-#: ../../Zotlabs/Module/Cover_photo.php:380
-#, fuzzy
-msgid "Change Cover Photo"
-msgstr "Endre omslagsbilde"
+#: ../../Zotlabs/Module/Contactedit.php:637
+#: ../../Zotlabs/Module/Connedit.php:524
+msgid "Archive"
+msgstr "Arkiver"
-#: ../../Zotlabs/Module/Cover_photo.php:384
-#: ../../Zotlabs/Module/Cover_photo.php:385
-#: ../../Zotlabs/Module/Profile_photo.php:551
-#: ../../Zotlabs/Module/Profile_photo.php:552
-msgid "Use a photo from your albums"
+#: ../../Zotlabs/Module/Contactedit.php:639
+#: ../../Zotlabs/Module/Connedit.php:527
+msgid ""
+"Archive (or Unarchive) this connection - mark channel dead but keep content"
msgstr ""
+"Arkiver eller fjern arkivering av denne forbindelsen - marker kanal som død, "
+"men behold innhold"
-#: ../../Zotlabs/Module/Cover_photo.php:390
-#: ../../Zotlabs/Module/Profile_photo.php:557
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:402
-msgid "Choose a different album"
-msgstr "Velg et annet album"
+#: ../../Zotlabs/Module/Contactedit.php:640
+#: ../../Zotlabs/Module/Connedit.php:528
+msgid "This connection is archived!"
+msgstr "Denne forbindelsen er arkivert!"
-#: ../../Zotlabs/Module/Cover_photo.php:396
-msgid "Select existing photo"
-msgstr "Velg eksisterende bilde"
+#: ../../Zotlabs/Module/Contactedit.php:644
+#: ../../Zotlabs/Module/Connedit.php:532
+msgid "Unhide"
+msgstr "Ikke skjul lenger"
-#: ../../Zotlabs/Module/Cover_photo.php:413
-#: ../../Zotlabs/Module/Profile_photo.php:581
-msgid "Crop Image"
-msgstr "Beskjær bildet"
+#: ../../Zotlabs/Module/Contactedit.php:644
+#: ../../Zotlabs/Module/Connedit.php:532
+msgid "Hide"
+msgstr "Skjul"
-#: ../../Zotlabs/Module/Cover_photo.php:414
-#: ../../Zotlabs/Module/Profile_photo.php:582
-msgid "Please adjust the image cropping for optimum viewing."
-msgstr "Vennligst juster bildebeskjæringen for optimal visning."
+#: ../../Zotlabs/Module/Contactedit.php:646
+#: ../../Zotlabs/Module/Connedit.php:535
+msgid "Hide or Unhide this connection from your other connections"
+msgstr ""
+"Skjul eller fjern skjuling av denne forbindelsen fra dine andre forbindelser"
-#: ../../Zotlabs/Module/Cover_photo.php:416
-msgid "Done Editing"
-msgstr "Lagre endringer"
+#: ../../Zotlabs/Module/Contactedit.php:647
+#: ../../Zotlabs/Module/Connedit.php:536
+msgid "This connection is hidden!"
+msgstr "Denne forbindelsen er skjult!"
-#: ../../Zotlabs/Module/Chat.php:29 ../../Zotlabs/Module/Ochannel.php:32
-msgid "You must be logged in to see this page."
-msgstr "Du må være innloegget for å se denne siden."
+#: ../../Zotlabs/Module/Contactedit.php:653
+#: ../../Zotlabs/Module/Connedit.php:543
+msgid "Delete this connection"
+msgstr "Slett denne forbindelsen"
-#: ../../Zotlabs/Module/Chat.php:193
-msgid "Room not found"
-msgstr "Rommet ble ikke funnet"
+#: ../../Zotlabs/Module/Search.php:251
+#, php-format
+msgid "Items tagged with: %s"
+msgstr "Elementer merket med: %s"
-#: ../../Zotlabs/Module/Chat.php:209
-msgid "Leave Room"
-msgstr "Forlat rom"
+#: ../../Zotlabs/Module/Search.php:253
+#, php-format
+msgid "Search results for: %s"
+msgstr "Søkeresultater for: %s"
-#: ../../Zotlabs/Module/Chat.php:210
-msgid "Delete Room"
-msgstr "Slett rom"
+#: ../../Zotlabs/Module/Register.php:113
+msgid "Email address required"
+msgstr ""
-#: ../../Zotlabs/Module/Chat.php:211
-msgid "I am away right now"
-msgstr "Jeg er borte akkurat nå"
+#: ../../Zotlabs/Module/Register.php:157
+msgid "No password provided"
+msgstr ""
-#: ../../Zotlabs/Module/Chat.php:212
-msgid "I am online"
-msgstr "Jeg er pålogget"
+#: ../../Zotlabs/Module/Register.php:180
+msgid "Terms of Service not accepted"
+msgstr ""
-#: ../../Zotlabs/Module/Chat.php:214
-msgid "Bookmark this room"
-msgstr "Bokmerk dette rommet"
+#: ../../Zotlabs/Module/Register.php:242
+msgid "Invitation code succesfully applied"
+msgstr ""
-#: ../../Zotlabs/Module/Chat.php:237
-msgid "New Chatroom"
-msgstr "Nytt chatrom"
+#: ../../Zotlabs/Module/Register.php:262
+msgid "Invitation not in time or too late"
+msgstr ""
-#: ../../Zotlabs/Module/Chat.php:238
-msgid "Chatroom name"
-msgstr "Romnavn"
+#: ../../Zotlabs/Module/Register.php:268
+msgid "Invitation email failed"
+msgstr ""
-#: ../../Zotlabs/Module/Chat.php:239
-msgid "Expiration of chats (minutes)"
-msgstr "Chat utgår (antall minutter)"
+#: ../../Zotlabs/Module/Register.php:276
+msgid "Invitation code failed"
+msgstr ""
-#: ../../Zotlabs/Module/Chat.php:255
-#, php-format
-msgid "%1$s's Chatrooms"
-msgstr "%1$s sine chatrom"
+#: ../../Zotlabs/Module/Register.php:283
+msgid "Invitations are not available"
+msgstr ""
-#: ../../Zotlabs/Module/Chat.php:260
-msgid "No chatrooms available"
-msgstr "Ingen rom tilgjengelige"
+#: ../../Zotlabs/Module/Register.php:293
+msgid "Registration on this hub is by invitation only"
+msgstr ""
-#: ../../Zotlabs/Module/Chat.php:261
-msgid "Add Room"
+#: ../../Zotlabs/Module/Register.php:400
+msgid "New register request"
msgstr ""
-#: ../../Zotlabs/Module/Chat.php:264
-msgid "Expiration"
-msgstr "Utløp"
+#: ../../Zotlabs/Module/Register.php:418
+msgid "Error creating dId A"
+msgstr ""
-#: ../../Zotlabs/Module/Chat.php:265
-msgid "min"
-msgstr "min"
+#: ../../Zotlabs/Module/Register.php:437
+msgid "Registration on this hub is disabled."
+msgstr "Registrering ved dette nettstedet er skrudd av."
-#: ../../Zotlabs/Module/Dreport.php:37
-msgid "Invalid message"
-msgstr "Ugyldig melding"
+#: ../../Zotlabs/Module/Register.php:446
+msgid "Why do you want to join this hub?"
+msgstr ""
-#: ../../Zotlabs/Module/Dreport.php:67
-msgid "no results"
-msgstr "ingen resultater"
+#: ../../Zotlabs/Module/Register.php:446
+msgid "This will help to review your registration"
+msgstr ""
-#: ../../Zotlabs/Module/Dreport.php:81
-msgid "channel sync processed"
-msgstr "kanalsynkronisering er behandlet"
+#: ../../Zotlabs/Module/Register.php:447
+msgid "Registration on this hub is by approval only."
+msgstr "Registrering ved dette nettstedet skjer på godkjenning."
-#: ../../Zotlabs/Module/Dreport.php:85
-msgid "queued"
-msgstr "lagt i kø"
+#: ../../Zotlabs/Module/Register.php:448
+msgid "Register at another affiliated hub in case when prefered"
+msgstr ""
-#: ../../Zotlabs/Module/Dreport.php:89
-msgid "posted"
-msgstr "lagt inn"
+#: ../../Zotlabs/Module/Register.php:461
+msgid "Registration on this hub is by invitation only."
+msgstr ""
-#: ../../Zotlabs/Module/Dreport.php:93
-msgid "accepted for delivery"
-msgstr "akseptert for levering"
+#: ../../Zotlabs/Module/Register.php:462
+msgid "Register at another affiliated hub"
+msgstr ""
-#: ../../Zotlabs/Module/Dreport.php:97
-msgid "updated"
-msgstr "oppdatert"
+#: ../../Zotlabs/Module/Register.php:476 ../../Zotlabs/Module/Siteinfo.php:29
+msgid "Terms of Service"
+msgstr "Tjenesteavtale"
-#: ../../Zotlabs/Module/Dreport.php:101
-msgid "update ignored"
-msgstr "oppdatering ignorert"
+#: ../../Zotlabs/Module/Register.php:482
+#, php-format
+msgid "I accept the %s for this website"
+msgstr "Jeg godtar %s for dette nettstedet"
-#: ../../Zotlabs/Module/Dreport.php:104
-msgid "permission denied"
-msgstr "tillatelse avvist"
+#: ../../Zotlabs/Module/Register.php:489
+#, php-format
+msgid "I am over %s years of age and accept the %s for this website"
+msgstr "Jeg er mer enn %s år gammel, og godtar %s for dette nettstedet"
-#: ../../Zotlabs/Module/Dreport.php:108
-msgid "recipient not found"
-msgstr "mottaker ble ikke funnet"
+#: ../../Zotlabs/Module/Register.php:499
+msgid "Your email address"
+msgstr "Din e-postadresse"
-#: ../../Zotlabs/Module/Dreport.php:128
-#, php-format
-msgid "Delivery report for %1$s"
-msgstr "Leveringsrapport for %1$s"
+#: ../../Zotlabs/Module/Register.php:506
+msgid "Choose a password"
+msgstr "Velg et passord"
-#: ../../Zotlabs/Module/Dreport.php:132
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:906
-#: ../../extend/addon/hzaddons/wiki/Widget/Wiki_pages.php:71
-msgid "Options"
-msgstr "Valg"
+#: ../../Zotlabs/Module/Register.php:507
+msgid "Please re-enter your password"
+msgstr "Vennligst skriv ditt passord en gang til"
-#: ../../Zotlabs/Module/Dreport.php:133
-msgid "Redeliver"
-msgstr ""
+#: ../../Zotlabs/Module/Register.php:509
+msgid "Please enter your invitation code"
+msgstr "Vennligst skriv din invitasjonskode"
-#: ../../Zotlabs/Module/Rbmark.php:72
-msgid "Select a bookmark folder"
-msgstr "Velg en bokmerkemappe"
+#: ../../Zotlabs/Module/Register.php:511
+msgid "Your name"
+msgstr "Navn"
-#: ../../Zotlabs/Module/Rbmark.php:80
-msgid "Save Bookmark"
-msgstr "Lagre bokmerke"
+#: ../../Zotlabs/Module/Register.php:511
+msgid "Real name is preferred"
+msgstr ""
-#: ../../Zotlabs/Module/Rbmark.php:81
-msgid "URL of bookmark"
-msgstr "URL-en til bokmerket"
+#: ../../Zotlabs/Module/Register.php:513
+#: ../../Zotlabs/Module/New_channel.php:178
+msgid "Choose a short nickname"
+msgstr "Velg et kort kallenavn"
-#: ../../Zotlabs/Module/Rbmark.php:82 ../../Zotlabs/Module/Appman.php:220
-#: ../../Zotlabs/Module/Cdav.php:1005
-#: ../../extend/addon/hzaddons/cart/submodules/manualcat.php:260
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:657
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:173
-msgid "Description"
-msgstr "Beskrivelse"
+#: ../../Zotlabs/Module/Register.php:513
+msgid ""
+"Your nickname will be used to create an easy to remember channel address"
+msgstr ""
-#: ../../Zotlabs/Module/Rbmark.php:86
-msgid "Or enter new bookmark folder name"
-msgstr "Eller skriv nytt navn på bokmerkemappe"
+#: ../../Zotlabs/Module/Register.php:521
+#: ../../Zotlabs/Module/Admin/Site.php:404
+msgid "Registration"
+msgstr "Registrering"
-#: ../../Zotlabs/Module/Achievements.php:38
-msgid "Some blurb about what to do when you're new here"
-msgstr "En standardtekst om hva du bør gjøre som ny her"
+#: ../../Zotlabs/Module/Register.php:529
+msgid "I have an invite code"
+msgstr ""
-#: ../../Zotlabs/Module/Sse_bs.php:631
-msgid "Private forum"
+#: ../../Zotlabs/Module/Register.php:576
+msgid ""
+"This site has exceeded the number of allowed daily account registrations."
msgstr ""
-#: ../../Zotlabs/Module/Sse_bs.php:631
-#, fuzzy
-msgid "Public forum"
-msgstr "Offentlig forum:"
+#: ../../Zotlabs/Module/Pconfig.php:33 ../../Zotlabs/Module/Pconfig.php:69
+msgid "This setting requires special processing and editing has been blocked."
+msgstr ""
+"Denne innstillingen krever spesiell behandling og redigering har blitt "
+"blokkert."
+
+#: ../../Zotlabs/Module/Pconfig.php:58
+msgid "Configuration Editor"
+msgstr "Konfigurasjonsbehandler"
+
+#: ../../Zotlabs/Module/Pconfig.php:59
+msgid ""
+"Warning: Changing some settings could render your channel inoperable. Please "
+"leave this page unless you are comfortable with and knowledgeable about how "
+"to correctly use this feature."
+msgstr ""
+"Advarsel: kanalen din kan slutte å virke ved endring av enkelte "
+"innstillinger. Vennligst forlat denne siden med mindre du er komfortabel med "
+"dette og vet hvordan du bruker denne funksjonen riktig."
#: ../../Zotlabs/Module/Appman.php:39 ../../Zotlabs/Module/Appman.php:56
msgid "App installed."
@@ -7732,119 +9943,55 @@ msgstr "Pris på app"
msgid "Location (URL) to purchase app"
msgstr "Plassering (URL) for å kjøpe app"
-#: ../../Zotlabs/Module/Directory.php:124
-msgid "No default suggestions were found."
-msgstr ""
-
-#: ../../Zotlabs/Module/Directory.php:292
-msgid "Gender: "
-msgstr "Kjønn: "
-
-#: ../../Zotlabs/Module/Directory.php:294
-msgid "Status: "
-msgstr "Status: "
-
-#: ../../Zotlabs/Module/Directory.php:296
-msgid "Homepage: "
-msgstr "Hjemmeside: "
-
-#: ../../Zotlabs/Module/Directory.php:357
-msgid "Description:"
-msgstr "Beskrivelse:"
-
-#: ../../Zotlabs/Module/Directory.php:359
-msgid "Unsafe"
+#: ../../Zotlabs/Module/Viewsrc.php:43
+msgid "item"
msgstr ""
-#: ../../Zotlabs/Module/Directory.php:362
-msgid "Spam"
-msgstr ""
+#: ../../Zotlabs/Module/Item.php:254 ../../Zotlabs/Module/Pin.php:37
+msgid "Unable to locate original post."
+msgstr "Ikke i stand til å finne opprinnelig innlegg."
-#: ../../Zotlabs/Module/Directory.php:372
-msgid "Public Forum:"
-msgstr "Offentlig forum:"
+#: ../../Zotlabs/Module/Item.php:542
+msgid "Empty post discarded."
+msgstr "Tomt innlegg forkastet."
-#: ../../Zotlabs/Module/Directory.php:375
-msgid "Keywords: "
-msgstr "Nøkkelord: "
+#: ../../Zotlabs/Module/Item.php:1005
+msgid "Duplicate post suppressed."
+msgstr "Duplikat av innlegg forhindret."
-#: ../../Zotlabs/Module/Directory.php:378
-msgid "Don't suggest"
-msgstr "Ikke foreslå"
+#: ../../Zotlabs/Module/Item.php:1155
+msgid "System error. Post not saved."
+msgstr "Systemfeil. Innlegg ble ikke lagret."
-#: ../../Zotlabs/Module/Directory.php:380
-msgid "Common connections (estimated):"
+#: ../../Zotlabs/Module/Item.php:1195
+msgid "Your comment is awaiting approval."
msgstr ""
-#: ../../Zotlabs/Module/Directory.php:430
-msgid "Global Directory"
-msgstr "Global katalog"
-
-#: ../../Zotlabs/Module/Directory.php:430
-msgid "Local Directory"
-msgstr "Lokal katalog"
-
-#: ../../Zotlabs/Module/Directory.php:436
-msgid "Finding:"
-msgstr "Finner:"
-
-#: ../../Zotlabs/Module/Directory.php:441
-msgid "next page"
-msgstr "neste side"
-
-#: ../../Zotlabs/Module/Directory.php:441
-msgid "previous page"
-msgstr "forrige side"
-
-#: ../../Zotlabs/Module/Directory.php:442
-msgid "Sort options"
-msgstr "Sorteringsvalg"
-
-#: ../../Zotlabs/Module/Directory.php:443
-msgid "Alphabetic"
-msgstr "Alfabetisk"
-
-#: ../../Zotlabs/Module/Directory.php:444
-msgid "Reverse Alphabetic"
-msgstr "Omvendt alfabetisk"
-
-#: ../../Zotlabs/Module/Directory.php:445
-msgid "Newest to Oldest"
-msgstr "Nyest til eldst"
-
-#: ../../Zotlabs/Module/Directory.php:446
-msgid "Oldest to Newest"
-msgstr "Eldst til nyest"
-
-#: ../../Zotlabs/Module/Directory.php:464
-msgid "No entries (some entries may be hidden)."
-msgstr "Ingen oppføringer (noen oppføringer kan være skjult)."
-
-#: ../../Zotlabs/Module/Service_limits.php:23
-msgid "No service class restrictions found."
-msgstr "Ingen restriksjoner er funnet i tjenesteklasse."
+#: ../../Zotlabs/Module/Item.php:1333
+msgid "Unable to obtain post information from database."
+msgstr "Ikke i stand til å få tak i informasjon om innlegg fra databasen."
-#: ../../Zotlabs/Module/Tagrm.php:48 ../../Zotlabs/Module/Tagrm.php:98
-msgid "Tag removed"
-msgstr "Merkelapp fjernet"
+#: ../../Zotlabs/Module/Item.php:1340
+#, php-format
+msgid "You have reached your limit of %1$.0f top level posts."
+msgstr "Du har nådd din grense på %1$.0f startinnlegg."
-#: ../../Zotlabs/Module/Tagrm.php:123
-msgid "Remove Item Tag"
-msgstr "Fjern merkelapp fra element"
+#: ../../Zotlabs/Module/Item.php:1347
+#, php-format
+msgid "You have reached your limit of %1$.0f webpages."
+msgstr "Du har nådd din grense på %1$.0f websider."
-#: ../../Zotlabs/Module/Tagrm.php:125
-msgid "Select a tag to remove: "
-msgstr "Velg merkelapp å fjerne: "
+#: ../../Zotlabs/Module/Achievements.php:38
+msgid "Some blurb about what to do when you're new here"
+msgstr "En standardtekst om hva du bør gjøre som ny her"
-#: ../../Zotlabs/Module/Connedit.php:80 ../../Zotlabs/Module/Defperms.php:67
-#: ../../Zotlabs/Module/Contactedit.php:78
-msgid "Could not access contact record."
-msgstr "Fikk ikke tilgang til kontaktinformasjonen."
+#: ../../Zotlabs/Module/Oexchange.php:27
+msgid "Unable to find your hub."
+msgstr "Ikke i stand til å finne hubben din."
-#: ../../Zotlabs/Module/Connedit.php:101
-#: ../../Zotlabs/Module/Contactedit.php:107
-msgid "Could not locate selected profile."
-msgstr "Fant ikke valgt profil."
+#: ../../Zotlabs/Module/Oexchange.php:41
+msgid "Post successful."
+msgstr "Innlegg vellykket."
#: ../../Zotlabs/Module/Connedit.php:171
msgid "Connection updated."
@@ -7854,11 +10001,6 @@ msgstr "Forbindelsen er oppdatert."
msgid "Failed to update connection record."
msgstr "Mislyktes med å oppdatere forbindelsesinformasjonen."
-#: ../../Zotlabs/Module/Connedit.php:215
-#: ../../Zotlabs/Module/Contactedit.php:180
-msgid "is now connected to"
-msgstr "er nå forbundet til"
-
#: ../../Zotlabs/Module/Connedit.php:320
msgid "Could not access address book record."
msgstr "Fikk ikke tilgang til informasjonen i adresseboken."
@@ -7902,101 +10044,6 @@ msgstr ""
msgid "View recent posts and comments"
msgstr "Vis nylige innlegg og kommentarer"
-#: ../../Zotlabs/Module/Connedit.php:508
-#: ../../Zotlabs/Module/Admin/Accounts.php:322
-#: ../../Zotlabs/Module/Contactedit.php:623
-msgid "Unblock"
-msgstr "Ikke blokker lenger"
-
-#: ../../Zotlabs/Module/Connedit.php:508
-#: ../../Zotlabs/Module/Admin/Accounts.php:321
-#: ../../Zotlabs/Module/Contactedit.php:623
-msgid "Block"
-msgstr "Blokker"
-
-#: ../../Zotlabs/Module/Connedit.php:511
-#: ../../Zotlabs/Module/Contactedit.php:625
-msgid "Block (or Unblock) all communications with this connection"
-msgstr ""
-"Blokker eller fjern blokkering av all kommunikasjon med denne forbindelsen"
-
-#: ../../Zotlabs/Module/Connedit.php:512
-#: ../../Zotlabs/Module/Contactedit.php:626
-msgid "This connection is blocked!"
-msgstr "Denne forbindelsen er blokkert!"
-
-#: ../../Zotlabs/Module/Connedit.php:516
-#: ../../Zotlabs/Module/Contactedit.php:630
-msgid "Unignore"
-msgstr "Ikke ignorer lenger"
-
-#: ../../Zotlabs/Module/Connedit.php:516
-#: ../../Zotlabs/Module/Connections.php:360
-#: ../../Zotlabs/Module/Contactedit.php:630
-msgid "Ignore"
-msgstr "Ignorer"
-
-#: ../../Zotlabs/Module/Connedit.php:519
-#: ../../Zotlabs/Module/Contactedit.php:632
-msgid "Ignore (or Unignore) all inbound communications from this connection"
-msgstr ""
-"Ignorer eller fjern ignorering av all inngående kommunikasjon fra denne "
-"forbindelsen"
-
-#: ../../Zotlabs/Module/Connedit.php:520
-#: ../../Zotlabs/Module/Contactedit.php:633
-msgid "This connection is ignored!"
-msgstr "Denne forbindelsen er ignorert!"
-
-#: ../../Zotlabs/Module/Connedit.php:524
-#: ../../Zotlabs/Module/Contactedit.php:637
-msgid "Unarchive"
-msgstr "Ikke arkiver lenger"
-
-#: ../../Zotlabs/Module/Connedit.php:524
-#: ../../Zotlabs/Module/Contactedit.php:637
-msgid "Archive"
-msgstr "Arkiver"
-
-#: ../../Zotlabs/Module/Connedit.php:527
-#: ../../Zotlabs/Module/Contactedit.php:639
-msgid ""
-"Archive (or Unarchive) this connection - mark channel dead but keep content"
-msgstr ""
-"Arkiver eller fjern arkivering av denne forbindelsen - marker kanal som død, "
-"men behold innhold"
-
-#: ../../Zotlabs/Module/Connedit.php:528
-#: ../../Zotlabs/Module/Contactedit.php:640
-msgid "This connection is archived!"
-msgstr "Denne forbindelsen er arkivert!"
-
-#: ../../Zotlabs/Module/Connedit.php:532
-#: ../../Zotlabs/Module/Contactedit.php:644
-msgid "Unhide"
-msgstr "Ikke skjul lenger"
-
-#: ../../Zotlabs/Module/Connedit.php:532
-#: ../../Zotlabs/Module/Contactedit.php:644
-msgid "Hide"
-msgstr "Skjul"
-
-#: ../../Zotlabs/Module/Connedit.php:535
-#: ../../Zotlabs/Module/Contactedit.php:646
-msgid "Hide or Unhide this connection from your other connections"
-msgstr ""
-"Skjul eller fjern skjuling av denne forbindelsen fra dine andre forbindelser"
-
-#: ../../Zotlabs/Module/Connedit.php:536
-#: ../../Zotlabs/Module/Contactedit.php:647
-msgid "This connection is hidden!"
-msgstr "Denne forbindelsen er skjult!"
-
-#: ../../Zotlabs/Module/Connedit.php:543
-#: ../../Zotlabs/Module/Contactedit.php:653
-msgid "Delete this connection"
-msgstr "Slett denne forbindelsen"
-
#: ../../Zotlabs/Module/Connedit.php:550
msgid "Fetch Vcard"
msgstr ""
@@ -8005,41 +10052,10 @@ msgstr ""
msgid "Fetch electronic calling card for this connection"
msgstr ""
-#: ../../Zotlabs/Module/Connedit.php:572
-#: ../../Zotlabs/Module/Contactedit.php:431
-msgid "Affinity"
-msgstr ""
-
#: ../../Zotlabs/Module/Connedit.php:575
msgid "Open Set Affinity section by default"
msgstr ""
-#: ../../Zotlabs/Module/Connedit.php:579
-#: ../../Zotlabs/Module/Contactedit.php:281
-#: ../../Zotlabs/Widget/Affinity.php:36
-msgid "Me"
-msgstr "Meg"
-
-#: ../../Zotlabs/Module/Connedit.php:580
-#: ../../Zotlabs/Module/Contactedit.php:282
-#: ../../Zotlabs/Widget/Affinity.php:37
-msgid "Family"
-msgstr "Familie"
-
-#: ../../Zotlabs/Module/Connedit.php:582
-#: ../../Zotlabs/Module/Contactedit.php:284
-#: ../../Zotlabs/Widget/Affinity.php:39
-msgid "Acquaintances"
-msgstr "Bekjente"
-
-#: ../../Zotlabs/Module/Connedit.php:583
-#: ../../Zotlabs/Module/Connections.php:97
-#: ../../Zotlabs/Module/Connections.php:111
-#: ../../Zotlabs/Module/Contactedit.php:285
-#: ../../Zotlabs/Widget/Affinity.php:40
-msgid "All"
-msgstr "Alle"
-
#: ../../Zotlabs/Module/Connedit.php:612
msgid "Filter"
msgstr ""
@@ -8048,16 +10064,6 @@ msgstr ""
msgid "Open Custom Filter section by default"
msgstr ""
-#: ../../Zotlabs/Module/Connedit.php:622
-#: ../../Zotlabs/Module/Contactedit.php:315
-msgid "Approve this contact"
-msgstr ""
-
-#: ../../Zotlabs/Module/Connedit.php:622
-#: ../../Zotlabs/Module/Contactedit.php:315
-msgid "Accept contact to allow communication"
-msgstr ""
-
#: ../../Zotlabs/Module/Connedit.php:626
msgid "Set Affinity"
msgstr "Angi nærhet"
@@ -8070,26 +10076,6 @@ msgstr "Angi profil"
msgid "Set Affinity & Profile"
msgstr "Angi nærhet og profil"
-#: ../../Zotlabs/Module/Connedit.php:658
-#: ../../Zotlabs/Module/Contactedit.php:348
-msgid "Please select a role for this contact!"
-msgstr ""
-
-#: ../../Zotlabs/Module/Connedit.php:678
-#: ../../Zotlabs/Module/Contactedit.php:368
-msgid "This contact is unreachable from this location."
-msgstr ""
-
-#: ../../Zotlabs/Module/Connedit.php:679
-#: ../../Zotlabs/Module/Contactedit.php:369
-msgid "This contact may be unreachable from other channel locations."
-msgstr ""
-
-#: ../../Zotlabs/Module/Connedit.php:681
-#: ../../Zotlabs/Module/Contactedit.php:371
-msgid "Location independence is not supported by their network."
-msgstr ""
-
#: ../../Zotlabs/Module/Connedit.php:685
#, php-format
msgid "Contact: %s"
@@ -8120,46 +10106,6 @@ msgstr ""
"Tillatelsene angitt på denne siden gjøres gjeldende for alle nye "
"forbindelser."
-#: ../../Zotlabs/Module/Connedit.php:701
-#: ../../Zotlabs/Module/Contactedit.php:238
-#: ../../Zotlabs/Module/Contactedit.php:387
-msgid "Contact Tools"
-msgstr ""
-
-#: ../../Zotlabs/Module/Connedit.php:703
-#: ../../Zotlabs/Module/Contactedit.php:406
-msgid "Slide to adjust your degree of friendship"
-msgstr "Flytt for å justere din grad av vennskap"
-
-#: ../../Zotlabs/Module/Connedit.php:705
-#: ../../Zotlabs/Module/Contactedit.php:408
-msgid "Custom Filter"
-msgstr "Tilpasset filter"
-
-#: ../../Zotlabs/Module/Connedit.php:706
-#: ../../Zotlabs/Module/Settings/Channel.php:287
-#: ../../Zotlabs/Module/Contactedit.php:409
-msgid "Only import posts with this text"
-msgstr "Bare importer innlegg med disse ordene"
-
-#: ../../Zotlabs/Module/Connedit.php:706 ../../Zotlabs/Module/Connedit.php:707
-#: ../../Zotlabs/Module/Admin/Site.php:501
-#: ../../Zotlabs/Module/Admin/Site.php:502
-#: ../../Zotlabs/Module/Contactedit.php:409
-#: ../../Zotlabs/Module/Contactedit.php:410
-msgid ""
-"words one per line or #tags or /patterns/ or lang=xx, leave blank to import "
-"all posts"
-msgstr ""
-"ord per linje eller #merkelapper eller /mønster/ eller språk lang=xx, la stå "
-"blankt for å importere alle innlegg"
-
-#: ../../Zotlabs/Module/Connedit.php:707
-#: ../../Zotlabs/Module/Settings/Channel.php:286
-#: ../../Zotlabs/Module/Contactedit.php:410
-msgid "Do not import posts with this text"
-msgstr "Ikke importer innlegg med denne teksten"
-
#: ../../Zotlabs/Module/Connedit.php:710
msgid "Contact Pending Approval"
msgstr ""
@@ -8169,11 +10115,6 @@ msgstr ""
msgid "inherited"
msgstr "arvet"
-#: ../../Zotlabs/Module/Connedit.php:714
-#: ../../Zotlabs/Module/Contactedit.php:415
-msgid "Approve contact"
-msgstr ""
-
#: ../../Zotlabs/Module/Connedit.php:715
#, php-format
msgid ""
@@ -8183,16 +10124,6 @@ msgstr ""
"Vennligst velg profilen du ønsker å vise %s når profilen din ses på en "
"sikret måte."
-#: ../../Zotlabs/Module/Connedit.php:717
-#: ../../Zotlabs/Module/Contactedit.php:417
-msgid "Their"
-msgstr ""
-
-#: ../../Zotlabs/Module/Connedit.php:718
-#: ../../Zotlabs/Module/Contactedit.php:418
-msgid "My"
-msgstr ""
-
#: ../../Zotlabs/Module/Connedit.php:720 ../../Zotlabs/Module/Defperms.php:266
msgid "Individual Permissions"
msgstr "Individuelle tillatelser"
@@ -8234,12 +10165,6 @@ msgstr ""
msgid "Organisation"
msgstr ""
-#: ../../Zotlabs/Module/Connedit.php:734 ../../Zotlabs/Module/Cdav.php:1370
-#: ../../extend/addon/hzaddons/workflow/workflow.php:2653
-#: ../../extend/addon/hzaddons/workflow/workflow.php:2722
-msgid "Title"
-msgstr "Tittel"
-
#: ../../Zotlabs/Module/Connedit.php:735 ../../Zotlabs/Module/Cdav.php:1371
msgid "Phone"
msgstr ""
@@ -8252,6 +10177,12 @@ msgstr ""
msgid "Website"
msgstr ""
+#: ../../Zotlabs/Module/Connedit.php:739 ../../Zotlabs/Module/Locs.php:122
+#: ../../Zotlabs/Module/Admin/Channels.php:182
+#: ../../Zotlabs/Module/Profiles.php:469 ../../Zotlabs/Module/Cdav.php:1375
+msgid "Address"
+msgstr "Adresse"
+
#: ../../Zotlabs/Module/Connedit.php:740 ../../Zotlabs/Module/Cdav.php:1376
msgid "Note"
msgstr ""
@@ -8288,323 +10219,355 @@ msgstr ""
msgid "ZIP Code"
msgstr ""
-#: ../../Zotlabs/Module/Import.php:71
-msgid "Nothing to import."
-msgstr "Ingenting å importere."
+#: ../../Zotlabs/Module/Connedit.php:757 ../../Zotlabs/Module/Profiles.php:772
+#: ../../Zotlabs/Module/Cdav.php:1393
+msgid "Country"
+msgstr "Land"
-#: ../../Zotlabs/Module/Import.php:87 ../../Zotlabs/Module/Import.php:101
-msgid "Unable to download data from old server"
-msgstr "Ikke i stand til å laste ned data fra gammel tjener"
+#: ../../Zotlabs/Module/Defperms.php:252
+msgid "Connection Default Permissions"
+msgstr "Forbindelsens standard tillatelser"
-#: ../../Zotlabs/Module/Import.php:164
-#, php-format
-msgid "Your service plan only allows %d channels."
-msgstr "Din tjenesteplan tillater bare %d kanaler."
+#: ../../Zotlabs/Module/Defperms.php:253
+msgid "Apply these permissions automatically"
+msgstr "Bruk disse tillatelsene automatisk"
-#: ../../Zotlabs/Module/Import.php:191
-msgid "No channel. Import failed."
-msgstr "Ingen kanal. Import mislyktes."
+#: ../../Zotlabs/Module/Defperms.php:253
+msgid ""
+"If enabled, connection requests will be approved without your interaction"
+msgstr ""
-#: ../../Zotlabs/Module/Import.php:197
-msgid "Channel exists but has been marked removed on this hub. Import failed."
+#: ../../Zotlabs/Module/Defperms.php:254
+msgid "Permission role"
msgstr ""
-#: ../../Zotlabs/Module/Import.php:568
-msgid ""
-"Automatic content and files import was not possible due to API version "
-"incompatiblity. Please import content and files manually!"
+#: ../../Zotlabs/Module/Defperms.php:255
+msgid "Add permission role"
msgstr ""
-#: ../../Zotlabs/Module/Import.php:596
-msgid "You must be logged in to use this feature."
-msgstr "Du må være innlogget for å bruke denne funksjonen."
+#: ../../Zotlabs/Module/Defperms.php:260
+msgid "Automatic approval settings"
+msgstr ""
-#: ../../Zotlabs/Module/Import.php:603
-msgid "Channel Import"
+#: ../../Zotlabs/Module/Defperms.php:263
+msgid "My Settings"
+msgstr "Mine innstillinger"
+
+#: ../../Zotlabs/Module/Defperms.php:267
+msgid ""
+"Some individual permissions may have been preset or locked based on your "
+"channel type and privacy settings."
msgstr ""
-#: ../../Zotlabs/Module/Import.php:604
+#: ../../Zotlabs/Module/Suggest.php:52
msgid ""
-"Use this form to import an existing channel from a different server/hub. You "
-"may retrieve the channel identity from the old server/hub via the network or "
-"provide an export file."
+"No suggestions available. If this is a new site, please try again in 24 "
+"hours."
msgstr ""
-"Bruk dette skjemaet for å importere en eksisterende kanal fra en annen "
-"tjener/hub. Du kan hente inn kanalidentiteten fra den gamle tjeneren/huben "
-"via nettverket eller ved å bruke en eksportfil."
+"Ingen forslag tilgjengelige. Hvis dette er et nytt nettsted, vennligst prøv "
+"igjen om 24 timer."
-#: ../../Zotlabs/Module/Import.php:606
-msgid "Or provide the old server/hub details"
-msgstr "Eller oppgi detaljene fra den gamle tjeneren/hub-en"
+#: ../../Zotlabs/Module/Follow.php:75
+msgid "Connection added."
+msgstr ""
-#: ../../Zotlabs/Module/Import.php:608
-msgid "Your old identity address (xyz@example.com)"
-msgstr "Din gamle identitetsadresse (xyz@example.com)"
+#: ../../Zotlabs/Module/Regmod.php:15
+msgid "Please login."
+msgstr "Vennligst logg inn."
-#: ../../Zotlabs/Module/Import.php:609
-msgid "Your old login email address"
-msgstr "Din gamle innloggings e-postadresse"
+#: ../../Zotlabs/Module/Directory.php:124
+msgid "No default suggestions were found."
+msgstr ""
-#: ../../Zotlabs/Module/Import.php:610
-msgid "Your old login password"
-msgstr "Ditt gamle innloggingspassord"
+#: ../../Zotlabs/Module/Directory.php:292
+msgid "Gender: "
+msgstr "Kjønn: "
-#: ../../Zotlabs/Module/Import.php:611
-msgid "Import your items and files (limited by available memory)"
-msgstr ""
+#: ../../Zotlabs/Module/Directory.php:294
+msgid "Status: "
+msgstr "Status: "
-#: ../../Zotlabs/Module/Import.php:613
-msgid ""
-"For either option, please choose whether to make this hub your new primary "
-"address, or whether your old location should continue this role. You will be "
-"able to post from either location, but only one can be marked as the primary "
-"location for files, photos, and media."
-msgstr ""
-"Enten du tar det ene eller det andre valget, vennligst angi om du vil at "
-"denne hubben skal være din nye primære adresse, eller om din gamle "
-"plassering skal fortsette å ha denne rollen. Du kan lage innlegg fra den ene "
-"eller den andre plasseringen, men bare en av dem kan markeres som den "
-"primære plasseringen for filer, bilder og media."
+#: ../../Zotlabs/Module/Directory.php:296
+msgid "Homepage: "
+msgstr "Hjemmeside: "
-#: ../../Zotlabs/Module/Import.php:615
-msgid "Make this hub my primary location"
-msgstr "Gjør dette nettstedet til min primære plassering"
+#: ../../Zotlabs/Module/Directory.php:357
+msgid "Description:"
+msgstr "Beskrivelse:"
-#: ../../Zotlabs/Module/Import.php:616
-msgid "Move this channel (disable all previous locations)"
+#: ../../Zotlabs/Module/Directory.php:359
+msgid "Unsafe"
msgstr ""
-#: ../../Zotlabs/Module/Import.php:617
-msgid "Use this channel nickname instead of the one provided"
+#: ../../Zotlabs/Module/Directory.php:362
+msgid "Spam"
msgstr ""
-#: ../../Zotlabs/Module/Import.php:617
-msgid ""
-"Leave blank to keep your existing channel nickname. You will be randomly "
-"assigned a similar nickname if either name is already allocated on this site."
-msgstr ""
+#: ../../Zotlabs/Module/Directory.php:372
+msgid "Public Forum:"
+msgstr "Offentlig forum:"
-#: ../../Zotlabs/Module/Import.php:619
-msgid ""
-"This process may take several minutes to complete. Please submit the form "
-"only once and leave this page open until finished."
+#: ../../Zotlabs/Module/Directory.php:375
+msgid "Keywords: "
+msgstr "Nøkkelord: "
+
+#: ../../Zotlabs/Module/Directory.php:378
+msgid "Don't suggest"
+msgstr "Ikke foreslå"
+
+#: ../../Zotlabs/Module/Directory.php:380
+msgid "Common connections (estimated):"
msgstr ""
-"Denne prosessen kan ta flere minutter å fullføre. Vennligst send inn dette "
-"skjemaet bare en gang og la siden være åpen inntil den er ferdig."
-#: ../../Zotlabs/Module/Editwebpage.php:139
-msgid "Page link"
-msgstr "Sidelenke"
+#: ../../Zotlabs/Module/Directory.php:430
+msgid "Global Directory"
+msgstr "Global katalog"
-#: ../../Zotlabs/Module/Editwebpage.php:166
-msgid "Edit Webpage"
-msgstr "Endre webside"
+#: ../../Zotlabs/Module/Directory.php:430
+msgid "Local Directory"
+msgstr "Lokal katalog"
-#: ../../Zotlabs/Module/Filer.php:53
-msgid "Enter a folder name"
-msgstr ""
+#: ../../Zotlabs/Module/Directory.php:436
+msgid "Finding:"
+msgstr "Finner:"
-#: ../../Zotlabs/Module/Filer.php:53
-msgid "or select an existing folder (doubleclick)"
-msgstr ""
+#: ../../Zotlabs/Module/Directory.php:441
+msgid "next page"
+msgstr "neste side"
-#: ../../Zotlabs/Module/Profperm.php:35 ../../Zotlabs/Module/Profperm.php:64
-msgid "Invalid profile identifier."
-msgstr "Ugyldig profil-identifikator."
+#: ../../Zotlabs/Module/Directory.php:441
+msgid "previous page"
+msgstr "forrige side"
-#: ../../Zotlabs/Module/Profperm.php:112
-msgid "Profile Visibility Editor"
-msgstr "Endre profilsynlighet"
+#: ../../Zotlabs/Module/Directory.php:442
+msgid "Sort options"
+msgstr "Sorteringsvalg"
-#: ../../Zotlabs/Module/Profperm.php:116
-msgid "Click on a contact to add or remove."
-msgstr "Klikk på en kontakt for å legge til eller fjerne."
+#: ../../Zotlabs/Module/Directory.php:443
+msgid "Alphabetic"
+msgstr "Alfabetisk"
-#: ../../Zotlabs/Module/Profperm.php:125
-msgid "Visible To"
-msgstr "Synlig for"
+#: ../../Zotlabs/Module/Directory.php:444
+msgid "Reverse Alphabetic"
+msgstr "Omvendt alfabetisk"
-#: ../../Zotlabs/Module/Profperm.php:141
-#: ../../Zotlabs/Module/Connections.php:221
-msgid "All Connections"
-msgstr "Alle forbindelser"
+#: ../../Zotlabs/Module/Directory.php:445
+msgid "Newest to Oldest"
+msgstr "Nyest til eldst"
-#: ../../Zotlabs/Module/Cal.php:62
-msgid "Permissions denied."
-msgstr "Tillatelse avvist."
+#: ../../Zotlabs/Module/Directory.php:446
+msgid "Oldest to Newest"
+msgstr "Eldst til nyest"
-#: ../../Zotlabs/Module/Cal.php:201 ../../Zotlabs/Module/Cdav.php:1028
-msgid "Today"
-msgstr "Idag"
+#: ../../Zotlabs/Module/Directory.php:464
+msgid "No entries (some entries may be hidden)."
+msgstr "Ingen oppføringer (noen oppføringer kan være skjult)."
-#: ../../Zotlabs/Module/Dircensor.php:61
-msgid "Entry censored"
+#: ../../Zotlabs/Module/Profile_photo.php:544
+msgid ""
+"This profile photo will be visible to anybody on the internet and may be "
+"distributed to other websites."
msgstr ""
+"Dette profilbildet vil være synlig for alle besøkende, og kan bli gjort "
+"tilgjengelig på andre nettsteder."
-#: ../../Zotlabs/Module/Dircensor.php:64
-msgid "Entry OK"
+#: ../../Zotlabs/Module/Profile_photo.php:544
+msgid ""
+"This profile photo will be visible only to channels with permission to view "
+"this profile."
msgstr ""
-#: ../../Zotlabs/Module/Acl.php:371
-msgid "network"
-msgstr "nettverk"
-
-#: ../../Zotlabs/Module/Pconfig.php:33 ../../Zotlabs/Module/Pconfig.php:69
-msgid "This setting requires special processing and editing has been blocked."
+#: ../../Zotlabs/Module/Profile_photo.php:546
+msgid "Use Photo for Profile"
msgstr ""
-"Denne innstillingen krever spesiell behandling og redigering har blitt "
-"blokkert."
-#: ../../Zotlabs/Module/Pconfig.php:58
-msgid "Configuration Editor"
-msgstr "Konfigurasjonsbehandler"
+#: ../../Zotlabs/Module/Profile_photo.php:546
+msgid "Change Profile Photo"
+msgstr "Endre profilbilde"
-#: ../../Zotlabs/Module/Pconfig.php:59
-msgid ""
-"Warning: Changing some settings could render your channel inoperable. Please "
-"leave this page unless you are comfortable with and knowledgeable about how "
-"to correctly use this feature."
-msgstr ""
-"Advarsel: kanalen din kan slutte å virke ved endring av enkelte "
-"innstillinger. Vennligst forlat denne siden med mindre du er komfortabel med "
-"dette og vet hvordan du bruker denne funksjonen riktig."
+#: ../../Zotlabs/Module/Profile_photo.php:548
+msgid "Reset to default"
+msgstr "Tilbakestill til standard"
-#: ../../Zotlabs/Module/Xchan.php:10
-msgid "Xchan Lookup"
-msgstr "Xchan oppslag"
+#: ../../Zotlabs/Module/Profile_photo.php:562
+msgid "Select existing"
+msgstr "Velg eksisterende bilde"
-#: ../../Zotlabs/Module/Xchan.php:13
-msgid "Lookup xchan beginning with (or webbie): "
-msgstr "Slå opp xchan som begynner med (eller webbie): "
+#: ../../Zotlabs/Module/Profile_photo.php:584
+msgid "Done editing"
+msgstr "Lagre endringer"
-#: ../../Zotlabs/Module/Register.php:113
-msgid "Email address required"
+#: ../../Zotlabs/Module/Authorize.php:17
+msgid "Unknown App"
msgstr ""
-#: ../../Zotlabs/Module/Register.php:157
-msgid "No password provided"
+#: ../../Zotlabs/Module/Authorize.php:29
+msgid "Authorize"
msgstr ""
-#: ../../Zotlabs/Module/Register.php:180
-msgid "Terms of Service not accepted"
+#: ../../Zotlabs/Module/Authorize.php:30
+#, php-format
+msgid "Do you authorize the app %s to access your channel data?"
msgstr ""
-#: ../../Zotlabs/Module/Register.php:242
-msgid "Invitation code succesfully applied"
-msgstr ""
+#: ../../Zotlabs/Module/Authorize.php:33
+#: ../../Zotlabs/Module/Admin/Accounts.php:219
+msgid "Deny"
+msgstr "Avslå"
-#: ../../Zotlabs/Module/Register.php:262
-msgid "Invitation not in time or too late"
-msgstr ""
+#: ../../Zotlabs/Module/Viewconnections.php:65
+msgid "No connections."
+msgstr "Ingen forbindelser."
-#: ../../Zotlabs/Module/Register.php:268
-msgid "Invitation email failed"
-msgstr ""
+#: ../../Zotlabs/Module/Viewconnections.php:105
+#, php-format
+msgid "Visit %s's profile [%s]"
+msgstr "Besøk %s sin profil [%s]"
-#: ../../Zotlabs/Module/Register.php:276
-msgid "Invitation code failed"
-msgstr ""
+#: ../../Zotlabs/Module/Viewconnections.php:135
+msgid "View Connections"
+msgstr "Vis forbindelser"
-#: ../../Zotlabs/Module/Register.php:283
-msgid "Invitations are not available"
-msgstr ""
+#: ../../Zotlabs/Module/Api.php:76 ../../Zotlabs/Module/Api.php:97
+msgid "Authorize application connection"
+msgstr "Tillat programforbindelse"
-#: ../../Zotlabs/Module/Register.php:293
-msgid "Registration on this hub is by invitation only"
+#: ../../Zotlabs/Module/Api.php:77
+msgid "Return to your app and insert this Security Code:"
msgstr ""
-#: ../../Zotlabs/Module/Register.php:400
-msgid "New register request"
-msgstr ""
+#: ../../Zotlabs/Module/Api.php:87
+msgid "Please login to continue."
+msgstr "Vennligst logg inn for å fortsette."
-#: ../../Zotlabs/Module/Register.php:418
-msgid "Error creating dId A"
+#: ../../Zotlabs/Module/Api.php:99
+msgid ""
+"Do you want to authorize this application to access your posts and contacts, "
+"and/or create new posts for you?"
msgstr ""
+"Vil du tillate dette programmet å få tilgang til dine innlegg og kontakter, "
+"og/eller lage nye innlegg for deg?"
-#: ../../Zotlabs/Module/Register.php:437
-msgid "Registration on this hub is disabled."
-msgstr "Registrering ved dette nettstedet er skrudd av."
+#: ../../Zotlabs/Module/Pdledit_gui.php:119
+#, fuzzy
+msgid "Layout not found"
+msgstr "Layouten ble ikke funnet."
-#: ../../Zotlabs/Module/Register.php:446
-msgid "Why do you want to join this hub?"
+#: ../../Zotlabs/Module/Pdledit_gui.php:127
+msgid "This template does not support pdledi_gui (no content regions defined)"
msgstr ""
-#: ../../Zotlabs/Module/Register.php:446
-msgid "This will help to review your registration"
+#: ../../Zotlabs/Module/Pdledit_gui.php:404
+msgid "Main page content"
msgstr ""
-#: ../../Zotlabs/Module/Register.php:447
-msgid "Registration on this hub is by approval only."
-msgstr "Registrering ved dette nettstedet skjer på godkjenning."
-
-#: ../../Zotlabs/Module/Register.php:448
-msgid "Register at another affiliated hub in case when prefered"
+#: ../../Zotlabs/Module/Pdledit_gui.php:405
+msgid "The main page content can not be edited!"
msgstr ""
-#: ../../Zotlabs/Module/Register.php:461
-msgid "Registration on this hub is by invitation only."
-msgstr ""
+#: ../../Zotlabs/Module/Home.php:112
+#, php-format
+msgid "Welcome to %s"
+msgstr "Velkommen til %s"
-#: ../../Zotlabs/Module/Register.php:462
-msgid "Register at another affiliated hub"
-msgstr ""
+#: ../../Zotlabs/Module/Tagrm.php:48 ../../Zotlabs/Module/Tagrm.php:98
+msgid "Tag removed"
+msgstr "Merkelapp fjernet"
-#: ../../Zotlabs/Module/Register.php:476 ../../Zotlabs/Module/Siteinfo.php:29
-msgid "Terms of Service"
-msgstr "Tjenesteavtale"
+#: ../../Zotlabs/Module/Tagrm.php:123
+msgid "Remove Item Tag"
+msgstr "Fjern merkelapp fra element"
-#: ../../Zotlabs/Module/Register.php:482
-#, php-format
-msgid "I accept the %s for this website"
-msgstr "Jeg godtar %s for dette nettstedet"
+#: ../../Zotlabs/Module/Tagrm.php:125
+msgid "Select a tag to remove: "
+msgstr "Velg merkelapp å fjerne: "
-#: ../../Zotlabs/Module/Register.php:489
-#, php-format
-msgid "I am over %s years of age and accept the %s for this website"
-msgstr "Jeg er mer enn %s år gammel, og godtar %s for dette nettstedet"
+#: ../../Zotlabs/Module/Layouts.php:129 ../../Zotlabs/Module/Layouts.php:187
+#: ../../Zotlabs/Module/Editlayout.php:128
+msgid "Layout Name"
+msgstr "Layout-navn"
-#: ../../Zotlabs/Module/Register.php:499
-msgid "Your email address"
-msgstr "Din e-postadresse"
+#: ../../Zotlabs/Module/Layouts.php:132 ../../Zotlabs/Module/Editlayout.php:129
+msgid "Layout Description (Optional)"
+msgstr "Layoutens beskrivelse (valgfritt)"
-#: ../../Zotlabs/Module/Register.php:506
-msgid "Choose a password"
-msgstr "Velg et passord"
+#: ../../Zotlabs/Module/Layouts.php:184
+msgid "Comanche page description language help"
+msgstr "Hjelp med Comanche sidebeskrivelsesspråk"
-#: ../../Zotlabs/Module/Register.php:507
-msgid "Please re-enter your password"
-msgstr "Vennligst skriv ditt passord en gang til"
+#: ../../Zotlabs/Module/Layouts.php:188
+msgid "Layout Description"
+msgstr "Layout-beskrivelse"
-#: ../../Zotlabs/Module/Register.php:509
-msgid "Please enter your invitation code"
-msgstr "Vennligst skriv din invitasjonskode"
+#: ../../Zotlabs/Module/Layouts.php:193
+msgid "Download PDL file"
+msgstr "Last ned PDL-fil"
-#: ../../Zotlabs/Module/Register.php:511
-msgid "Your name"
-msgstr "Navn"
+#: ../../Zotlabs/Module/Common.php:14
+msgid "No channel."
+msgstr "Ingen kanal."
-#: ../../Zotlabs/Module/Register.php:511
-msgid "Real name is preferred"
+#: ../../Zotlabs/Module/Common.php:45
+msgid "No connections in common."
+msgstr "Ingen forbindelser felles."
+
+#: ../../Zotlabs/Module/Common.php:65
+msgid "View Common Connections"
msgstr ""
-#: ../../Zotlabs/Module/Register.php:513
+#: ../../Zotlabs/Module/Locs.php:27 ../../Zotlabs/Module/Locs.php:65
+msgid "Location not found."
+msgstr "Plassering er ikke funnet."
+
+#: ../../Zotlabs/Module/Locs.php:74
+msgid "Location lookup failed."
+msgstr "Oppslag på plassering mislyktes."
+
+#: ../../Zotlabs/Module/Locs.php:78
msgid ""
-"Your nickname will be used to create an easy to remember channel address"
+"Please select another location to become primary before removing the primary "
+"location."
msgstr ""
+"Vennligst velg en annen plassering som primær før du sletter gjeldende "
+"primære plassering."
-#: ../../Zotlabs/Module/Register.php:521
-#: ../../Zotlabs/Module/Admin/Site.php:403
-msgid "Registration"
-msgstr "Registrering"
+#: ../../Zotlabs/Module/Locs.php:106
+msgid "Syncing locations"
+msgstr "Synkroniserer plasseringer"
-#: ../../Zotlabs/Module/Register.php:529
-msgid "I have an invite code"
-msgstr ""
+#: ../../Zotlabs/Module/Locs.php:115
+msgid "No locations found."
+msgstr "Ingen plasseringer ble funnet."
-#: ../../Zotlabs/Module/Register.php:576
+#: ../../Zotlabs/Module/Locs.php:120
+msgid "Manage Channel Locations"
+msgstr "Håndter kanalplasseringer"
+
+#: ../../Zotlabs/Module/Locs.php:124 ../../Zotlabs/Module/Menu.php:177
+msgid "Drop"
+msgstr "Slett"
+
+#: ../../Zotlabs/Module/Locs.php:126
+msgid "Sync Now"
+msgstr "Synkroniser nå"
+
+#: ../../Zotlabs/Module/Locs.php:127
+msgid "Please wait several minutes between consecutive operations."
+msgstr "Vennligst vent flere minutter mellom hver etterfølgende operasjon."
+
+#: ../../Zotlabs/Module/Locs.php:128
msgid ""
-"This site has exceeded the number of allowed daily account registrations."
+"When possible, drop a location by logging into that website/hub and removing "
+"your channel."
msgstr ""
+"Når mulig, fjern en plassering ved å logge inn på det nettstedet eller den "
+"hub-en og fjern din kanal."
+
+#: ../../Zotlabs/Module/Locs.php:129
+msgid "Use this form to drop the location if the hub is no longer operating."
+msgstr ""
+"Bruk dette skjemaet for å fjerne plasseringen hvis huben ikke er i drift "
+"lenger."
#: ../../Zotlabs/Module/Pubsites.php:27
msgid "Public Hubs"
@@ -8652,998 +10615,823 @@ msgstr "Vurderinger"
msgid "Rate"
msgstr "Vurder"
-#: ../../Zotlabs/Module/Thing.php:139
-msgid "Thing updated"
-msgstr "Tingen er oppdatert"
-
-#: ../../Zotlabs/Module/Thing.php:191
-msgid "Object store: failed"
-msgstr "Objektlagring: mislyktes"
-
-#: ../../Zotlabs/Module/Thing.php:195
-msgid "Thing added"
-msgstr "Ting lagt til"
-
-#: ../../Zotlabs/Module/Thing.php:210
-#, php-format
-msgid "OBJ: %1$s %2$s %3$s"
-msgstr "OBJ: %1$s %2$s %3$s"
-
-#: ../../Zotlabs/Module/Thing.php:277 ../../Zotlabs/Module/Thing.php:301
-msgid "item not found."
-msgstr "element ble ikke funnet."
-
-#: ../../Zotlabs/Module/Thing.php:336
-msgid "Edit Thing"
-msgstr "Endre ting"
-
-#: ../../Zotlabs/Module/Thing.php:338 ../../Zotlabs/Module/Thing.php:394
-msgid "Select a profile"
-msgstr "Velg en profil"
-
-#: ../../Zotlabs/Module/Thing.php:342 ../../Zotlabs/Module/Thing.php:397
-msgid "Post an activity"
-msgstr "Legg inn en aktivitet"
-
-#: ../../Zotlabs/Module/Thing.php:342 ../../Zotlabs/Module/Thing.php:397
-msgid "Only sends to viewers of the applicable profile"
-msgstr "Sender bare til seere av den aktuelle profilen"
-
-#: ../../Zotlabs/Module/Thing.php:344 ../../Zotlabs/Module/Thing.php:399
-msgid "Name of thing e.g. something"
-msgstr "Navn på ting for eksempel noe"
-
-#: ../../Zotlabs/Module/Thing.php:346 ../../Zotlabs/Module/Thing.php:400
-msgid "URL of thing (optional)"
-msgstr "URL til ting (valgfritt)"
-
-#: ../../Zotlabs/Module/Thing.php:348 ../../Zotlabs/Module/Thing.php:401
-msgid "URL for photo of thing (optional)"
-msgstr "URL til bilde av ting (valgfritt)"
-
-#: ../../Zotlabs/Module/Thing.php:392
-msgid "Add Thing to your Profile"
-msgstr "Legg til ting i din profil"
-
-#: ../../Zotlabs/Module/Home.php:105
-#, php-format
-msgid "Welcome to %s"
-msgstr "Velkommen til %s"
-
-#: ../../Zotlabs/Module/Blocks.php:154
-msgid "Block Title"
-msgstr "Byggeklossens tittel"
-
-#: ../../Zotlabs/Module/Defperms.php:111
-#: ../../Zotlabs/Module/Settings/Channel.php:151
-#: ../../extend/addon/hzaddons/openstreetmap/openstreetmap.php:163
-#: ../../extend/addon/hzaddons/piwik/piwik.php:116
-#: ../../extend/addon/hzaddons/twitter/twitter.php:493
-#: ../../extend/addon/hzaddons/diaspora/diaspora.php:108
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:82
-#: ../../extend/addon/hzaddons/logrot/logrot.php:54
-#: ../../extend/addon/hzaddons/xmpp/xmpp.php:54
-#: ../../extend/addon/hzaddons/faces/faces.php:291
-#: ../../extend/addon/hzaddons/msgfooter/msgfooter.php:54
-msgid "Settings updated."
-msgstr "Innstillinger oppdatert."
-
-#: ../../Zotlabs/Module/Defperms.php:252
-msgid "Connection Default Permissions"
-msgstr "Forbindelsens standard tillatelser"
-
-#: ../../Zotlabs/Module/Defperms.php:253
-msgid "Apply these permissions automatically"
-msgstr "Bruk disse tillatelsene automatisk"
-
-#: ../../Zotlabs/Module/Defperms.php:253
+#: ../../Zotlabs/Module/Changeaddr.php:35
msgid ""
-"If enabled, connection requests will be approved without your interaction"
+"Channel name changes are not allowed within 48 hours of changing the account "
+"password."
msgstr ""
-#: ../../Zotlabs/Module/Defperms.php:254
-msgid "Permission role"
+#: ../../Zotlabs/Module/Changeaddr.php:77
+msgid "Change channel nickname/address"
msgstr ""
-#: ../../Zotlabs/Module/Defperms.php:255
-msgid "Add permission role"
+#: ../../Zotlabs/Module/Changeaddr.php:78
+msgid "Any/all connections on other networks will be lost!"
msgstr ""
-#: ../../Zotlabs/Module/Defperms.php:260
-msgid "Automatic approval settings"
+#: ../../Zotlabs/Module/Changeaddr.php:80
+msgid "New channel address"
msgstr ""
-#: ../../Zotlabs/Module/Defperms.php:263
-msgid "My Settings"
-msgstr "Mine innstillinger"
-
-#: ../../Zotlabs/Module/Defperms.php:267
-msgid ""
-"Some individual permissions may have been preset or locked based on your "
-"channel type and privacy settings."
+#: ../../Zotlabs/Module/Changeaddr.php:81
+msgid "Rename Channel"
msgstr ""
-#: ../../Zotlabs/Module/Lostpass.php:19
-msgid "No valid account found."
-msgstr "Ingen gyldig konto funnet."
-
-#: ../../Zotlabs/Module/Lostpass.php:33
-msgid "Password reset request issued. Check your email."
-msgstr "Forespørsel om å tilbakestille passord er mottatt. Sjekk e-posten din."
-
-#: ../../Zotlabs/Module/Lostpass.php:39 ../../Zotlabs/Module/Lostpass.php:108
-#, php-format
-msgid "Site Member (%s)"
-msgstr "Nettstedsmedlem (%s)"
-
-#: ../../Zotlabs/Module/Lostpass.php:44 ../../Zotlabs/Module/Lostpass.php:49
-#, php-format
-msgid "Password reset requested at %s"
-msgstr "Forespurt om å tilbakestille passord hos %s"
+#: ../../Zotlabs/Module/Menu.php:68
+msgid "Unable to update menu."
+msgstr "Ikke i stand til å oppdatere meny."
-#: ../../Zotlabs/Module/Lostpass.php:68
-msgid ""
-"Request could not be verified. (You may have previously submitted it.) "
-"Password reset failed."
-msgstr ""
-"Forespørsel kunne ikke bekreftes. (Du kan ha sendt den inn tidligere.) "
-"Tilbakestilling av passord mislyktes."
+#: ../../Zotlabs/Module/Menu.php:79
+msgid "Unable to create menu."
+msgstr "Ikke i stand til å lage meny."
-#: ../../Zotlabs/Module/Lostpass.php:92
-msgid "Your password has been reset as requested."
-msgstr "Ditt passord har blitt tilbakestilt som forespurt."
+#: ../../Zotlabs/Module/Menu.php:161 ../../Zotlabs/Module/Menu.php:174
+msgid "Menu Name"
+msgstr "Menynavn"
-#: ../../Zotlabs/Module/Lostpass.php:93
-msgid "Your new password is"
-msgstr "Ditt nye passord er"
+#: ../../Zotlabs/Module/Menu.php:161
+msgid "Unique name (not visible on webpage) - required"
+msgstr "Unikt navn (ikke synlig på websiden) - påkrevet"
-#: ../../Zotlabs/Module/Lostpass.php:94
-msgid "Save or copy your new password - and then"
-msgstr "Lagre eller kopier ditt nye passord, og deretter kan du"
+#: ../../Zotlabs/Module/Menu.php:162 ../../Zotlabs/Module/Menu.php:175
+msgid "Menu Title"
+msgstr "Menytittel"
-#: ../../Zotlabs/Module/Lostpass.php:95
-msgid "click here to login"
-msgstr "klikke her for å logge inn"
+#: ../../Zotlabs/Module/Menu.php:162
+msgid "Visible on webpage - leave empty for no title"
+msgstr "Synlig på websiden - la stå tomt for ingen tittel"
-#: ../../Zotlabs/Module/Lostpass.php:96
-msgid ""
-"Your password may be changed from the <em>Settings</em> page after "
-"successful login."
-msgstr ""
-"Ditt passord kan endres på siden <em>Innstillinger</em> etter vellykket "
-"innlogging."
+#: ../../Zotlabs/Module/Menu.php:163
+msgid "Allow Bookmarks"
+msgstr "Tillat bokmerker"
-#: ../../Zotlabs/Module/Lostpass.php:117
-#, php-format
-msgid "Your password has changed at %s"
-msgstr "Ditt passord er endret hos %s"
+#: ../../Zotlabs/Module/Menu.php:163 ../../Zotlabs/Module/Menu.php:222
+msgid "Menu may be used to store saved bookmarks"
+msgstr "Menyen kan brukes til å lagre lagrede bokmerker"
-#: ../../Zotlabs/Module/Lostpass.php:130
-msgid "Forgot your Password?"
-msgstr "Glemt passord ditt?"
+#: ../../Zotlabs/Module/Menu.php:164 ../../Zotlabs/Module/Menu.php:225
+msgid "Submit and proceed"
+msgstr "Send inn og fortsett"
-#: ../../Zotlabs/Module/Lostpass.php:131
-msgid ""
-"Enter your email address and submit to have your password reset. Then check "
-"your email for further instructions."
-msgstr ""
-"Skriv e-postadressen din og send inn for å tilbakestille passordet ditt. "
-"Sjekk deretter din e-post for videre instruksjoner."
+#: ../../Zotlabs/Module/Menu.php:181
+msgid "Bookmarks allowed"
+msgstr "Bokmerker tillatt"
-#: ../../Zotlabs/Module/Lostpass.php:132
-msgid "Email Address"
-msgstr "E-postadresse"
+#: ../../Zotlabs/Module/Menu.php:183
+msgid "Delete this menu"
+msgstr "Slett denne menyen"
-#: ../../Zotlabs/Module/Lostpass.php:133 ../../Zotlabs/Module/Pdledit.php:76
-#: ../../Zotlabs/Module/Pdledit.php:93
-msgid "Reset"
-msgstr "Tilbakestill"
+#: ../../Zotlabs/Module/Menu.php:184 ../../Zotlabs/Module/Menu.php:219
+msgid "Edit menu contents"
+msgstr "Endre menyinnholdet"
-#: ../../Zotlabs/Module/Locs.php:27 ../../Zotlabs/Module/Locs.php:65
-msgid "Location not found."
-msgstr "Plassering er ikke funnet."
+#: ../../Zotlabs/Module/Menu.php:185
+msgid "Edit this menu"
+msgstr "Endre denne menyen"
-#: ../../Zotlabs/Module/Locs.php:74
-msgid "Location lookup failed."
-msgstr "Oppslag på plassering mislyktes."
+#: ../../Zotlabs/Module/Menu.php:201
+msgid "Menu could not be deleted."
+msgstr "Menyen kunne ikke bli slettet."
-#: ../../Zotlabs/Module/Locs.php:78
-msgid ""
-"Please select another location to become primary before removing the primary "
-"location."
-msgstr ""
-"Vennligst velg en annen plassering som primær før du sletter gjeldende "
-"primære plassering."
+#: ../../Zotlabs/Module/Menu.php:209 ../../Zotlabs/Module/Mitem.php:31
+msgid "Menu not found."
+msgstr "Menyen ble ikke funnet."
-#: ../../Zotlabs/Module/Locs.php:106
-msgid "Syncing locations"
-msgstr "Synkroniserer plasseringer"
+#: ../../Zotlabs/Module/Menu.php:214
+msgid "Edit Menu"
+msgstr "Endre meny"
-#: ../../Zotlabs/Module/Locs.php:115
-msgid "No locations found."
-msgstr "Ingen plasseringer ble funnet."
+#: ../../Zotlabs/Module/Menu.php:218
+msgid "Add or remove entries to this menu"
+msgstr "Legg til eller fjern punkter i denne menyen"
-#: ../../Zotlabs/Module/Locs.php:120
-msgid "Manage Channel Locations"
-msgstr "Håndter kanalplasseringer"
+#: ../../Zotlabs/Module/Menu.php:220
+msgid "Menu name"
+msgstr "Menynavn"
-#: ../../Zotlabs/Module/Locs.php:123
-#: ../../extend/addon/hzaddons/workflow/workflow.php:288
-msgid "Primary"
-msgstr "Primær"
+#: ../../Zotlabs/Module/Menu.php:220
+msgid "Must be unique, only seen by you"
+msgstr "Må være unik, ses bare av deg"
-#: ../../Zotlabs/Module/Locs.php:126
-msgid "Sync Now"
-msgstr "Synkroniser nå"
+#: ../../Zotlabs/Module/Menu.php:221
+msgid "Menu title"
+msgstr "Menytittel"
-#: ../../Zotlabs/Module/Locs.php:127
-msgid "Please wait several minutes between consecutive operations."
-msgstr "Vennligst vent flere minutter mellom hver etterfølgende operasjon."
+#: ../../Zotlabs/Module/Menu.php:221
+msgid "Menu title as seen by others"
+msgstr "Menytittelen andre ser"
-#: ../../Zotlabs/Module/Locs.php:128
-msgid ""
-"When possible, drop a location by logging into that website/hub and removing "
-"your channel."
-msgstr ""
-"Når mulig, fjern en plassering ved å logge inn på det nettstedet eller den "
-"hub-en og fjern din kanal."
+#: ../../Zotlabs/Module/Menu.php:222
+msgid "Allow bookmarks"
+msgstr "Tillat bokmerker"
-#: ../../Zotlabs/Module/Locs.php:129
-msgid "Use this form to drop the location if the hub is no longer operating."
-msgstr ""
-"Bruk dette skjemaet for å fjerne plasseringen hvis huben ikke er i drift "
-"lenger."
+#: ../../Zotlabs/Module/Menu.php:232 ../../Zotlabs/Module/Xchan.php:41
+#: ../../Zotlabs/Module/Mitem.php:134
+msgid "Not found."
+msgstr "Ikke funnet."
-#: ../../Zotlabs/Module/Totp_check.php:71
-#: ../../Zotlabs/Module/Admin/Account_edit.php:61
-#, fuzzy
-msgid "Account not found."
+#: ../../Zotlabs/Module/Admin/Accounts.php:71
+msgid "Account not found"
msgstr "Kontoen ble ikke funnet"
-#: ../../Zotlabs/Module/Totp_check.php:78
-msgid "Multifactor Verification"
-msgstr ""
+#: ../../Zotlabs/Module/Admin/Accounts.php:92
+#, php-format
+msgid "Account '%s' blocked"
+msgstr "Kontoen '%s' blokkert"
-#: ../../Zotlabs/Module/Totp_check.php:80
-msgid "Please enter the verification key from your authenticator app"
-msgstr ""
+#: ../../Zotlabs/Module/Admin/Accounts.php:100
+#, php-format
+msgid "Account '%s' unblocked"
+msgstr "Kontoen '%s' er ikke blokkert lenger"
-#: ../../Zotlabs/Module/Totp_check.php:81
-msgid "Verify"
+#: ../../Zotlabs/Module/Admin/Accounts.php:140
+msgid "Unverified"
msgstr ""
-#: ../../Zotlabs/Module/Page.php:174
-msgid ""
-"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
-"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, "
-"quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo "
-"consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse "
-"cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat "
-"non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
+#: ../../Zotlabs/Module/Admin/Accounts.php:143
+msgid "Expired"
msgstr ""
-"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
-"tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, "
-"quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo "
-"consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse "
-"cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat "
-"non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
-#: ../../Zotlabs/Module/Vote.php:40
-msgid "Poll not found."
-msgstr "Fant ikke spørreskjema."
+#: ../../Zotlabs/Module/Admin/Accounts.php:207
+#: ../../Zotlabs/Module/Admin/Themes.php:138
+#: ../../Zotlabs/Module/Admin/Themes.php:172
+#: ../../Zotlabs/Module/Admin/Addons.php:124
+#: ../../Zotlabs/Module/Admin/Addons.php:191
+#: ../../Zotlabs/Module/Admin/Channels.php:167
+#: ../../Zotlabs/Module/Admin/Logs.php:83
+#: ../../Zotlabs/Module/Admin/Site.php:399
+#: ../../Zotlabs/Module/Admin/Security.php:106
+#: ../../Zotlabs/Module/Admin.php:142
+msgid "Administration"
+msgstr "Administrasjon"
-#: ../../Zotlabs/Module/Vote.php:69
-msgid "Invalid response."
+#: ../../Zotlabs/Module/Admin/Accounts.php:210
+msgid "Show verified registrations"
msgstr ""
-#: ../../Zotlabs/Module/Vote.php:125
-msgid "Response submitted. Updates may not appear instantly."
+#: ../../Zotlabs/Module/Admin/Accounts.php:210
+msgid "Show all registrations"
msgstr ""
-#: ../../Zotlabs/Module/Profile_photo.php:269
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:299
-msgid ""
-"Shift-reload the page or clear browser cache if the new photo does not "
-"display immediately."
+#: ../../Zotlabs/Module/Admin/Accounts.php:212
+msgid "Select toggle"
msgstr ""
-"Hold nede Shift-knappen og last siden på nytt eller tøm nettleserens "
-"mellomlager hvis det nye bildet ikke vises umiddelbart."
-#: ../../Zotlabs/Module/Profile_photo.php:544
-msgid ""
-"This profile photo will be visible to anybody on the internet and may be "
-"distributed to other websites."
+#: ../../Zotlabs/Module/Admin/Accounts.php:213
+msgid "Deny selected"
msgstr ""
-"Dette profilbildet vil være synlig for enhver besøkende, og kan bli gjort tilg"
-"jengelig på andre nettsteder."
-#: ../../Zotlabs/Module/Profile_photo.php:544
-msgid ""
-"This profile photo will be visible only to channels with permission to view "
-"this profile."
+#: ../../Zotlabs/Module/Admin/Accounts.php:214
+msgid "Approve selected"
msgstr ""
-#: ../../Zotlabs/Module/Profile_photo.php:546
-msgid "Use Photo for Profile"
+#: ../../Zotlabs/Module/Admin/Accounts.php:215
+msgid "All registrations"
msgstr ""
-#: ../../Zotlabs/Module/Profile_photo.php:546
-msgid "Change Profile Photo"
-msgstr "Endre profilbilde"
-
-#: ../../Zotlabs/Module/Profile_photo.php:548
-msgid "Reset to default"
-msgstr "Sett tilbake til standard"
+#: ../../Zotlabs/Module/Admin/Accounts.php:215
+msgid "Verified registrations waiting for approval"
+msgstr ""
-#: ../../Zotlabs/Module/Profile_photo.php:562
-msgid "Select existing"
-msgstr "Velg eksisterende bilde"
+#: ../../Zotlabs/Module/Admin/Accounts.php:216
+msgid "Request date"
+msgstr "Dato for forespørsel"
-#: ../../Zotlabs/Module/Profile_photo.php:584
-msgid "Done editing"
-msgstr "Lagre endringer"
+#: ../../Zotlabs/Module/Admin/Accounts.php:216
+msgid "Requests"
+msgstr ""
-#: ../../Zotlabs/Module/Webpages.php:67
-msgid "Import Webpage Elements"
+#: ../../Zotlabs/Module/Admin/Accounts.php:217
+msgid "No registrations available"
msgstr ""
-#: ../../Zotlabs/Module/Webpages.php:68
-msgid "Import selected"
+#: ../../Zotlabs/Module/Admin/Accounts.php:217
+msgid "No verified registrations available"
msgstr ""
-#: ../../Zotlabs/Module/Webpages.php:91
-msgid "Export Webpage Elements"
+#: ../../Zotlabs/Module/Admin/Accounts.php:223
+msgid "Verified"
msgstr ""
-#: ../../Zotlabs/Module/Webpages.php:92
-msgid "Export selected"
+#: ../../Zotlabs/Module/Admin/Accounts.php:224
+msgid "Not yet verified"
msgstr ""
-#: ../../Zotlabs/Module/Webpages.php:258
-msgid "Actions"
-msgstr "Handlinger"
+#: ../../Zotlabs/Module/Admin/Accounts.php:229
+msgid "ID"
+msgstr "ID"
-#: ../../Zotlabs/Module/Webpages.php:259
-msgid "Page Link"
-msgstr "Sidelenke"
+#: ../../Zotlabs/Module/Admin/Accounts.php:231
+msgid "All channels"
+msgstr ""
-#: ../../Zotlabs/Module/Webpages.php:260
-msgid "Page Title"
-msgstr "Sidetittel"
+#: ../../Zotlabs/Module/Admin/Accounts.php:232
+msgid "Register date"
+msgstr "Registreringsdato"
-#: ../../Zotlabs/Module/Webpages.php:290
-msgid "Invalid file type."
-msgstr ""
+#: ../../Zotlabs/Module/Admin/Accounts.php:233
+msgid "Last login"
+msgstr "Siste innlogging"
-#: ../../Zotlabs/Module/Webpages.php:302
-msgid "Error opening zip file"
-msgstr ""
+#: ../../Zotlabs/Module/Admin/Accounts.php:234
+msgid "Expires"
+msgstr "Utløper"
-#: ../../Zotlabs/Module/Webpages.php:313
-msgid "Invalid folder path."
+#: ../../Zotlabs/Module/Admin/Accounts.php:235
+#: ../../Zotlabs/Module/Admin/Account_edit.php:77
+msgid "Service class"
msgstr ""
-#: ../../Zotlabs/Module/Webpages.php:340
-msgid "No webpage elements detected."
+#: ../../Zotlabs/Module/Admin/Accounts.php:237
+msgid ""
+"Selected accounts will be deleted!\\n\\nEverything these accounts had posted "
+"on this site will be permanently deleted!\\n\\nAre you sure?"
msgstr ""
+"Valgte kontoer vil bli slettet!\\n\\nAlt disse kontoene har lagt inn på "
+"dette nettstedet vil bli slettet permanent!\\n\\nEr du sikker på at du vil "
+"slette disse valgte kontoene?"
-#: ../../Zotlabs/Module/Webpages.php:415
-msgid "Import complete."
+#: ../../Zotlabs/Module/Admin/Accounts.php:238
+msgid ""
+"The account {0} will be deleted!\\n\\nEverything this account has posted on "
+"this site will be permanently deleted!\\n\\nAre you sure?"
msgstr ""
+"Kontoen {0} vl bli slettet!\\n\\nAlt denne kontoen har lagt inn på dette "
+"nettstedet vil bli slettet permanent!\\n\\nEr du sikker på at du vil slette "
+"denne kontoen?"
-#: ../../Zotlabs/Module/Connections.php:58
-#: ../../Zotlabs/Module/Connections.php:116
-msgid "Active"
-msgstr "Aktiv"
-
-#: ../../Zotlabs/Module/Connections.php:63
-#: ../../Zotlabs/Module/Connections.php:185
-#: ../../Zotlabs/Module/Connections.php:296
-msgid "Blocked"
-msgstr "Blokkert"
-
-#: ../../Zotlabs/Module/Connections.php:68
-#: ../../Zotlabs/Module/Connections.php:192
-#: ../../Zotlabs/Module/Connections.php:295
-msgid "Ignored"
-msgstr "Ignorert"
-
-#: ../../Zotlabs/Module/Connections.php:73
-#: ../../Zotlabs/Module/Connections.php:206
-#: ../../Zotlabs/Module/Connections.php:294
-msgid "Hidden"
-msgstr "Skjult"
+#: ../../Zotlabs/Module/Admin/Accounts.php:247
+msgid "Message"
+msgstr "Melding"
-#: ../../Zotlabs/Module/Connections.php:78
-#: ../../Zotlabs/Module/Connections.php:199
-msgid "Archived/Unreachable"
-msgstr "Arkivert/utilgjengelig"
+#: ../../Zotlabs/Module/Admin/Accounts.php:366
+#, php-format
+msgid "%s account blocked/unblocked"
+msgid_plural "%s account blocked/unblocked"
+msgstr[0] "%s konto blokkert/ikke blokkert lenger"
+msgstr[1] "%s kontoer blokkert/ikke blokkert lenger"
-#: ../../Zotlabs/Module/Connections.php:161
-msgid "Active Connections"
-msgstr "Aktive forbindelser"
+#: ../../Zotlabs/Module/Admin/Accounts.php:389
+#, php-format
+msgid "%s account deleted"
+msgid_plural "%s accounts deleted"
+msgstr[0] "%s konto slettet"
+msgstr[1] "%s kontoer slettet"
-#: ../../Zotlabs/Module/Connections.php:164
-msgid "Show active connections"
+#: ../../Zotlabs/Module/Admin/Queueworker.php:66
+msgid "Max queueworker threads"
msgstr ""
-#: ../../Zotlabs/Module/Connections.php:168
-#: ../../Zotlabs/Widget/Notifications.php:96
-msgid "New Connections"
-msgstr "Nye forbindelser"
-
-#: ../../Zotlabs/Module/Connections.php:171
-msgid "Show pending (new) connections"
-msgstr "Vis ventende (nye) forbindelser"
-
-#: ../../Zotlabs/Module/Connections.php:188
-msgid "Only show blocked connections"
-msgstr "Vis bare forbindelser som er blokkert"
-
-#: ../../Zotlabs/Module/Connections.php:195
-msgid "Only show ignored connections"
-msgstr "Vis bare ignorerte forbindelser"
+#: ../../Zotlabs/Module/Admin/Queueworker.php:68
+msgid "Minimum 4, default 4"
+msgstr ""
-#: ../../Zotlabs/Module/Connections.php:202
-msgid "Only show archived/unreachable connections"
+#: ../../Zotlabs/Module/Admin/Queueworker.php:78
+msgid "Assume workers dead after"
msgstr ""
-#: ../../Zotlabs/Module/Connections.php:209
-msgid "Only show hidden connections"
-msgstr "Vis bare skjulte forbindelser"
+#: ../../Zotlabs/Module/Admin/Queueworker.php:80
+msgid "Minimum 120, default 300 seconds"
+msgstr ""
-#: ../../Zotlabs/Module/Connections.php:224
-msgid "Show all connections"
-msgstr "Vis alle forbindelser"
+#: ../../Zotlabs/Module/Admin/Queueworker.php:92
+msgid "Pause before starting next task"
+msgstr ""
-#: ../../Zotlabs/Module/Connections.php:292
-msgid "Pending approval"
-msgstr "Venter på godkjenning"
+#: ../../Zotlabs/Module/Admin/Queueworker.php:94
+msgid "Minimum 100, default 100 microseconds"
+msgstr ""
-#: ../../Zotlabs/Module/Connections.php:293
-msgid "Archived"
-msgstr "Arkivert"
+#: ../../Zotlabs/Module/Admin/Queueworker.php:103
+msgid "Automatically adjust pause before starting next task"
+msgstr ""
-#: ../../Zotlabs/Module/Connections.php:297
-msgid "Not connected at this location"
+#: ../../Zotlabs/Module/Admin/Queueworker.php:112
+msgid "Queueworker Settings"
msgstr ""
-#: ../../Zotlabs/Module/Connections.php:336
-#, php-format
-msgid "%1$s [%2$s]"
-msgstr "%1$s [%2$s]"
+#: ../../Zotlabs/Module/Admin/Themes.php:27
+msgid "Theme settings updated."
+msgstr "Temainnstillinger er oppdatert."
-#: ../../Zotlabs/Module/Connections.php:337
-msgid "Edit connection"
-msgstr "Endre forbindelse"
+#: ../../Zotlabs/Module/Admin/Themes.php:62
+msgid "No themes found."
+msgstr "Ingen temaer er funnet."
-#: ../../Zotlabs/Module/Connections.php:339
-msgid "Delete connection"
-msgstr "Slett forbindelse"
+#: ../../Zotlabs/Module/Admin/Themes.php:111
+#: ../../Zotlabs/Module/Admin/Addons.php:93
+msgid "Disable"
+msgstr "Skru av"
-#: ../../Zotlabs/Module/Connections.php:345
-msgid "Channel address"
-msgstr "Kanaladresse"
+#: ../../Zotlabs/Module/Admin/Themes.php:113
+#: ../../Zotlabs/Module/Admin/Addons.php:96
+msgid "Enable"
+msgstr "Skru på"
-#: ../../Zotlabs/Module/Connections.php:350
-msgid "Call"
-msgstr ""
+#: ../../Zotlabs/Module/Admin/Themes.php:132
+msgid "Screenshot"
+msgstr "Skjermbilde"
-#: ../../Zotlabs/Module/Connections.php:352
-msgid "Status"
-msgstr "Status"
+#: ../../Zotlabs/Module/Admin/Themes.php:140
+#: ../../Zotlabs/Module/Admin/Addons.php:126
+msgid "Toggle"
+msgstr "Skru av og på"
-#: ../../Zotlabs/Module/Connections.php:355
-msgid "Connected"
-msgstr "Forbundet"
+#: ../../Zotlabs/Module/Admin/Themes.php:150
+#: ../../Zotlabs/Module/Admin/Addons.php:134
+msgid "Author: "
+msgstr "Forfatter: "
-#: ../../Zotlabs/Module/Connections.php:357
-#: ../../Zotlabs/Module/Contactedit.php:448
-msgid "Approve connection"
-msgstr "Godkjenn forbindelse"
+#: ../../Zotlabs/Module/Admin/Themes.php:151
+#: ../../Zotlabs/Module/Admin/Addons.php:135
+msgid "Maintainer: "
+msgstr "Vedlikeholder: "
-#: ../../Zotlabs/Module/Connections.php:359
-msgid "Ignore connection"
-msgstr "Ignorer forbindelse"
+#: ../../Zotlabs/Module/Admin/Themes.php:178
+msgid "[Experimental]"
+msgstr "[Eksperimentelt]"
-#: ../../Zotlabs/Module/Connections.php:361
-msgid "Recent activity"
-msgstr "Nylig aktivitet"
+#: ../../Zotlabs/Module/Admin/Themes.php:179
+msgid "[Unsupported]"
+msgstr "[Ingen støtte]"
-#: ../../Zotlabs/Module/Connections.php:367
-msgid "Connect at this location"
-msgstr ""
+#: ../../Zotlabs/Module/Admin/Addons.php:72
+#, php-format
+msgid "Plugin %s disabled."
+msgstr "Tilleggsfunksjonen %s er avskrudd."
-#: ../../Zotlabs/Module/Connections.php:377 ../../Zotlabs/Widget/Follow.php:27
+#: ../../Zotlabs/Module/Admin/Addons.php:77
#, php-format
-msgid "You have %1$.0f of %2$.0f allowed connections."
-msgstr "Du har %1$.0f av %2$.0f tillate forbindelser."
+msgid "Plugin %s enabled."
+msgstr "Tilleggsfunksjonen %s er påskrudd."
-#: ../../Zotlabs/Module/Connections.php:405
-msgid "Search your connections"
-msgstr "Søk blant dine forbindelser"
+#: ../../Zotlabs/Module/Admin/Addons.php:136
+msgid "Minimum project version: "
+msgstr "Minimum prosjektversjon: "
-#: ../../Zotlabs/Module/Connections.php:406
-msgid "Contact search"
-msgstr ""
+#: ../../Zotlabs/Module/Admin/Addons.php:137
+msgid "Maximum project version: "
+msgstr "Maksimum prosjektversjon: "
-#: ../../Zotlabs/Module/Connections.php:413
-#: ../../Zotlabs/Module/Contactedit.php:383
-msgid "This is a group/forum channel"
+#: ../../Zotlabs/Module/Admin/Addons.php:138
+msgid "Minimum PHP version: "
+msgstr "Minimum PHP-versjon: "
+
+#: ../../Zotlabs/Module/Admin/Addons.php:139
+msgid "Compatible Server Roles: "
msgstr ""
-#: ../../Zotlabs/Module/Connect.php:65 ../../Zotlabs/Module/Connect.php:118
-msgid "Continue"
-msgstr "Fortsett"
+#: ../../Zotlabs/Module/Admin/Addons.php:140
+msgid "Requires: "
+msgstr "Krever: "
-#: ../../Zotlabs/Module/Connect.php:99
-msgid "Premium Channel Setup"
-msgstr "Premiumkanal-oppsett"
+#: ../../Zotlabs/Module/Admin/Addons.php:141
+#: ../../Zotlabs/Module/Admin/Addons.php:197
+msgid "Disabled - version incompatibility"
+msgstr "Skrudd av - versjonsinkompatibilitet"
-#: ../../Zotlabs/Module/Connect.php:101
-msgid "Enable premium channel connection restrictions"
-msgstr "Slå på restriksjoner for forbindelse med premiumkanal"
+#: ../../Zotlabs/Module/Admin/Features.php:56
+#, php-format
+msgid "Lock feature %s"
+msgstr "Lås funksjon %s"
-#: ../../Zotlabs/Module/Connect.php:102
-msgid ""
-"Please enter your restrictions or conditions, such as paypal receipt, usage "
-"guidelines, etc."
-msgstr ""
-"Vennligst skriv dine restriksjoner og betingelser, slik som PayPal-"
-"kvittering, retningslinjer for bruk, og så videre."
+#: ../../Zotlabs/Module/Admin/Features.php:65
+msgid "Manage Additional Features"
+msgstr "Håndter tilleggsfunksjoner"
-#: ../../Zotlabs/Module/Connect.php:104 ../../Zotlabs/Module/Connect.php:124
-msgid ""
-"This channel may require additional steps or acknowledgement of the "
-"following conditions prior to connecting:"
-msgstr ""
-"Denne kanalen kan kreve ytterligere steg og bekreftelse av følgende "
-"betingelser før tilkobling:"
+#: ../../Zotlabs/Module/Admin/Profs.php:90
+msgid "New Profile Field"
+msgstr "Nytt profilfelt"
-#: ../../Zotlabs/Module/Connect.php:105
-msgid ""
-"Potential connections will then see the following text before proceeding:"
-msgstr "Potensielle forbindelser vil da se følgende tekst før de går videre:"
+#: ../../Zotlabs/Module/Admin/Profs.php:91
+#: ../../Zotlabs/Module/Admin/Profs.php:111
+msgid "Field nickname"
+msgstr "Feltets kallenavn"
-#: ../../Zotlabs/Module/Connect.php:106 ../../Zotlabs/Module/Connect.php:127
-msgid ""
-"By continuing, I certify that I have complied with any instructions provided "
-"on this page."
-msgstr ""
-"Ved å fortsette bekrefter jeg at jeg har oppfylt alle instruksjoner gitt på "
-"denne siden."
+#: ../../Zotlabs/Module/Admin/Profs.php:91
+#: ../../Zotlabs/Module/Admin/Profs.php:111
+msgid "System name of field"
+msgstr "Systemnavnet til feltet"
-#: ../../Zotlabs/Module/Connect.php:115
-msgid "(No specific instructions have been provided by the channel owner.)"
-msgstr "(Ingen spesifikke instruksjoner er gitt av kanaleieren.)"
+#: ../../Zotlabs/Module/Admin/Profs.php:92
+#: ../../Zotlabs/Module/Admin/Profs.php:112
+msgid "Input type"
+msgstr "Inndata-type"
-#: ../../Zotlabs/Module/Connect.php:123
-msgid "Restricted or Premium Channel"
-msgstr "Begrenset kanal eller premiumkanal"
+#: ../../Zotlabs/Module/Admin/Profs.php:93
+#: ../../Zotlabs/Module/Admin/Profs.php:113
+msgid "Field Name"
+msgstr "Feltnavn"
-#: ../../Zotlabs/Module/Affinity.php:35
-msgid "Affinity Tool settings updated."
-msgstr ""
+#: ../../Zotlabs/Module/Admin/Profs.php:93
+#: ../../Zotlabs/Module/Admin/Profs.php:113
+msgid "Label on profile pages"
+msgstr "Merkelapp på profilsider"
-#: ../../Zotlabs/Module/Affinity.php:54
-msgid ""
-"The numbers below represent the minimum and maximum slider default positions "
-"for your network/stream page as a percentage."
-msgstr ""
+#: ../../Zotlabs/Module/Admin/Profs.php:94
+#: ../../Zotlabs/Module/Admin/Profs.php:114
+msgid "Help text"
+msgstr "Hjelpetekst"
-#: ../../Zotlabs/Module/Affinity.php:61
-msgid "Default maximum affinity level"
-msgstr ""
+#: ../../Zotlabs/Module/Admin/Profs.php:94
+#: ../../Zotlabs/Module/Admin/Profs.php:114
+msgid "Additional info (optional)"
+msgstr "Tilleggsinformasjon (valgfritt)"
-#: ../../Zotlabs/Module/Affinity.php:61
-#, fuzzy
-msgid "0-99 default 99"
-msgstr "Standard"
+#: ../../Zotlabs/Module/Admin/Profs.php:104
+msgid "Field definition not found"
+msgstr "Feltdefinisjonen ble ikke funnet"
-#: ../../Zotlabs/Module/Affinity.php:67
-msgid "Default minimum affinity level"
-msgstr ""
+#: ../../Zotlabs/Module/Admin/Profs.php:110
+msgid "Edit Profile Field"
+msgstr "Endre profilfelt"
-#: ../../Zotlabs/Module/Affinity.php:67
-#, fuzzy
-msgid "0-99 - default 0"
-msgstr "Standard"
+#: ../../Zotlabs/Module/Admin/Profs.php:170
+msgid "Basic Profile Fields"
+msgstr "Grunnleggende profilfelter"
-#: ../../Zotlabs/Module/Affinity.php:73
-msgid "Persistent affinity levels"
-msgstr ""
+#: ../../Zotlabs/Module/Admin/Profs.php:171
+msgid "Advanced Profile Fields"
+msgstr "Utvidede profilfelter"
-#: ../../Zotlabs/Module/Affinity.php:73
-msgid ""
-"If disabled the max and min levels will be reset to default after page reload"
-msgstr ""
+#: ../../Zotlabs/Module/Admin/Profs.php:171
+msgid "(In addition to basic fields)"
+msgstr "(I tillegg til grunnleggende felt)"
-#: ../../Zotlabs/Module/Affinity.php:81
-msgid "Affinity Tool Settings"
+#: ../../Zotlabs/Module/Admin/Profs.php:173
+msgid "All available fields"
+msgstr "Alle tilgjengelige felt"
+
+#: ../../Zotlabs/Module/Admin/Profs.php:174
+msgid "Custom Fields"
msgstr ""
-#: ../../Zotlabs/Module/Siteinfo.php:22
-msgid "About this site"
-msgstr "Om dette nettstedet "
+#: ../../Zotlabs/Module/Admin/Profs.php:178
+msgid "Create Custom Field"
+msgstr "Legg til egendefinert felt"
-#: ../../Zotlabs/Module/Siteinfo.php:23
-#, fuzzy
-msgid "Site Name"
-msgstr "Nettstedets navn"
+#: ../../Zotlabs/Module/Admin/Channels.php:28
+#: ../../Zotlabs/Module/Admin/Channels.php:80
+msgid "Channel not found"
+msgstr "Kanalen ble ikke funnet"
-#: ../../Zotlabs/Module/Siteinfo.php:25 ../../Zotlabs/Module/Admin/Site.php:414
-msgid "Site Information"
-msgstr "Nettstedsinformasjon"
+#: ../../Zotlabs/Module/Admin/Channels.php:48
+#, php-format
+msgid "%s channel censored/uncensored"
+msgid_plural "%s channels censored/uncensored"
+msgstr[0] "%s kanal er sensurert/ikke sensurert lenger"
+msgstr[1] "%s kanaler er sensurert/ikke sensurert lenger"
-#: ../../Zotlabs/Module/Siteinfo.php:27
-msgid "Administrator"
-msgstr "Administrator"
+#: ../../Zotlabs/Module/Admin/Channels.php:57
+#, php-format
+msgid "%s channel code allowed/disallowed"
+msgid_plural "%s channels code allowed/disallowed"
+msgstr[0] "%s kanal med kode tillatt/ikke tillatt"
+msgstr[1] "%s kanaler med kode tillatt/ikke tillatt"
-#: ../../Zotlabs/Module/Siteinfo.php:30
-msgid "Software and Project information"
-msgstr "Program- og prosjektinformasjon"
+#: ../../Zotlabs/Module/Admin/Channels.php:63
+#, php-format
+msgid "%s channel deleted"
+msgid_plural "%s channels deleted"
+msgstr[0] "%s kanal slettet"
+msgstr[1] "%s kanaler slettet"
-#: ../../Zotlabs/Module/Siteinfo.php:31
-msgid "This site is powered by $Projectname"
-msgstr "Dette nettstedet drives av $Projectname"
+#: ../../Zotlabs/Module/Admin/Channels.php:90
+#, php-format
+msgid "Channel '%s' deleted"
+msgstr "Kanalen '%s' er slettet"
-#: ../../Zotlabs/Module/Siteinfo.php:32
-#, fuzzy
-#| msgid ""
-#| "Federated and decentralised networking and identity services provided by "
-#| "Zot"
-msgid ""
-"Federated and decentralised networking and identity services provided by"
-msgstr "Fødererte og desentraliserte nettverks- og identitetstjenester via Zot"
+#: ../../Zotlabs/Module/Admin/Channels.php:109
+#, php-format
+msgid "Channel '%s' censored"
+msgstr "Kanalen '%s' er sensurert"
-#: ../../Zotlabs/Module/Siteinfo.php:35
-msgid "Additional federated transport protocols:"
-msgstr "Øvrige fødererte transportprotokoller:"
+#: ../../Zotlabs/Module/Admin/Channels.php:109
+#, php-format
+msgid "Channel '%s' uncensored"
+msgstr "Kanalen '%s' er ikke sensurert lenger"
-#: ../../Zotlabs/Module/Siteinfo.php:37
+#: ../../Zotlabs/Module/Admin/Channels.php:120
#, php-format
-msgid "Version %s"
-msgstr "Versjon %s"
+msgid "Channel '%s' code allowed"
+msgstr "Kanal '%s' kode tillatt"
-#: ../../Zotlabs/Module/Siteinfo.php:38
-msgid "Project homepage"
-msgstr "Prosjektets hjemmeside"
+#: ../../Zotlabs/Module/Admin/Channels.php:120
+#, php-format
+msgid "Channel '%s' code disallowed"
+msgstr "Kanal '%s' kode ikke tillatt"
-#: ../../Zotlabs/Module/Siteinfo.php:39
-msgid "Developer homepage"
-msgstr "Utviklers hjemmeside"
+#: ../../Zotlabs/Module/Admin/Channels.php:170
+msgid "select all"
+msgstr "velg alle"
-#: ../../Zotlabs/Module/Siteinfo.php:42 ../../Zotlabs/Module/Admin.php:149
-msgid "Active addons"
-msgstr "Aktive tillegg"
+#: ../../Zotlabs/Module/Admin/Channels.php:172
+msgid "Censor"
+msgstr "Sensurer"
-#: ../../Zotlabs/Module/Siteinfo.php:43
-#, fuzzy
-#| msgid "Block Title"
-msgid "Blocked sites"
-msgstr "Byggeklossens tittel"
+#: ../../Zotlabs/Module/Admin/Channels.php:173
+msgid "Uncensor"
+msgstr "Ikke sensurer lenger"
-#: ../../Zotlabs/Module/Import_progress.php:40
-msgid "Item sync completed!"
-msgstr ""
+#: ../../Zotlabs/Module/Admin/Channels.php:174
+msgid "Allow Code"
+msgstr "Tillat kode"
-#: ../../Zotlabs/Module/Import_progress.php:62
-#: ../../Zotlabs/Module/Import_progress.php:111
-msgid "Import host does not seem to be online or compatible"
-msgstr ""
+#: ../../Zotlabs/Module/Admin/Channels.php:175
+msgid "Disallow Code"
+msgstr "Ikke tillat kode"
-#: ../../Zotlabs/Module/Import_progress.php:73
-msgid "Item sync completed but no items were found!"
-msgstr ""
+#: ../../Zotlabs/Module/Admin/Channels.php:180
+msgid "UID"
+msgstr "UID"
-#: ../../Zotlabs/Module/Import_progress.php:90
-msgid "File sync completed!"
+#: ../../Zotlabs/Module/Admin/Channels.php:184
+msgid ""
+"Selected channels will be deleted!\\n\\nEverything that was posted in these "
+"channels on this site will be permanently deleted!\\n\\nAre you sure?"
msgstr ""
+"Valgte kanaler vil bli slettet!\\n\\nAlt innhold som er lagt inn i disse "
+"kanalene på dette nettstedet vil bli slettet for alltid!\\n\\nEr du sikker "
+"på at du vil slette disse kanalene med alt innhold?"
-#: ../../Zotlabs/Module/Import_progress.php:122
-msgid "File sync completed but no files were found!"
+#: ../../Zotlabs/Module/Admin/Channels.php:185
+msgid ""
+"The channel {0} will be deleted!\\n\\nEverything that was posted in this "
+"channel on this site will be permanently deleted!\\n\\nAre you sure?"
msgstr ""
+"Kanalen {0} vil bli slettet!\\n\\nAlt innhold som er lagt inn i denne "
+"kanalen på dettet nettstedet vil bli slettet for alltid!\\n\\nEr du sikker "
+"på at du vil slette denne kanalen med alt innhold?"
-#: ../../Zotlabs/Module/Import_progress.php:137
-msgid "Channel clone status"
+#: ../../Zotlabs/Module/Admin/Account_edit.php:34
+#, php-format
+msgid "Password changed for account %d."
msgstr ""
-#: ../../Zotlabs/Module/Import_progress.php:138
-msgid "Item sync status"
+#: ../../Zotlabs/Module/Admin/Account_edit.php:51
+msgid "Account settings updated."
msgstr ""
-#: ../../Zotlabs/Module/Import_progress.php:139
-msgid "File sync status"
+#: ../../Zotlabs/Module/Admin/Account_edit.php:73
+msgid "Account Edit"
msgstr ""
-#: ../../Zotlabs/Module/Import_progress.php:146
-msgid "Channel cloning completed!"
+#: ../../Zotlabs/Module/Admin/Account_edit.php:74
+msgid "New Password"
msgstr ""
-#: ../../Zotlabs/Module/Import_progress.php:147
-msgid "Resume"
+#: ../../Zotlabs/Module/Admin/Account_edit.php:75
+msgid "New Password again"
msgstr ""
-#: ../../Zotlabs/Module/Import_progress.php:148
-msgid "Only resume if sync stalled!"
+#: ../../Zotlabs/Module/Admin/Account_edit.php:76
+msgid "Account language (for emails)"
msgstr ""
-#: ../../Zotlabs/Module/Dirsearch.php:24 ../../Zotlabs/Module/Regdir.php:53
-msgid "This site is not a directory server"
-msgstr "Dette nettstedet er ikke en katalogtjener"
-
-#: ../../Zotlabs/Module/Dirsearch.php:32
-msgid "This directory server requires an access token"
-msgstr "Denne katalogtjeneren krever en tilgangsnøkkel (access token)"
+#: ../../Zotlabs/Module/Admin/Dbsync.php:19
+msgid "Update has been marked successful"
+msgstr "Oppdateringen har blitt merket som en suksess"
-#: ../../Zotlabs/Module/Filestorage.php:14
-#: ../../Zotlabs/Module/Filestorage.php:53
-msgid "Deprecated!"
+#: ../../Zotlabs/Module/Admin/Dbsync.php:32
+#, php-format
+msgid "Verification of update %s failed. Check system logs."
msgstr ""
-#: ../../Zotlabs/Module/Filestorage.php:109
-#: ../../Zotlabs/Module/Attach_edit.php:69
-msgid "File not found."
-msgstr "Filen ble ikke funnet."
-
-#: ../../Zotlabs/Module/Filestorage.php:157
-msgid "Permission Denied."
-msgstr "Tillatelse avvist."
-
-#: ../../Zotlabs/Module/Filestorage.php:190
-msgid "Edit file permissions"
-msgstr "Endre filtillatelser"
-
-#: ../../Zotlabs/Module/Filestorage.php:202
-#: ../../extend/addon/hzaddons/flashcards/Mod_Flashcards.php:268
-msgid "Set/edit permissions"
-msgstr "Angi/endre tillatelser"
-
-#: ../../Zotlabs/Module/Filestorage.php:203
-msgid "Include all files and sub folders"
-msgstr "Inkluder alle filer og undermapper"
-
-#: ../../Zotlabs/Module/Filestorage.php:204
-msgid "Return to file list"
-msgstr "Gå tilbake til filoversikten"
-
-#: ../../Zotlabs/Module/Filestorage.php:209
-msgid "Share this file"
-msgstr "Del denne filen"
-
-#: ../../Zotlabs/Module/Filestorage.php:210
-msgid "Show URL to this file"
-msgstr "Vis URLen til denne filen"
+#: ../../Zotlabs/Module/Admin/Dbsync.php:35
+#: ../../Zotlabs/Module/Admin/Dbsync.php:62
+#, php-format
+msgid "Update %s was successfully applied."
+msgstr "Oppdatering %s ble gjennomført med suksess."
-#: ../../Zotlabs/Module/Pdledit_gui.php:119
-#, fuzzy
-msgid "Layout not found"
-msgstr "Layouten ble ikke funnet."
+#: ../../Zotlabs/Module/Admin/Dbsync.php:39
+#, php-format
+msgid "Verifying update %s did not return a status. Unknown if it succeeded."
+msgstr ""
-#: ../../Zotlabs/Module/Pdledit_gui.php:127
-msgid "This template does not support pdledi_gui (no content regions defined)"
+#: ../../Zotlabs/Module/Admin/Dbsync.php:42
+#, php-format
+msgid "Update %s does not contain a verification function."
msgstr ""
-#: ../../Zotlabs/Module/Pdledit_gui.php:404
-msgid "Main page content"
+#: ../../Zotlabs/Module/Admin/Dbsync.php:46
+#: ../../Zotlabs/Module/Admin/Dbsync.php:69
+#, php-format
+msgid "Update function %s could not be found."
+msgstr "Oppdatering av funksjon %s kunne ikke finnes."
+
+#: ../../Zotlabs/Module/Admin/Dbsync.php:59
+#, php-format
+msgid "Executing update procedure %s failed. Check system logs."
msgstr ""
-#: ../../Zotlabs/Module/Pdledit_gui.php:405
-msgid "The main page content can not be edited!"
+#: ../../Zotlabs/Module/Admin/Dbsync.php:66
+#, php-format
+msgid ""
+"Update %s did not return a status. It cannot be determined if it was "
+"successful."
msgstr ""
-#: ../../Zotlabs/Module/Pdledit.php:27
-msgid "Layout updated."
-msgstr "Layout er oppdatert."
+#: ../../Zotlabs/Module/Admin/Dbsync.php:87
+msgid "Failed Updates"
+msgstr "Mislykkede oppdateringer"
-#: ../../Zotlabs/Module/Pdledit.php:55 ../../Zotlabs/Module/Pdledit.php:129
-msgid "Edit System Page Description"
-msgstr "Endre beskrivelsen av systemsiden"
+#: ../../Zotlabs/Module/Admin/Dbsync.php:89
+msgid "Mark success (if update was manually applied)"
+msgstr "Marker suksess (hvis oppdateringen ble gjennomført manuelt)"
-#: ../../Zotlabs/Module/Pdledit.php:76 ../../Zotlabs/Module/Pdledit.php:93
-msgid "(modified)"
-msgstr "(endret)"
+#: ../../Zotlabs/Module/Admin/Dbsync.php:90
+msgid "Attempt to verify this update if a verification procedure exists"
+msgstr ""
-#: ../../Zotlabs/Module/Pdledit.php:124
-msgid "Layout not found."
-msgstr "Layouten ble ikke funnet."
+#: ../../Zotlabs/Module/Admin/Dbsync.php:91
+msgid "Attempt to execute this update step automatically"
+msgstr "Prøv å gjennomføre dette oppdateringstrinnet automatisk"
-#: ../../Zotlabs/Module/Pdledit.php:130
-msgid "Module Name:"
-msgstr "Modulnavn:"
+#: ../../Zotlabs/Module/Admin/Dbsync.php:96
+msgid "No failed updates."
+msgstr "Ingen mislykkede oppdateringer."
-#: ../../Zotlabs/Module/Pdledit.php:131
-msgid "Layout Help"
-msgstr "Layout-hjelp"
+#: ../../Zotlabs/Module/Admin/Logs.php:29
+msgid "Log settings updated."
+msgstr "Logginnstillinger er oppdatert."
-#: ../../Zotlabs/Module/Pdledit.php:132
-msgid "Edit another layout"
-msgstr ""
+#: ../../Zotlabs/Module/Admin/Logs.php:86
+msgid "Clear"
+msgstr "Tøm"
-#: ../../Zotlabs/Module/Pdledit.php:133
-msgid "System layout"
-msgstr ""
+#: ../../Zotlabs/Module/Admin/Logs.php:92
+msgid "Debugging"
+msgstr "Feilsøking"
-#: ../../Zotlabs/Module/Moderate.php:70
-#, fuzzy
-#| msgid "Account approved."
-msgid "Item approved"
-msgstr "Konto godkjent."
+#: ../../Zotlabs/Module/Admin/Logs.php:93
+msgid "Log file"
+msgstr "Loggfil"
-#: ../../Zotlabs/Module/Removeaccount.php:35
+#: ../../Zotlabs/Module/Admin/Logs.php:93
msgid ""
-"Account removals are not allowed within 48 hours of changing the account "
-"password."
+"Must be writable by web server. Relative to your top-level webserver "
+"directory."
msgstr ""
-"Sletting av kontoer er ikke tillatt innen 48 timer etter endring av "
-"kontopassordet."
-#: ../../Zotlabs/Module/Removeaccount.php:57
-msgid "Remove This Account"
-msgstr "Slett denne kontoen"
+#: ../../Zotlabs/Module/Admin/Logs.php:94
+msgid "Log level"
+msgstr "Loggnivå"
-#: ../../Zotlabs/Module/Removeaccount.php:58
-#: ../../Zotlabs/Module/Removeme.php:61 ../../Zotlabs/Module/Changeaddr.php:78
-msgid "WARNING: "
-msgstr "ADVARSEL: "
+#: ../../Zotlabs/Module/Admin/Queue.php:45
+msgid "Queue Statistics"
+msgstr "Køstatistikk"
-#: ../../Zotlabs/Module/Removeaccount.php:58
-msgid ""
-"This account and all its channels will be completely removed from the "
-"network. "
-msgstr ""
-"Denne kontoen og alle dens kanaler vil bli fullstendig fjernet fra "
-"nettverket. "
+#: ../../Zotlabs/Module/Admin/Queue.php:46
+msgid "Total Entries"
+msgstr "Totalt antall oppføringer"
-#: ../../Zotlabs/Module/Removeaccount.php:58
-msgid "This action is permanent and can not be undone!"
-msgstr "Denne handlingen er permanent og kan ikke angres!"
+#: ../../Zotlabs/Module/Admin/Queue.php:48
+msgid "Destination URL"
+msgstr "Mål-URL"
-#: ../../Zotlabs/Module/Removeaccount.php:59
-#: ../../Zotlabs/Module/Removeme.php:62 ../../Zotlabs/Module/Changeaddr.php:79
-msgid "Please enter your password for verification:"
-msgstr "Vennligst skriv ditt passord for å få bekreftelse:"
+#: ../../Zotlabs/Module/Admin/Queue.php:49
+msgid "Mark hub permanently offline"
+msgstr "Merk hub som permanent offline"
-#: ../../Zotlabs/Module/Removeaccount.php:61
-#: ../../Zotlabs/Module/Settings/Account.php:113
-msgid "Remove Account"
-msgstr "Slett konto"
+#: ../../Zotlabs/Module/Admin/Queue.php:50
+msgid "Retry delivery to this hub"
+msgstr ""
-#: ../../Zotlabs/Module/Regmod.php:15
-msgid "Please login."
-msgstr "Vennligst logg inn."
+#: ../../Zotlabs/Module/Admin/Queue.php:51
+msgid "Empty queue for this hub"
+msgstr "Tøm køen for denne hubben"
+
+#: ../../Zotlabs/Module/Admin/Queue.php:52
+msgid "Last known contact"
+msgstr "Siste kjente kontakt"
-#: ../../Zotlabs/Module/Admin/Site.php:112
+#: ../../Zotlabs/Module/Admin/Site.php:114
msgid "Invalid input"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:132
+#: ../../Zotlabs/Module/Admin/Site.php:134
msgid "Errors"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:220
+#: ../../Zotlabs/Module/Admin/Site.php:221
msgid "Site settings updated."
msgstr "Nettstedsinnstillinger er oppdatert."
-#: ../../Zotlabs/Module/Admin/Site.php:243
+#: ../../Zotlabs/Module/Admin/Site.php:244
#: ../../Zotlabs/Module/Settings/Display.php:117
#, php-format
msgid "%s - (Incompatible)"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:250
+#: ../../Zotlabs/Module/Admin/Site.php:251
msgid "mobile"
msgstr "mobil"
-#: ../../Zotlabs/Module/Admin/Site.php:252
+#: ../../Zotlabs/Module/Admin/Site.php:253
msgid "experimental"
msgstr "eksperimentell"
-#: ../../Zotlabs/Module/Admin/Site.php:254
+#: ../../Zotlabs/Module/Admin/Site.php:255
msgid "unsupported"
msgstr "ikke støttet"
-#: ../../Zotlabs/Module/Admin/Site.php:307
+#: ../../Zotlabs/Module/Admin/Site.php:308
msgid "Yes - with approval"
msgstr "Ja - med godkjenning"
-#: ../../Zotlabs/Module/Admin/Site.php:315
+#: ../../Zotlabs/Module/Admin/Site.php:316
msgid "My site is not a public server"
msgstr "Mitt nettsted er ikke en offentlig tjeneste"
-#: ../../Zotlabs/Module/Admin/Site.php:316
+#: ../../Zotlabs/Module/Admin/Site.php:317
msgid "My site has paid access only"
msgstr "Mitt nettsted gir kun tilgang mot betaling"
-#: ../../Zotlabs/Module/Admin/Site.php:317
+#: ../../Zotlabs/Module/Admin/Site.php:318
msgid "My site has free access only"
msgstr "Mitt nettsted har kun gratis tilgang"
-#: ../../Zotlabs/Module/Admin/Site.php:318
+#: ../../Zotlabs/Module/Admin/Site.php:319
msgid "My site offers free accounts with optional paid upgrades"
msgstr ""
"Mitt nettsted tilbyr gratis konto med valgfri oppgradering til betalt "
"tjeneste"
-#: ../../Zotlabs/Module/Admin/Site.php:336
+#: ../../Zotlabs/Module/Admin/Site.php:337
msgid "Default permission role for new accounts"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:336
+#: ../../Zotlabs/Module/Admin/Site.php:337
msgid ""
"This role will be used for the first channel created after registration."
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:346
-msgid "Week(s)"
+#: ../../Zotlabs/Module/Admin/Site.php:344 ../../Zotlabs/Module/Invite.php:394
+msgid "Minute(s)"
+msgstr ""
+
+#: ../../Zotlabs/Module/Admin/Site.php:345 ../../Zotlabs/Module/Invite.php:395
+msgid "Hour(s)"
+msgstr ""
+
+#: ../../Zotlabs/Module/Admin/Site.php:346 ../../Zotlabs/Module/Invite.php:396
+msgid "Day(s)"
msgstr ""
#: ../../Zotlabs/Module/Admin/Site.php:347
-msgid "Month(s)"
+msgid "Week(s)"
msgstr ""
#: ../../Zotlabs/Module/Admin/Site.php:348
+msgid "Month(s)"
+msgstr ""
+
+#: ../../Zotlabs/Module/Admin/Site.php:349
msgid "Year(s)"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:356
+#: ../../Zotlabs/Module/Admin/Site.php:357
msgid "Register verification delay"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:359
+#: ../../Zotlabs/Module/Admin/Site.php:360
msgid "Time to wait before a registration can be verified"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:378
+#: ../../Zotlabs/Module/Admin/Site.php:363
+#: ../../Zotlabs/Module/Admin/Site.php:385 ../../Zotlabs/Module/Invite.php:405
+msgid "duration up from now"
+msgstr ""
+
+#: ../../Zotlabs/Module/Admin/Site.php:379
msgid "Register verification expiration time"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:381
+#: ../../Zotlabs/Module/Admin/Site.php:382
msgid "Time before an unverified registration will expire"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:398
-#: ../../Zotlabs/Module/Admin/Accounts.php:307
-#: ../../Zotlabs/Module/Admin/Security.php:106
-#: ../../Zotlabs/Module/Admin/Channels.php:167
-#: ../../Zotlabs/Module/Admin/Addons.php:346
-#: ../../Zotlabs/Module/Admin/Addons.php:444
-#: ../../Zotlabs/Module/Admin/Themes.php:138
-#: ../../Zotlabs/Module/Admin/Themes.php:172
-#: ../../Zotlabs/Module/Admin/Logs.php:83 ../../Zotlabs/Module/Admin.php:142
-msgid "Administration"
-msgstr "Administrasjon"
-
-#: ../../Zotlabs/Module/Admin/Site.php:401 ../../Zotlabs/Widget/Admin.php:27
-msgid "Site"
-msgstr "Nettsted"
-
-#: ../../Zotlabs/Module/Admin/Site.php:404
+#: ../../Zotlabs/Module/Admin/Site.php:405
msgid "File upload"
msgstr "Last opp fil"
-#: ../../Zotlabs/Module/Admin/Site.php:405
+#: ../../Zotlabs/Module/Admin/Site.php:406
msgid "Policies"
msgstr "Retningslinjer"
-#: ../../Zotlabs/Module/Admin/Site.php:410
-#: ../../extend/addon/hzaddons/statusnet/statusnet.php:593
-msgid "Site name"
-msgstr "Nettstedets navn"
-
-#: ../../Zotlabs/Module/Admin/Site.php:412
+#: ../../Zotlabs/Module/Admin/Site.php:413
msgid "Banner/Logo"
msgstr "Banner/Logo"
-#: ../../Zotlabs/Module/Admin/Site.php:412
+#: ../../Zotlabs/Module/Admin/Site.php:413
msgid "Unfiltered HTML/CSS/JS is allowed"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:413
+#: ../../Zotlabs/Module/Admin/Site.php:414
msgid "Administrator Information"
msgstr "Administratorinformasjon"
-#: ../../Zotlabs/Module/Admin/Site.php:413
+#: ../../Zotlabs/Module/Admin/Site.php:414
msgid ""
"Contact information for site administrators. Displayed on siteinfo page. "
"BBCode can be used here"
@@ -9651,17 +11439,21 @@ msgstr ""
"Kontaktinformasjon til nettstedsadministratorer. Vises på siteinfo-siden. "
"BBCode kan brukes her"
-#: ../../Zotlabs/Module/Admin/Site.php:414
+#: ../../Zotlabs/Module/Admin/Site.php:415 ../../Zotlabs/Module/Siteinfo.php:25
+msgid "Site Information"
+msgstr "Nettstedsinformasjon"
+
+#: ../../Zotlabs/Module/Admin/Site.php:415
msgid ""
"Publicly visible description of this site. Displayed on siteinfo page. "
"BBCode can be used here"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:415
+#: ../../Zotlabs/Module/Admin/Site.php:416
msgid "System theme"
msgstr "Systemtema"
-#: ../../Zotlabs/Module/Admin/Site.php:415
+#: ../../Zotlabs/Module/Admin/Site.php:416
msgid ""
"Default system theme - may be over-ridden by user profiles - <a href='#' "
"id='cnftheme'>change theme settings</a>"
@@ -9669,19 +11461,19 @@ msgstr ""
"Standard systemtema - kan overstyres av brukerprofiler - <a href='#' "
"id='cnftheme'>endre temainnstillinger</a>"
-#: ../../Zotlabs/Module/Admin/Site.php:418
+#: ../../Zotlabs/Module/Admin/Site.php:419
msgid "Allow Feeds as Connections"
msgstr "Tillat strømmer som forbindelser"
-#: ../../Zotlabs/Module/Admin/Site.php:418
+#: ../../Zotlabs/Module/Admin/Site.php:419
msgid "(Heavy system resource usage)"
msgstr "(Tung bruk av systemressurser)"
-#: ../../Zotlabs/Module/Admin/Site.php:419
+#: ../../Zotlabs/Module/Admin/Site.php:420
msgid "Maximum image size"
msgstr "Største bildestørrelse"
-#: ../../Zotlabs/Module/Admin/Site.php:419
+#: ../../Zotlabs/Module/Admin/Site.php:420
msgid ""
"Maximum size in bytes of uploaded images. Default is 0, which means no "
"limits."
@@ -9689,122 +11481,122 @@ msgstr ""
"Største størrelse i bytes for opplastede bilder. Standard er 0, som betyr "
"ubegrenset."
-#: ../../Zotlabs/Module/Admin/Site.php:420
+#: ../../Zotlabs/Module/Admin/Site.php:421
msgid "Minimum age"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:420
+#: ../../Zotlabs/Module/Admin/Site.php:421
msgid "Minimum age (in years) for who may register on this site."
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:421
+#: ../../Zotlabs/Module/Admin/Site.php:422
msgid "Which best describes the types of account offered by this hub?"
msgstr ""
"Hvilket alternativ beskriver best hva slags kontotype som tilbys av dette "
"nettstedet/denne hubben?"
-#: ../../Zotlabs/Module/Admin/Site.php:421
+#: ../../Zotlabs/Module/Admin/Site.php:422
msgid "This is displayed on the public server site list."
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:427
+#: ../../Zotlabs/Module/Admin/Site.php:428
msgid "Register text"
msgstr "Registreringstekst"
-#: ../../Zotlabs/Module/Admin/Site.php:429
+#: ../../Zotlabs/Module/Admin/Site.php:430
msgid "This text will be displayed prominently at the registration page"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:433
+#: ../../Zotlabs/Module/Admin/Site.php:434
msgid "Does this site allow new member registration?"
msgstr "Tillater dette nettstedet registrering av nye medlemmer?"
-#: ../../Zotlabs/Module/Admin/Site.php:440
+#: ../../Zotlabs/Module/Admin/Site.php:441
msgid "Configure the registration open days/hours"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:442
+#: ../../Zotlabs/Module/Admin/Site.php:443
msgid "Empty or '-:-' value will keep registration open 24/7 (default)"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:443
+#: ../../Zotlabs/Module/Admin/Site.php:444
msgid ""
"Weekdays and hours must be separated by colon ':', From-To ranges with a "
"dash `-` example: 1:800-1200"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:444
+#: ../../Zotlabs/Module/Admin/Site.php:445
msgid ""
"Weekday:Hour pairs must be separated by space ' ' example: 1:900-1700 "
"2:900-1700"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:445
+#: ../../Zotlabs/Module/Admin/Site.php:446
msgid ""
"From-To ranges must be separated by comma ',' example: 1:800-1200,1300-1700 "
"or 1-2,4-5:900-1700"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:446
+#: ../../Zotlabs/Module/Admin/Site.php:447
msgid "Advanced examples:"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:446
+#: ../../Zotlabs/Module/Admin/Site.php:447
#: ../../Zotlabs/Module/Settings/Channel.php:184
msgid "or"
msgstr "eller"
-#: ../../Zotlabs/Module/Admin/Site.php:447
+#: ../../Zotlabs/Module/Admin/Site.php:448
msgid "Check your configuration"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:451
+#: ../../Zotlabs/Module/Admin/Site.php:452
msgid "Max account registrations per day"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:453
+#: ../../Zotlabs/Module/Admin/Site.php:454
msgid "Unlimited if zero or no value - default 50"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:457
+#: ../../Zotlabs/Module/Admin/Site.php:458
msgid "Max account registrations from same IP"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:459
+#: ../../Zotlabs/Module/Admin/Site.php:460
msgid "Unlimited if zero or no value - default 3"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:465
+#: ../../Zotlabs/Module/Admin/Site.php:466
msgid "Auto channel create"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:467
+#: ../../Zotlabs/Module/Admin/Site.php:468
msgid ""
"If disabled the channel will be created in a separate step during the "
"registration process"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:471
+#: ../../Zotlabs/Module/Admin/Site.php:472
msgid "Require invite code"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:476
+#: ../../Zotlabs/Module/Admin/Site.php:477
msgid "Allow invite code"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:481
+#: ../../Zotlabs/Module/Admin/Site.php:482
msgid "Require email address"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:483
+#: ../../Zotlabs/Module/Admin/Site.php:484
msgid "The provided email address will be verified (recommended)"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:487
+#: ../../Zotlabs/Module/Admin/Site.php:488
msgid "Abandon account after x days"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:489
+#: ../../Zotlabs/Module/Admin/Site.php:490
msgid ""
"Will not waste system resources polling external sites for abandonded "
"accounts. Enter 0 for no time limit."
@@ -9812,22 +11604,22 @@ msgstr ""
"Vil ikke kaste bort systemressurser på å spørre eksterne nettsteder etter "
"forlatte kontoer. Skriv 0 for å ikke sette noen tidsgrense."
-#: ../../Zotlabs/Module/Admin/Site.php:494
+#: ../../Zotlabs/Module/Admin/Site.php:495
msgid "Site homepage to show visitors (default: login box)"
msgstr ""
"Nettstedets hjemmeside som vises til besøkende (standard: innloggingsboks)"
-#: ../../Zotlabs/Module/Admin/Site.php:494
+#: ../../Zotlabs/Module/Admin/Site.php:495
msgid ""
"example: 'pubstream' to show public stream, 'page/sys/home' to show a system "
"webpage called 'home' or 'include:home.html' to include a file."
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:495
+#: ../../Zotlabs/Module/Admin/Site.php:496
msgid "Preserve site homepage URL"
msgstr "Bevar URL-en til nettstedets hjemmeside"
-#: ../../Zotlabs/Module/Admin/Site.php:495
+#: ../../Zotlabs/Module/Admin/Site.php:496
msgid ""
"Present the site homepage in a frame at the original location instead of "
"redirecting"
@@ -9835,11 +11627,11 @@ msgstr ""
"Presenter hjemmesiden til nettstedet i en ramme fra den opprinnelige "
"plasseringen i stedet for å omdirigere"
-#: ../../Zotlabs/Module/Admin/Site.php:496
+#: ../../Zotlabs/Module/Admin/Site.php:497
msgid "Allowed friend domains"
msgstr "Tillatte vennedomener"
-#: ../../Zotlabs/Module/Admin/Site.php:496
+#: ../../Zotlabs/Module/Admin/Site.php:497
msgid ""
"Comma separated list of domains which are allowed to establish friendships "
"with this site. Wildcards are accepted. Empty to allow any domains"
@@ -9847,66 +11639,56 @@ msgstr ""
"Kommaseparert liste over domener som har lov til å etablere vennskap med "
"dette nettstedet. Jokertegn er akseptert. Tøm for å tillate alle domener"
-#: ../../Zotlabs/Module/Admin/Site.php:497
+#: ../../Zotlabs/Module/Admin/Site.php:498
msgid "Force publish"
msgstr "Tving publisering"
-#: ../../Zotlabs/Module/Admin/Site.php:497
-#, fuzzy
-#| msgid ""
-#| "Check to force all profiles on this site to be listed in the site "
-#| "directory."
+#: ../../Zotlabs/Module/Admin/Site.php:498
msgid ""
"Check to force all profiles on this site to be listed in the site directory"
msgstr ""
"Kryss av for å tvinge alle profiler på dette nettstedet til å bli oppført i "
-"nettstedet sin katalog."
+"nettstedet sin katalog"
-#: ../../Zotlabs/Module/Admin/Site.php:498
+#: ../../Zotlabs/Module/Admin/Site.php:499
msgid "Enable public stream"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:498
-#, fuzzy
-#| msgid ""
-#| "Import and allow access to public content pulled from other sites. "
-#| "Warning: this content is unmoderated."
+#: ../../Zotlabs/Module/Admin/Site.php:499
msgid "Enable the public stream. Warning: this content is unmoderated"
msgstr ""
"Importer og gi tilgang til offentlig innhold trukket inn fra andre "
-"nettsteder. Advarsel: dette innholdet er ikke moderert."
+"nettsteder. Advarsel: dette innholdet er ikke moderert"
-#: ../../Zotlabs/Module/Admin/Site.php:499
-#, fuzzy
-#| msgid "My site is not a public server"
+#: ../../Zotlabs/Module/Admin/Site.php:500
msgid "Site only public stream"
-msgstr "Mitt nettsted er ikke en offentlig tjeneste"
+msgstr "Offentlig strøm kun fra nettstedet"
-#: ../../Zotlabs/Module/Admin/Site.php:499
+#: ../../Zotlabs/Module/Admin/Site.php:500
msgid "Restrict the public stream to content originating at this site"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:500
+#: ../../Zotlabs/Module/Admin/Site.php:501
msgid "Allow anybody on the internet to access the public streams"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:500
+#: ../../Zotlabs/Module/Admin/Site.php:501
msgid "Disable to require authentication before viewing"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:501
+#: ../../Zotlabs/Module/Admin/Site.php:502
msgid "Only import Public stream posts with this text"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:502
+#: ../../Zotlabs/Module/Admin/Site.php:503
msgid "Do not import Public stream posts with this text"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:505
+#: ../../Zotlabs/Module/Admin/Site.php:506
msgid "Login on Homepage"
msgstr "Logg inn på hjemmesiden"
-#: ../../Zotlabs/Module/Admin/Site.php:505
+#: ../../Zotlabs/Module/Admin/Site.php:506
msgid ""
"Present a login box to visitors on the home page if no other content has "
"been configured."
@@ -9914,68 +11696,68 @@ msgstr ""
"Presenter en innloggingsboks til besøkende på hjemmesiden hvis ikke noe "
"annet innhold har blitt konfigurert."
-#: ../../Zotlabs/Module/Admin/Site.php:506
+#: ../../Zotlabs/Module/Admin/Site.php:507
msgid "Enable context help"
msgstr "Slå på kontekstuell hjelp"
-#: ../../Zotlabs/Module/Admin/Site.php:506
+#: ../../Zotlabs/Module/Admin/Site.php:507
msgid ""
"Display contextual help for the current page when the help button is pressed."
msgstr ""
"Vis kontekstuell hjelp for den gjeldende siden når du klikker på "
"hjelpeknappen."
-#: ../../Zotlabs/Module/Admin/Site.php:508
+#: ../../Zotlabs/Module/Admin/Site.php:509
msgid "Reply-to email address for system generated email."
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:509
+#: ../../Zotlabs/Module/Admin/Site.php:510
msgid "Sender (From) email address for system generated email."
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:510
+#: ../../Zotlabs/Module/Admin/Site.php:511
msgid "Name of email sender for system generated email."
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:512
+#: ../../Zotlabs/Module/Admin/Site.php:513
msgid "Directory Server URL"
msgstr "Katalogtjener URL"
-#: ../../Zotlabs/Module/Admin/Site.php:512
+#: ../../Zotlabs/Module/Admin/Site.php:513
msgid "Default directory server"
msgstr "Standard katalogtjener"
-#: ../../Zotlabs/Module/Admin/Site.php:514
+#: ../../Zotlabs/Module/Admin/Site.php:515
msgid "Enable SSE Notifications"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:514
+#: ../../Zotlabs/Module/Admin/Site.php:515
msgid ""
"If disabled, traditional polling will be used. Warning: this setting might "
"not be suited for shared hosting"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:516
+#: ../../Zotlabs/Module/Admin/Site.php:517
msgid "Proxy user"
msgstr "Brukernavn mellomtjener"
-#: ../../Zotlabs/Module/Admin/Site.php:517
+#: ../../Zotlabs/Module/Admin/Site.php:518
msgid "Proxy URL"
msgstr "Mellomtjener URL"
-#: ../../Zotlabs/Module/Admin/Site.php:518
+#: ../../Zotlabs/Module/Admin/Site.php:519
msgid "Network timeout"
msgstr "Nettverk tidsavbrudd"
-#: ../../Zotlabs/Module/Admin/Site.php:518
+#: ../../Zotlabs/Module/Admin/Site.php:519
msgid "Value is in seconds. Set to 0 for unlimited (not recommended)."
msgstr "Verdien i sekunder. Skriv 0 for ubegrenset (ikke anbefalt)."
-#: ../../Zotlabs/Module/Admin/Site.php:519
+#: ../../Zotlabs/Module/Admin/Site.php:520
msgid "Delivery interval"
msgstr "Leveringsinterval"
-#: ../../Zotlabs/Module/Admin/Site.php:519
+#: ../../Zotlabs/Module/Admin/Site.php:520
msgid ""
"Delay background delivery processes by this many seconds to reduce system "
"load. Recommend: 4-5 for shared hosts, 2-3 for virtual private servers. 0-1 "
@@ -9985,11 +11767,11 @@ msgstr ""
"redusere systembelastningen. Anbefaling: 4-5 for delte tjenere, 2-3 for "
"virtuelle tjenere, 0-1 for større dedikerte tjenere."
-#: ../../Zotlabs/Module/Admin/Site.php:520
+#: ../../Zotlabs/Module/Admin/Site.php:521
msgid "Deliveries per process"
msgstr "Leveranser per prosess"
-#: ../../Zotlabs/Module/Admin/Site.php:520
+#: ../../Zotlabs/Module/Admin/Site.php:521
msgid ""
"Number of deliveries to attempt in a single operating system process. Adjust "
"if necessary to tune system performance. Recommend: 1-5."
@@ -9997,11 +11779,11 @@ msgstr ""
"Antall leveranser som forsøkes i en enkelt operativsystemprosess. Juster om "
"nødvendig for å fininnstille systemets yteevne. Anbefaling: 1-5."
-#: ../../Zotlabs/Module/Admin/Site.php:522
+#: ../../Zotlabs/Module/Admin/Site.php:523
msgid "Poll interval"
msgstr "Spørreintervall"
-#: ../../Zotlabs/Module/Admin/Site.php:522
+#: ../../Zotlabs/Module/Admin/Site.php:523
msgid ""
"Delay background polling processes by this many seconds to reduce system "
"load. If 0, use delivery interval."
@@ -10009,22 +11791,22 @@ msgstr ""
"Forsink spørreprosessene i bakgrunnen med dette antall sekunder for å "
"redusere systembelastningen. Hvis 0, bruk dette leveringsintervallet."
-#: ../../Zotlabs/Module/Admin/Site.php:523
+#: ../../Zotlabs/Module/Admin/Site.php:524
msgid "Path to ImageMagick convert program"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:523
+#: ../../Zotlabs/Module/Admin/Site.php:524
msgid ""
"If set, use this program to generate photo thumbnails for huge images ( > "
"4000 pixels in either dimension), otherwise memory exhaustion may occur. "
"Example: /usr/bin/convert"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:524
+#: ../../Zotlabs/Module/Admin/Site.php:525
msgid "Maximum Load Average"
msgstr "Største belastningsgjennomsnitt"
-#: ../../Zotlabs/Module/Admin/Site.php:524
+#: ../../Zotlabs/Module/Admin/Site.php:525
msgid ""
"Maximum system load before delivery and poll processes are deferred - "
"default 50."
@@ -10032,316 +11814,50 @@ msgstr ""
"Største systembelastning før leverings- og spørreprosesser blir utsatt - "
"standard 50."
-#: ../../Zotlabs/Module/Admin/Site.php:525
+#: ../../Zotlabs/Module/Admin/Site.php:526
msgid "Expiration period in days for imported (grid/network) content"
msgstr "Antall dager før importert innhold (nettet/nettverk) utgår"
-#: ../../Zotlabs/Module/Admin/Site.php:525
+#: ../../Zotlabs/Module/Admin/Site.php:526
msgid "0 for no expiration of imported content"
msgstr "0 dersom importert innhold ikke skal utgå"
-#: ../../Zotlabs/Module/Admin/Site.php:526
+#: ../../Zotlabs/Module/Admin/Site.php:527
msgid ""
"Do not expire any posts which have comments less than this many days ago"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:527
+#: ../../Zotlabs/Module/Admin/Site.php:528
msgid ""
"Public servers: Optional landing (marketing) webpage for new registrants"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:527
+#: ../../Zotlabs/Module/Admin/Site.php:528
#, php-format
msgid "Create this page first. Default is %s/register"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:528
+#: ../../Zotlabs/Module/Admin/Site.php:529
msgid "Page to display after creating a new channel"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:528
+#: ../../Zotlabs/Module/Admin/Site.php:529
msgid "Default: profiles"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:529
+#: ../../Zotlabs/Module/Admin/Site.php:530
msgid "Optional: site location"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:529
+#: ../../Zotlabs/Module/Admin/Site.php:530
msgid "Region or country"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Site.php:614
#: ../../Zotlabs/Module/Admin/Site.php:615
+#: ../../Zotlabs/Module/Admin/Site.php:616
msgid "Invalid 24h time value (hhmm/hmm)"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Dbsync.php:19
-msgid "Update has been marked successful"
-msgstr "Oppdateringen har blitt merket som en suksess"
-
-#: ../../Zotlabs/Module/Admin/Dbsync.php:32
-#, php-format
-msgid "Verification of update %s failed. Check system logs."
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Dbsync.php:35
-#: ../../Zotlabs/Module/Admin/Dbsync.php:62
-#, php-format
-msgid "Update %s was successfully applied."
-msgstr "Oppdatering %s ble gjennomført med suksess."
-
-#: ../../Zotlabs/Module/Admin/Dbsync.php:39
-#, php-format
-msgid "Verifying update %s did not return a status. Unknown if it succeeded."
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Dbsync.php:42
-#, php-format
-msgid "Update %s does not contain a verification function."
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Dbsync.php:46
-#: ../../Zotlabs/Module/Admin/Dbsync.php:69
-#, php-format
-msgid "Update function %s could not be found."
-msgstr "Oppdatering av funksjon %s kunne ikke finnes."
-
-#: ../../Zotlabs/Module/Admin/Dbsync.php:59
-#, php-format
-msgid "Executing update procedure %s failed. Check system logs."
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Dbsync.php:66
-#, php-format
-msgid ""
-"Update %s did not return a status. It cannot be determined if it was "
-"successful."
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Dbsync.php:87
-msgid "Failed Updates"
-msgstr "Mislykkede oppdateringer"
-
-#: ../../Zotlabs/Module/Admin/Dbsync.php:89
-msgid "Mark success (if update was manually applied)"
-msgstr "Marker suksess (hvis oppdateringen ble gjennomført manuelt)"
-
-#: ../../Zotlabs/Module/Admin/Dbsync.php:90
-msgid "Attempt to verify this update if a verification procedure exists"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Dbsync.php:91
-msgid "Attempt to execute this update step automatically"
-msgstr "Prøv å gjennomføre dette oppdateringstrinnet automatisk"
-
-#: ../../Zotlabs/Module/Admin/Dbsync.php:96
-msgid "No failed updates."
-msgstr "Ingen mislykkede oppdateringer."
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:128
-#, php-format
-msgid "%s account blocked/unblocked"
-msgid_plural "%s account blocked/unblocked"
-msgstr[0] "%s konto blokkert/ikke blokkert lenger"
-msgstr[1] "%s kontoer blokkert/ikke blokkert lenger"
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:135
-#, php-format
-msgid "%s account deleted"
-msgid_plural "%s accounts deleted"
-msgstr[0] "%s konto slettet"
-msgstr[1] "%s kontoer slettet"
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:171
-msgid "Account not found"
-msgstr "Kontoen ble ikke funnet"
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:192
-#, php-format
-msgid "Account '%s' blocked"
-msgstr "Kontoen '%s' blokkert"
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:200
-#, php-format
-msgid "Account '%s' unblocked"
-msgstr "Kontoen '%s' er ikke blokkert lenger"
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:240
-msgid "Unverified"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:243
-msgid "Expired"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:308
-#: ../../Zotlabs/Module/Admin/Accounts.php:327
-#: ../../Zotlabs/Module/Admin.php:97 ../../Zotlabs/Widget/Admin.php:28
-msgid "Accounts"
-msgstr "Kontoer"
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:310
-msgid "Show verified registrations"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:310
-msgid "Show all registrations"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:312
-msgid "Select toggle"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:313
-msgid "Deny selected"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:314
-msgid "Approve selected"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:315
-msgid "All registrations"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:315
-msgid "Verified registrations waiting for approval"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:316
-msgid "Request date"
-msgstr "Dato for forespørsel"
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:316
-msgid "Requests"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:317
-msgid "No registrations available"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:317
-msgid "No verified registrations available"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:319
-#: ../../Zotlabs/Module/Authorize.php:33
-msgid "Deny"
-msgstr "Avslå"
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:323
-msgid "Verified"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:324
-msgid "Not yet verified"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:329
-msgid "ID"
-msgstr "ID"
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:331
-msgid "All channels"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:332
-msgid "Register date"
-msgstr "Registreringsdato"
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:333
-msgid "Last login"
-msgstr "Siste innlogging"
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:334
-msgid "Expires"
-msgstr "Utløper"
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:335
-#: ../../Zotlabs/Module/Admin/Account_edit.php:72
-msgid "Service class"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:337
-msgid ""
-"Selected accounts will be deleted!\\n\\nEverything these accounts had posted "
-"on this site will be permanently deleted!\\n\\nAre you sure?"
-msgstr ""
-"Valgte kontoer vil bli slettet!\\n\\nAlt disse kontoene har lagt inn på "
-"dette nettstedet vil bli slettet permanent!\\n\\nEr du sikker på at du vil "
-"slette disse valgte kontoene?"
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:338
-msgid ""
-"The account {0} will be deleted!\\n\\nEverything this account has posted on "
-"this site will be permanently deleted!\\n\\nAre you sure?"
-msgstr ""
-"Kontoen {0} vl bli slettet!\\n\\nAlt denne kontoen har lagt inn på dette "
-"nettstedet vil bli slettet permanent!\\n\\nEr du sikker på at du vil slette "
-"denne kontoen?"
-
-#: ../../Zotlabs/Module/Admin/Accounts.php:347
-msgid "Message"
-msgstr "Melding"
-
-#: ../../Zotlabs/Module/Admin/Queueworker.php:66
-msgid "Max queueworker threads"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Queueworker.php:68
-msgid "Minimum 4, default 4"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Queueworker.php:78
-msgid "Assume workers dead after"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Queueworker.php:80
-msgid "Minimum 120, default 300 seconds"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Queueworker.php:92
-msgid "Pause before starting next task"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Queueworker.php:94
-msgid "Minimum 100, default 100 microseconds"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Queueworker.php:103
-msgid "Automatically adjust pause before starting next task"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Queueworker.php:112
-msgid "Queueworker Settings"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Account_edit.php:29
-#, php-format
-msgid "Password changed for account %d."
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Account_edit.php:46
-msgid "Account settings updated."
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Account_edit.php:68
-msgid "Account Edit"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Account_edit.php:69
-msgid "New Password"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Account_edit.php:70
-msgid "New Password again"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Account_edit.php:71
-msgid "Account language (for emails)"
-msgstr ""
-
#: ../../Zotlabs/Module/Admin/Security.php:97
msgid ""
"By default, unfiltered HTML is allowed in embedded media. This is inherently "
@@ -10373,11 +11889,6 @@ msgstr ""
"Alt annet innebygget innhold vil bli filtrert, <strong>med mindre</strong> "
"innebygget innhold fra den aktuelle siden eksplisitt er blokkert."
-#: ../../Zotlabs/Module/Admin/Security.php:107
-#: ../../Zotlabs/Widget/Admin.php:30
-msgid "Security"
-msgstr "Sikkerhet"
-
#: ../../Zotlabs/Module/Admin/Security.php:109
msgid "Block public"
msgstr "Blokker offentlig tilgang"
@@ -10511,395 +12022,109 @@ msgid ""
"https://example.tld"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Channels.php:28
-#: ../../Zotlabs/Module/Admin/Channels.php:80
-msgid "Channel not found"
-msgstr "Kanalen ble ikke funnet"
-
-#: ../../Zotlabs/Module/Admin/Channels.php:48
-#, php-format
-msgid "%s channel censored/uncensored"
-msgid_plural "%s channels censored/uncensored"
-msgstr[0] "%s kanal er sensurert/ikke sensurert lenger"
-msgstr[1] "%s kanaler er sensurert/ikke sensurert lenger"
-
-#: ../../Zotlabs/Module/Admin/Channels.php:57
-#, php-format
-msgid "%s channel code allowed/disallowed"
-msgid_plural "%s channels code allowed/disallowed"
-msgstr[0] "%s kanal med kode tillatt/ikke tillatt"
-msgstr[1] "%s kanaler med kode tillatt/ikke tillatt"
-
-#: ../../Zotlabs/Module/Admin/Channels.php:63
-#, php-format
-msgid "%s channel deleted"
-msgid_plural "%s channels deleted"
-msgstr[0] "%s kanal slettet"
-msgstr[1] "%s kanaler slettet"
-
-#: ../../Zotlabs/Module/Admin/Channels.php:90
-#, php-format
-msgid "Channel '%s' deleted"
-msgstr "Kanalen '%s' er slettet"
-
-#: ../../Zotlabs/Module/Admin/Channels.php:109
-#, php-format
-msgid "Channel '%s' censored"
-msgstr "Kanalen '%s' er sensurert"
-
-#: ../../Zotlabs/Module/Admin/Channels.php:109
-#, php-format
-msgid "Channel '%s' uncensored"
-msgstr "Kanalen '%s' er ikke sensurert lenger"
-
-#: ../../Zotlabs/Module/Admin/Channels.php:120
-#, php-format
-msgid "Channel '%s' code allowed"
-msgstr "Kanal '%s' kode tillatt"
-
-#: ../../Zotlabs/Module/Admin/Channels.php:120
+#: ../../Zotlabs/Module/Manage.php:130 ../../Zotlabs/Module/New_channel.php:148
#, php-format
-msgid "Channel '%s' code disallowed"
-msgstr "Kanal '%s' kode ikke tillatt"
-
-#: ../../Zotlabs/Module/Admin/Channels.php:170
-msgid "select all"
-msgstr "velg alle"
-
-#: ../../Zotlabs/Module/Admin/Channels.php:172
-msgid "Censor"
-msgstr "Sensurer"
-
-#: ../../Zotlabs/Module/Admin/Channels.php:173
-msgid "Uncensor"
-msgstr "Ikke sensurer lenger"
-
-#: ../../Zotlabs/Module/Admin/Channels.php:174
-msgid "Allow Code"
-msgstr "Tillat kode"
-
-#: ../../Zotlabs/Module/Admin/Channels.php:175
-msgid "Disallow Code"
-msgstr "Ikke tillat kode"
-
-#: ../../Zotlabs/Module/Admin/Channels.php:180
-msgid "UID"
-msgstr "UID"
+msgid "You have created %1$.0f of %2$.0f allowed channels."
+msgstr "Du har laget %1$.0f av %2$.0f tillatte kanaler."
-#: ../../Zotlabs/Module/Admin/Channels.php:184
-msgid ""
-"Selected channels will be deleted!\\n\\nEverything that was posted in these "
-"channels on this site will be permanently deleted!\\n\\nAre you sure?"
-msgstr ""
-"Valgte kanaler vil bli slettet!\\n\\nAlt innhold som er lagt inn i disse "
-"kanalene på dette nettstedet vil bli slettet for alltid!\\n\\nEr du sikker "
-"på at du vil slette disse kanalene med alt innhold?"
+#: ../../Zotlabs/Module/Manage.php:137
+msgid "Create a new channel"
+msgstr "Lag en ny kanal"
-#: ../../Zotlabs/Module/Admin/Channels.php:185
-msgid ""
-"The channel {0} will be deleted!\\n\\nEverything that was posted in this "
-"channel on this site will be permanently deleted!\\n\\nAre you sure?"
-msgstr ""
-"Kanalen {0} vil bli slettet!\\n\\nAlt innhold som er lagt inn i denne "
-"kanalen på dettet nettstedet vil bli slettet for alltid!\\n\\nEr du sikker "
-"på at du vil slette denne kanalen med alt innhold?"
+#: ../../Zotlabs/Module/Manage.php:163
+msgid "Current Channel"
+msgstr "Gjeldende kanal"
-#: ../../Zotlabs/Module/Admin/Features.php:56
-#, php-format
-msgid "Lock feature %s"
-msgstr "Lås funksjon %s"
+#: ../../Zotlabs/Module/Manage.php:165
+msgid "Switch to one of your channels by selecting it."
+msgstr "Bytt til en av dine kanaler ved å velge den."
-#: ../../Zotlabs/Module/Admin/Features.php:64
-msgid "Manage Additional Features"
-msgstr "Håndter tilleggsfunksjoner"
+#: ../../Zotlabs/Module/Manage.php:166
+msgid "Default Channel"
+msgstr "Standardkanal"
-#: ../../Zotlabs/Module/Admin/Addons.php:294
-#, php-format
-msgid "Plugin %s disabled."
-msgstr "Tilleggsfunksjonen %s er avskrudd."
+#: ../../Zotlabs/Module/Manage.php:167
+msgid "Make Default"
+msgstr "Gjør til standard"
-#: ../../Zotlabs/Module/Admin/Addons.php:299
+#: ../../Zotlabs/Module/Manage.php:170
#, php-format
-msgid "Plugin %s enabled."
-msgstr "Tilleggsfunksjonen %s er påskrudd."
-
-#: ../../Zotlabs/Module/Admin/Addons.php:315
-#: ../../Zotlabs/Module/Admin/Themes.php:111
-msgid "Disable"
-msgstr "Skru av"
-
-#: ../../Zotlabs/Module/Admin/Addons.php:318
-#: ../../Zotlabs/Module/Admin/Themes.php:113
-msgid "Enable"
-msgstr "Skru på"
-
-#: ../../Zotlabs/Module/Admin/Addons.php:347
-#: ../../Zotlabs/Module/Admin/Addons.php:445 ../../Zotlabs/Widget/Admin.php:32
-msgid "Addons"
-msgstr "Tillegg"
-
-#: ../../Zotlabs/Module/Admin/Addons.php:348
-#: ../../Zotlabs/Module/Admin/Themes.php:140
-msgid "Toggle"
-msgstr "Skru av og på"
-
-#: ../../Zotlabs/Module/Admin/Addons.php:356
-#: ../../Zotlabs/Module/Admin/Themes.php:150
-msgid "Author: "
-msgstr "Forfatter: "
-
-#: ../../Zotlabs/Module/Admin/Addons.php:357
-#: ../../Zotlabs/Module/Admin/Themes.php:151
-msgid "Maintainer: "
-msgstr "Vedlikeholder: "
-
-#: ../../Zotlabs/Module/Admin/Addons.php:358
-msgid "Minimum project version: "
-msgstr "Minimum prosjektversjon: "
-
-#: ../../Zotlabs/Module/Admin/Addons.php:359
-msgid "Maximum project version: "
-msgstr "Maksimum prosjektversjon: "
-
-#: ../../Zotlabs/Module/Admin/Addons.php:360
-msgid "Minimum PHP version: "
-msgstr "Minimum PHP-versjon: "
-
-#: ../../Zotlabs/Module/Admin/Addons.php:361
-msgid "Compatible Server Roles: "
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Addons.php:362
-msgid "Requires: "
-msgstr "Krever: "
-
-#: ../../Zotlabs/Module/Admin/Addons.php:363
-#: ../../Zotlabs/Module/Admin/Addons.php:450
-msgid "Disabled - version incompatibility"
-msgstr "Skrudd av - versjonsinkompatibilitet"
-
-#: ../../Zotlabs/Module/Admin/Addons.php:419
-msgid "Enter the public git repository URL of the addon repo."
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Addons.php:420
-msgid "Addon repo git URL"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Addons.php:421
-msgid "Custom repo name"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Addons.php:421
-#, fuzzy
-msgid "(optional)"
-msgstr "Valgfritt"
-
-#: ../../Zotlabs/Module/Admin/Addons.php:422
-msgid "Download Addon Repo"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Addons.php:429
-msgid "Install new repo"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Addons.php:453
-msgid "Manage Repos"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin/Addons.php:454
-msgid "Installed Addon Repositories"
-msgstr ""
+msgid "%d new introductions"
+msgstr "%d nye introduksjoner"
-#: ../../Zotlabs/Module/Admin/Addons.php:455
-msgid "Install a New Addon Repository"
-msgstr ""
+#: ../../Zotlabs/Module/Manage.php:172
+msgid "Delegated Channel"
+msgstr "Delegert kanal"
-#: ../../Zotlabs/Module/Admin/Addons.php:462
-msgid "Switch branch"
+#: ../../Zotlabs/Module/Email_resend.php:12
+#: ../../Zotlabs/Module/Email_validation.php:25
+msgid "Token verification failed."
msgstr ""
-#: ../../Zotlabs/Module/Admin/Queue.php:45
-msgid "Queue Statistics"
-msgstr "Køstatistikk"
-
-#: ../../Zotlabs/Module/Admin/Queue.php:46
-msgid "Total Entries"
-msgstr "Totalt antall oppføringer"
-
-#: ../../Zotlabs/Module/Admin/Queue.php:47
-#: ../../extend/addon/hzaddons/workflow/workflow.php:2668
-msgid "Priority"
-msgstr "Prioritet"
-
-#: ../../Zotlabs/Module/Admin/Queue.php:48
-msgid "Destination URL"
-msgstr "Mål-URL"
-
-#: ../../Zotlabs/Module/Admin/Queue.php:49
-msgid "Mark hub permanently offline"
-msgstr "Merk hub som permanent offline"
-
-#: ../../Zotlabs/Module/Admin/Queue.php:50
-msgid "Retry delivery to this hub"
+#: ../../Zotlabs/Module/Email_resend.php:30
+msgid "Email verification resent"
msgstr ""
-#: ../../Zotlabs/Module/Admin/Queue.php:51
-msgid "Empty queue for this hub"
-msgstr "Tøm køen for denne hubben"
-
-#: ../../Zotlabs/Module/Admin/Queue.php:52
-msgid "Last known contact"
-msgstr "Siste kjente kontakt"
-
-#: ../../Zotlabs/Module/Admin/Profs.php:90
-msgid "New Profile Field"
-msgstr "Nytt profilfelt"
-
-#: ../../Zotlabs/Module/Admin/Profs.php:91
-#: ../../Zotlabs/Module/Admin/Profs.php:111
-msgid "Field nickname"
-msgstr "Feltets kallenavn"
-
-#: ../../Zotlabs/Module/Admin/Profs.php:91
-#: ../../Zotlabs/Module/Admin/Profs.php:111
-msgid "System name of field"
-msgstr "Systemnavnet til feltet"
-
-#: ../../Zotlabs/Module/Admin/Profs.php:92
-#: ../../Zotlabs/Module/Admin/Profs.php:112
-msgid "Input type"
-msgstr "Inndata-type"
-
-#: ../../Zotlabs/Module/Admin/Profs.php:93
-#: ../../Zotlabs/Module/Admin/Profs.php:113
-msgid "Field Name"
-msgstr "Feltnavn"
-
-#: ../../Zotlabs/Module/Admin/Profs.php:93
-#: ../../Zotlabs/Module/Admin/Profs.php:113
-msgid "Label on profile pages"
-msgstr "Merkelapp på profilsider"
-
-#: ../../Zotlabs/Module/Admin/Profs.php:94
-#: ../../Zotlabs/Module/Admin/Profs.php:114
-msgid "Help text"
-msgstr "Hjelpetekst"
-
-#: ../../Zotlabs/Module/Admin/Profs.php:94
-#: ../../Zotlabs/Module/Admin/Profs.php:114
-msgid "Additional info (optional)"
-msgstr "Tilleggsinformasjon (valgfritt)"
-
-#: ../../Zotlabs/Module/Admin/Profs.php:104
-msgid "Field definition not found"
-msgstr "Feltdefinisjonen ble ikke funnet"
-
-#: ../../Zotlabs/Module/Admin/Profs.php:110
-msgid "Edit Profile Field"
-msgstr "Endre profilfelt"
-
-#: ../../Zotlabs/Module/Admin/Profs.php:169 ../../Zotlabs/Widget/Admin.php:36
-msgid "Profile Fields"
-msgstr "Profilfelter"
-
-#: ../../Zotlabs/Module/Admin/Profs.php:170
-msgid "Basic Profile Fields"
-msgstr "Grunnleggende profilfelter"
-
-#: ../../Zotlabs/Module/Admin/Profs.php:171
-msgid "Advanced Profile Fields"
-msgstr "Utvidede profilfelter"
-
-#: ../../Zotlabs/Module/Admin/Profs.php:171
-msgid "(In addition to basic fields)"
-msgstr "(I tillegg til grunnleggende felt)"
-
-#: ../../Zotlabs/Module/Admin/Profs.php:173
-msgid "All available fields"
-msgstr "Alle tilgjengelige felt"
-
-#: ../../Zotlabs/Module/Admin/Profs.php:174
-msgid "Custom Fields"
+#: ../../Zotlabs/Module/Email_resend.php:33
+msgid "Unable to resend email verification message."
msgstr ""
-#: ../../Zotlabs/Module/Admin/Profs.php:178
-msgid "Create Custom Field"
-msgstr "Legg til egendefinert felt"
-
-#: ../../Zotlabs/Module/Admin/Themes.php:27
-msgid "Theme settings updated."
-msgstr "Temainnstillinger er oppdatert."
-
-#: ../../Zotlabs/Module/Admin/Themes.php:62
-msgid "No themes found."
-msgstr "Ingen temaer er funnet."
-
-#: ../../Zotlabs/Module/Admin/Themes.php:132
-msgid "Screenshot"
-msgstr "Skjermbilde"
-
-#: ../../Zotlabs/Module/Admin/Themes.php:139
-#: ../../Zotlabs/Module/Admin/Themes.php:173 ../../Zotlabs/Widget/Admin.php:33
-msgid "Themes"
-msgstr "Utseende"
-
-#: ../../Zotlabs/Module/Admin/Themes.php:178
-msgid "[Experimental]"
-msgstr "[Eksperimentelt]"
+#: ../../Zotlabs/Module/Dreport.php:43
+msgid "Invalid message"
+msgstr "Ugyldig melding"
-#: ../../Zotlabs/Module/Admin/Themes.php:179
-msgid "[Unsupported]"
-msgstr "[Ingen støtte]"
+#: ../../Zotlabs/Module/Dreport.php:73
+msgid "no results"
+msgstr "ingen resultater"
-#: ../../Zotlabs/Module/Admin/Logs.php:29
-msgid "Log settings updated."
-msgstr "Logginnstillinger er oppdatert."
+#: ../../Zotlabs/Module/Dreport.php:87
+msgid "channel sync processed"
+msgstr "kanalsynkronisering er behandlet"
-#: ../../Zotlabs/Module/Admin/Logs.php:84 ../../Zotlabs/Widget/Admin.php:54
-#: ../../Zotlabs/Widget/Admin.php:64
-msgid "Logs"
-msgstr "Logger"
+#: ../../Zotlabs/Module/Dreport.php:91
+msgid "queued"
+msgstr "lagt i kø"
-#: ../../Zotlabs/Module/Admin/Logs.php:86
-msgid "Clear"
-msgstr "Tøm"
+#: ../../Zotlabs/Module/Dreport.php:95
+msgid "posted"
+msgstr "lagt inn"
-#: ../../Zotlabs/Module/Admin/Logs.php:92
-msgid "Debugging"
-msgstr "Feilsøking"
+#: ../../Zotlabs/Module/Dreport.php:99
+msgid "accepted for delivery"
+msgstr "akseptert for levering"
-#: ../../Zotlabs/Module/Admin/Logs.php:93
-msgid "Log file"
-msgstr "Loggfil"
+#: ../../Zotlabs/Module/Dreport.php:103
+msgid "updated"
+msgstr "oppdatert"
-#: ../../Zotlabs/Module/Admin/Logs.php:93
-msgid ""
-"Must be writable by web server. Relative to your top-level webserver "
-"directory."
-msgstr ""
+#: ../../Zotlabs/Module/Dreport.php:107
+msgid "update ignored"
+msgstr "oppdatering ignorert"
-#: ../../Zotlabs/Module/Admin/Logs.php:94
-msgid "Log level"
-msgstr "Loggnivå"
+#: ../../Zotlabs/Module/Dreport.php:110
+msgid "permission denied"
+msgstr "tillatelse avvist"
-#: ../../Zotlabs/Module/Attach_edit.php:118
-msgid "Can not copy folder into itself."
-msgstr ""
+#: ../../Zotlabs/Module/Dreport.php:114
+msgid "recipient not found"
+msgstr "mottaker ble ikke funnet"
-#: ../../Zotlabs/Module/Attach_edit.php:131
+#: ../../Zotlabs/Module/Dreport.php:134
#, php-format
-msgid "Can not move folder \"%s\" into itself."
+msgid "Delivery report for %1$s"
+msgstr "Leveringsrapport for %1$s"
+
+#: ../../Zotlabs/Module/Dreport.php:139
+msgid "Redeliver"
msgstr ""
-#: ../../Zotlabs/Module/Pin.php:36 ../../Zotlabs/Module/Item.php:477
-msgid "Unable to locate original post."
-msgstr "Ikke i stand til å finne opprinnelig innlegg."
+#: ../../Zotlabs/Module/Chatsvc.php:131
+msgid "Away"
+msgstr "Borte"
-#: ../../Zotlabs/Module/Subthread.php:112 ../../Zotlabs/Module/Like.php:456
-#: ../../extend/addon/hzaddons/diaspora/Receiver.php:1693
-msgid "status"
-msgstr "status"
+#: ../../Zotlabs/Module/Chatsvc.php:136
+msgid "Online"
+msgstr "Online"
#: ../../Zotlabs/Module/Subthread.php:125
#, php-format
@@ -10911,358 +12136,135 @@ msgstr "%1$s følger %2$s sin %3$s"
msgid "%1$s stopped following %2$s's %3$s"
msgstr "%1$s stopped å følge %2$s sin %3$s"
-#: ../../Zotlabs/Module/Attach.php:68
-msgid "Item not available."
-msgstr "Elementet er ikke tilgjengelig."
-
-#: ../../Zotlabs/Module/Removeme.php:35
-msgid ""
-"Channel removals are not allowed within 48 hours of changing the account "
-"password."
-msgstr ""
-"Fjerning av kanaler er ikke tillatt innen 48 timer etter endring av "
-"kontopassordet."
-
-#: ../../Zotlabs/Module/Removeme.php:60 ../../Zotlabs/Module/Removeme.php:64
-#: ../../Zotlabs/Module/Settings/Channel.php:283
-msgid "Remove Channel"
-msgstr "Fjern kanal"
-
-#: ../../Zotlabs/Module/Removeme.php:61
-msgid "This channel will be permanently removed. "
-msgstr ""
-
-#: ../../Zotlabs/Module/Removeme.php:61
-msgid "This action can not be undone!"
-msgstr ""
-
-#: ../../Zotlabs/Module/Rmagic.php:46
-msgid "Authentication failed."
-msgstr "Autentisering mislyktes."
-
-#: ../../Zotlabs/Module/Item.php:763
-msgid "Empty post discarded."
-msgstr "Tomt innlegg forkastet."
-
-#: ../../Zotlabs/Module/Item.php:1207
-msgid "Duplicate post suppressed."
-msgstr "Duplikat av innlegg forhindret."
-
-#: ../../Zotlabs/Module/Item.php:1350
-msgid "System error. Post not saved."
-msgstr "Systemfeil. Innlegg ble ikke lagret."
-
-#: ../../Zotlabs/Module/Item.php:1384
-msgid "Your comment is awaiting approval."
-msgstr ""
-
-#: ../../Zotlabs/Module/Item.php:1520
-msgid "Unable to obtain post information from database."
-msgstr "Ikke i stand til å få tak i informasjon om innlegg fra databasen."
-
-#: ../../Zotlabs/Module/Item.php:1527
-#, php-format
-msgid "You have reached your limit of %1$.0f top level posts."
-msgstr "Du har nådd din grense på %1$.0f startinnlegg."
-
-#: ../../Zotlabs/Module/Item.php:1534
-#, php-format
-msgid "You have reached your limit of %1$.0f webpages."
-msgstr "Du har nådd din grense på %1$.0f websider."
-
-#: ../../Zotlabs/Module/Display.php:340
-msgid "Article"
-msgstr ""
-
-#: ../../Zotlabs/Module/Display.php:384
-msgid "Item has been removed."
-msgstr ""
-
-#: ../../Zotlabs/Module/Mitem.php:63
-msgid "Unable to create element."
-msgstr "Klarer ikke å lage element."
-
-#: ../../Zotlabs/Module/Mitem.php:87
-msgid "Unable to update menu element."
-msgstr "Ikke i stand til å oppdatere menyelement."
-
-#: ../../Zotlabs/Module/Mitem.php:103
-msgid "Unable to add menu element."
-msgstr "Ikke i stand til å legge til menyelement."
-
-#: ../../Zotlabs/Module/Mitem.php:167 ../../Zotlabs/Module/Mitem.php:246
-msgid "Menu Item Permissions"
-msgstr "Menyelement Tillatelser"
-
-#: ../../Zotlabs/Module/Mitem.php:168 ../../Zotlabs/Module/Mitem.php:247
-msgid "(click to open/close)"
-msgstr "(klikk for å åpne/lukke)"
-
-#: ../../Zotlabs/Module/Mitem.php:174 ../../Zotlabs/Module/Mitem.php:191
-msgid "Link Name"
-msgstr "Lenkenavn"
-
-#: ../../Zotlabs/Module/Mitem.php:175 ../../Zotlabs/Module/Mitem.php:255
-msgid "Link or Submenu Target"
-msgstr "Lenke- eller undermeny-mål"
-
-#: ../../Zotlabs/Module/Mitem.php:175
-msgid "Enter URL of the link or select a menu name to create a submenu"
-msgstr "Skriv URL-en til lenken eller velg et menynavn for å lage en undermeny"
-
-#: ../../Zotlabs/Module/Mitem.php:176 ../../Zotlabs/Module/Mitem.php:256
-msgid "Use magic-auth if available"
-msgstr "Bruk magic-autent hvis mulig"
-
-#: ../../Zotlabs/Module/Mitem.php:177 ../../Zotlabs/Module/Mitem.php:257
-msgid "Open link in new window"
-msgstr "Åpne lenke i nytt vindu"
-
-#: ../../Zotlabs/Module/Mitem.php:178 ../../Zotlabs/Module/Mitem.php:258
-msgid "Order in list"
-msgstr "Ordne i liste"
-
-#: ../../Zotlabs/Module/Mitem.php:178 ../../Zotlabs/Module/Mitem.php:258
-msgid "Higher numbers will sink to bottom of listing"
-msgstr "Høyere tall vil synke mot bunnen av listen"
-
-#: ../../Zotlabs/Module/Mitem.php:179
-msgid "Submit and finish"
-msgstr "Send inn og avslutt"
-
-#: ../../Zotlabs/Module/Mitem.php:180
-msgid "Submit and continue"
-msgstr "Send inn og fortsett"
-
-#: ../../Zotlabs/Module/Mitem.php:189
-msgid "Menu:"
-msgstr "Meny:"
-
-#: ../../Zotlabs/Module/Mitem.php:192
-msgid "Link Target"
-msgstr "Lenkemål"
-
-#: ../../Zotlabs/Module/Mitem.php:195
-msgid "Edit menu"
-msgstr "Endre meny"
-
-#: ../../Zotlabs/Module/Mitem.php:198
-msgid "Edit element"
-msgstr "Endre element"
-
-#: ../../Zotlabs/Module/Mitem.php:199
-msgid "Drop element"
-msgstr "Slett element"
-
-#: ../../Zotlabs/Module/Mitem.php:200
-msgid "New element"
-msgstr "Nytt element"
-
-#: ../../Zotlabs/Module/Mitem.php:201
-msgid "Edit this menu container"
-msgstr "Endre denne menybeholderen"
-
-#: ../../Zotlabs/Module/Mitem.php:202
-msgid "Add menu element"
-msgstr "Legg til menyelement"
-
-#: ../../Zotlabs/Module/Mitem.php:203
-msgid "Delete this menu item"
-msgstr "Slett dette menyelementet"
+#: ../../Zotlabs/Module/Cal.php:62
+msgid "Permissions denied."
+msgstr "Tillatelse avvist."
-#: ../../Zotlabs/Module/Mitem.php:204
-msgid "Edit this menu item"
-msgstr "Endre dette menyelementet"
+#: ../../Zotlabs/Module/Cal.php:161
+#: ../../Zotlabs/Module/Channel_calendar.php:380
+#: ../../Zotlabs/Module/Cdav.php:935
+#, fuzzy
+msgid "Link to source"
+msgstr "Lenke til kilde"
-#: ../../Zotlabs/Module/Mitem.php:222
-msgid "Menu item not found."
-msgstr "Menyelement ble ikke funnet."
+#: ../../Zotlabs/Module/Cal.php:199 ../../Zotlabs/Module/Photos.php:948
+#: ../../Zotlabs/Module/Cdav.php:1026
+msgid "Previous"
+msgstr "Forrige"
-#: ../../Zotlabs/Module/Mitem.php:235
-msgid "Menu item deleted."
-msgstr "Menyelement slettet."
+#: ../../Zotlabs/Module/Cal.php:200 ../../Zotlabs/Module/Photos.php:957
+#: ../../Zotlabs/Module/Setup.php:278 ../../Zotlabs/Module/Cdav.php:1027
+msgid "Next"
+msgstr "Neste"
-#: ../../Zotlabs/Module/Mitem.php:237
-msgid "Menu item could not be deleted."
-msgstr "Menyelement kunne ikke bli slettet."
+#: ../../Zotlabs/Module/Cal.php:201 ../../Zotlabs/Module/Cdav.php:1028
+msgid "Today"
+msgstr "Idag"
-#: ../../Zotlabs/Module/Mitem.php:244
-msgid "Edit Menu Element"
-msgstr "Endre menyelement"
+#: ../../Zotlabs/Module/Bookmarks.php:62
+msgid "Bookmark added"
+msgstr "Bokmerke lagt til"
-#: ../../Zotlabs/Module/Mitem.php:254
-msgid "Link text"
-msgstr "Lenketekst"
+#: ../../Zotlabs/Module/Bookmarks.php:101
+msgid "My Connections Bookmarks"
+msgstr "Mine forbindelsers bokmerker"
-#: ../../Zotlabs/Module/Email_validation.php:37
-msgid "Email Verification Required"
+#: ../../Zotlabs/Module/Affinity.php:35
+msgid "Affinity Tool settings updated."
msgstr ""
-#: ../../Zotlabs/Module/Email_validation.php:38
-#, php-format
+#: ../../Zotlabs/Module/Affinity.php:54
msgid ""
-"A verification token was sent to your email address [%s]. Enter that token "
-"here to complete the account verification step. Please allow a few minutes "
-"for delivery, and check your spam folder if you do not see the message."
+"The numbers below represent the minimum and maximum slider default positions "
+"for your network/stream page as a percentage."
msgstr ""
-"En verifikasjonskode ble sendt til epostadressen din [%s]. Skriv inn koden "
-"du mottok her for å fullføre kontoverifikasjonen. Det kan ta noen minutter "
-"før du mottar koden. Sjekk også at eposten ikke har havnet i mappen for "
-"søppelpost om du ikke ser den etter en stund."
-#: ../../Zotlabs/Module/Email_validation.php:39
-msgid "Resend Email"
+#: ../../Zotlabs/Module/Affinity.php:61
+msgid "Default maximum affinity level"
msgstr ""
-#: ../../Zotlabs/Module/Email_validation.php:42
-msgid "Validation token"
-msgstr ""
+#: ../../Zotlabs/Module/Affinity.php:61
+#, fuzzy
+msgid "0-99 default 99"
+msgstr "Standard"
-#: ../../Zotlabs/Module/Uexport.php:108
-msgid "No content available for year"
+#: ../../Zotlabs/Module/Affinity.php:67
+msgid "Default minimum affinity level"
msgstr ""
-#: ../../Zotlabs/Module/Uexport.php:171
-msgid "Export Channel"
-msgstr "Eksporter kanal"
-
-#: ../../Zotlabs/Module/Uexport.php:173
-msgid "Export channel"
-msgstr "Eksporter kanal"
-
-#: ../../Zotlabs/Module/Uexport.php:174
-msgid ""
-"This will export your identity and social graph into a file which can be "
-"used to import your channel to a new hub."
-msgstr ""
+#: ../../Zotlabs/Module/Affinity.php:67
+#, fuzzy
+msgid "0-99 - default 0"
+msgstr "Standard"
-#: ../../Zotlabs/Module/Uexport.php:177
-msgid "Export content"
+#: ../../Zotlabs/Module/Affinity.php:73
+msgid "Persistent affinity levels"
msgstr ""
-#: ../../Zotlabs/Module/Uexport.php:178
+#: ../../Zotlabs/Module/Affinity.php:73
msgid ""
-"This will export your posts, direct messages, articles and cards per month "
-"stored into a zip file per year. Months with no posts will be dismissed."
-msgstr ""
-"Dette vil eksportere dine innlegg, direktemeldinger, artikler og kort, en "
-"fil for hver måned, pakket i en zip fil for hvert år. Måneder uten innhold "
-"blir ignorert."
-
-#: ../../Zotlabs/Module/Uexport.php:180
-msgid "Export wikis"
-msgstr ""
-
-#: ../../Zotlabs/Module/Uexport.php:181
-msgid "This will export your wikis and wiki pages."
-msgstr ""
-
-#: ../../Zotlabs/Module/Uexport.php:183
-msgid "Export webpages"
-msgstr ""
-
-#: ../../Zotlabs/Module/Uexport.php:184
-msgid "This will export your webpages and menus."
-msgstr ""
-
-#: ../../Zotlabs/Module/Uexport.php:186
-msgid "Export channel calendar"
+"If disabled the max and min levels will be reset to default after page reload"
msgstr ""
-#: ../../Zotlabs/Module/Uexport.php:187
-msgid ""
-"This will export your channel calendar events and associated items. CalDAV "
-"calendars are not included."
+#: ../../Zotlabs/Module/Affinity.php:81
+msgid "Affinity Tool Settings"
msgstr ""
-#: ../../Zotlabs/Module/Uexport.php:189
-msgid "Export chatrooms"
+#: ../../Zotlabs/Module/Admin.php:98
+msgid "Blocked accounts"
msgstr ""
-#: ../../Zotlabs/Module/Uexport.php:190
-msgid "This will export your chatrooms. Chat history is dismissed."
+#: ../../Zotlabs/Module/Admin.php:99
+msgid "Expired accounts"
msgstr ""
-#: ../../Zotlabs/Module/Uexport.php:192
-#, php-format
-msgid ""
-"This export can be imported or restored by visiting <a href=\"%1$s\">%2$s</"
-"a> on any site containing your channel."
+#: ../../Zotlabs/Module/Admin.php:100
+msgid "Expiring accounts"
msgstr ""
-#: ../../Zotlabs/Module/Notifications.php:106
-#: ../../Zotlabs/Module/Notify.php:85
-msgid "No more system notifications."
-msgstr "Ingen flere systemvarsler."
-
-#: ../../Zotlabs/Module/Notifications.php:110
-#: ../../Zotlabs/Module/Notify.php:89
-msgid "System Notifications"
-msgstr "Systemvarsler"
-
-#: ../../Zotlabs/Module/Notifications.php:111
-msgid "Mark all seen"
-msgstr "Merk alle som sett"
-
-#: ../../Zotlabs/Module/Sharedwithme.php:106
-msgid "Files: shared with me"
-msgstr "Filer: delt med meg"
-
-#: ../../Zotlabs/Module/Sharedwithme.php:108
-msgid "NEW"
-msgstr "NY"
-
-#: ../../Zotlabs/Module/Sharedwithme.php:111
-msgid "Remove all files"
-msgstr "Fjern alle filer"
-
-#: ../../Zotlabs/Module/Sharedwithme.php:112
-msgid "Remove this file"
-msgstr "Fjern denne filen"
+#: ../../Zotlabs/Module/Admin.php:124
+msgid "Message queues"
+msgstr "Meldingskøer"
-#: ../../Zotlabs/Module/Tokens.php:94
-#, php-format
-msgid "This channel is limited to %d tokens"
-msgstr ""
+#: ../../Zotlabs/Module/Admin.php:138
+msgid "Your software should be updated"
+msgstr "Programvaren bør oppdateres"
-#: ../../Zotlabs/Module/Tokens.php:100
-msgid "Name and Password are required."
-msgstr ""
+#: ../../Zotlabs/Module/Admin.php:143
+msgid "Summary"
+msgstr "Sammendrag"
-#: ../../Zotlabs/Module/Tokens.php:215
-msgid "Token saved."
-msgstr ""
+#: ../../Zotlabs/Module/Admin.php:146
+msgid "Registered accounts"
+msgstr "Registrerte kontoer"
-#: ../../Zotlabs/Module/Tokens.php:261
-msgid ""
-"Use this form to create temporary access identifiers to share things with "
-"non-members. These identities may be used in privacy groups and visitors may "
-"login using these credentials to access private content."
-msgstr ""
+#: ../../Zotlabs/Module/Admin.php:147
+msgid "Pending registrations"
+msgstr "Ventende registreringer"
-#: ../../Zotlabs/Module/Tokens.php:274
-msgid "Please select a role for this guest!"
-msgstr ""
+#: ../../Zotlabs/Module/Admin.php:148
+msgid "Registered channels"
+msgstr "Registrerte kanaler"
-#: ../../Zotlabs/Module/Tokens.php:287
-msgid "Select a role for this guest"
-msgstr ""
+#: ../../Zotlabs/Module/Admin.php:149 ../../Zotlabs/Module/Siteinfo.php:42
+msgid "Active addons"
+msgstr "Aktive tillegg"
-#: ../../Zotlabs/Module/Tokens.php:291
-msgid "Login Name"
-msgstr ""
+#: ../../Zotlabs/Module/Admin.php:150
+msgid "Version"
+msgstr "Versjon"
-#: ../../Zotlabs/Module/Tokens.php:292
-msgid "Login Password"
+#: ../../Zotlabs/Module/Admin.php:151
+msgid "Repository version (master)"
msgstr ""
-#: ../../Zotlabs/Module/Tokens.php:293
-msgid "Expires (yyyy-mm-dd)"
+#: ../../Zotlabs/Module/Admin.php:152
+msgid "Repository version (dev)"
msgstr ""
#: ../../Zotlabs/Module/Permcats.php:59
msgid "Contact role deleted."
-msgstr ""
+msgstr "Kontaktrollen ble slettet."
#: ../../Zotlabs/Module/Permcats.php:86
msgid "Permission category name is required."
@@ -11270,735 +12272,428 @@ msgstr ""
#: ../../Zotlabs/Module/Permcats.php:102 ../../Zotlabs/Module/Permcats.php:154
msgid "Contact role saved."
-msgstr ""
+msgstr "Kontaktrollen ble lagret."
#: ../../Zotlabs/Module/Permcats.php:204
msgid "Role to assign affected contacts and default role to"
-msgstr ""
+msgstr "Ny rolle for berørte forbindelser og standardrolle"
#: ../../Zotlabs/Module/Permcats.php:204
msgid "Role to assign affected contacts to"
-msgstr ""
+msgstr "Ny rolle for berørte forbindelser"
#: ../../Zotlabs/Module/Permcats.php:234
msgid "Assign this role to"
-msgstr ""
+msgstr "Bruk denne rollen for"
#: ../../Zotlabs/Module/Permcats.php:236
msgid "All my contacts"
-msgstr ""
+msgstr "Alle mine forbindelser"
#: ../../Zotlabs/Module/Permcats.php:247
msgid "Automatically assign this role to new contacts"
-msgstr ""
+msgstr "Bruk denne rollen for nye kontakter automatisk"
#: ../../Zotlabs/Module/Permcats.php:249
msgid "Role name"
-msgstr ""
+msgstr "Navn"
#: ../../Zotlabs/Module/Permcats.php:249
msgid "System role - not editable"
-msgstr ""
+msgstr "Systemrolle - kan ikke redigeres"
#: ../../Zotlabs/Module/Permcats.php:250
msgid "Deleting"
-msgstr ""
+msgstr "Sletter kanalrolle"
#: ../../Zotlabs/Module/Permcats.php:255
msgid "Role Permissions"
-msgstr ""
+msgstr "Tillatelser"
#: ../../Zotlabs/Module/Permcats.php:256
msgid ""
"Some permissions may be inherited from your <a href=\"settings\">channel "
"role</a>, which have higher priority than contact role settings."
msgstr ""
+"Noen tillatelser kan være arvet fra din <a href=\"settings\">kanalrole</a>, "
+"som vil ha høyere prioritet enn innstillingene for kontaktrollen."
-#: ../../Zotlabs/Module/Settings/Channel.php:106
-#: ../../Zotlabs/Module/Settings/Channel.php:218
-msgid "Please select a channel role"
-msgstr "Velg en kanalrolle"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:195
-msgid "Your channel address is"
-msgstr "Din kanaladresse er"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:198
-msgid "Your files/photos are accessible via WebDAV at"
-msgstr "Dine filer og foto er tilgjengelig via WebDAV på"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:229
-msgid "Channel Settings"
-msgstr "Kanalinnstillinger"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:236
-msgid "Basic Settings"
-msgstr "Grunninnstillinger"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:237
-msgid "Channel timezone:"
-msgstr "Tidssone for kanalen:"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:238
-msgid "Default post location:"
-msgstr "Standard plassering for innlegg:"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:238
-msgid "Geographical location to display on your posts"
-msgstr "Geografisk plassering som vises på dine innlegg"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:239
-msgid "Use browser location"
-msgstr "Bruk nettleserplassering"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:240
-msgid "Adult content"
-msgstr "Voksent innhold"
+#: ../../Zotlabs/Module/Attach_edit.php:118
+msgid "Can not copy folder into itself."
+msgstr "Kan ikke kopiere mappe inn i seg selv."
-#: ../../Zotlabs/Module/Settings/Channel.php:240
-msgid "This channel frequently or regularly publishes adult content"
+#: ../../Zotlabs/Module/Attach_edit.php:131
+#, php-format
+msgid "Can not move folder \"%s\" into itself."
msgstr ""
-"Denne kanalen vil ofte, eller regelmessig poste innlegg med voksent innhold"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:241
-msgid "Maximum Friend Requests/Day:"
-msgstr "Maksimalt antall venneforespørsler per dag:"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:241
-msgid "May reduce spam activity"
-msgstr "Kan redusere søppelpostaktivitet"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:243
-msgid "By default post a status message when:"
-msgstr "Legg inn en statusmelding når du:"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:244
-msgid "accepting a friend request"
-msgstr "aksepterer en venneforespørsel"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:245
-msgid "joining a forum/community"
-msgstr "blir med i et forum/miljø"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:246
-msgid "making an <em>interesting</em> profile change"
-msgstr "gjør en <em>interessant</em> profilendring"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:247
-msgid "Send a notification email when:"
-msgstr "Send en varsel-e-post når:"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:248
-msgid "You receive a connection request"
-msgstr "Du har mottatt en forespørsel om forbindelse"
-#: ../../Zotlabs/Module/Settings/Channel.php:249
-msgid "Your connections are confirmed"
-msgstr "Dine forbindelser er bekreftet"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:250
-msgid "Someone writes on your profile wall"
-msgstr "Noen skriver på din profilvegg"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:251
-msgid "Someone writes a followup comment"
-msgstr "Noen skriver en oppfølgende kommentar"
+#: ../../Zotlabs/Module/Xchan.php:10
+msgid "Xchan Lookup"
+msgstr "Xchan oppslag"
-#: ../../Zotlabs/Module/Settings/Channel.php:252
-msgid "You receive a private message"
-msgstr "Du mottar en privat melding"
+#: ../../Zotlabs/Module/Xchan.php:13
+msgid "Lookup xchan beginning with (or webbie): "
+msgstr "Slå opp xchan som begynner med (eller webbie): "
-#: ../../Zotlabs/Module/Settings/Channel.php:253
-msgid "You receive a friend suggestion"
-msgstr "Du mottok et venneforslag"
+#: ../../Zotlabs/Module/Group.php:48
+msgid "Privacy group created."
+msgstr "Personverngruppen er opprettet."
-#: ../../Zotlabs/Module/Settings/Channel.php:254
-msgid "You are tagged in a post"
-msgstr "Du merkes i et innlegg"
+#: ../../Zotlabs/Module/Group.php:51
+msgid "Could not create privacy group."
+msgstr "Kunne ikke opprette personverngruppen."
-#: ../../Zotlabs/Module/Settings/Channel.php:255
-msgid "You are poked/prodded/etc. in a post"
-msgstr "Du ble prikket/oppildnet/og så vider i et innlegg"
+#: ../../Zotlabs/Module/Group.php:83
+msgid "Privacy group updated."
+msgstr "Personverngruppen er oppdatert."
-#: ../../Zotlabs/Module/Settings/Channel.php:256
-msgid "Someone likes your post/comment"
+#: ../../Zotlabs/Module/Group.php:138 ../../Zotlabs/Module/Group.php:302
+msgid "Post to this group by default"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Channel.php:257
-msgid "Show visual notifications including:"
-msgstr "Vis visuelle varslinger om:"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:258
-msgid "Unseen stream activity"
-msgstr "Ny aktivitet i nettverksstrømmen"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:259
-msgid "Unseen channel activity"
-msgstr "Ny kanalaktivitet"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:260
-msgid "Unseen private messages"
-msgstr "Nye private meldinger"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:260
-#: ../../Zotlabs/Module/Settings/Channel.php:265
-#: ../../Zotlabs/Module/Settings/Channel.php:266
-#: ../../Zotlabs/Module/Settings/Channel.php:267
-msgid "Recommended"
-msgstr "Anbefalt"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:261
-msgid "Upcoming events"
-msgstr "Kommende hendelser"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:262
-msgid "Events today"
-msgstr "Hendelser idag"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:263
-msgid "Upcoming birthdays"
-msgstr "Kommende fødselsdager"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:263
-msgid "Not available in all themes"
-msgstr "Ikke tilgjengelig i alle temaer"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:264
-msgid "System (personal) notifications"
-msgstr "System (personlige) varslinger"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:265
-msgid "System info messages"
-msgstr "System infomeldinger"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:266
-msgid "System critical alerts"
-msgstr "System kritiske varsel"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:267
-msgid "New connections"
-msgstr "Nye forbindelser"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:268
-msgid "System Registrations"
-msgstr "Systemregistreringer"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:269
-msgid "Unseen shared files"
-msgstr "Nye delte filer"
+#: ../../Zotlabs/Module/Group.php:139 ../../Zotlabs/Module/Group.php:303
+msgid "Add new contacts to this group by default"
+msgstr ""
-#: ../../Zotlabs/Module/Settings/Channel.php:270
-msgid "Unseen public stream activity"
-msgstr "Ny aktivitet i den offentlige strømmen"
+#: ../../Zotlabs/Module/Group.php:147
+msgid "Privacy group name"
+msgstr "Personverngruppens navn"
-#: ../../Zotlabs/Module/Settings/Channel.php:271
-msgid "Unseen likes and dislikes"
-msgstr "Nye liker/ikke liker"
+#: ../../Zotlabs/Module/Group.php:148 ../../Zotlabs/Module/Group.php:250
+msgid "Members are visible to other channels"
+msgstr "Medlemmer er synlig for andre kanaler"
-#: ../../Zotlabs/Module/Settings/Channel.php:272
-msgid "Unseen forum posts"
-msgstr "Ny forumpost"
+#: ../../Zotlabs/Module/Group.php:176
+msgid "Privacy group removed."
+msgstr "Personverngruppen er fjernet."
-#: ../../Zotlabs/Module/Settings/Channel.php:273
-msgid "Email notification hub (hostname)"
-msgstr "Hub for epostvarsler (tjenernavn)"
+#: ../../Zotlabs/Module/Group.php:179
+msgid "Unable to remove privacy group."
+msgstr "Ikke i stand til å fjerne personverngruppen."
-#: ../../Zotlabs/Module/Settings/Channel.php:273
+#: ../../Zotlabs/Module/Group.php:245
#, php-format
-msgid ""
-"If your channel is mirrored to multiple hubs, set this to your preferred "
-"location. This will prevent duplicate email notifications. Example: %s"
-msgstr ""
-"Hvis kanalen din er klonet til flere tjenere kan du sette dette til den du vil"
-" skal sende deg epostvarsler. Dette vil forhindre duplikate epostvarsler. Ekse"
-"mpel: %s"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:274
-msgid "Show new wall posts, private messages and connections under Notices"
-msgstr "Vis nye veggposter, private meldinger og forbindelser under varsler"
+msgid "Privacy Group: %s"
+msgstr "Personverngruppe: %s"
-#: ../../Zotlabs/Module/Settings/Channel.php:275
-msgid "Mark all notices of the thread read if a notice is clicked"
-msgstr "Merk alle varsler for en tråd som lest når du klikker på ett varsel"
+#: ../../Zotlabs/Module/Group.php:247
+msgid "Privacy group name: "
+msgstr "Personverngruppens navn: "
-#: ../../Zotlabs/Module/Settings/Channel.php:275
-msgid "If no, only the clicked notice will be marked read"
+#: ../../Zotlabs/Module/Group.php:263
+msgid "Group members"
msgstr ""
-"Om du velger \"Nei\", blir kun det varslet du klikker på merket som lest"
-#: ../../Zotlabs/Module/Settings/Channel.php:276
-msgid ""
-"Desktop notifications are unavailable because the required browser "
-"permission has not been granted"
+#: ../../Zotlabs/Module/Group.php:265
+msgid "Not in this group"
msgstr ""
-"Skriverbordsvarsler er ikke tilgjengelig fordi nettstedet ikke har de nødvendi"
-"ge nettlesertillatelsene"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:277
-msgid "Grant permission"
-msgstr "Gi tilgang"
-#: ../../Zotlabs/Module/Settings/Channel.php:278
-msgid "Notify me of events this many days in advance"
-msgstr "Varsle meg om hendelser dette antall dager på forhånd"
-
-#: ../../Zotlabs/Module/Settings/Channel.php:278
-msgid "Must be greater than 0"
-msgstr "Må være større enn 0"
+#: ../../Zotlabs/Module/Group.php:297
+msgid "Click a channel to toggle membership"
+msgstr ""
-#: ../../Zotlabs/Module/Settings/Channel.php:281
-msgid "Default photo upload folder"
-msgstr "Standard mappe for opplasting av bilder"
+#: ../../Zotlabs/Module/Help.php:61
+msgid "Documentation Search"
+msgstr "Søk i dokumentasjon"
-#: ../../Zotlabs/Module/Settings/Channel.php:281
-#: ../../Zotlabs/Module/Settings/Channel.php:282
-msgid "%Y - current year, %m - current month"
-msgstr "%Y - nåværende år, %m - nåværende måned"
+#: ../../Zotlabs/Module/Help.php:170
+msgid "Not Found"
+msgstr "Ikke funnet"
-#: ../../Zotlabs/Module/Settings/Channel.php:282
-msgid "Default file upload folder"
-msgstr "Standard mappe for opplasting av filer"
+#: ../../Zotlabs/Module/Help.php:223
+msgid "$Projectname Documentation"
+msgstr "$Projectname dokumentasjon"
-#: ../../Zotlabs/Module/Settings/Channel.php:284
-msgid "Remove this channel."
-msgstr "Fjern denne kanalen."
+#: ../../Zotlabs/Module/Help.php:234
+msgid "Contents"
+msgstr ""
-#: ../../Zotlabs/Module/Settings/Channel.php:285
-msgid "Expire other channel content after this many days"
-msgstr "Annet kanal innhold utløper etter så mange dager"
+#: ../../Zotlabs/Module/Help.php:241
+msgid "Members"
+msgstr "Medlemmer"
-#: ../../Zotlabs/Module/Settings/Channel.php:285
-msgid "0 or blank to use the website limit."
-msgstr "0 eller la være tomt for å bruke grensen til nettstedet."
+#: ../../Zotlabs/Module/Help.php:242
+msgid "Administrators"
+msgstr ""
-#: ../../Zotlabs/Module/Settings/Channel.php:285
-#, php-format
-msgid "This website expires after %d days."
+#: ../../Zotlabs/Module/Help.php:243
+msgid "Developers"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Channel.php:285
-msgid "This website does not expire imported content."
-msgstr "Dette nettstedet sletter ikke importert innhold."
+#: ../../Zotlabs/Module/Help.php:244
+msgid "Tutorials"
+msgstr ""
-#: ../../Zotlabs/Module/Settings/Channel.php:285
-msgid "The website limit takes precedence if lower than your limit."
-msgstr "Nettstedets grense vil gjelde dersom den er lavere enn din grense."
+#: ../../Zotlabs/Module/Help.php:262
+msgid "Help:"
+msgstr "Hjelp:"
-#: ../../Zotlabs/Module/Settings/Channel.php:286
-#: ../../Zotlabs/Module/Settings/Channel.php:287
-msgid ""
-"Words one per line or #tags, $categories, /patterns/, lang=xx, lang!=xx - "
-"leave blank to import all posts"
+#: ../../Zotlabs/Module/Tagger.php:52
+msgid "Post not found."
msgstr ""
-"Ett ord per linje eller #emneknagger, $kategorier, /mønster/, lang=xx, lang!"
-"=xx - la være tomt for å importere alle innlegg"
-
-#: ../../Zotlabs/Module/Settings/Featured.php:25
-msgid "No feature settings configured"
-msgstr "Ingen funksjonsinnstillinger er konfigurert"
-#: ../../Zotlabs/Module/Settings/Featured.php:34
-msgid "Addon Settings"
-msgstr "Instillinger for tillegg"
+#: ../../Zotlabs/Module/Tagger.php:83
+msgid "comment"
+msgstr "kommentar"
-#: ../../Zotlabs/Module/Settings/Featured.php:35
-msgid "Please save/submit changes to any panel before opening another."
-msgstr "Husk å lagre endringene i ett panel før du åpner andre."
+#: ../../Zotlabs/Module/Tagger.php:123
+#, php-format
+msgid "%1$s tagged %2$s's %3$s with %4$s"
+msgstr "%1$s merket %3$s til %2$s med %4$s"
-#: ../../Zotlabs/Module/Settings/Editor.php:40
-msgid "Editor Settings"
-msgstr "Instillinger for redigering"
+#: ../../Zotlabs/Module/Invite.php:70
+msgid "Invite App"
+msgstr ""
-#: ../../Zotlabs/Module/Settings/Photos.php:40
-msgid "Photos Settings"
+#: ../../Zotlabs/Module/Invite.php:82
+msgid "Register is closed"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Profiles.php:41
-msgid "Default profile for new contacts"
+#: ../../Zotlabs/Module/Invite.php:116 ../../Zotlabs/Module/Invite.php:546
+msgid "Note, the invitation code is valid up to"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Profiles.php:49
-msgid "Profiles Settings"
+#: ../../Zotlabs/Module/Invite.php:129
+#, php-format
+msgid "Too many recipients for one invitation (max %d)"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Network.php:42
-#: ../../Zotlabs/Module/Settings/Channel_home.php:46
-msgid "Max height of content (in pixels)"
-msgstr "Maks høyde for innhold (i piksler)"
+#: ../../Zotlabs/Module/Invite.php:133
+msgid "No recipients for this invitation"
+msgstr ""
-#: ../../Zotlabs/Module/Settings/Network.php:44
-#: ../../Zotlabs/Module/Settings/Channel_home.php:48
-msgid "Click to expand content exceeding this height"
-msgstr "Klikk for å vise hele innlegg som overskrider denne grensen"
+#: ../../Zotlabs/Module/Invite.php:152
+#, php-format
+msgid "(%s) : Not a real email address"
+msgstr "(%s): Ikke en virkelig epostadresse"
-#: ../../Zotlabs/Module/Settings/Network.php:59
-msgid "Stream Settings"
-msgstr "Instillinger for tidslinjen"
+#: ../../Zotlabs/Module/Invite.php:159
+#, php-format
+msgid "(%s) : Not allowed email address"
+msgstr "(%s): Ikke en tillatt epostadresse"
-#: ../../Zotlabs/Module/Settings/Directory.php:40
-msgid "Directory Settings"
-msgstr ""
+#: ../../Zotlabs/Module/Invite.php:172
+#, php-format
+msgid "(%s) : email address already in use"
+msgstr "(%s): epostadressen er allerede i bruk"
-#: ../../Zotlabs/Module/Settings/Channel_home.php:61
-msgid "Personal menu to display in your channel pages"
-msgstr "Personlig meny som kan vises på dine kanalsider"
+#: ../../Zotlabs/Module/Invite.php:179
+#, php-format
+msgid "(%s) : Accepted email address"
+msgstr "(%s): Godkjent epostadresse"
-#: ../../Zotlabs/Module/Settings/Channel_home.php:88
-msgid "Channel Home Settings"
+#: ../../Zotlabs/Module/Invite.php:271
+#, php-format
+msgid "To %s : Message delivery success."
msgstr ""
-#: ../../Zotlabs/Module/Settings/Calendar.php:40
-msgid "Calendar Settings"
+#: ../../Zotlabs/Module/Invite.php:303
+#, php-format
+msgid "%1$d mail(s) sent, %2$d mail error(s)"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Conversation.php:42
-msgid "Conversation Settings"
+#: ../../Zotlabs/Module/Invite.php:328
+msgid "Invites not proposed by configuration"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Features.php:45
-msgid "Additional Features"
-msgstr "Ekstra funksjoner"
-
-#: ../../Zotlabs/Module/Settings/Connections.php:40
-msgid "Connections Settings"
+#: ../../Zotlabs/Module/Invite.php:329
+msgid "Contact the site admin"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Multifactor.php:23
-#, fuzzy
-#| msgid "Name is required"
-msgid "Password is required"
-msgstr "Navn er påkrevd"
-
-#: ../../Zotlabs/Module/Settings/Multifactor.php:29
-msgid "The provided password is not correct"
+#: ../../Zotlabs/Module/Invite.php:345
+msgid "Invites by users not enabled"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Multifactor.php:68
-msgid "Account Multi-Factor Authentication"
-msgstr "Tofaktorautentisering"
+#: ../../Zotlabs/Module/Invite.php:350
+msgid "You have no more invitations available"
+msgstr "Du har ikke flere invitasjoner tilgjengelig"
-#: ../../Zotlabs/Module/Settings/Multifactor.php:69
-msgid ""
-"This is your generated secret. It may be used in some cases if the QR image "
-"cannot be read. Please store it in a safe place."
+#: ../../Zotlabs/Module/Invite.php:360
+msgid "Not on xchan"
msgstr ""
-"Dette er en generert sikkerhetskode. Den kan være nyttig i enkelte tilfeller "
-"hvor QR koden ikke kan leses. Lagre den et sikkert sted."
-
-#: ../../Zotlabs/Module/Settings/Multifactor.php:70
-msgid "Please enter the code from your authenticator app"
-msgstr "Skriv inn koden fra din autentiseringsapp"
-#: ../../Zotlabs/Module/Settings/Multifactor.php:71
-msgid "You will only be able to enable MFA if the test passes"
+#: ../../Zotlabs/Module/Invite.php:400
+msgid "Invitation expires after"
msgstr ""
-"Flerfaktorautentisering vil kun bli slått på dersom denne testen lykkes"
-#: ../../Zotlabs/Module/Settings/Multifactor.php:75
-msgid "Congratulations, the provided code was correct"
+#: ../../Zotlabs/Module/Invite.php:501 ../../Zotlabs/Module/Invite.php:540
+msgid "Invitation"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Multifactor.php:76
-msgid "Incorrect code"
-msgstr ""
+#: ../../Zotlabs/Module/Invite.php:531
+msgid "Send invitations"
+msgstr "Send invitasjoner"
-#: ../../Zotlabs/Module/Settings/Multifactor.php:79
-#, fuzzy
-#| msgid "Failed authentication"
-msgid "Enable Multi-Factor Authentication"
-msgstr "Mislykket autentisering"
+#: ../../Zotlabs/Module/Invite.php:532
+msgid "Invitations I am using"
+msgstr "Invitasjoner jeg bruker"
-#: ../../Zotlabs/Module/Settings/Multifactor.php:81
-msgid "Logging in will require you to be in possession of your smartphone"
+#: ../../Zotlabs/Module/Invite.php:533
+msgid "Invitations we are using"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Multifactor.php:84
-#, fuzzy
-#| msgid "Your new password is"
-msgid "Your account password"
-msgstr "Ditt nye passord er"
-
-#: ../../Zotlabs/Module/Settings/Multifactor.php:86
-msgid "Test"
-msgstr "Sjekk koden"
-
-#: ../../Zotlabs/Module/Settings/Events.php:40
-msgid "Events Settings"
+#: ../../Zotlabs/Module/Invite.php:534
+msgid "§ Note, the email(s) sent will be recorded in the system logs"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Account.php:23
-msgid "Not valid email."
-msgstr "Ikke gyldig e-post."
-
-#: ../../Zotlabs/Module/Settings/Account.php:26
-msgid "Protected email address. Cannot change to that email."
-msgstr "Beskyttet e-postadresse. Kan ikke endre til den e-postadressen."
-
-#: ../../Zotlabs/Module/Settings/Account.php:35
-msgid "System failure storing new email. Please try again."
-msgstr "Systemfeil ved lagring av ny e-post. Vennligst prøv igjen."
-
-#: ../../Zotlabs/Module/Settings/Account.php:53
-msgid "Password verification failed."
-msgstr "Passordbekreftelsen mislyktes."
-
-#: ../../Zotlabs/Module/Settings/Account.php:60
-msgid "Passwords do not match. Password unchanged."
-msgstr "Passordene stemmer ikke overens. Passord uforandret."
+#: ../../Zotlabs/Module/Invite.php:535
+msgid "Enter email addresses, one per line:"
+msgstr "Skriv e-postadresser, en per linje:"
-#: ../../Zotlabs/Module/Settings/Account.php:64
-msgid "Empty passwords are not allowed. Password unchanged."
-msgstr "Tomme passord er ikke tillatt. Passord uforandret."
+#: ../../Zotlabs/Module/Invite.php:536
+msgid "Your message:"
+msgstr "Din melding:"
-#: ../../Zotlabs/Module/Settings/Account.php:78
-msgid "Password changed."
-msgstr "Passord endret."
+#: ../../Zotlabs/Module/Invite.php:537
+msgid "Invite template"
+msgstr ""
-#: ../../Zotlabs/Module/Settings/Account.php:80
-msgid "Password update failed. Please try again."
-msgstr "Passord oppdatering mislyktes. Vennligst prøv igjen."
+#: ../../Zotlabs/Module/Invite.php:539
+msgid "Subject:"
+msgstr "Emne:"
-#: ../../Zotlabs/Module/Settings/Account.php:105
-msgid "Account Settings"
-msgstr "Kontoinnstillinger"
+#: ../../Zotlabs/Module/Invite.php:545
+msgid "Here you may enter personal notes to the recipient(s)"
+msgstr ""
-#: ../../Zotlabs/Module/Settings/Account.php:106
-msgid "Current Password"
-msgstr "Nåværende passord"
+#: ../../Zotlabs/Module/Siteinfo.php:22
+msgid "About this site"
+msgstr "Om dette nettstedet "
-#: ../../Zotlabs/Module/Settings/Account.php:107
-msgid "Enter New Password"
-msgstr "Skriv nytt passord"
+#: ../../Zotlabs/Module/Siteinfo.php:23
+#, fuzzy
+msgid "Site Name"
+msgstr "Nettstedets navn"
-#: ../../Zotlabs/Module/Settings/Account.php:108
-msgid "Confirm New Password"
-msgstr "Bekreft nytt passord"
+#: ../../Zotlabs/Module/Siteinfo.php:27
+msgid "Administrator"
+msgstr "Administrator"
-#: ../../Zotlabs/Module/Settings/Account.php:108
-msgid "Leave password fields blank unless changing"
-msgstr "La passordfeltene stå blanke om det ikke skal endres"
+#: ../../Zotlabs/Module/Siteinfo.php:30
+msgid "Software and Project information"
+msgstr "Program- og prosjektinformasjon"
-#: ../../Zotlabs/Module/Settings/Account.php:110
-msgid "Multi-Factor Authentication"
-msgstr "Flerfaktorautentisering"
+#: ../../Zotlabs/Module/Siteinfo.php:31
+msgid "This site is powered by $Projectname"
+msgstr "Dette nettstedet drives av $Projectname"
-#: ../../Zotlabs/Module/Settings/Account.php:111
-msgid "DId2 or Email Address:"
-msgstr "DId2 eller epostadresse:"
+#: ../../Zotlabs/Module/Siteinfo.php:32
+#, fuzzy
+#| msgid ""
+#| "Federated and decentralised networking and identity services provided by "
+#| "Zot"
+msgid ""
+"Federated and decentralised networking and identity services provided by"
+msgstr "Fødererte og desentraliserte nettverks- og identitetstjenester via Zot"
-#: ../../Zotlabs/Module/Settings/Account.php:114
-msgid "Remove this account including all its channels"
-msgstr "Slett denne kontoen inkludert alle dens kanaler"
+#: ../../Zotlabs/Module/Siteinfo.php:35
+msgid "Additional federated transport protocols:"
+msgstr "Øvrige fødererte transportprotokoller:"
-#: ../../Zotlabs/Module/Settings/Privacy.php:49
-msgid "Privacy settings updated."
-msgstr "Personverninnstillingene ble oppdatert."
+#: ../../Zotlabs/Module/Siteinfo.php:37
+#, php-format
+msgid "Version %s"
+msgstr "Versjon %s"
-#: ../../Zotlabs/Module/Settings/Privacy.php:67
-msgid "Only those you specifically allow"
-msgstr "Bare de du spesifikt tillater"
+#: ../../Zotlabs/Module/Siteinfo.php:38
+msgid "Project homepage"
+msgstr "Prosjektets hjemmeside"
-#: ../../Zotlabs/Module/Settings/Privacy.php:68
-msgid "Approved connections"
-msgstr "Godkjente forbindelser"
+#: ../../Zotlabs/Module/Siteinfo.php:39
+msgid "Developer homepage"
+msgstr "Utviklers hjemmeside"
-#: ../../Zotlabs/Module/Settings/Privacy.php:69
-msgid "Any connections"
-msgstr "Enhver forbindelse"
+#: ../../Zotlabs/Module/Siteinfo.php:43
+#, fuzzy
+#| msgid "Block Title"
+msgid "Blocked sites"
+msgstr "Byggeklossens tittel"
-#: ../../Zotlabs/Module/Settings/Privacy.php:70
-msgid "Anybody on this website"
-msgstr "Enhver ved dette nettstedet"
+#: ../../Zotlabs/Module/Attach.php:68
+msgid "Item not available."
+msgstr "Elementet er ikke tilgjengelig."
-#: ../../Zotlabs/Module/Settings/Privacy.php:71
-msgid "Anybody in this network"
-msgstr "Enhver i dette nettverket"
+#: ../../Zotlabs/Module/Uexport.php:108
+msgid "No content available for year"
+msgstr ""
-#: ../../Zotlabs/Module/Settings/Privacy.php:72
-msgid "Anybody authenticated"
-msgstr "Enhver som er autentisert"
+#: ../../Zotlabs/Module/Uexport.php:171
+msgid "Export Channel"
+msgstr "Eksporter kanal"
-#: ../../Zotlabs/Module/Settings/Privacy.php:73
-msgid "Anybody on the internet"
-msgstr "Enhver på Internett"
+#: ../../Zotlabs/Module/Uexport.php:173
+msgid "Export channel"
+msgstr "Eksporter kanal"
-#: ../../Zotlabs/Module/Settings/Privacy.php:83
+#: ../../Zotlabs/Module/Uexport.php:174
msgid ""
-"Advise: set to \"Anybody on the internet\" and use privacy groups to "
-"restrict access"
-msgstr ""
-
-#: ../../Zotlabs/Module/Settings/Privacy.php:122
-msgid "Privacy Settings"
-msgstr "Personverninnstillinger"
-
-#: ../../Zotlabs/Module/Settings/Privacy.php:127
-msgid "Advanced configuration"
+"This will export your identity and social graph into a file which can be "
+"used to import your channel to a new hub."
msgstr ""
-#: ../../Zotlabs/Module/Settings/Privacy.php:129
-msgid "Proceed with caution"
+#: ../../Zotlabs/Module/Uexport.php:177
+msgid "Export content"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Privacy.php:130
+#: ../../Zotlabs/Module/Uexport.php:178
msgid ""
-"Changing advanced configuration settings can impact your, and your contacts "
-"channels functionality and security."
+"This will export your posts, direct messages, articles and cards per month "
+"stored into a zip file per year. Months with no posts will be dismissed."
msgstr ""
+"Dette vil eksportere dine innlegg, direktemeldinger, artikler og kort, en "
+"fil for hver måned, pakket i en zip fil for hvert år. Måneder uten innhold "
+"blir ignorert."
-#: ../../Zotlabs/Module/Settings/Privacy.php:131
-msgid "Accept the risk and continue"
+#: ../../Zotlabs/Module/Uexport.php:180
+msgid "Export wikis"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Privacy.php:133
-msgid "Automatically approve new contacts"
-msgstr "Automatisk godkjenn nye kontakter"
-
-#: ../../Zotlabs/Module/Settings/Privacy.php:134
-msgid "Opt-out of search engine indexing"
-msgstr "Ikke la søkemotorer indeksere denne kanalen"
-
-#: ../../Zotlabs/Module/Settings/Privacy.php:135
-msgid "Group actor"
+#: ../../Zotlabs/Module/Uexport.php:181
+msgid "This will export your wikis and wiki pages."
msgstr ""
-#: ../../Zotlabs/Module/Settings/Privacy.php:135
-msgid "Allow this channel to act as a forum"
+#: ../../Zotlabs/Module/Uexport.php:183
+msgid "Export webpages"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Privacy.php:136
-msgid "Accept all messages which mention you"
-msgstr "Godta alle meldinger som nevner deg"
-
-#: ../../Zotlabs/Module/Settings/Privacy.php:136
-msgid "This setting bypasses normal permissions"
-msgstr "Denne instillingen overstyrer vanlig tilgangskontroll"
-
-#: ../../Zotlabs/Module/Settings/Privacy.php:137
-msgid "Accept unsolicited comments for moderation"
-msgstr "Godta kommentarer fra fremmede for moderering"
-
-#: ../../Zotlabs/Module/Settings/Privacy.php:137
-msgid "Otherwise they will be silently dropped"
-msgstr "Ellers vil de avvises uten varsel"
-
-#: ../../Zotlabs/Module/Settings/Privacy.php:138
-msgid "Enable OCAP access"
-msgstr "Slå på OCAP tilgangskontroll"
-
-#: ../../Zotlabs/Module/Settings/Privacy.php:138
-msgid "Grant limited posts the right to access linked private media"
-msgstr "Gi begrensede innlegg tilgang til private media som er lenket fra dem"
-
-#: ../../Zotlabs/Module/Settings/Display.php:126
-#, php-format
-msgid "%s - (Experimental)"
-msgstr "%s - (Eksperimentelt)"
-
-#: ../../Zotlabs/Module/Settings/Display.php:183
-msgid "Display Settings"
-msgstr "Visningsinnstillinger"
-
-#: ../../Zotlabs/Module/Settings/Display.php:184
-msgid "Theme Settings"
-msgstr "Temainnstillinger"
-
-#: ../../Zotlabs/Module/Settings/Display.php:185
-msgid "Custom Theme Settings"
-msgstr "Tilpassede temainnstillinger"
-
-#: ../../Zotlabs/Module/Settings/Display.php:186
-msgid "Content Settings"
-msgstr "Innholdsinnstillinger"
-
-#: ../../Zotlabs/Module/Settings/Display.php:192
-msgid "Display Theme:"
-msgstr "Visningstema:"
-
-#: ../../Zotlabs/Module/Settings/Display.php:193
-msgid "Select scheme"
-msgstr "Velg skjema"
-
-#: ../../Zotlabs/Module/Settings/Display.php:195
-msgid "Preload images before rendering the page"
-msgstr "Last inn bildene før gjengivelsen av siden"
-
-#: ../../Zotlabs/Module/Settings/Display.php:195
-msgid ""
-"The subjective page load time will be longer but the page will be ready when "
-"displayed"
+#: ../../Zotlabs/Module/Uexport.php:184
+msgid "This will export your webpages and menus."
msgstr ""
-"Den personlige opplevelsen av lastetiden vil være lenger, men siden vil være "
-"klar når den vises"
-
-#: ../../Zotlabs/Module/Settings/Display.php:196
-msgid "Enable user zoom on mobile devices"
-msgstr "Skru på brukerstyrt zoom på mobile enheter"
-
-#: ../../Zotlabs/Module/Settings/Display.php:197
-msgid "Update browser every xx seconds"
-msgstr "Oppdater nettleser hvert xx sekunder"
-
-#: ../../Zotlabs/Module/Settings/Display.php:197
-msgid "Minimum of 10 seconds, no maximum"
-msgstr "Minimum 10 sekunder, ikke noe maksimum"
-
-#: ../../Zotlabs/Module/Settings/Display.php:198
-msgid "Maximum number of conversations to load at any time:"
-msgstr "Maksimalt antall samtaler å laste samtidig:"
-#: ../../Zotlabs/Module/Settings/Display.php:198
-msgid "Maximum of 30 items"
+#: ../../Zotlabs/Module/Uexport.php:186
+msgid "Export channel calendar"
msgstr ""
-#: ../../Zotlabs/Module/Settings/Display.php:199
-msgid "Show emoticons (smilies) as images"
-msgstr "Vis emoticons (smilefjes) som bilder"
-
-#: ../../Zotlabs/Module/Settings/Display.php:200
-msgid "Link post titles to source"
-msgstr "Lenk innleggets tittel til kilden"
-
-#: ../../Zotlabs/Module/Settings/Display.php:202
-#: ../../Zotlabs/Widget/Newmember.php:82
-msgid "New Member Links"
-msgstr "Lenker for nye medlemmer"
-
-#: ../../Zotlabs/Module/Settings/Display.php:202
-msgid "Display new member quick links menu"
-msgstr "Vis hurtiglenker for nye medlemmer"
-
-#: ../../Zotlabs/Module/Settings/Manage.php:41
-msgid "Channel Manager Settings"
+#: ../../Zotlabs/Module/Uexport.php:187
+msgid ""
+"This will export your channel calendar events and associated items. CalDAV "
+"calendars are not included."
msgstr ""
-#: ../../Zotlabs/Module/Apporder.php:47
-msgid "Change Order of Pinned Navbar Apps"
+#: ../../Zotlabs/Module/Uexport.php:189
+msgid "Export chatrooms"
msgstr ""
-#: ../../Zotlabs/Module/Apporder.php:47
-msgid "Change Order of App Tray Apps"
+#: ../../Zotlabs/Module/Uexport.php:190
+msgid "This will export your chatrooms. Chat history is dismissed."
msgstr ""
-#: ../../Zotlabs/Module/Apporder.php:48
+#: ../../Zotlabs/Module/Uexport.php:192
+#, php-format
msgid ""
-"Use arrows to move the corresponding app left (top) or right (bottom) in the "
-"navbar"
-msgstr ""
-
-#: ../../Zotlabs/Module/Apporder.php:48
-msgid "Use arrows to move the corresponding app up or down in the app tray"
+"This export can be imported or restored by visiting <a href=\"%1$s\">%2$s</"
+"a> on any site containing your channel."
msgstr ""
-#: ../../Zotlabs/Module/Like.php:111
+#: ../../Zotlabs/Module/Like.php:99
msgid "Like/Dislike"
msgstr "Liker/Liker ikke"
-#: ../../Zotlabs/Module/Like.php:117
+#: ../../Zotlabs/Module/Like.php:105
msgid "This action is restricted to members."
msgstr "Denne handlingen er begrenset til medlemmer."
-#: ../../Zotlabs/Module/Like.php:118
+#: ../../Zotlabs/Module/Like.php:106
msgid ""
"Please <a href=\"rmagic\">login with your $Projectname ID</a> or <a "
"href=\"register\">register as a new $Projectname member</a> to continue."
@@ -12007,3639 +12702,2821 @@ msgstr ""
"href=\"register\">registrer deg som et nytt $Projectname-medlem</a> for å "
"fortsette."
-#: ../../Zotlabs/Module/Like.php:171 ../../Zotlabs/Module/Like.php:197
-#: ../../Zotlabs/Module/Like.php:230
+#: ../../Zotlabs/Module/Like.php:159 ../../Zotlabs/Module/Like.php:185
+#: ../../Zotlabs/Module/Like.php:218
msgid "Invalid request."
msgstr "Ugyldig forespørsel."
-#: ../../Zotlabs/Module/Like.php:212
+#: ../../Zotlabs/Module/Like.php:200
msgid "thing"
msgstr "ting"
-#: ../../Zotlabs/Module/Like.php:253
+#: ../../Zotlabs/Module/Like.php:241
msgid "Channel unavailable."
msgstr "Kanalen er utilgjengelig."
-#: ../../Zotlabs/Module/Like.php:289
+#: ../../Zotlabs/Module/Like.php:277
msgid "Previous action reversed."
msgstr "Forrige handling er omgjort."
-#: ../../Zotlabs/Module/Like.php:453
+#: ../../Zotlabs/Module/Like.php:444
#, fuzzy
#| msgid "Profile"
msgid "profile"
msgstr "Profil"
-#: ../../Zotlabs/Module/Like.php:487
-#: ../../extend/addon/hzaddons/diaspora/Receiver.php:2248
-#, php-format
-msgid "%1$s is attending %2$s's %3$s"
-msgstr "%1$s deltar på %2$ss %3$s"
-
-#: ../../Zotlabs/Module/Like.php:489
-#: ../../extend/addon/hzaddons/diaspora/Receiver.php:2250
-#, php-format
-msgid "%1$s is not attending %2$s's %3$s"
-msgstr "%1$s deltar ikke på %2$ss %3$s"
-
-#: ../../Zotlabs/Module/Like.php:491
-#: ../../extend/addon/hzaddons/diaspora/Receiver.php:2252
-#, php-format
-msgid "%1$s may attend %2$s's %3$s"
-msgstr "%1$s deltar kanskje på %2$ss %3$s"
-
-#: ../../Zotlabs/Module/Like.php:615
+#: ../../Zotlabs/Module/Like.php:611
msgid "Action completed."
msgstr "Handling ferdig."
-#: ../../Zotlabs/Module/Like.php:616
+#: ../../Zotlabs/Module/Like.php:612
msgid "Thank you."
msgstr "Tusen takk."
-#: ../../Zotlabs/Module/Follow.php:75
-msgid "Connection added."
-msgstr ""
-
-#: ../../Zotlabs/Module/Chatsvc.php:131
-msgid "Away"
-msgstr "Borte"
-
-#: ../../Zotlabs/Module/Chatsvc.php:136
-msgid "Online"
-msgstr "Online"
-
-#: ../../Zotlabs/Module/Admin.php:98
-msgid "Blocked accounts"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin.php:99
-msgid "Expired accounts"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin.php:100
-msgid "Expiring accounts"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin.php:124
-msgid "Message queues"
-msgstr "Meldingskøer"
-
-#: ../../Zotlabs/Module/Admin.php:138
-msgid "Your software should be updated"
-msgstr "Programvaren bør oppdateres"
-
-#: ../../Zotlabs/Module/Admin.php:143
-msgid "Summary"
-msgstr "Sammendrag"
-
-#: ../../Zotlabs/Module/Admin.php:146
-msgid "Registered accounts"
-msgstr "Registrerte kontoer"
-
-#: ../../Zotlabs/Module/Admin.php:147
-msgid "Pending registrations"
-msgstr "Ventende registreringer"
-
-#: ../../Zotlabs/Module/Admin.php:148
-msgid "Registered channels"
-msgstr "Registrerte kanaler"
-
-#: ../../Zotlabs/Module/Admin.php:150
-msgid "Version"
-msgstr "Versjon"
-
-#: ../../Zotlabs/Module/Admin.php:151
-msgid "Repository version (master)"
-msgstr ""
-
-#: ../../Zotlabs/Module/Admin.php:152
-msgid "Repository version (dev)"
-msgstr ""
-
-#: ../../Zotlabs/Module/Authorize.php:17
-msgid "Unknown App"
-msgstr ""
-
-#: ../../Zotlabs/Module/Authorize.php:29
-msgid "Authorize"
-msgstr ""
-
-#: ../../Zotlabs/Module/Authorize.php:30
-#, php-format
-msgid "Do you authorize the app %s to access your channel data?"
-msgstr ""
-
-#: ../../Zotlabs/Module/Go.php:22
-msgid "This page is available only to site members"
-msgstr ""
-
-#: ../../Zotlabs/Module/Go.php:28
-#: ../../Zotlabs/Widget/Channel_activities.php:29
-msgid "Welcome"
-msgstr "Velkommen"
-
-#: ../../Zotlabs/Module/Go.php:30
-msgid "What would you like to do?"
-msgstr ""
-
-#: ../../Zotlabs/Module/Go.php:32
-msgid ""
-"Please bookmark this page if you would like to return to it in the future"
-msgstr ""
-
-#: ../../Zotlabs/Module/Go.php:36
-msgid "Upload a profile photo"
-msgstr ""
-
-#: ../../Zotlabs/Module/Go.php:37
-msgid "Upload a cover photo"
-msgstr ""
-
-#: ../../Zotlabs/Module/Go.php:38
-msgid "Edit your default profile"
-msgstr ""
-
-#: ../../Zotlabs/Module/Go.php:39 ../../Zotlabs/Widget/Newmember.php:48
-msgid "View friend suggestions"
-msgstr "Vis venneforslag"
-
-#: ../../Zotlabs/Module/Go.php:40
-msgid "View the channel directory"
-msgstr ""
-
-#: ../../Zotlabs/Module/Go.php:41
-msgid "View/edit your channel settings"
-msgstr ""
-
-#: ../../Zotlabs/Module/Go.php:42
-msgid "View the site or project documentation"
-msgstr ""
-
-#: ../../Zotlabs/Module/Go.php:43
-msgid "Visit your channel homepage"
-msgstr ""
-
-#: ../../Zotlabs/Module/Go.php:44
-msgid ""
-"View your connections and/or add somebody whose address you already know"
-msgstr ""
-
-#: ../../Zotlabs/Module/Go.php:45
-msgid ""
-"View your personal stream (this may be empty until you add some connections)"
-msgstr ""
+#: ../../Zotlabs/Module/Blocks.php:97 ../../Zotlabs/Module/Blocks.php:153
+#: ../../Zotlabs/Module/Editblock.php:113
+msgid "Block Name"
+msgstr "Byggeklossens navn"
-#: ../../Zotlabs/Module/Go.php:53
-msgid "View the public stream. Warning: this content is not moderated"
-msgstr ""
+#: ../../Zotlabs/Module/Blocks.php:154
+msgid "Block Title"
+msgstr "Byggeklossens tittel"
-#: ../../Zotlabs/Module/Manage.php:137
-msgid "Create a new channel"
-msgstr "Lag en ny kanal"
+#: ../../Zotlabs/Module/Mitem.php:63
+msgid "Unable to create element."
+msgstr "Klarer ikke å lage element."
-#: ../../Zotlabs/Module/Manage.php:163
-msgid "Current Channel"
-msgstr "Gjeldende kanal"
+#: ../../Zotlabs/Module/Mitem.php:87
+msgid "Unable to update menu element."
+msgstr "Ikke i stand til å oppdatere menyelement."
-#: ../../Zotlabs/Module/Manage.php:165
-msgid "Switch to one of your channels by selecting it."
-msgstr "Bytt til en av dine kanaler ved å velge den."
+#: ../../Zotlabs/Module/Mitem.php:103
+msgid "Unable to add menu element."
+msgstr "Ikke i stand til å legge til menyelement."
-#: ../../Zotlabs/Module/Manage.php:166
-msgid "Default Channel"
-msgstr "Standardkanal"
+#: ../../Zotlabs/Module/Mitem.php:167 ../../Zotlabs/Module/Mitem.php:244
+msgid "Menu Item Permissions"
+msgstr "Menyelement Tillatelser"
-#: ../../Zotlabs/Module/Manage.php:167
-msgid "Make Default"
-msgstr "Gjør til standard"
+#: ../../Zotlabs/Module/Mitem.php:168 ../../Zotlabs/Module/Mitem.php:245
+msgid "(click to open/close)"
+msgstr "(klikk for å åpne/lukke)"
-#: ../../Zotlabs/Module/Manage.php:170
-#, php-format
-msgid "%d new introductions"
-msgstr "%d nye introduksjoner"
+#: ../../Zotlabs/Module/Mitem.php:174 ../../Zotlabs/Module/Mitem.php:191
+msgid "Link Name"
+msgstr "Lenkenavn"
-#: ../../Zotlabs/Module/Manage.php:172
-msgid "Delegated Channel"
-msgstr "Delegert kanal"
+#: ../../Zotlabs/Module/Mitem.php:175 ../../Zotlabs/Module/Mitem.php:253
+msgid "Link or Submenu Target"
+msgstr "Lenke- eller undermeny-mål"
-#: ../../Zotlabs/Module/Search.php:251
-#, php-format
-msgid "Items tagged with: %s"
-msgstr "Elementer merket med: %s"
+#: ../../Zotlabs/Module/Mitem.php:175
+msgid "Enter URL of the link or select a menu name to create a submenu"
+msgstr "Skriv URL-en til lenken eller velg et menynavn for å lage en undermeny"
-#: ../../Zotlabs/Module/Search.php:253
-#, php-format
-msgid "Search results for: %s"
-msgstr "Søkeresultater for: %s"
+#: ../../Zotlabs/Module/Mitem.php:176 ../../Zotlabs/Module/Mitem.php:254
+msgid "Use magic-auth if available"
+msgstr "Bruk magic-autent hvis mulig"
-#: ../../Zotlabs/Module/Changeaddr.php:35
-msgid ""
-"Channel name changes are not allowed within 48 hours of changing the account "
-"password."
-msgstr ""
+#: ../../Zotlabs/Module/Mitem.php:177 ../../Zotlabs/Module/Mitem.php:255
+msgid "Open link in new window"
+msgstr "Åpne lenke i nytt vindu"
-#: ../../Zotlabs/Module/Changeaddr.php:77
-msgid "Change channel nickname/address"
-msgstr ""
+#: ../../Zotlabs/Module/Mitem.php:178 ../../Zotlabs/Module/Mitem.php:256
+msgid "Order in list"
+msgstr "Ordne i liste"
-#: ../../Zotlabs/Module/Changeaddr.php:78
-msgid "Any/all connections on other networks will be lost!"
-msgstr ""
+#: ../../Zotlabs/Module/Mitem.php:178 ../../Zotlabs/Module/Mitem.php:256
+msgid "Higher numbers will sink to bottom of listing"
+msgstr "Høyere tall vil synke mot bunnen av listen"
-#: ../../Zotlabs/Module/Changeaddr.php:80
-msgid "New channel address"
-msgstr ""
+#: ../../Zotlabs/Module/Mitem.php:179
+msgid "Submit and finish"
+msgstr "Send inn og avslutt"
-#: ../../Zotlabs/Module/Changeaddr.php:81
-msgid "Rename Channel"
-msgstr ""
+#: ../../Zotlabs/Module/Mitem.php:180
+msgid "Submit and continue"
+msgstr "Send inn og fortsett"
-#: ../../Zotlabs/Module/Cdav.php:819
-msgid "Calendar entries imported."
-msgstr "Kalenderhendelsene er importert."
+#: ../../Zotlabs/Module/Mitem.php:189
+msgid "Menu:"
+msgstr "Meny:"
-#: ../../Zotlabs/Module/Cdav.php:821
-msgid "No calendar entries found."
-msgstr "Ingen kalenderhendelser funnet."
+#: ../../Zotlabs/Module/Mitem.php:192
+msgid "Link Target"
+msgstr "Lenkemål"
-#: ../../Zotlabs/Module/Cdav.php:1000
-msgid "Event title"
-msgstr "Tittel på hendelse"
+#: ../../Zotlabs/Module/Mitem.php:195
+msgid "Edit menu"
+msgstr "Endre meny"
-#: ../../Zotlabs/Module/Cdav.php:1001
-msgid "Start date and time"
-msgstr "Startdato og tidspunkt"
+#: ../../Zotlabs/Module/Mitem.php:198
+msgid "Edit element"
+msgstr "Endre element"
-#: ../../Zotlabs/Module/Cdav.php:1002
-msgid "End date and time"
-msgstr ""
+#: ../../Zotlabs/Module/Mitem.php:199
+msgid "Drop element"
+msgstr "Slett element"
-#: ../../Zotlabs/Module/Cdav.php:1003
-#, fuzzy
-msgid "Timezone:"
-msgstr "Tidssone"
+#: ../../Zotlabs/Module/Mitem.php:200
+msgid "New element"
+msgstr "Nytt element"
-#: ../../Zotlabs/Module/Cdav.php:1029
-#, fuzzy
-msgid "Month"
-msgstr "måned"
+#: ../../Zotlabs/Module/Mitem.php:201
+msgid "Edit this menu container"
+msgstr "Endre denne menybeholderen"
-#: ../../Zotlabs/Module/Cdav.php:1030
-#, fuzzy
-msgid "Week"
-msgstr "uke"
+#: ../../Zotlabs/Module/Mitem.php:202
+msgid "Add menu element"
+msgstr "Legg til menyelement"
-#: ../../Zotlabs/Module/Cdav.php:1031
-#, fuzzy
-msgid "Day"
-msgstr "dag"
+#: ../../Zotlabs/Module/Mitem.php:203
+msgid "Delete this menu item"
+msgstr "Slett dette menyelementet"
-#: ../../Zotlabs/Module/Cdav.php:1032
-msgid "List month"
-msgstr ""
+#: ../../Zotlabs/Module/Mitem.php:204
+msgid "Edit this menu item"
+msgstr "Endre dette menyelementet"
-#: ../../Zotlabs/Module/Cdav.php:1033
-msgid "List week"
-msgstr ""
+#: ../../Zotlabs/Module/Mitem.php:220
+msgid "Menu item not found."
+msgstr "Menyelement ble ikke funnet."
-#: ../../Zotlabs/Module/Cdav.php:1034
-msgid "List day"
-msgstr ""
+#: ../../Zotlabs/Module/Mitem.php:233
+msgid "Menu item deleted."
+msgstr "Menyelement slettet."
-#: ../../Zotlabs/Module/Cdav.php:1042
-msgid "More"
-msgstr ""
+#: ../../Zotlabs/Module/Mitem.php:235
+msgid "Menu item could not be deleted."
+msgstr "Menyelement kunne ikke bli slettet."
-#: ../../Zotlabs/Module/Cdav.php:1043
-msgid "Less"
-msgstr ""
+#: ../../Zotlabs/Module/Mitem.php:242
+msgid "Edit Menu Element"
+msgstr "Endre menyelement"
-#: ../../Zotlabs/Module/Cdav.php:1045
-msgid "Select calendar"
-msgstr ""
+#: ../../Zotlabs/Module/Mitem.php:252
+msgid "Link text"
+msgstr "Lenketekst"
-#: ../../Zotlabs/Module/Cdav.php:1046 ../../Zotlabs/Widget/Cdav.php:149
-msgid "Channel Calendars"
+#: ../../Zotlabs/Module/Dircensor.php:61
+msgid "Entry censored"
msgstr ""
-#: ../../Zotlabs/Module/Cdav.php:1046 ../../Zotlabs/Widget/Cdav.php:135
-#: ../../Zotlabs/Widget/Cdav.php:149
-msgid "CalDAV Calendars"
+#: ../../Zotlabs/Module/Dircensor.php:64
+msgid "Entry OK"
msgstr ""
-#: ../../Zotlabs/Module/Cdav.php:1048
-msgid "Delete all"
+#: ../../Zotlabs/Module/Apporder.php:47
+msgid "Change Order of Pinned Navbar Apps"
msgstr ""
-#: ../../Zotlabs/Module/Cdav.php:1051
-msgid "Sorry! Editing of recurrent events is not yet implemented."
+#: ../../Zotlabs/Module/Apporder.php:47
+msgid "Change Order of App Tray Apps"
msgstr ""
-#: ../../Zotlabs/Module/Cdav.php:1052
+#: ../../Zotlabs/Module/Apporder.php:48
msgid ""
-"Could not fetch calendar resource. The selected calendar might be disabled."
-msgstr ""
-
-#: ../../Zotlabs/Module/Cdav.php:1452
-msgid "Default Calendar"
-msgstr ""
-
-#: ../../Zotlabs/Module/Cdav.php:1463
-msgid "Default Addressbook"
-msgstr ""
-
-#: ../../Zotlabs/Module/Contactedit.php:50
-msgid "Invalid abook_id"
-msgstr ""
-
-#: ../../Zotlabs/Module/Contactedit.php:381
-#, fuzzy
-msgid "View profile"
-msgstr "Vis profil"
-
-#: ../../Zotlabs/Module/Contactedit.php:394
-msgid "Select a role for this contact"
-msgstr ""
-
-#: ../../Zotlabs/Module/Contactedit.php:395
-#: ../../Zotlabs/Widget/Permcats.php:92
-msgid "Contact roles"
-msgstr ""
-
-#: ../../Zotlabs/Module/Contactedit.php:426
-msgid "Roles"
-msgstr ""
-
-#: ../../Zotlabs/Module/Contactedit.php:427
-msgid "Compare permissions"
+"Use arrows to move the corresponding app left (top) or right (bottom) in the "
+"navbar"
msgstr ""
-#: ../../Zotlabs/Module/Contactedit.php:428
-msgid "Permission"
+#: ../../Zotlabs/Module/Apporder.php:48
+msgid "Use arrows to move the corresponding app up or down in the app tray"
msgstr ""
-#: ../../Zotlabs/Module/Contactedit.php:429
-#: ../../Zotlabs/Widget/Privacygroups.php:54
-#, fuzzy
-msgid "Privacy groups"
-msgstr "Personverngrupper"
-
-#: ../../Zotlabs/Module/Contactedit.php:432
-msgid "Content filter"
+#: ../../Zotlabs/Module/New_channel.php:160
+msgid "Your real name is recommended."
msgstr ""
-#: ../../Zotlabs/Module/Contactedit.php:442
-msgid "Contact updated"
+#: ../../Zotlabs/Module/New_channel.php:161
+msgid ""
+"Examples: \"Bob Jameson\", \"Lisa and her Horses\", \"Soccer\", \"Aviation "
+"Group\""
msgstr ""
+"Eksempel: \"Ola Nordmann\", \"Lisa og hestene hennes\", \"Fotball\", "
+"\"Sykkelgruppa\""
-#: ../../Zotlabs/Module/Contactedit.php:442
-msgid "Contact update failed"
+#: ../../Zotlabs/Module/New_channel.php:166
+msgid ""
+"This will be used to create a unique network address (like an email address)."
msgstr ""
-#: ../../Zotlabs/Module/Contactedit.php:491
-#: ../../extend/addon/hzaddons/diaspora/diaspora.php:417
-#: ../../extend/addon/hzaddons/pubcrawl/pubcrawl.php:1085
-msgid "Refresh succeeded"
+#: ../../Zotlabs/Module/New_channel.php:168
+msgid "Allowed characters are a-z 0-9, - and _"
msgstr ""
-#: ../../Zotlabs/Module/Contactedit.php:494
-#: ../../extend/addon/hzaddons/diaspora/diaspora.php:412
-#: ../../extend/addon/hzaddons/pubcrawl/pubcrawl.php:1078
+#: ../../Zotlabs/Module/New_channel.php:176
#, fuzzy
-#| msgid "Refresh"
-msgid "Refresh failed"
-msgstr "Forny"
-
-#: ../../Zotlabs/Module/Contactedit.php:520
-msgid "Block status updated"
-msgstr ""
-
-#: ../../Zotlabs/Module/Contactedit.php:524
-msgid "Block failed"
-msgstr ""
-
-#: ../../Zotlabs/Module/Contactedit.php:535
-msgid "Ignore status updated"
-msgstr ""
-
-#: ../../Zotlabs/Module/Contactedit.php:539
-msgid "Ignore failed"
-msgstr ""
-
-#: ../../Zotlabs/Module/Contactedit.php:550
-msgid "Archive status updated"
-msgstr ""
+msgid "Channel name"
+msgstr "Kanalnavn"
-#: ../../Zotlabs/Module/Contactedit.php:554
-msgid "Archive failed"
-msgstr ""
+#: ../../Zotlabs/Module/New_channel.php:179
+#: ../../Zotlabs/Module/Settings/Channel.php:234
+msgid "Channel role"
+msgstr "Kanalrolle"
-#: ../../Zotlabs/Module/Contactedit.php:565
-msgid "Hide status updated"
+#: ../../Zotlabs/Module/New_channel.php:182
+msgid "Create a Channel"
msgstr ""
-#: ../../Zotlabs/Module/Contactedit.php:569
-msgid "Hide failed"
+#: ../../Zotlabs/Module/New_channel.php:183
+msgid ""
+"A channel is a unique network identity. It can represent a person (social "
+"network profile), a forum (group), a business or celebrity page, a newsfeed, "
+"and many other things."
msgstr ""
-#: ../../Zotlabs/Module/Contactedit.php:604
-msgid "Contact removed"
+#: ../../Zotlabs/Module/New_channel.php:184
+msgid ""
+"or <a href=\"import\">import an existing channel</a> from another location."
msgstr ""
+"eller <a href=\"import\">importer en eksisterende kanal</a> fra et annet "
+"sted."
-#: ../../Zotlabs/Module/Contactedit.php:608
-msgid "Delete failed"
+#: ../../Zotlabs/Module/New_channel.php:189
+msgid "Validate"
msgstr ""
-#: ../../Zotlabs/Module/Contactedit.php:618
-#: ../../Zotlabs/Widget/Affinity.php:60
-msgid "Refresh"
-msgstr "Forny"
+#: ../../Zotlabs/Module/Photos.php:84 ../../Zotlabs/Module/Photos.php:103
+msgid "Album not found."
+msgstr "Albumet ble ikke funnet."
-#: ../../Zotlabs/Module/Contactedit.php:619
-msgid "Refetch contact info"
-msgstr ""
+#: ../../Zotlabs/Module/Photos.php:93
+msgid "Delete Album"
+msgstr "Slett album"
-#: ../../Zotlabs/Module/Profile.php:106
-msgid "vcard"
-msgstr ""
+#: ../../Zotlabs/Module/Photos.php:165 ../../Zotlabs/Module/Photos.php:1058
+msgid "Delete Photo"
+msgstr "Slett bilde"
-#: ../../Zotlabs/Widget/Suggestions.php:58
-msgid "Suggestions"
-msgstr "Forslag"
+#: ../../Zotlabs/Module/Photos.php:522
+msgid "No photos selected"
+msgstr "Ingen bilder valgt"
-#: ../../Zotlabs/Widget/Suggestions.php:59
-msgid "See more..."
-msgstr "Se mer..."
+#: ../../Zotlabs/Module/Photos.php:573
+msgid "Access to this item is restricted."
+msgstr "Tilgang til dette elementet er begrenset."
-#: ../../Zotlabs/Widget/Pinned.php:95
-msgid "Share This"
-msgstr "Del dette"
+#: ../../Zotlabs/Module/Photos.php:616
+#, php-format
+msgid "%1$.2f MB photo storage used."
+msgstr "%1$.2f MB lagringsplass til bilder er brukt."
-#: ../../Zotlabs/Widget/Pinned.php:118 ../../Zotlabs/Widget/Pinned.php:119
+#: ../../Zotlabs/Module/Photos.php:620
#, php-format
-msgid "View %s's profile - %s"
-msgstr "Vis %s sin profil - %s"
+msgid "%1$.2f MB of %2$.2f MB photo storage used."
+msgstr "%1$.2f MB av %2$.2f MB lagringsplass til bilder er brukt."
-#: ../../Zotlabs/Widget/Pinned.php:152
-msgid "Don't show"
-msgstr "Ikke vis"
+#: ../../Zotlabs/Module/Photos.php:662
+msgid "Upload Photos"
+msgstr "Last opp bilder"
-#: ../../Zotlabs/Widget/Privacygroups.php:45
-msgid "Add new group"
-msgstr ""
+#: ../../Zotlabs/Module/Photos.php:666
+msgid "Enter an album name"
+msgstr "Skriv et albumnavn"
-#: ../../Zotlabs/Widget/Savedsearch.php:81
-msgid "Remove term"
-msgstr "Fjern begrep"
+#: ../../Zotlabs/Module/Photos.php:667
+msgid "or select an existing album (doubleclick)"
+msgstr "eller velg et eksisterende album (dobbeltklikk)"
-#: ../../Zotlabs/Widget/Photo_rand.php:63 ../../Zotlabs/Widget/Photo.php:54
-msgid "photo/image"
-msgstr "foto/bilde"
+#: ../../Zotlabs/Module/Photos.php:668
+msgid "Create a status post for this upload"
+msgstr "Lag et statusinnlegg for denne opplastingen"
-#: ../../Zotlabs/Widget/Notes.php:39
-msgid "Read mode"
-msgstr ""
+#: ../../Zotlabs/Module/Photos.php:670
+msgid "Description (optional)"
+msgstr "Beskrivelse (valgritt)"
-#: ../../Zotlabs/Widget/Notes.php:40
-msgid "Edit mode"
-msgstr ""
+#: ../../Zotlabs/Module/Photos.php:758
+msgid "Show Newest First"
+msgstr "Vis nyeste først"
-#: ../../Zotlabs/Widget/Notes.php:41
-msgid "Editing"
-msgstr ""
+#: ../../Zotlabs/Module/Photos.php:760
+msgid "Show Oldest First"
+msgstr "Vis eldste først"
-#: ../../Zotlabs/Widget/Notes.php:42
-msgid "Saving"
-msgstr ""
+#: ../../Zotlabs/Module/Photos.php:817 ../../Zotlabs/Module/Photos.php:1349
+msgid "Add Photos"
+msgstr "Legg til bilder"
-#: ../../Zotlabs/Widget/Notes.php:43
-msgid "Saved"
-msgstr ""
+#: ../../Zotlabs/Module/Photos.php:869
+msgid "Permission denied. Access to this item may be restricted."
+msgstr "Tillatelse avvist. Tilgang til dette elementet kan være begrenset."
-#: ../../Zotlabs/Widget/Suggestedchats.php:36
-msgid "Suggested Chatrooms"
-msgstr "Foreslåtte chatrom"
+#: ../../Zotlabs/Module/Photos.php:871
+msgid "Photo not available"
+msgstr "Bilde er utilgjengelig"
-#: ../../Zotlabs/Widget/Bookmarkedchats.php:25
-msgid "Bookmarked Chatrooms"
-msgstr "Bokmerkede chatrom"
+#: ../../Zotlabs/Module/Photos.php:929
+msgid "Use as profile photo"
+msgstr "Bruk som profilbilde"
-#: ../../Zotlabs/Widget/Tasklist.php:31
-msgid "Tasks"
-msgstr "Oppgaver"
+#: ../../Zotlabs/Module/Photos.php:930
+msgid "Use as cover photo"
+msgstr "Bruk som omslagsbilde"
-#: ../../Zotlabs/Widget/Messages.php:32
-msgid "Public and restricted messages"
-msgstr ""
+#: ../../Zotlabs/Module/Photos.php:937
+msgid "Private Photo"
+msgstr "Privat bilde"
-#: ../../Zotlabs/Widget/Messages.php:33
-msgid "Direct messages"
-msgstr "Direktemeldinger"
+#: ../../Zotlabs/Module/Photos.php:952
+msgid "View Full Size"
+msgstr "Vis i full størrelse"
-#: ../../Zotlabs/Widget/Messages.php:34
-msgid "Starred messages"
-msgstr ""
+#: ../../Zotlabs/Module/Photos.php:1032
+msgid "Edit photo"
+msgstr "Endre bilde"
-#: ../../Zotlabs/Widget/Messages.php:35
-#: ../../Zotlabs/Widget/Notifications.php:116
-#: ../../Zotlabs/Widget/Notifications.php:117
-msgid "Notices"
-msgstr "Varsel"
+#: ../../Zotlabs/Module/Photos.php:1034
+msgid "Rotate CW (right)"
+msgstr "Roter med klokka (mot høyre)"
-#: ../../Zotlabs/Widget/Messages.php:37
-msgid "No messages"
-msgstr "Ingen meldinger"
+#: ../../Zotlabs/Module/Photos.php:1035
+msgid "Rotate CCW (left)"
+msgstr "Roter mot klokka (venstre)"
-#: ../../Zotlabs/Widget/Messages.php:38
-msgid "Unseen"
-msgstr "Ulest"
+#: ../../Zotlabs/Module/Photos.php:1038
+msgid "Move photo to album"
+msgstr ""
-#: ../../Zotlabs/Widget/Messages.php:39
-#: ../../Zotlabs/Widget/Notifications.php:34
-#: ../../Zotlabs/Widget/Notifications.php:54
-#: ../../Zotlabs/Widget/Notifications.php:73
-#: ../../Zotlabs/Widget/Notifications.php:134
-#: ../../Zotlabs/Widget/Notifications.php:167
-msgid "Filter by name or address"
-msgstr "Filtrer etter navn eller adresse"
+#: ../../Zotlabs/Module/Photos.php:1039
+msgid "Enter a new album name"
+msgstr "Skriv et nytt albumnavn"
-#: ../../Zotlabs/Widget/Cover_photo.php:74
-msgid "Click to show more"
-msgstr ""
+#: ../../Zotlabs/Module/Photos.php:1040
+msgid "or select an existing one (doubleclick)"
+msgstr "eller velg et eksisterende album (dobbeltklikk)"
-#: ../../Zotlabs/Widget/Activity.php:55
-#, fuzzy
-msgctxt "widget"
-msgid "Activity"
-msgstr "aktivitet"
+#: ../../Zotlabs/Module/Photos.php:1045
+msgid "Add a Tag"
+msgstr "Legg til merkelapp"
-#: ../../Zotlabs/Widget/Appstore.php:16
-msgid "App Collections"
-msgstr "Appsamlinger"
+#: ../../Zotlabs/Module/Photos.php:1053
+msgid "Example: @bob, @Barbara_Jensen, @jim@example.com"
+msgstr "Eksempel: @bob, @Barbara_Jensen, @jim@example.com"
-#: ../../Zotlabs/Widget/Appstore.php:18
-msgid "Installed apps"
-msgstr "Installerte apper"
+#: ../../Zotlabs/Module/Photos.php:1056
+msgid "Flag as adult in album view"
+msgstr "Flag som voksent i albumvisning"
-#: ../../Zotlabs/Widget/Hq_controls.php:23
-msgid "Toggle post editor"
-msgstr "Vis redigering av innlegg"
+#: ../../Zotlabs/Module/Photos.php:1075
+msgid "I like this (toggle)"
+msgstr "Jeg liker dette (skru av og på)"
-#: ../../Zotlabs/Widget/Hq_controls.php:33
-msgid "Toggle personal notes"
-msgstr ""
+#: ../../Zotlabs/Module/Photos.php:1076
+msgid "I don't like this (toggle)"
+msgstr "Jeg liker ikke dette (skru av og på)"
-#: ../../Zotlabs/Widget/Hq_controls.php:43
-msgid "Channel activities"
-msgstr ""
+#: ../../Zotlabs/Module/Photos.php:1232
+msgid "Photo Tools"
+msgstr "Fotoverktøy"
-#: ../../Zotlabs/Widget/Archive.php:49
-msgid "Archives"
-msgstr "Arkiv"
+#: ../../Zotlabs/Module/Photos.php:1241
+msgid "In This Photo:"
+msgstr "I dette bildet:"
-#: ../../Zotlabs/Widget/Channel_activities.php:46
-#, fuzzy
-#| msgid "Recent activity"
-msgid "No recent activities"
-msgstr "Nylig aktivitet"
+#: ../../Zotlabs/Module/Photos.php:1246
+msgid "Map"
+msgstr "Kart"
-#: ../../Zotlabs/Widget/Channel_activities.php:214
+#: ../../Zotlabs/Module/Photos.php:1254
msgctxt "noun"
-msgid "new connection"
-msgid_plural "new connections"
-msgstr[0] "ny forbindelse"
-msgstr[1] "nye forbindelser"
+msgid "Likes"
+msgstr "Liker"
-#: ../../Zotlabs/Widget/Channel_activities.php:220
+#: ../../Zotlabs/Module/Photos.php:1255
msgctxt "noun"
-msgid "notice"
-msgid_plural "notices"
-msgstr[0] "varsel"
-msgstr[1] "varsel"
+msgid "Dislikes"
+msgstr "Liker ikke"
-#: ../../Zotlabs/Widget/Settings_menu.php:37
-msgid "Account settings"
-msgstr "Kontoinnstillinger"
+#: ../../Zotlabs/Module/Ochannel.php:32 ../../Zotlabs/Module/Chat.php:29
+msgid "You must be logged in to see this page."
+msgstr "Du må være innloegget for å se denne siden."
-#: ../../Zotlabs/Widget/Settings_menu.php:43
-msgid "Channel settings"
-msgstr "Kanalinnstillinger"
+#: ../../Zotlabs/Module/Hcard.php:37 ../../Zotlabs/Module/Channel.php:150
+#: ../../Zotlabs/Module/Profile.php:62
+msgid "Posts and comments"
+msgstr "Innlegg og kommentarer"
-#: ../../Zotlabs/Widget/Settings_menu.php:49
-msgid "Privacy settings"
-msgstr "Personverninnstillinger"
+#: ../../Zotlabs/Module/Hcard.php:44 ../../Zotlabs/Module/Channel.php:157
+#: ../../Zotlabs/Module/Profile.php:69
+msgid "Only posts"
+msgstr "Kun innlegg"
-#: ../../Zotlabs/Widget/Settings_menu.php:56
-msgid "Display settings"
-msgstr "Visningsinnstillinger"
+#: ../../Zotlabs/Module/Channel.php:231
+msgid "Insufficient permissions. Request redirected to profile page."
+msgstr "Utilstrekkelig tillatelse. Forespørsel omdirigert til profilsiden."
-#: ../../Zotlabs/Widget/Settings_menu.php:63
-msgid "Manage locations"
+#: ../../Zotlabs/Module/Profile.php:106
+msgid "vcard"
msgstr ""
-#: ../../Zotlabs/Widget/Rating.php:59
-msgid "Rating Tools"
-msgstr "Vurderingsverktøy"
-
-#: ../../Zotlabs/Widget/Rating.php:63 ../../Zotlabs/Widget/Rating.php:65
-msgid "Rate Me"
-msgstr "Vurder meg"
-
-#: ../../Zotlabs/Widget/Rating.php:68
-msgid "View Ratings"
-msgstr "Vis vurderinger"
-
-#: ../../Zotlabs/Widget/Appcategories.php:49
-msgid "App Categories"
-msgstr "Appkategorier"
-
-#: ../../Zotlabs/Widget/Activity_filter.php:44
-#: ../../Zotlabs/Widget/Notifications.php:62
-msgid "Direct Messages"
-msgstr "Direktemeldinger"
-
-#: ../../Zotlabs/Widget/Activity_filter.php:48
-msgid "Show direct (private) messages"
-msgstr "Vis direktemeldinger (private meldinger)"
-
-#: ../../Zotlabs/Widget/Activity_filter.php:53
-#: ../../Zotlabs/Widget/Notifications.php:81
-msgid "Events"
-msgstr "Hendelser"
-
-#: ../../Zotlabs/Widget/Activity_filter.php:57
-msgid "Show posts that include events"
-msgstr ""
+#: ../../Zotlabs/Module/Moderate.php:70
+#, fuzzy
+#| msgid "Account approved."
+msgid "Item approved"
+msgstr "Konto godkjent."
-#: ../../Zotlabs/Widget/Activity_filter.php:63
-msgid "Polls"
-msgstr "Spørreskjema"
+#: ../../Zotlabs/Module/Sources.php:41
+msgid "Failed to create source. No channel selected."
+msgstr "Mislyktes med å lage kilde. Ingen kanal er valgt."
-#: ../../Zotlabs/Widget/Activity_filter.php:67
-msgid "Show posts that include polls"
-msgstr "Vis innlegg som inneholder spørreskjema"
+#: ../../Zotlabs/Module/Sources.php:57
+msgid "Source created."
+msgstr "Kilden er laget."
-#: ../../Zotlabs/Widget/Activity_filter.php:90
-#, php-format
-msgid "Show posts related to the %s privacy group"
-msgstr ""
+#: ../../Zotlabs/Module/Sources.php:70
+msgid "Source updated."
+msgstr "Kilden er oppdatert."
-#: ../../Zotlabs/Widget/Activity_filter.php:99
-msgid "Show my privacy groups"
-msgstr ""
+#: ../../Zotlabs/Module/Sources.php:99
+msgid "*"
+msgstr "*"
-#: ../../Zotlabs/Widget/Activity_filter.php:123
-msgid "Show posts to this forum"
-msgstr ""
+#: ../../Zotlabs/Module/Sources.php:106
+msgid "Manage remote sources of content for your channel."
+msgstr "Håndtere eksterne innholdskilder til din kanal."
-#: ../../Zotlabs/Widget/Activity_filter.php:134
-msgid "Show forums"
-msgstr ""
+#: ../../Zotlabs/Module/Sources.php:107 ../../Zotlabs/Module/Sources.php:117
+msgid "New Source"
+msgstr "Ny kilde"
-#: ../../Zotlabs/Widget/Activity_filter.php:148
-msgid "Starred Posts"
+#: ../../Zotlabs/Module/Sources.php:118 ../../Zotlabs/Module/Sources.php:152
+msgid ""
+"Import all or selected content from the following channel into this channel "
+"and distribute it according to your channel settings."
msgstr ""
+"Importer alt eller et utvalgt av innhold fra følgende kanal inn i denne "
+"kanalen og distribuer det i henhold til dine egne kanalinnstillinger."
-#: ../../Zotlabs/Widget/Activity_filter.php:152
-msgid "Show posts that I have starred"
-msgstr ""
+#: ../../Zotlabs/Module/Sources.php:119 ../../Zotlabs/Module/Sources.php:153
+msgid "Only import content with these words (one per line)"
+msgstr "Bare importer innhold med disse ordene (ett ord per linje)"
-#: ../../Zotlabs/Widget/Activity_filter.php:163
-msgid "Personal Posts"
-msgstr ""
+#: ../../Zotlabs/Module/Sources.php:119 ../../Zotlabs/Module/Sources.php:153
+msgid "Leave blank to import all public content"
+msgstr "La stå tomt for å importere alt offentlig innhold"
-#: ../../Zotlabs/Widget/Activity_filter.php:167
-msgid "Show posts that mention or involve me"
-msgstr ""
+#: ../../Zotlabs/Module/Sources.php:120 ../../Zotlabs/Module/Sources.php:159
+msgid "Channel Name"
+msgstr "Kanalnavn"
-#: ../../Zotlabs/Widget/Activity_filter.php:190
-#, php-format
-msgid "Show posts that I have filed to %s"
+#: ../../Zotlabs/Module/Sources.php:121 ../../Zotlabs/Module/Sources.php:156
+msgid ""
+"Add the following categories to posts imported from this source (comma "
+"separated)"
msgstr ""
-#: ../../Zotlabs/Widget/Activity_filter.php:200
-msgid "Show filed post categories"
+#: ../../Zotlabs/Module/Sources.php:122 ../../Zotlabs/Module/Sources.php:157
+msgid "Resend posts with this channel as author"
msgstr ""
-#: ../../Zotlabs/Widget/Activity_filter.php:214
-msgid "Panel search"
+#: ../../Zotlabs/Module/Sources.php:122 ../../Zotlabs/Module/Sources.php:157
+msgid "Copyrights may apply"
msgstr ""
-#: ../../Zotlabs/Widget/Activity_filter.php:224
-msgid "Filter by name"
-msgstr "Filtrer etter navn"
+#: ../../Zotlabs/Module/Sources.php:142 ../../Zotlabs/Module/Sources.php:172
+msgid "Source not found."
+msgstr "Kilden ble ikke funnet."
-#: ../../Zotlabs/Widget/Activity_filter.php:239
-msgid "Remove active filter"
-msgstr ""
+#: ../../Zotlabs/Module/Sources.php:149
+msgid "Edit Source"
+msgstr "Endre kilde"
-#: ../../Zotlabs/Widget/Activity_filter.php:255
-msgid "Stream Filters"
-msgstr "Filtere for tidslinjen"
+#: ../../Zotlabs/Module/Sources.php:150
+msgid "Delete Source"
+msgstr "Slett kilde"
-#: ../../Zotlabs/Widget/Notifications.php:24
-msgid "New network activity notifications"
-msgstr ""
+#: ../../Zotlabs/Module/Sources.php:180
+msgid "Source removed"
+msgstr "Kilden er fjernet"
-#: ../../Zotlabs/Widget/Notifications.php:27
-msgid "Network stream"
-msgstr ""
+#: ../../Zotlabs/Module/Sources.php:182
+msgid "Unable to remove source."
+msgstr "Ikke i stand til å fjerne kilde."
-#: ../../Zotlabs/Widget/Notifications.php:30
-#: ../../Zotlabs/Widget/Notifications.php:69
-msgid "Mark all notifications read"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:23 ../../Zotlabs/Module/Profiles.php:211
+#: ../../Zotlabs/Module/Profiles.php:641
+msgid "Profile not found."
+msgstr "Profilen ble ikke funnet."
-#: ../../Zotlabs/Widget/Notifications.php:33
-#: ../../Zotlabs/Widget/Notifications.php:53
-#: ../../Zotlabs/Widget/Notifications.php:72
-#: ../../Zotlabs/Widget/Notifications.php:166
-msgid "Show new posts only"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:43
+msgid "Profile deleted."
+msgstr "Profilen er slettet."
-#: ../../Zotlabs/Widget/Notifications.php:44
-msgid "New home activity notifications"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:67 ../../Zotlabs/Module/Profiles.php:104
+msgid "Profile-"
+msgstr "Profil-"
-#: ../../Zotlabs/Widget/Notifications.php:47
-msgid "Home stream"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:89 ../../Zotlabs/Module/Profiles.php:126
+msgid "New profile created."
+msgstr "Ny profil opprettet."
-#: ../../Zotlabs/Widget/Notifications.php:50
-msgid "Mark all notifications seen"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:110
+msgid "Profile unavailable to clone."
+msgstr "Profilen er utilgjengelig for klonen."
-#: ../../Zotlabs/Widget/Notifications.php:63
-msgid "New direct messages notifications"
-msgstr "Varsel om nye direktemeldinger"
+#: ../../Zotlabs/Module/Profiles.php:145
+msgid "Profile unavailable to export."
+msgstr "Profilen er utilgjengelig for eksport."
-#: ../../Zotlabs/Widget/Notifications.php:66
-msgid "Direct messages stream"
-msgstr "Strøm for direktemeldinger"
+#: ../../Zotlabs/Module/Profiles.php:221
+msgid "Profile Name is required."
+msgstr "Profilnavn er påkrevd."
-#: ../../Zotlabs/Widget/Notifications.php:82
-msgid "New events notifications"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:426
+msgid "Marital Status"
+msgstr "Sivilstand"
-#: ../../Zotlabs/Widget/Notifications.php:85
-msgid "View events"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:430
+msgid "Romantic Partner"
+msgstr "Romantisk partner"
-#: ../../Zotlabs/Widget/Notifications.php:88
-msgid "Mark all events seen"
-msgstr "Merk alle hendelser som sett"
+#: ../../Zotlabs/Module/Profiles.php:434 ../../Zotlabs/Module/Profiles.php:787
+msgid "Likes"
+msgstr "Liker"
-#: ../../Zotlabs/Widget/Notifications.php:97
-msgid "New connections notifications"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:438 ../../Zotlabs/Module/Profiles.php:788
+msgid "Dislikes"
+msgstr "Liker ikke"
-#: ../../Zotlabs/Widget/Notifications.php:100
-msgid "View all connections"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:442 ../../Zotlabs/Module/Profiles.php:795
+msgid "Work/Employment"
+msgstr "Arbeid/sysselsetting"
-#: ../../Zotlabs/Widget/Notifications.php:109
-msgid "New files notifications"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:445
+msgid "Religion"
+msgstr "Religion"
-#: ../../Zotlabs/Widget/Notifications.php:120
-msgid "View all notices"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:449
+msgid "Political Views"
+msgstr "Politiske synspunkter"
-#: ../../Zotlabs/Widget/Notifications.php:123
-msgid "Mark all notices seen"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:457
+msgid "Sexual Preference"
+msgstr "Seksuelle preferanser"
-#: ../../Zotlabs/Widget/Notifications.php:144
-msgid "Registrations"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:461
+msgid "Homepage"
+msgstr "Hjemmeside"
-#: ../../Zotlabs/Widget/Notifications.php:145
-msgid "New registrations notifications"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:465
+msgid "Interests"
+msgstr "Interesser"
-#: ../../Zotlabs/Widget/Notifications.php:155
-msgid "New public stream notifications"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:573
+msgid "Profile updated."
+msgstr "Profilen er oppdatert."
-#: ../../Zotlabs/Widget/Notifications.php:158
-msgid "Public stream"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:671
+msgid "Hide my connections from viewers of this profile"
+msgstr "Skjul mine forbindelser fra besøkende som ser denne profilen"
-#: ../../Zotlabs/Widget/Notifications.php:174
-msgid "Sorry, you have got no notifications at the moment"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:684
+msgid "Publish my default profile in the network directory"
+msgstr "La standardprofilen min vises i nettverkskatalogen"
-#: ../../Zotlabs/Widget/Tokens.php:41
-msgid "Add new guest"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:692
+msgid "Suggest me as a potential contact to new members"
+msgstr "Foreslå meg som mulig kontakt til nye medlemmer"
-#: ../../Zotlabs/Widget/Permcats.php:43
-msgid "Add new role"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:696
+msgid "Reveal my online status"
+msgstr "La andre se om jeg er pålogget eller ikke"
-#: ../../Zotlabs/Widget/Permcats.php:93
-msgid "Role members"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:737
+msgid "Edit Profile Details"
+msgstr "Endre profildetaljer"
-#: ../../Zotlabs/Widget/Newmember.php:38
-msgid "Profile Creation"
-msgstr "Oppretting av profil"
+#: ../../Zotlabs/Module/Profiles.php:739
+msgid "View this profile"
+msgstr "Vis denne profilen"
-#: ../../Zotlabs/Widget/Newmember.php:40
-msgid "Upload profile photo"
-msgstr "Last opp profilbilde"
+#: ../../Zotlabs/Module/Profiles.php:741
+msgid "Profile Tools"
+msgstr "Profilverktøy"
-#: ../../Zotlabs/Widget/Newmember.php:41
-msgid "Upload cover photo"
-msgstr "Last opp omslagsbilde"
+#: ../../Zotlabs/Module/Profiles.php:742
+msgid "Change cover photo"
+msgstr "Endre omslagsbilde"
-#: ../../Zotlabs/Widget/Newmember.php:45
-msgid "Find and Connect with others"
-msgstr "Finn andre"
+#: ../../Zotlabs/Module/Profiles.php:744
+msgid "Create a new profile using these settings"
+msgstr "Lag en ny profil ved å bruke disse innstillingene"
-#: ../../Zotlabs/Widget/Newmember.php:47
-msgid "View the directory"
-msgstr "Se i katalogen"
+#: ../../Zotlabs/Module/Profiles.php:745
+msgid "Clone this profile"
+msgstr "Klon denne profilen"
-#: ../../Zotlabs/Widget/Newmember.php:49
-msgid "Manage your connections"
-msgstr "Behandle forbindelser"
+#: ../../Zotlabs/Module/Profiles.php:746
+msgid "Delete this profile"
+msgstr "Slett denne profilen"
-#: ../../Zotlabs/Widget/Newmember.php:52
-msgid "Communicate"
-msgstr "Kommuniser"
+#: ../../Zotlabs/Module/Profiles.php:747
+msgid "Add profile things"
+msgstr "Legg til profilting"
-#: ../../Zotlabs/Widget/Newmember.php:54
-msgid "View your channel homepage"
-msgstr "Vis kanalens hjemmeside"
+#: ../../Zotlabs/Module/Profiles.php:748
+msgid "Basic"
+msgstr "Grunnleggende"
-#: ../../Zotlabs/Widget/Newmember.php:55
-msgid "View your network stream"
-msgstr "Vis nettverksstrømmen"
+#: ../../Zotlabs/Module/Profiles.php:750
+msgid "Relationship"
+msgstr "Forhold"
-#: ../../Zotlabs/Widget/Newmember.php:61
-msgid "Documentation"
-msgstr "Dokumentasjon"
+#: ../../Zotlabs/Module/Profiles.php:753
+msgid "Import profile from file"
+msgstr "Importer profil fra fil"
-#: ../../Zotlabs/Widget/Newmember.php:64
-msgid "Missing Features?"
-msgstr "Noe som mangler?"
+#: ../../Zotlabs/Module/Profiles.php:754
+msgid "Export profile to file"
+msgstr "Eksporter profil til fil"
-#: ../../Zotlabs/Widget/Newmember.php:66
-msgid "Pin apps to navigation bar"
-msgstr "Fest apper til navigasjonslinjen"
+#: ../../Zotlabs/Module/Profiles.php:755
+msgid "Your gender"
+msgstr "Kjønn"
-#: ../../Zotlabs/Widget/Newmember.php:67
-msgid "Install more apps"
-msgstr "Legg til flere apper"
+#: ../../Zotlabs/Module/Profiles.php:756
+msgid "Marital status"
+msgstr "Sivilstatus"
-#: ../../Zotlabs/Widget/Newmember.php:78
-msgid "View public stream"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:757
+msgid "Sexual preference"
+msgstr "Legning"
-#: ../../Zotlabs/Widget/Follow.php:34
-msgid "Add New Connection"
-msgstr "Legg til ny forbindelse"
+#: ../../Zotlabs/Module/Profiles.php:760
+msgid "Profile name"
+msgstr "Profilnavn"
-#: ../../Zotlabs/Widget/Follow.php:35
-msgid "Enter channel address"
-msgstr "Skriv kanaladressen"
+#: ../../Zotlabs/Module/Profiles.php:762
+msgid "This is your default profile."
+msgstr "Dette er din standardprofil."
-#: ../../Zotlabs/Widget/Follow.php:36
-msgid "Examples: bob@example.com, https://example.com/barbara"
-msgstr "Eksempel: ola@eksempel.no, https://eksempel.no/kari"
+#: ../../Zotlabs/Module/Profiles.php:764
+msgid "Your full name"
+msgstr "Fullt navn"
-#: ../../Zotlabs/Widget/Admin.php:28 ../../Zotlabs/Widget/Admin.php:66
-msgid "Member registrations waiting for confirmation"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:765
+msgid "Short title/description"
+msgstr "Kort tittel/beskrivelse"
-#: ../../Zotlabs/Widget/Admin.php:34
-msgid "Inspect queue"
-msgstr "Inspiser kø"
+#: ../../Zotlabs/Module/Profiles.php:765
+msgid "Maximal 190 characters"
+msgstr "Maksimum 190 tegn"
-#: ../../Zotlabs/Widget/Admin.php:35
-msgid "Queueworker"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:768
+msgid "Street address"
+msgstr "Gateadresse"
-#: ../../Zotlabs/Widget/Admin.php:37
-msgid "DB updates"
-msgstr "Databaseoppdateringer"
+#: ../../Zotlabs/Module/Profiles.php:769
+msgid "Locality/City"
+msgstr "Sted/by"
-#: ../../Zotlabs/Widget/Admin.php:62
-msgid "Addon Features"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:770
+msgid "Region/State"
+msgstr "Region"
-#: ../../Zotlabs/Widget/Chatroom_list.php:26
-msgid "Overview"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:771
+msgid "Postal/Zip code"
+msgstr "Postnummer"
-#: ../../Zotlabs/Widget/Cdav.php:41
-msgid "Select Channel"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:777
+msgid "Who (if applicable)"
+msgstr "Hvem (om relevant)"
-#: ../../Zotlabs/Widget/Cdav.php:46
-msgid "Read-write"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:777
+msgid "Examples: cathy123, Cathy Williams, cathy@example.com"
+msgstr "Eksempler: kari123, Kari Villiamsen, kari@example.com"
-#: ../../Zotlabs/Widget/Cdav.php:47
-msgid "Read-only"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:778
+msgid "Since (date)"
+msgstr "Fra (dato)"
-#: ../../Zotlabs/Widget/Cdav.php:133
-msgid "Channel Calendar"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:781
+msgid "Tell us about yourself"
+msgstr "Fortell oss om deg selv"
-#: ../../Zotlabs/Widget/Cdav.php:137
-msgid "Shared CalDAV Calendars"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:783
+msgid "Hometown"
+msgstr "Hjemsted"
-#: ../../Zotlabs/Widget/Cdav.php:141
-msgid "Share this calendar"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:784
+msgid "Political views"
+msgstr "Politiske holdninger"
-#: ../../Zotlabs/Widget/Cdav.php:143
-msgid "Calendar name and color"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:785
+msgid "Religious views"
+msgstr "Religiøse holdninger"
-#: ../../Zotlabs/Widget/Cdav.php:145
-msgid "Create new CalDAV calendar"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:786
+msgid "Keywords used in directory listings"
+msgstr "Nøkkelord for bruk i katalogoppføringen"
-#: ../../Zotlabs/Widget/Cdav.php:147
-msgid "Calendar Name"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:786
+msgid "Example: fishing photography software"
+msgstr "Eksempel: fisking fotografering programvare"
-#: ../../Zotlabs/Widget/Cdav.php:148
-msgid "Calendar Tools"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:789
+msgid "Musical interests"
+msgstr "Musikkinteresser"
-#: ../../Zotlabs/Widget/Cdav.php:150
-msgid "Import calendar"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:790
+msgid "Books, literature"
+msgstr "Bøker, litteratur"
-#: ../../Zotlabs/Widget/Cdav.php:151
-msgid "Select a calendar to import to"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:791
+msgid "Television"
+msgstr "TV/fjernsyn"
-#: ../../Zotlabs/Widget/Cdav.php:178
-msgid "Addressbooks"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:792
+msgid "Film/Dance/Culture/Entertainment"
+msgstr "Film/dans/kultur/underholdning"
-#: ../../Zotlabs/Widget/Cdav.php:180
-msgid "Addressbook name"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:793
+msgid "Hobbies/Interests"
+msgstr "Hobbier/Interesser"
-#: ../../Zotlabs/Widget/Cdav.php:182
-msgid "Create new addressbook"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:794
+msgid "Love/Romance"
+msgstr "Kjærlighet/romantikk"
-#: ../../Zotlabs/Widget/Cdav.php:183
-msgid "Addressbook Name"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:796
+msgid "School/Education"
+msgstr "Skolle/utdanning"
-#: ../../Zotlabs/Widget/Cdav.php:185
-msgid "Addressbook Tools"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:797
+msgid "Contact information and social networks"
+msgstr "Kontaktinformasjon og andre sosiale nettverk"
-#: ../../Zotlabs/Widget/Cdav.php:186
-msgid "Import addressbook"
-msgstr ""
+#: ../../Zotlabs/Module/Profiles.php:798
+msgid "My other channels"
+msgstr "Mine andre kanaler"
-#: ../../Zotlabs/Widget/Cdav.php:187
-msgid "Select an addressbook to import to"
-msgstr ""
+#: ../../Zotlabs/Module/Sharedwithme.php:106
+msgid "Files: shared with me"
+msgstr "Filer: delt med meg"
-#: ../../Zotlabs/Widget/Activity_order.php:96
-msgid "Commented Date"
-msgstr "Sist kommentert"
+#: ../../Zotlabs/Module/Sharedwithme.php:108
+msgid "NEW"
+msgstr "NY"
-#: ../../Zotlabs/Widget/Activity_order.php:100
-msgid "Order by last commented date"
-msgstr "Sorter etter dato for siste kommentar"
+#: ../../Zotlabs/Module/Sharedwithme.php:111
+msgid "Remove all files"
+msgstr "Fjern alle filer"
-#: ../../Zotlabs/Widget/Activity_order.php:103
-msgid "Posted Date"
-msgstr "Innleggsdato"
+#: ../../Zotlabs/Module/Sharedwithme.php:112
+msgid "Remove this file"
+msgstr "Fjern denne filen"
-#: ../../Zotlabs/Widget/Activity_order.php:107
-msgid "Order by last posted date"
-msgstr "Sorter etter dato innlegg ble postet"
+#: ../../Zotlabs/Module/Rbmark.php:72
+msgid "Select a bookmark folder"
+msgstr "Velg en bokmerkemappe"
-#: ../../Zotlabs/Widget/Activity_order.php:110
-msgid "Date Unthreaded"
-msgstr "Utrådet"
+#: ../../Zotlabs/Module/Rbmark.php:80
+msgid "Save Bookmark"
+msgstr "Lagre bokmerke"
-#: ../../Zotlabs/Widget/Activity_order.php:114
-msgid "Order unthreaded by date"
-msgstr "Sorter innlegg og kommentarer uavhengig av hverandre etter dato"
+#: ../../Zotlabs/Module/Rbmark.php:81
+msgid "URL of bookmark"
+msgstr "URL-en til bokmerket"
-#: ../../Zotlabs/Widget/Activity_order.php:129
-msgid "Stream Order"
-msgstr "Sortering av innlegg"
+#: ../../Zotlabs/Module/Rbmark.php:86
+msgid "Or enter new bookmark folder name"
+msgstr "Eller skriv nytt navn på bokmerkemappe"
-#: ../../Zotlabs/Widget/Chatroom_members.php:17
-msgid "Chat Members"
+#: ../../Zotlabs/Module/Lockview.php:101
+msgid "Remote privacy information not available"
msgstr ""
-#: ../../util/nconfig.php:34
-msgid "Source channel not found."
-msgstr "Fant ikke kildekanalen."
+#: ../../Zotlabs/Module/Lockview.php:144 ../../Zotlabs/Module/Lockview.php:203
+#: ../../Zotlabs/Module/Acl.php:124
+msgctxt "acl"
+msgid "Profile"
+msgstr "Profil"
-#: ../../extend/addon/hzaddons/gravatar/gravatar.php:123
-msgid "generic profile image"
-msgstr ""
+#: ../../Zotlabs/Module/Lockview.php:155 ../../Zotlabs/Module/Lockview.php:212
+#, fuzzy
+msgid "Privacy group"
+msgstr "Personverngruppe:"
-#: ../../extend/addon/hzaddons/gravatar/gravatar.php:124
-msgid "random geometric pattern"
+#: ../../Zotlabs/Module/Lockview.php:183
+msgid "Item"
msgstr ""
-#: ../../extend/addon/hzaddons/gravatar/gravatar.php:125
-msgid "monster face"
+#: ../../Zotlabs/Module/Lockview.php:230
+#, php-format
+msgid "Click to copy link to this ressource for guest %s to clipboard"
msgstr ""
-#: ../../extend/addon/hzaddons/gravatar/gravatar.php:126
-msgid "computer generated face"
+#: ../../Zotlabs/Module/Lockview.php:230
+msgid "Link copied"
msgstr ""
-#: ../../extend/addon/hzaddons/gravatar/gravatar.php:127
-msgid "retro arcade style face"
-msgstr ""
+#: ../../Zotlabs/Module/Lockview.php:235
+msgid "Access"
+msgstr "Tilgang"
-#: ../../extend/addon/hzaddons/gravatar/gravatar.php:128
-msgid "Hub default profile photo"
+#: ../../Zotlabs/Module/Lockview.php:237
+msgid "OCAP access"
msgstr ""
-#: ../../extend/addon/hzaddons/gravatar/gravatar.php:143
-msgid "Information"
-msgstr ""
+#: ../../Zotlabs/Module/Channel_calendar.php:62
+msgid "Event can not end before it has started."
+msgstr "Hendelsen kan ikke slutte før den starter."
-#: ../../extend/addon/hzaddons/gravatar/gravatar.php:143
-msgid ""
-"Libravatar addon is installed, too. Please disable Libravatar addon or this "
-"Gravatar addon.<br>The Libravatar addon will fall back to Gravatar if "
-"nothing was found at Libravatar."
-msgstr ""
+#: ../../Zotlabs/Module/Channel_calendar.php:64
+#: ../../Zotlabs/Module/Channel_calendar.php:72
+#: ../../Zotlabs/Module/Channel_calendar.php:87
+msgid "Unable to generate preview."
+msgstr "Klarer ikke å lage forhåndsvisning."
-#: ../../extend/addon/hzaddons/gravatar/gravatar.php:150
-#: ../../extend/addon/hzaddons/xmpp/xmpp.php:43
-#: ../../extend/addon/hzaddons/msgfooter/msgfooter.php:46
-msgid "Save Settings"
-msgstr ""
+#: ../../Zotlabs/Module/Channel_calendar.php:70
+msgid "Event title and start time are required."
+msgstr "Hendelsestittel og starttidspunkt er påkrevd."
-#: ../../extend/addon/hzaddons/gravatar/gravatar.php:151
-msgid "Default avatar image"
-msgstr ""
+#: ../../Zotlabs/Module/Channel_calendar.php:85
+#: ../../Zotlabs/Module/Channel_calendar.php:214
+msgid "Event not found."
+msgstr "Hendelsen ble ikke funnet."
-#: ../../extend/addon/hzaddons/gravatar/gravatar.php:151
-msgid "Select default avatar image if none was found at Gravatar. See README"
-msgstr ""
+#: ../../Zotlabs/Module/Channel_calendar.php:358
+msgid "Edit event"
+msgstr "Endre hendelse"
-#: ../../extend/addon/hzaddons/gravatar/gravatar.php:152
-msgid "Rating of images"
-msgstr ""
+#: ../../Zotlabs/Module/Channel_calendar.php:360
+msgid "Delete event"
+msgstr "Slett hendelse"
-#: ../../extend/addon/hzaddons/gravatar/gravatar.php:152
-msgid "Select the appropriate avatar rating for your site. See README"
-msgstr ""
+#: ../../Zotlabs/Module/Channel_calendar.php:394
+msgid "calendar"
+msgstr "kalender"
-#: ../../extend/addon/hzaddons/gravatar/gravatar.php:165
-msgid "Gravatar settings updated."
-msgstr ""
+#: ../../Zotlabs/Module/Channel_calendar.php:485
+msgid "Failed to remove event"
+msgstr "Mislyktes med å slette hendelse"
-#: ../../extend/addon/hzaddons/sendzid/Mod_Sendzid.php:21
-msgid "Send your identity to all websites"
+#: ../../Zotlabs/Module/Go.php:22
+msgid "This page is available only to site members"
msgstr ""
-#: ../../extend/addon/hzaddons/sendzid/Mod_Sendzid.php:29
-msgid "Send ZID"
+#: ../../Zotlabs/Module/Go.php:30
+msgid "What would you like to do?"
msgstr ""
-#: ../../extend/addon/hzaddons/ldapauth/ldapauth.php:101
-msgid "An account has been created for you."
+#: ../../Zotlabs/Module/Go.php:32
+msgid ""
+"Please bookmark this page if you would like to return to it in the future"
msgstr ""
-#: ../../extend/addon/hzaddons/ldapauth/ldapauth.php:108
-msgid "Authentication successful but rejected: account creation is disabled."
+#: ../../Zotlabs/Module/Go.php:36
+msgid "Upload a profile photo"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/cart.php:259
-msgid "DB Cleanup Failure"
+#: ../../Zotlabs/Module/Go.php:37
+msgid "Upload a cover photo"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/cart.php:692
-msgid "[cart] Item Added"
+#: ../../Zotlabs/Module/Go.php:38
+msgid "Edit your default profile"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/cart.php:1107
-msgid "Order already checked out."
+#: ../../Zotlabs/Module/Go.php:40
+msgid "View the channel directory"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/cart.php:1416
-msgid "Drop database tables when uninstalling."
+#: ../../Zotlabs/Module/Go.php:41
+msgid "View/edit your channel settings"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/cart.php:1423
-#: ../../extend/addon/hzaddons/cart/Settings/Cart.php:129
-msgid "Cart Settings"
+#: ../../Zotlabs/Module/Go.php:42
+msgid "View the site or project documentation"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/cart.php:1435
-#: ../../extend/addon/hzaddons/cart/cart.php:1438
-msgid "Shop"
+#: ../../Zotlabs/Module/Go.php:43
+msgid "Visit your channel homepage"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/cart.php:1458
-#: ../../extend/addon/hzaddons/faces/Mod_Faces.php:64
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:36
-#: ../../extend/addon/hzaddons/flashcards/Mod_Flashcards.php:52
-msgid "Profile Unavailable."
-msgstr "Profilen er ikke tilgjengelig."
-
-#: ../../extend/addon/hzaddons/cart/cart.php:1494
-#: ../../extend/addon/hzaddons/cart/myshop.php:113
-msgid "Order Not Found"
+#: ../../Zotlabs/Module/Go.php:44
+msgid ""
+"View your connections and/or add somebody whose address you already know"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/cart.php:1598
-msgid "You must be logged into the Grid to shop."
+#: ../../Zotlabs/Module/Go.php:45
+msgid ""
+"View your personal stream (this may be empty until you add some connections)"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/cart.php:1609
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbuttonV2.php:486
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbutton.php:456
-#: ../../extend/addon/hzaddons/cart/myshop.php:37
-#: ../../extend/addon/hzaddons/cart/manual_payments.php:93
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:81
-msgid "Invalid channel"
-msgstr "Ugyldig kanal"
-
-#: ../../extend/addon/hzaddons/cart/cart.php:1631
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbuttonV2.php:417
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbutton.php:392
-#: ../../extend/addon/hzaddons/cart/manual_payments.php:68
-msgid "Order not found."
+#: ../../Zotlabs/Module/Go.php:53
+msgid "View the public stream. Warning: this content is not moderated"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/cart.php:1647
-#, fuzzy
-msgid "Access denied."
-msgstr "Ingen tilgang"
+#: ../../Zotlabs/Module/Chat.php:193
+msgid "Room not found"
+msgstr "Rommet ble ikke funnet"
-#: ../../extend/addon/hzaddons/cart/cart.php:1699
-#: ../../extend/addon/hzaddons/cart/cart.php:1842
-msgid "No Order Found"
-msgstr ""
+#: ../../Zotlabs/Module/Chat.php:209
+msgid "Leave Room"
+msgstr "Forlat rom"
-#: ../../extend/addon/hzaddons/cart/cart.php:1708
-msgid "An unknown error has occurred Please start again."
-msgstr ""
+#: ../../Zotlabs/Module/Chat.php:210
+msgid "Delete Room"
+msgstr "Slett rom"
-#: ../../extend/addon/hzaddons/cart/cart.php:1851
-msgid "Requirements not met."
-msgstr ""
+#: ../../Zotlabs/Module/Chat.php:211
+msgid "I am away right now"
+msgstr "Jeg er borte akkurat nå"
-#: ../../extend/addon/hzaddons/cart/cart.php:1851
-msgid "Review your order and complete any needed requirements."
-msgstr ""
+#: ../../Zotlabs/Module/Chat.php:212
+msgid "I am online"
+msgstr "Jeg er pålogget"
-#: ../../extend/addon/hzaddons/cart/cart.php:1877
-msgid "Invalid Payment Type. Please start again."
-msgstr ""
+#: ../../Zotlabs/Module/Chat.php:214
+msgid "Bookmark this room"
+msgstr "Bokmerk dette rommet"
-#: ../../extend/addon/hzaddons/cart/cart.php:1884
-msgid "Order not found"
-msgstr ""
+#: ../../Zotlabs/Module/Chat.php:237
+msgid "New Chatroom"
+msgstr "Nytt chatrom"
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:70
-msgid "Enable Order/Item Options"
-msgstr ""
+#: ../../Zotlabs/Module/Chat.php:238
+msgid "Chatroom name"
+msgstr "Romnavn"
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:333
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:357
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:433
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:457
-msgid "Label"
-msgstr ""
+#: ../../Zotlabs/Module/Chat.php:239
+msgid "Expiration of chats (minutes)"
+msgstr "Chat utgår (antall minutter)"
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:336
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:360
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:436
-#: ../../extend/addon/hzaddons/cart/submodules/orderoptions.php:460
-msgid "Instructions"
-msgstr ""
+#: ../../Zotlabs/Module/Chat.php:255
+#, php-format
+msgid "%1$s's Chatrooms"
+msgstr "%1$s sine chatrom"
-#: ../../extend/addon/hzaddons/cart/submodules/subscriptions.php:151
-msgid "Enable Subscription Management Module"
-msgstr ""
+#: ../../Zotlabs/Module/Chat.php:260
+msgid "No chatrooms available"
+msgstr "Ingen rom tilgjengelige"
-#: ../../extend/addon/hzaddons/cart/submodules/subscriptions.php:223
-msgid ""
-"Cannot include subscription items with different terms in the same order."
+#: ../../Zotlabs/Module/Chat.php:261
+msgid "Add Room"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/subscriptions.php:372
-msgid "Select Subscription to Edit"
-msgstr ""
+#: ../../Zotlabs/Module/Chat.php:264
+msgid "Expiration"
+msgstr "Utløp"
-#: ../../extend/addon/hzaddons/cart/submodules/subscriptions.php:380
-msgid "Edit Subscriptions"
-msgstr ""
+#: ../../Zotlabs/Module/Chat.php:265
+msgid "min"
+msgstr "min"
-#: ../../extend/addon/hzaddons/cart/submodules/subscriptions.php:414
-msgid "Subscription SKU"
+#: ../../Zotlabs/Module/Regate.php:85
+msgid "Email resent"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/subscriptions.php:419
-msgid "Catalog Description"
+#: ../../Zotlabs/Module/Regate.php:85
+msgid "Email resend failed"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/subscriptions.php:423
-msgid "Subscription available for purchase."
+#: ../../Zotlabs/Module/Regate.php:110
+msgid "Verification successful"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/subscriptions.php:428
-msgid "Maximum active subscriptions to this item per account."
+#: ../../Zotlabs/Module/Regate.php:154
+msgid "Account successfull created"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/subscriptions.php:431
-msgid "Subscription price."
+#: ../../Zotlabs/Module/Regate.php:212
+msgid "Channel successfull created"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/subscriptions.php:435
-msgid "Quantity"
+#: ../../Zotlabs/Module/Regate.php:218
+msgid "Automatic channel creation failed. Please create a channel."
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/subscriptions.php:439
-msgid "Term"
+#: ../../Zotlabs/Module/Regate.php:230
+msgid "Account creation error"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/manualcat.php:61
-msgid "Enable Manual Cart Module"
+#: ../../Zotlabs/Module/Regate.php:242
+msgid "Verify failed"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/manualcat.php:172
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:164
-msgid "New Sku"
+#: ../../Zotlabs/Module/Regate.php:247
+msgid "Token verification failed"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/manualcat.php:208
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:199
-msgid "Cannot save edits to locked item."
+#: ../../Zotlabs/Module/Regate.php:252
+msgid "Request not inside time frame"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/manualcat.php:252
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:649
-msgid "Changes Locked"
+#: ../../Zotlabs/Module/Regate.php:258 ../../Zotlabs/Module/Regate.php:288
+msgid "Identity unknown"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/manualcat.php:256
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:653
-msgid "Item available for purchase."
+#: ../../Zotlabs/Module/Regate.php:264
+msgid "dId2 mistaken"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/manualcat.php:263
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:660
-#: ../../extend/addon/hzaddons/cart/widgets/catalogitem.php:57
-msgid "Price"
+#: ../../Zotlabs/Module/Regate.php:292
+msgid "Your Registration ID"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/manualcat.php:266
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:663
-msgid "Photo URL"
+#: ../../Zotlabs/Module/Regate.php:305 ../../Zotlabs/Module/Regate.php:397
+#: ../../Zotlabs/Module/Regate.php:429
+msgid "Registration verification"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbuttonV2.php:86
-msgid "Enable Paypal Button Module (API-v2)"
+#: ../../Zotlabs/Module/Regate.php:312 ../../Zotlabs/Module/Regate.php:434
+msgid "Hold on, you can start verification in"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbuttonV2.php:96
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbutton.php:93
-msgid "Use Production Key"
+#: ../../Zotlabs/Module/Regate.php:313
+msgid "Please remember your verification token for ID"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbuttonV2.php:103
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbutton.php:100
-msgid "Paypal Sandbox Client Key"
+#: ../../Zotlabs/Module/Regate.php:315
+msgid "Token validity"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbuttonV2.php:110
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbutton.php:107
-msgid "Paypal Sandbox Secret Key"
+#: ../../Zotlabs/Module/Regate.php:351
+msgid "Resend email"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbuttonV2.php:116
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbutton.php:113
-msgid "Paypal Production Client Key"
+#: ../../Zotlabs/Module/Regate.php:356
+msgid "Registration status"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbuttonV2.php:123
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbutton.php:120
-msgid "Paypal Production Secret Key"
+#: ../../Zotlabs/Module/Regate.php:359
+msgid "Verification successful!"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbuttonV2.php:271
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbutton.php:252
-msgid "Paypal button payments are not enabled."
+#: ../../Zotlabs/Module/Regate.php:360
+msgid "Your login ID is"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbuttonV2.php:289
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbutton.php:270
+#: ../../Zotlabs/Module/Regate.php:361
msgid ""
-"Paypal button payments are not properly configured. Please choose another "
-"payment option."
-msgstr ""
-
-#: ../../extend/addon/hzaddons/cart/submodules/paypalbutton.php:85
-msgid "Enable Paypal Button Module"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:65
-msgid "Enable Hubzilla Services Module"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:248
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:335
-msgid "SKU not found."
-msgstr ""
-
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:301
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:305
-msgid "Invalid Activation Directive."
-msgstr ""
-
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:376
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:380
-msgid "Invalid Deactivation Directive."
-msgstr ""
-
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:566
-msgid "Add to this privacy group"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:582
-msgid "Set user service class"
+"After your account has been approved by our administrator you will be able "
+"to login with your login ID and your provided password."
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:609
-msgid "You must be using a local account to purchase this service."
+#: ../../Zotlabs/Module/Regate.php:373
+msgid "Registration request revoked"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:667
-msgid "Add buyer to privacy group"
+#: ../../Zotlabs/Module/Regate.php:374
+msgid "Sorry for any inconvience. Thank you for your response."
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:672
-msgid "Add buyer as connection"
+#: ../../Zotlabs/Module/Regate.php:398
+msgid "Please enter your verification token for ID"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:680
-#: ../../extend/addon/hzaddons/cart/submodules/hzservices.php:722
-msgid "Set Service Class"
+#: ../../Zotlabs/Module/Regate.php:399 ../../Zotlabs/Module/Regate.php:426
+msgid "Please check your email!"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/myshop.php:30
-#, fuzzy
-msgid "Access Denied."
-msgstr "Ingen tilgang"
-
-#: ../../extend/addon/hzaddons/cart/myshop.php:145
-#: ../../extend/addon/hzaddons/cart/myshop.php:181
-#: ../../extend/addon/hzaddons/cart/myshop.php:215
-#: ../../extend/addon/hzaddons/cart/myshop.php:265
-#: ../../extend/addon/hzaddons/cart/myshop.php:300
-#: ../../extend/addon/hzaddons/cart/myshop.php:323
-msgid "Access Denied"
-msgstr "Ingen tilgang"
-
-#: ../../extend/addon/hzaddons/cart/myshop.php:190
-#: ../../extend/addon/hzaddons/cart/myshop.php:224
-#: ../../extend/addon/hzaddons/cart/myshop.php:275
-#: ../../extend/addon/hzaddons/cart/myshop.php:333
-#, fuzzy
-msgid "Invalid Item"
-msgstr "Ugyldig element."
-
-#: ../../extend/addon/hzaddons/cart/manual_payments.php:7
-msgid "Error: order mismatch. Please try again."
+#: ../../Zotlabs/Module/Regate.php:409
+msgid "Verification token"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/manual_payments.php:61
-msgid "Manual payments are not enabled."
+#: ../../Zotlabs/Module/Regate.php:420
+msgid "ID expired"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/manual_payments.php:77
-msgid "Finished"
+#: ../../Zotlabs/Module/Regate.php:435
+msgid "You will require the verification token for ID"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/Settings/Cart.php:58
-msgid "Enable Test Catalog"
+#: ../../Zotlabs/Module/Regate.php:444
+msgid "Unknown or expired ID"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/Settings/Cart.php:70
-msgid "Enable Manual Payments"
+#: ../../Zotlabs/Module/Regate.php:455
+msgid "dId2 malformed"
msgstr ""
-#: ../../extend/addon/hzaddons/cart/Settings/Cart.php:90
-msgid "Base Merchant Currency"
-msgstr ""
+#: ../../Zotlabs/Module/Editlayout.php:137
+msgid "Edit Layout"
+msgstr "Endre layout"
-#: ../../extend/addon/hzaddons/articles/Mod_Article_edit.php:127
-msgid "Edit Article"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:180
+msgid "$Projectname Server - Setup"
+msgstr "$Projectname-tjener - oppsett"
-#: ../../extend/addon/hzaddons/articles/articles.php:51
-msgid "View Articles"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:184
+msgid "Could not connect to database."
+msgstr "Fikk ikke kontakt med databasen."
-#: ../../extend/addon/hzaddons/articles/Mod_Articles.php:119
-msgid "Add Article"
+#: ../../Zotlabs/Module/Setup.php:188
+msgid ""
+"Could not connect to specified site URL. Possible SSL certificate or DNS "
+"issue."
msgstr ""
+"Fikk ikke kontakt med det angitte nettstedets URL. Problemet kan muligens "
+"skyldes SSL-sertifikatet eller DNS."
-#: ../../extend/addon/hzaddons/hzfiles/hzfiles.php:81
-msgid "Hubzilla File Storage Import"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:195
+msgid "Could not create table."
+msgstr "Kunne ikke lage tabellen."
-#: ../../extend/addon/hzaddons/hzfiles/hzfiles.php:82
-msgid "This will import all your cloud files from another server."
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:201
+msgid "Your site database has been installed."
+msgstr "Databasen til ditt nettsted har blitt installert."
-#: ../../extend/addon/hzaddons/hzfiles/hzfiles.php:83
-msgid "Hubzilla Server base URL"
+#: ../../Zotlabs/Module/Setup.php:207
+msgid ""
+"You may need to import the file \"install/schema_xxx.sql\" manually using a "
+"database client."
msgstr ""
+"Du må kanskje importere filen \"install/schmea_xxx.sql\" manuelt ved å bruke "
+"en databaseklient."
-#: ../../extend/addon/hzaddons/hzfiles/hzfiles.php:84
-#: ../../extend/addon/hzaddons/content_import/Mod_content_import.php:138
-msgid "Since modified date yyyy-mm-dd"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:208 ../../Zotlabs/Module/Setup.php:277
+#: ../../Zotlabs/Module/Setup.php:802
+msgid "Please see the file \"install/INSTALL.txt\"."
+msgstr "Vennligst les filen \"install/INSTALL.txt\"."
-#: ../../extend/addon/hzaddons/hzfiles/hzfiles.php:85
-#: ../../extend/addon/hzaddons/content_import/Mod_content_import.php:139
-msgid "Until modified date yyyy-mm-dd"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:274
+msgid "System check"
+msgstr "Systemsjekk"
-#: ../../extend/addon/hzaddons/openstreetmap/openstreetmap.php:125
-msgid "View Larger"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:279
+msgid "Check again"
+msgstr "Sjekk igjen"
-#: ../../extend/addon/hzaddons/openstreetmap/openstreetmap.php:148
-msgid "Tile Server URL"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:300
+msgid "Database connection"
+msgstr "Databaseforbindelse"
-#: ../../extend/addon/hzaddons/openstreetmap/openstreetmap.php:148
+#: ../../Zotlabs/Module/Setup.php:301
msgid ""
-"A list of <a href=\"http://wiki.openstreetmap.org/wiki/TMS\" "
-"target=\"_blank\">public tile servers</a>"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/openstreetmap/openstreetmap.php:149
-msgid "Nominatim (reverse geocoding) Server URL"
+"In order to install $Projectname we need to know how to connect to your "
+"database."
msgstr ""
+"For å installere $Projectname må du oppgi hvordan din database kan kontaktes."
-#: ../../extend/addon/hzaddons/openstreetmap/openstreetmap.php:149
+#: ../../Zotlabs/Module/Setup.php:302
msgid ""
-"A list of <a href=\"http://wiki.openstreetmap.org/wiki/Nominatim\" "
-"target=\"_blank\">Nominatim servers</a>"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/openstreetmap/openstreetmap.php:150
-msgid "Default zoom"
+"Please contact your hosting provider or site administrator if you have "
+"questions about these settings."
msgstr ""
+"Vennligst kontakt din nettstedstilbyder eller nettstedsadministrator hvis du "
+"har spørsmål om disse innstillingene."
-#: ../../extend/addon/hzaddons/openstreetmap/openstreetmap.php:150
+#: ../../Zotlabs/Module/Setup.php:303
msgid ""
-"The default zoom level. (1:world, 18:highest, also depends on tile server)"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/openstreetmap/openstreetmap.php:151
-msgid "Include marker on map"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/openstreetmap/openstreetmap.php:151
-msgid "Include a marker on the map."
+"The database you specify below should already exist. If it does not, please "
+"create it before continuing."
msgstr ""
+"Databasen du oppgir nedenfor må finnes på forhånd. Hvis den ikke finnes, "
+"vennligst lag den før du fortsetter."
-#: ../../extend/addon/hzaddons/hsse/Mod_Hsse.php:15
-msgid "WYSIWYG status editor"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:307
+msgid "Database Server Name"
+msgstr "Navn på databasetjener"
-#: ../../extend/addon/hzaddons/hsse/Mod_Hsse.php:24
-msgid "WYSIWYG Status App"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:307
+msgid "Default is 127.0.0.1"
+msgstr "Standard er 127.0.0.1"
-#: ../../extend/addon/hzaddons/hsse/Mod_Hsse.php:32
-msgid "WYSIWYG Status"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:308
+msgid "Database Port"
+msgstr "Databaseport"
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:106
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:179
-msgid "Network error"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:308
+msgid "Communication port number - use 0 for default"
+msgstr "Kommunikasjonsportnummer - bruk 0 for standard"
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:110
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:183
-msgid "API error"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:309
+msgid "Database Login Name"
+msgstr "Database innloggingsnavn"
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:114
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:187
-msgid "Unknown issue"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:310
+msgid "Database Login Password"
+msgstr "Database innloggingspassord"
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:152
-msgid "Unable to retrieve email address from remote identity provider"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:311
+msgid "Database Name"
+msgstr "Databasenavn"
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:171
-msgid "Unable to login using email address "
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:312
+msgid "Database Type"
+msgstr "Databasetype"
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:211
-msgid "Social Authentication using your social media account"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:314 ../../Zotlabs/Module/Setup.php:354
+msgid "Site administrator email address"
+msgstr "E-postadressen til administrator ved nettstedet"
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:215
+#: ../../Zotlabs/Module/Setup.php:314 ../../Zotlabs/Module/Setup.php:354
msgid ""
-"This app enables one or more social provider sign-in buttons on the login "
-"page."
-msgstr ""
-
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:233
-msgid "Add an identity provider"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:260
-#, fuzzy
-msgid "Enable "
-msgstr "Skru på"
-
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:267
-msgid "Key"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:267
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:272
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:288
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:299
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:308
-msgid "Word"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:272
-msgid "Secret"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:308
-msgid "Add a custom provider"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:328
-msgid "Remove an identity provider"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:338
-msgid "Social authentication"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:380
-msgid "Error while saving provider settings"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:403
-msgid "Custom provider already exists"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/socialauth/Mod_SocialAuth.php:420
-msgid "Social authentication settings saved."
-msgstr ""
-
-#: ../../extend/addon/hzaddons/pumpio/pumpio.php:152
-msgid "You are now authenticated to pumpio."
-msgstr ""
-
-#: ../../extend/addon/hzaddons/pumpio/pumpio.php:153
-msgid "return to the featured settings page"
-msgstr "tilbake til funksjonsinnstillinger"
-
-#: ../../extend/addon/hzaddons/pumpio/pumpio.php:168
-msgid "Post to Pump.io"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/pumpio/Mod_Pumpio.php:40
-msgid "Pump.io Settings saved."
-msgstr ""
-
-#: ../../extend/addon/hzaddons/pumpio/Mod_Pumpio.php:71
-msgid "Pump.io servername"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/pumpio/Mod_Pumpio.php:71
-msgid "Without \"http://\" or \"https://\""
-msgstr ""
-
-#: ../../extend/addon/hzaddons/pumpio/Mod_Pumpio.php:75
-msgid "Pump.io username"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/pumpio/Mod_Pumpio.php:75
-msgid "Without the servername"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/pumpio/Mod_Pumpio.php:86
-msgid "You are not authenticated to pumpio"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/pumpio/Mod_Pumpio.php:88
-msgid "(Re-)Authenticate your pump.io connection"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/pumpio/Mod_Pumpio.php:92
-msgid "Post to pump.io by default"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/pumpio/Mod_Pumpio.php:96
-msgid "Should posts be public"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/pumpio/Mod_Pumpio.php:100
-msgid "Mirror all public posts"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/pumpio/Mod_Pumpio.php:110
-msgid "Pump.io Crosspost Connector"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/superblock/superblock.php:355
-msgid "Block Completely"
-msgstr "Blokker helt"
-
-#: ../../extend/addon/hzaddons/superblock/Mod_Superblock.php:62
-msgid "superblock settings updated"
-msgstr "innstillingene til superblokk ble oppdatert"
-
-#: ../../extend/addon/hzaddons/superblock/Mod_Superblock.php:86
-msgid "Currently blocked"
-msgstr "Blokkert for øyeblikket"
-
-#: ../../extend/addon/hzaddons/superblock/Mod_Superblock.php:88
-msgid "No channels currently blocked"
-msgstr "Ingen kanaler er blokkert i øyeblikket"
-
-#: ../../extend/addon/hzaddons/gallery/gallery.php:43
-#: ../../extend/addon/hzaddons/gallery/Mod_Gallery.php:135
-msgid "Gallery"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/gallery/gallery.php:46
-msgid "Photo Gallery"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/randpost/randpost.php:99
-msgid "You're welcome."
-msgstr ""
-
-#: ../../extend/addon/hzaddons/randpost/randpost.php:100
-msgid "Ah shucks..."
-msgstr ""
-
-#: ../../extend/addon/hzaddons/randpost/randpost.php:101
-msgid "Don't mention it."
-msgstr ""
-
-#: ../../extend/addon/hzaddons/randpost/randpost.php:102
-msgid "&lt;blush&gt;"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:51
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:129
-msgid "System defaults:"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:55
-msgid "Preferred Clipart IDs"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:55
-msgid "List of preferred clipart ids. These will be shown first."
+"Your account email address must match this in order to use the web admin "
+"panel."
msgstr ""
+"Din konto sin e-postadresse må være lik denne for å kunne bruke web-"
+"administrasjonspanelet."
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:56
-msgid "Default Search Term"
-msgstr "Standard søkeord"
-
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:56
-msgid "The default search term. These will be shown second."
-msgstr "Standard søkeord. Disse vil vises som nummer to."
+#: ../../Zotlabs/Module/Setup.php:315 ../../Zotlabs/Module/Setup.php:356
+msgid "Website URL"
+msgstr "Nettstedets URL"
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:57
-msgid "Return After"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:315 ../../Zotlabs/Module/Setup.php:356
+msgid "Please use SSL (https) URL if available."
+msgstr "Vennligst bruk SSL (https) URL hvis tilgjengelig."
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:57
-msgid "Page to load after image selection."
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:316 ../../Zotlabs/Module/Setup.php:358
+msgid "Please select a default timezone for your website"
+msgstr "Vennligst velg en standard tidssone for ditt nettsted"
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:60
-msgid "Profile List"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:343
+msgid "Site settings"
+msgstr "Nettstedets innstillinger"
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:62
-msgid "Order of Preferred"
+#: ../../Zotlabs/Module/Setup.php:397
+msgid "PHP version 8.0 or greater is required."
msgstr ""
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:62
-msgid "Sort order of preferred clipart ids."
+#: ../../Zotlabs/Module/Setup.php:398
+msgid "PHP version"
msgstr ""
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:63
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:69
-msgid "Newest first"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:414
+msgid "Could not find a command line version of PHP in the web server PATH."
+msgstr "Fant ikke en kommandolinjeversjon av PHP i webtjenerens sti (PATH)."
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:66
-msgid "As entered"
+#: ../../Zotlabs/Module/Setup.php:415
+msgid ""
+"If you don't have a command line version of PHP installed on server, you "
+"will not be able to run background polling via cron."
msgstr ""
+"Hvis du ikke har en kommandolinjeversjon av PHP installert på tjeneren, så "
+"vil du ikke kunne kjøre bakgrunnshenting via cron."
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:68
-msgid "Order of other"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:419
+msgid "PHP executable path"
+msgstr "PHP-kjørefilens sti"
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:68
-msgid "Sort order of other clipart ids."
+#: ../../Zotlabs/Module/Setup.php:419
+msgid ""
+"Enter full path to php executable. You can leave this blank to continue the "
+"installation."
msgstr ""
+"Skriv full sti til kjørefilen for PHP. Du kan la denne stå blank for å "
+"fortsette installasjonen."
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:70
-msgid "Most downloaded first"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:424
+msgid "Command line PHP"
+msgstr "Kommandolinje PHP"
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:71
-msgid "Most liked first"
+#: ../../Zotlabs/Module/Setup.php:434
+msgid ""
+"Unable to check command line PHP, as shell_exec() is disabled. This is "
+"required."
msgstr ""
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:73
-msgid "Preferred IDs Message"
+#: ../../Zotlabs/Module/Setup.php:438
+msgid ""
+"The command line version of PHP on your system does not have "
+"\"register_argc_argv\" enabled."
msgstr ""
+"Kommandolinjeversjonen av PHP på ditt system har ikke \"register_argc_argv\" "
+"påskrudd."
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:73
-msgid "Message to display above preferred results."
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:439
+msgid "This is required for message delivery to work."
+msgstr "Dette er påkrevd for at meldingslevering skal virke."
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:79
-msgid "Uploaded by: "
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:442
+msgid "PHP register_argc_argv"
+msgstr "PHP register_argc_argv"
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:79
-msgid "Drawn by: "
+#: ../../Zotlabs/Module/Setup.php:462
+msgid ""
+"This is not sufficient to upload larger images or files. You should be able "
+"to upload at least 4 MB at once."
msgstr ""
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:183
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:195
-msgid "Use this image"
+#: ../../Zotlabs/Module/Setup.php:464
+#, php-format
+msgid ""
+"Your max allowed total upload size is set to %s. Maximum size of one file to "
+"upload is set to %s. You are allowed to upload up to %d files at once."
msgstr ""
+"Den største totale opplastingsstørrelsen du er tillatt er satt til %s. "
+"Filstørrelsen på en enkelt fil er satt til å maksimalt være %s. Du har lov "
+"til å laste opp inntil %d filer samtidig."
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:193
-msgid "Or select from a free OpenClipart.org image:"
+#: ../../Zotlabs/Module/Setup.php:470
+msgid "You can adjust these settings in the server php.ini file."
msgstr ""
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:196
-msgid "Search Term"
-msgstr "Søkeord"
+#: ../../Zotlabs/Module/Setup.php:472
+msgid "PHP upload limits"
+msgstr "PHP opplastingsgrenser"
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:233
-msgid "Unknown error. Please try again later."
+#: ../../Zotlabs/Module/Setup.php:495
+msgid ""
+"Error: the \"openssl_pkey_new\" function on this system is not able to "
+"generate encryption keys"
msgstr ""
+"Feil: \"openssl_pkey_new\"-funksjonen på dette systemet er ikke i stand til "
+"å lage krypteringsnøkler"
-#: ../../extend/addon/hzaddons/openclipatar/openclipatar.php:309
-msgid "Profile photo updated successfully."
+#: ../../Zotlabs/Module/Setup.php:496
+msgid ""
+"If running under Windows, please see \"http://www.php.net/manual/en/"
+"openssl.installation.php\"."
msgstr ""
+"Ved kjøring på Windows, vennligst se \"http://www.php.net/manual/en/"
+"openssl.installation.php\"."
-#: ../../extend/addon/hzaddons/dirstats/dirstats.php:94
-msgid "Hubzilla Directory Stats"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:499
+msgid "Generate encryption keys"
+msgstr "Lag krypteringsnøkler"
-#: ../../extend/addon/hzaddons/dirstats/dirstats.php:95
-msgid "Total Hubs"
+#: ../../Zotlabs/Module/Setup.php:503
+msgid "Error: the sodium encryption library is not installed."
msgstr ""
-#: ../../extend/addon/hzaddons/dirstats/dirstats.php:97
-msgid "Hubzilla Hubs"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:505
+#, fuzzy
+#| msgid "Generate encryption keys"
+msgid "Generate ed25519 encryption keys"
+msgstr "Lag krypteringsnøkler"
-#: ../../extend/addon/hzaddons/dirstats/dirstats.php:99
-msgid "Friendica Hubs"
+#: ../../Zotlabs/Module/Setup.php:510
+msgid ""
+"Error: one of \"bcmath\" or \"gmp\" (bigmath library) extensions are "
+"required."
msgstr ""
-#: ../../extend/addon/hzaddons/dirstats/dirstats.php:101
-msgid "Diaspora Pods"
+#: ../../Zotlabs/Module/Setup.php:512
+msgid "Bigmath library (either bcmath or gmp)"
msgstr ""
-#: ../../extend/addon/hzaddons/dirstats/dirstats.php:103
-msgid "Hubzilla Channels"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:529
+msgid "libCurl PHP module"
+msgstr "libCurl PHP-modul"
-#: ../../extend/addon/hzaddons/dirstats/dirstats.php:105
-msgid "Friendica Channels"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:530
+msgid "GD graphics PHP module"
+msgstr "GD graphics PHP-modul"
-#: ../../extend/addon/hzaddons/dirstats/dirstats.php:107
-msgid "Diaspora Channels"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:531
+msgid "OpenSSL PHP module"
+msgstr "OpenSSL PHP-modul"
-#: ../../extend/addon/hzaddons/dirstats/dirstats.php:109
-msgid "Aged 35 and above"
+#: ../../Zotlabs/Module/Setup.php:532
+msgid "PDO database PHP module"
msgstr ""
-#: ../../extend/addon/hzaddons/dirstats/dirstats.php:111
-msgid "Aged 34 and under"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:533
+msgid "mb_string PHP module"
+msgstr "mb_string PHP-modul"
-#: ../../extend/addon/hzaddons/dirstats/dirstats.php:113
-msgid "Average Age"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:534
+msgid "xml PHP module"
+msgstr "xml PHP modul"
-#: ../../extend/addon/hzaddons/dirstats/dirstats.php:115
-msgid "Known Chatrooms"
+#: ../../Zotlabs/Module/Setup.php:535
+msgid "zip PHP module"
msgstr ""
-#: ../../extend/addon/hzaddons/dirstats/dirstats.php:117
-msgid "Known Tags"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:536
+#, fuzzy
+#| msgid "xml PHP module"
+msgid "intl PHP module"
+msgstr "xml PHP modul"
-#: ../../extend/addon/hzaddons/dirstats/dirstats.php:119
-msgid ""
-"Please note Diaspora and Friendica statistics are merely those **this "
-"directory** is aware of, and not all those known in the network. This also "
-"applies to chatrooms,"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:540 ../../Zotlabs/Module/Setup.php:542
+msgid "Apache mod_rewrite module"
+msgstr "Apache mod_rewrite-modul"
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:61
+#: ../../Zotlabs/Module/Setup.php:540
msgid ""
-"Please contact your site administrator.<br />The provided API URL is not "
-"valid."
-msgstr ""
-"Den oppgitte URLen for APIet er ikke gyldig. Kontakt serverens administrator."
-
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:98
-msgid "We could not contact the GNU social API with the Path you entered."
-msgstr ""
-
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:130
-msgid "GNU social settings updated."
+"Error: Apache webserver mod-rewrite module is required but not installed."
msgstr ""
+"Feil: Apache web-tjenerens mod-rewrite-modul er påkrevd, men ikke installert."
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:179
-msgid "Globally Available GNU social OAuthKeys"
+#: ../../Zotlabs/Module/Setup.php:546 ../../Zotlabs/Module/Setup.php:549
+msgid "exec"
msgstr ""
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:181
+#: ../../Zotlabs/Module/Setup.php:546
msgid ""
-"There are preconfigured OAuth key pairs for some GNU social servers "
-"available. If you are using one of them, please use these credentials.<br /"
-">If not feel free to connect to any other GNU social instance (see below)."
+"Error: exec is required but is either not installed or has been disabled in "
+"php.ini"
msgstr ""
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:196
-msgid "Provide your own OAuth Credentials"
+#: ../../Zotlabs/Module/Setup.php:552 ../../Zotlabs/Module/Setup.php:555
+msgid "shell_exec"
msgstr ""
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:198
+#: ../../Zotlabs/Module/Setup.php:552
msgid ""
-"No consumer key pair for GNU social found. Register your Hubzilla Account as "
-"an desktop client on your GNU social account, copy the consumer key pair "
-"here and enter the API base root.<br />Before you register your own OAuth "
-"key pair ask the administrator if there is already a key pair for this "
-"Hubzilla installation at your favourite GNU social installation."
-msgstr ""
-
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:202
-msgid "OAuth Consumer Key"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:206
-msgid "OAuth Consumer Secret"
+"Error: shell_exec is required but is either not installed or has been "
+"disabled in php.ini"
msgstr ""
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:210
-msgid "Base API Path"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:560
+msgid "Error: libCURL PHP module required but not installed."
+msgstr "Feil: libCURL PHP-modul er påkrevd, men er ikke installert."
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:210
-msgid "Remember the trailing /"
+#: ../../Zotlabs/Module/Setup.php:564
+msgid ""
+"Error: GD PHP module with JPEG support or ImageMagick graphics library "
+"required but not installed."
msgstr ""
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:214
-msgid "GNU social application name"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:568
+msgid "Error: openssl PHP module required but not installed."
+msgstr "Feil: openssl PHP-modul er påkrevd, men er ikke installert."
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:237
+#: ../../Zotlabs/Module/Setup.php:574
msgid ""
-"To connect to your GNU social account click the button below to get a "
-"security code from GNU social which you have to copy into the input box "
-"below and submit the form. Only your <strong>public</strong> posts will be "
-"posted to GNU social."
+"Error: PDO database PHP module missing a driver for either mysql or pgsql."
msgstr ""
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:239
-msgid "Log in with GNU social"
+#: ../../Zotlabs/Module/Setup.php:579
+msgid "Error: PDO database PHP module required but not installed."
msgstr ""
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:242
-msgid "Copy the security code from GNU social here"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:583
+msgid "Error: mb_string PHP module required but not installed."
+msgstr "Feil: mb_string PHP-modul er påkrevd, men er ikke installert."
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:252
-msgid "Cancel Connection Process"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:587
+msgid "Error: xml PHP module required for DAV but not installed."
+msgstr "Feil: XML PHP modul er påkrevet for DAV, men den er ikke installert."
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:254
-msgid "Current GNU social API is"
+#: ../../Zotlabs/Module/Setup.php:591
+msgid "Error: zip PHP module required but not installed."
msgstr ""
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:258
-msgid "Cancel GNU social Connection"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:595
+#, fuzzy
+#| msgid "Error: openssl PHP module required but not installed."
+msgid "Error: intl PHP module required but not installed."
+msgstr "Feil: openssl PHP-modul er påkrevd, men er ikke installert."
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:270
-#: ../../extend/addon/hzaddons/twitter/Mod_Twitter.php:145
-msgid "Currently connected to: "
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:614 ../../Zotlabs/Module/Setup.php:623
+msgid ".htconfig.php is writable"
+msgstr ".htconfig.php kan skrives til"
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:275
+#: ../../Zotlabs/Module/Setup.php:619
msgid ""
-"<strong>Note</strong>: Due your privacy settings (<em>Hide your profile "
-"details from unknown viewers?</em>) the link potentially included in public "
-"postings relayed to GNU social will lead the visitor to a blank page "
-"informing the visitor that the access to your profile has been restricted."
-msgstr ""
-
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:280
-msgid "Post to GNU social by default"
+"The web installer needs to be able to create a file called \".htconfig.php\" "
+"in the top folder of your web server and it is unable to do so."
msgstr ""
+"Web-installasjonen må kunne lage en fil kalt \".htconfig.php\" i "
+"toppkatalogen til web-tjeneren din, men dette får den ikke til."
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:280
+#: ../../Zotlabs/Module/Setup.php:620
msgid ""
-"If enabled your public postings will be posted to the associated GNU-social "
-"account by default"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:289
-#: ../../extend/addon/hzaddons/twitter/Mod_Twitter.php:169
-msgid "Clear OAuth configuration"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/statusnet/Mod_Statusnet.php:301
-msgid "GNU-Social Crosspost Connector"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/statusnet/statusnet.php:145
-msgid "Post to GNU social"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/statusnet/statusnet.php:594
-msgid "API URL"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/statusnet/statusnet.php:597
-msgid "Application name"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/ljpost/ljpost.php:49
-msgid "Post to Livejournal"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/ljpost/ljpost.php:127
-msgid "Posted by"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/ljpost/ljpost.php:134
-#: ../../extend/addon/hzaddons/dwpost/Mod_Dwpost.php:67
-#: ../../extend/addon/hzaddons/dwpost/dwpost.php:134
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:381
-#: ../../extend/addon/hzaddons/wppost/wppost.php:175
-#: ../../extend/addon/hzaddons/wppost/Mod_Wppost.php:96
-msgid "Source"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/ljpost/Mod_Ljpost.php:53
-msgid "Livejournal username"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/ljpost/Mod_Ljpost.php:57
-msgid "Livejournal password"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/ljpost/Mod_Ljpost.php:61
-msgid "Post to Livejournal by default"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/ljpost/Mod_Ljpost.php:65
-msgid "Send wall-to-wall posts to Livejournal"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/ljpost/Mod_Ljpost.php:69
-#: ../../extend/addon/hzaddons/dwpost/Mod_Dwpost.php:63
-#: ../../extend/addon/hzaddons/wppost/Mod_Wppost.php:92
-msgid "Add link to original post"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/ljpost/Mod_Ljpost.php:77
-msgid "Livejournal Crosspost Connector"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/pageheader/Mod_Pageheader.php:22
-msgid "pageheader Settings saved."
-msgstr ""
-
-#: ../../extend/addon/hzaddons/pageheader/Mod_Pageheader.php:41
-msgid "Message to display on every page on this server"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/pageheader/Mod_Pageheader.php:49
-msgid "Page Header"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/rainbowtag/Mod_Rainbowtag.php:22
-msgid "Rainbow Tag App"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/rainbowtag/Mod_Rainbowtag.php:23
-msgid "Add some colour to tag clouds"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/rainbowtag/Mod_Rainbowtag.php:30
-msgid "Rainbow Tag"
+"This is most often a permission setting, as the web server may not be able "
+"to write files in your folder - even if you can."
msgstr ""
+"Dette er oftest tillatelsesinnstilling, ettersom webtjeneren kanskje kan "
+"skrive til filer i din mappe - selv om du kan."
-#: ../../extend/addon/hzaddons/visage/Mod_Visage.php:23
-msgid "Recent Channel/Profile Viewers"
+#: ../../Zotlabs/Module/Setup.php:621
+msgid "Please see install/INSTALL.txt for additional information."
msgstr ""
-#: ../../extend/addon/hzaddons/visage/Mod_Visage.php:34
-msgid "No entries."
+#: ../../Zotlabs/Module/Setup.php:637
+msgid ""
+"This software uses the Smarty3 template engine to render its web views. "
+"Smarty3 compiles templates to PHP to speed up rendering."
msgstr ""
-#: ../../extend/addon/hzaddons/testdrive/testdrive.php:104
+#: ../../Zotlabs/Module/Setup.php:638
#, php-format
-msgid "Your account on %s will expire in a few days."
-msgstr ""
-
-#: ../../extend/addon/hzaddons/testdrive/testdrive.php:105
-msgid "Your test account is about to expire."
-msgstr ""
-
-#: ../../extend/addon/hzaddons/nofed/Mod_Nofed.php:21
-msgid "nofed Settings saved."
-msgstr ""
-
-#: ../../extend/addon/hzaddons/nofed/Mod_Nofed.php:40
-msgid "Federate posts by default"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/nofed/Mod_Nofed.php:48
-msgid "No Federation"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/nofed/nofed.php:47
-msgid "Federate"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/likebanner/likebanner.php:51
-msgid "Your Webbie:"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/likebanner/likebanner.php:54
-msgid "Fontsize (px):"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/likebanner/likebanner.php:68
-msgid "Link:"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/likebanner/likebanner.php:70
-msgid "Like us on Hubzilla"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/likebanner/likebanner.php:72
-msgid "Embed:"
+msgid ""
+"In order to store these compiled templates, the web server needs to have "
+"write access to the directory %s under the top level web folder."
msgstr ""
-#: ../../extend/addon/hzaddons/piwik/piwik.php:85
+#: ../../Zotlabs/Module/Setup.php:639 ../../Zotlabs/Module/Setup.php:660
msgid ""
-"This website is tracked using the <a href='http://www.piwik.org'>Piwik</a> "
-"analytics tool."
+"Please ensure that the user that your web server runs as (e.g. www-data) has "
+"write access to this folder."
msgstr ""
+"Vennligst sikre at brukeren som din web-tjeneste kjører som (for eksempel "
+"www-data) har skrivetilgang til denne katalogen."
-#: ../../extend/addon/hzaddons/piwik/piwik.php:88
+#: ../../Zotlabs/Module/Setup.php:640
#, php-format
msgid ""
-"If you do not want that your visits are logged this way you <a href='%s'>can "
-"set a cookie to prevent Piwik from tracking further visits of the site</a> "
-"(opt-out)."
+"Note: as a security measure, you should give the web server write access to "
+"%s only--not the template files (.tpl) that it contains."
msgstr ""
+"Merknad: som et sikkerhetstiltak bør du bare gi webtjerenn skrivetilgang til "
+"%s - ikke til malfilene (.tpl) som den inneholder."
-#: ../../extend/addon/hzaddons/piwik/piwik.php:96
-msgid "Piwik Base URL"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:643
+#, php-format
+msgid "%s is writable"
+msgstr "%s kan skrives til"
-#: ../../extend/addon/hzaddons/piwik/piwik.php:96
+#: ../../Zotlabs/Module/Setup.php:659
msgid ""
-"Absolute path to your Piwik installation. (without protocol (http/s), with "
-"trailing slash)"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/piwik/piwik.php:97
-msgid "Site ID"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/piwik/piwik.php:98
-msgid "Show opt-out cookie link?"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/piwik/piwik.php:99
-msgid "Asynchronous tracking"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/piwik/piwik.php:100
-msgid "Enable frontend JavaScript error tracking"
+"This software uses the store directory to save uploaded files. The web "
+"server needs to have write access to the store directory under the top level "
+"web folder"
msgstr ""
-#: ../../extend/addon/hzaddons/piwik/piwik.php:100
-msgid "This feature requires Piwik >= 2.2.0"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:663
+msgid "store is writable"
+msgstr "lageret kan skrives til"
-#: ../../extend/addon/hzaddons/startpage/Mod_Startpage.php:60
-msgid "Page to load after login"
+#: ../../Zotlabs/Module/Setup.php:695
+msgid ""
+"SSL certificate cannot be validated. Fix certificate or disable https access "
+"to this site."
msgstr ""
+"SSL-sertifikatet kan ikke kontrolleres. Fiks sertifikatet eller skru av "
+"https tilgang til dette nettstedet."
-#: ../../extend/addon/hzaddons/startpage/Mod_Startpage.php:60
+#: ../../Zotlabs/Module/Setup.php:696
msgid ""
-"Examples: &quot;apps&quot;, &quot;network?f=&gid=37&quot; (privacy "
-"collection), &quot;channel&quot; or &quot;notifications/system&quot; (leave "
-"blank for default network page (grid)."
+"If you have https access to your website or allow connections to TCP port "
+"443 (the https: port), you MUST use a browser-valid certificate. You MUST "
+"NOT use self-signed certificates!"
msgstr ""
+"Hvis du har HTTPS-tilgang til ditt nettsted eller tillater forbindelser til "
+"TCP port 443 (HTTPS-porten), så MÅ du bruke nettlesergodkjent sertifkater. "
+"Du MÅ IKKE bruke egensignert sertifikater!"
-#: ../../extend/addon/hzaddons/startpage/Mod_Startpage.php:68
-msgid "Startpage"
+#: ../../Zotlabs/Module/Setup.php:697
+msgid ""
+"This restriction is incorporated because public posts from you may for "
+"example contain references to images on your own hub."
msgstr ""
+"Denne begrensningen er tatt inn fordi offentlige innlegg fra deg kan for "
+"eksempel inneholde referanser til bilder på din egen hub."
-#: ../../extend/addon/hzaddons/twitter/Mod_Twitter.php:65
-msgid "Twitter settings updated."
+#: ../../Zotlabs/Module/Setup.php:698
+msgid ""
+"If your certificate is not recognized, members of other sites (who may "
+"themselves have valid certificates) will get a warning message on their own "
+"site complaining about security issues."
msgstr ""
+"Hvis sertifikatet ditt ikke gjenkjennes, så vil medlemmer på andre "
+"nettsteder (som selv kan ha godkjente sertifikater) få en beskjed med en "
+"advarsel på deres eget nettsted som klager over sikkerhetsproblemer."
-#: ../../extend/addon/hzaddons/twitter/Mod_Twitter.php:101
+#: ../../Zotlabs/Module/Setup.php:699
msgid ""
-"No consumer key pair for Twitter found. Please contact your site "
-"administrator."
+"This can cause usability issues elsewhere (not just on your own site) so we "
+"must insist on this requirement."
msgstr ""
+"Dette kan gi problemer med brukervennlighet (ikke bare på ditt eget "
+"nettsted), så vi må insistere på dette kravet."
-#: ../../extend/addon/hzaddons/twitter/Mod_Twitter.php:123
+#: ../../Zotlabs/Module/Setup.php:700
msgid ""
-"At this Hubzilla instance the Twitter plugin was enabled but you have not "
-"yet connected your account to your Twitter account. To do so click the "
-"button below to get a PIN from Twitter which you have to copy into the input "
-"box below and submit the form. Only your <strong>public</strong> posts will "
-"be posted to Twitter."
+"Providers are available that issue free certificates which are browser-valid."
msgstr ""
+"Det finnes tilbydere som utsteder gratis sertifikater som er gyldige i "
+"nettlesere."
-#: ../../extend/addon/hzaddons/twitter/Mod_Twitter.php:125
-msgid "Log in with Twitter"
+#: ../../Zotlabs/Module/Setup.php:701
+msgid ""
+"If you are confident that the certificate is valid and signed by a trusted "
+"authority, check to see if you have failed to install an intermediate cert. "
+"These are not normally required by browsers, but are required for server-to-"
+"server communications."
msgstr ""
-#: ../../extend/addon/hzaddons/twitter/Mod_Twitter.php:128
-msgid "Copy the PIN from Twitter here"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:703
+msgid "SSL certificate validation"
+msgstr "SSL sertifikat-kontroll"
-#: ../../extend/addon/hzaddons/twitter/Mod_Twitter.php:150
+#: ../../Zotlabs/Module/Setup.php:709
msgid ""
-"<strong>Note:</strong> Due your privacy settings (<em>Hide your profile "
-"details from unknown viewers?</em>) the link potentially included in public "
-"postings relayed to Twitter will lead the visitor to a blank page informing "
-"the visitor that the access to your profile has been restricted."
+"Url rewrite in .htaccess is not working. Check your server "
+"configuration.Test: "
msgstr ""
+"URL omskriving (rewrite) i .htaccess virker ikke. Sjekk konfigurasjonen til "
+"tjeneren din. Test: "
-#: ../../extend/addon/hzaddons/twitter/Mod_Twitter.php:155
-msgid "Twitter post length"
-msgstr ""
+#: ../../Zotlabs/Module/Setup.php:712
+msgid "Url rewrite is working"
+msgstr "URL rewrite virker"
-#: ../../extend/addon/hzaddons/twitter/Mod_Twitter.php:155
-msgid "Maximum tweet length"
+#: ../../Zotlabs/Module/Setup.php:725
+msgid ""
+"The database configuration file \".htconfig.php\" could not be written. "
+"Please use the enclosed text to create a configuration file in your web "
+"server root."
msgstr ""
+"Databasekonfigurasjonsfilen \".htconfig.php\" kunne ikke skrives. Vennligst "
+"bruk den medfølgende teksten for å lage en konfigurasjonsfil i toppkatalogen "
+"av din web-tjener."
-#: ../../extend/addon/hzaddons/twitter/Mod_Twitter.php:160
-msgid "Send public postings to Twitter by default"
+#: ../../Zotlabs/Module/Setup.php:800
+msgid "<h1>What next?</h1>"
msgstr ""
-#: ../../extend/addon/hzaddons/twitter/Mod_Twitter.php:160
+#: ../../Zotlabs/Module/Setup.php:801
msgid ""
-"If enabled your public postings will be posted to the associated Twitter "
-"account by default"
+"IMPORTANT: You will need to [manually] setup a scheduled task for the poller."
msgstr ""
+"VIKTIG: Du må [manuelt] sette opp en automatisert tidfestet oppgave til "
+"bakgrunnshenteren."
-#: ../../extend/addon/hzaddons/twitter/Mod_Twitter.php:179
-msgid "Twitter Crosspost Connector"
+#: ../../Zotlabs/Module/Chanview.php:132
+msgid "toggle full screen mode"
msgstr ""
-#: ../../extend/addon/hzaddons/twitter/twitter.php:109
-msgid "Post to Twitter"
-msgstr ""
+#: ../../Zotlabs/Module/Profperm.php:35 ../../Zotlabs/Module/Profperm.php:66
+msgid "Invalid profile identifier."
+msgstr "Ugyldig profil-identifikator."
-#: ../../extend/addon/hzaddons/twitter/twitter.php:502
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:95
-msgid "Submit Settings"
-msgstr ""
+#: ../../Zotlabs/Module/Profperm.php:114
+msgid "Profile Visibility Editor"
+msgstr "Endre profilsynlighet"
-#: ../../extend/addon/hzaddons/fuzzloc/Mod_Fuzzloc.php:22
-msgid "Fuzzloc Settings updated."
-msgstr ""
+#: ../../Zotlabs/Module/Profperm.php:118
+msgid "Click on a contact to add or remove."
+msgstr "Klikk på en kontakt for å legge til eller fjerne."
-#: ../../extend/addon/hzaddons/fuzzloc/Mod_Fuzzloc.php:38
-msgid "Minimum offset in meters"
-msgstr ""
+#: ../../Zotlabs/Module/Profperm.php:127
+msgid "Visible To"
+msgstr "Synlig for"
-#: ../../extend/addon/hzaddons/fuzzloc/Mod_Fuzzloc.php:42
-msgid "Maximum offset in meters"
-msgstr ""
+#: ../../Zotlabs/Module/Profperm.php:143
+#: ../../Zotlabs/Module/Connections.php:221
+msgid "All Connections"
+msgstr "Alle forbindelser"
-#: ../../extend/addon/hzaddons/fuzzloc/Mod_Fuzzloc.php:51
-msgid "Fuzzy Location"
+#: ../../Zotlabs/Module/Lang.php:20
+msgid "Language App"
msgstr ""
-#: ../../extend/addon/hzaddons/diaspora/import_diaspora.php:18
-msgid "No username found in import file."
-msgstr "Ingen brukernavn ble funnet i importfilen."
-
-#: ../../extend/addon/hzaddons/diaspora/import_diaspora.php:140
-msgid "Import completed."
-msgstr "Import ferdig."
+#: ../../Zotlabs/Module/Lang.php:80
+msgid "Select an alternate language"
+msgstr "Velg et annet språk"
-#: ../../extend/addon/hzaddons/diaspora/Receiver.php:1632
-#, php-format
-msgid "%1$s dislikes %2$s's %3$s"
-msgstr ""
+#: ../../Zotlabs/Module/Rpost.php:117 ../../Zotlabs/Module/Editpost.php:114
+msgid "Edit post"
+msgstr "Endre innlegg"
-#: ../../extend/addon/hzaddons/diaspora/Mod_Diaspora.php:43
-msgid "Diaspora Protocol Settings updated."
-msgstr ""
+#: ../../Zotlabs/Module/Editpost.php:38 ../../Zotlabs/Module/Editpost.php:43
+msgid "Item is not editable"
+msgstr "Elementet kan ikke endres"
-#: ../../extend/addon/hzaddons/diaspora/Mod_Diaspora.php:52
-msgid ""
-"The diaspora protocol does not support location independence. Connections "
-"you make within that network may be unreachable from alternate channel "
-"locations."
-msgstr ""
+#: ../../Zotlabs/Module/Connections.php:58
+#: ../../Zotlabs/Module/Connections.php:116
+msgid "Active"
+msgstr "Aktiv"
-#: ../../extend/addon/hzaddons/diaspora/Mod_Diaspora.php:80
-msgid "Prevent your hashtags from being redirected to other sites"
-msgstr ""
+#: ../../Zotlabs/Module/Connections.php:63
+#: ../../Zotlabs/Module/Connections.php:185
+#: ../../Zotlabs/Module/Connections.php:296
+msgid "Blocked"
+msgstr "Blokkert"
-#: ../../extend/addon/hzaddons/diaspora/Mod_Diaspora.php:84
-msgid "Sign and forward posts and comments with no existing Diaspora signature"
-msgstr ""
+#: ../../Zotlabs/Module/Connections.php:68
+#: ../../Zotlabs/Module/Connections.php:192
+#: ../../Zotlabs/Module/Connections.php:295
+msgid "Ignored"
+msgstr "Ignorert"
-#: ../../extend/addon/hzaddons/diaspora/Mod_Diaspora.php:89
-msgid "Followed hashtags (comma separated, do not include the #)"
-msgstr ""
+#: ../../Zotlabs/Module/Connections.php:73
+#: ../../Zotlabs/Module/Connections.php:206
+#: ../../Zotlabs/Module/Connections.php:294
+msgid "Hidden"
+msgstr "Skjult"
-#: ../../extend/addon/hzaddons/diaspora/Mod_Diaspora.php:98
-msgid "Diaspora Protocol"
-msgstr ""
+#: ../../Zotlabs/Module/Connections.php:78
+#: ../../Zotlabs/Module/Connections.php:199
+msgid "Archived/Unreachable"
+msgstr "Arkivert/utilgjengelig"
-#: ../../extend/addon/hzaddons/diaspora/diaspora.php:81
-msgid ""
-"Please install the statistics addon to be able to configure a diaspora relay"
-msgstr ""
+#: ../../Zotlabs/Module/Connections.php:161
+msgid "Active Connections"
+msgstr "Aktive forbindelser"
-#: ../../extend/addon/hzaddons/diaspora/diaspora.php:91
-msgid "Diaspora Relay Handle"
+#: ../../Zotlabs/Module/Connections.php:164
+msgid "Show active connections"
msgstr ""
-#: ../../extend/addon/hzaddons/diaspora/diaspora.php:91
-msgid "Address of a diaspora relay. Example: relay@diasporarelay.tld"
-msgstr ""
+#: ../../Zotlabs/Module/Connections.php:171
+msgid "Show pending (new) connections"
+msgstr "Vis ventende (nye) forbindelser"
-#: ../../extend/addon/hzaddons/diaspora/diaspora.php:111
-msgid "Diaspora relay could not be imported"
-msgstr ""
+#: ../../Zotlabs/Module/Connections.php:188
+msgid "Only show blocked connections"
+msgstr "Vis bare forbindelser som er blokkert"
-#: ../../extend/addon/hzaddons/diaspora/diaspora.php:1103
-msgid "No subject"
-msgstr ""
+#: ../../Zotlabs/Module/Connections.php:195
+msgid "Only show ignored connections"
+msgstr "Vis bare ignorerte forbindelser"
-#: ../../extend/addon/hzaddons/redphotos/redphotos.php:106
-msgid "Photos imported"
+#: ../../Zotlabs/Module/Connections.php:202
+msgid "Only show archived/unreachable connections"
msgstr ""
-#: ../../extend/addon/hzaddons/redphotos/redphotos.php:129
-msgid "Redmatrix Photo Album Import"
-msgstr ""
+#: ../../Zotlabs/Module/Connections.php:209
+msgid "Only show hidden connections"
+msgstr "Vis bare skjulte forbindelser"
-#: ../../extend/addon/hzaddons/redphotos/redphotos.php:130
-msgid "This will import all your Redmatrix photo albums to this channel."
-msgstr ""
+#: ../../Zotlabs/Module/Connections.php:224
+msgid "Show all connections"
+msgstr "Vis alle forbindelser"
-#: ../../extend/addon/hzaddons/redphotos/redphotos.php:131
-#: ../../extend/addon/hzaddons/redfiles/redfiles.php:121
-msgid "Redmatrix Server base URL"
-msgstr ""
+#: ../../Zotlabs/Module/Connections.php:292
+msgid "Pending approval"
+msgstr "Venter på godkjenning"
-#: ../../extend/addon/hzaddons/redphotos/redphotos.php:132
-#: ../../extend/addon/hzaddons/redfiles/redfiles.php:122
-msgid "Redmatrix Login Username"
-msgstr ""
+#: ../../Zotlabs/Module/Connections.php:293
+msgid "Archived"
+msgstr "Arkivert"
-#: ../../extend/addon/hzaddons/redphotos/redphotos.php:133
-#: ../../extend/addon/hzaddons/redfiles/redfiles.php:123
-msgid "Redmatrix Login Password"
+#: ../../Zotlabs/Module/Connections.php:297
+msgid "Not connected at this location"
msgstr ""
-#: ../../extend/addon/hzaddons/redphotos/redphotos.php:134
-msgid "Import just this album"
-msgstr ""
+#: ../../Zotlabs/Module/Connections.php:336
+#, php-format
+msgid "%1$s [%2$s]"
+msgstr "%1$s [%2$s]"
-#: ../../extend/addon/hzaddons/redphotos/redphotos.php:134
-msgid "Leave blank to import all albums"
-msgstr ""
+#: ../../Zotlabs/Module/Connections.php:337
+msgid "Edit connection"
+msgstr "Endre forbindelse"
-#: ../../extend/addon/hzaddons/redphotos/redphotos.php:135
-msgid "Maximum count to import"
-msgstr ""
+#: ../../Zotlabs/Module/Connections.php:339
+msgid "Delete connection"
+msgstr "Slett forbindelse"
-#: ../../extend/addon/hzaddons/redphotos/redphotos.php:135
-msgid "0 or blank to import all available"
-msgstr ""
+#: ../../Zotlabs/Module/Connections.php:345
+msgid "Channel address"
+msgstr "Kanaladresse"
-#: ../../extend/addon/hzaddons/skeleton/Mod_Skeleton.php:38
-msgid "Some setting"
+#: ../../Zotlabs/Module/Connections.php:350
+msgid "Call"
msgstr ""
-#: ../../extend/addon/hzaddons/skeleton/Mod_Skeleton.php:38
-msgid "A setting"
-msgstr ""
+#: ../../Zotlabs/Module/Connections.php:352
+msgid "Status"
+msgstr "Status"
-#: ../../extend/addon/hzaddons/skeleton/Mod_Skeleton.php:46
-msgid "Skeleton Settings"
-msgstr ""
+#: ../../Zotlabs/Module/Connections.php:355
+msgid "Connected"
+msgstr "Forbundet"
-#: ../../extend/addon/hzaddons/qrator/qrator.php:48
-msgid "QR code"
-msgstr ""
+#: ../../Zotlabs/Module/Connections.php:359
+msgid "Ignore connection"
+msgstr "Ignorer forbindelse"
-#: ../../extend/addon/hzaddons/qrator/qrator.php:63
-msgid "QR Generator"
-msgstr ""
+#: ../../Zotlabs/Module/Connections.php:361
+msgid "Recent activity"
+msgstr "Nylig aktivitet"
-#: ../../extend/addon/hzaddons/qrator/qrator.php:64
-msgid "Enter some text"
+#: ../../Zotlabs/Module/Connections.php:367
+msgid "Connect at this location"
msgstr ""
-#: ../../extend/addon/hzaddons/dwpost/Mod_Dwpost.php:26
-msgid "Dreamwidth Crosspost Connector Settings saved."
-msgstr ""
+#: ../../Zotlabs/Module/Connections.php:405
+msgid "Search your connections"
+msgstr "Søk blant dine forbindelser"
-#: ../../extend/addon/hzaddons/dwpost/Mod_Dwpost.php:51
-msgid "Dreamwidth username"
+#: ../../Zotlabs/Module/Connections.php:406
+msgid "Contact search"
msgstr ""
-#: ../../extend/addon/hzaddons/dwpost/Mod_Dwpost.php:55
-msgid "Dreamwidth password"
-msgstr ""
+#: ../../Zotlabs/Module/Cdav.php:819
+msgid "Calendar entries imported."
+msgstr "Kalenderhendelsene er importert."
-#: ../../extend/addon/hzaddons/dwpost/Mod_Dwpost.php:59
-msgid "Post to Dreamwidth by default"
-msgstr ""
+#: ../../Zotlabs/Module/Cdav.php:821
+msgid "No calendar entries found."
+msgstr "Ingen kalenderhendelser funnet."
-#: ../../extend/addon/hzaddons/dwpost/Mod_Dwpost.php:67
-#: ../../extend/addon/hzaddons/wppost/Mod_Wppost.php:96
-msgid "Link description (default:"
-msgstr ""
+#: ../../Zotlabs/Module/Cdav.php:1000
+msgid "Event title"
+msgstr "Tittel på hendelse"
-#: ../../extend/addon/hzaddons/dwpost/Mod_Dwpost.php:75
-msgid "Dreamwidth Crosspost Connector"
-msgstr ""
+#: ../../Zotlabs/Module/Cdav.php:1001
+msgid "Start date and time"
+msgstr "Startdato og tidspunkt"
-#: ../../extend/addon/hzaddons/dwpost/dwpost.php:49
-msgid "Post to Dreamwidth"
+#: ../../Zotlabs/Module/Cdav.php:1002
+msgid "End date and time"
msgstr ""
-#: ../../extend/addon/hzaddons/upload_limits/upload_limits.php:25
-msgid "Show Upload Limits"
-msgstr ""
+#: ../../Zotlabs/Module/Cdav.php:1003
+#, fuzzy
+msgid "Timezone:"
+msgstr "Tidssone"
-#: ../../extend/addon/hzaddons/upload_limits/upload_limits.php:27
-msgid "Hubzilla configured maximum size: "
-msgstr ""
+#: ../../Zotlabs/Module/Cdav.php:1029
+#, fuzzy
+msgid "Month"
+msgstr "måned"
-#: ../../extend/addon/hzaddons/upload_limits/upload_limits.php:28
-msgid "PHP upload_max_filesize: "
-msgstr ""
+#: ../../Zotlabs/Module/Cdav.php:1030
+#, fuzzy
+msgid "Week"
+msgstr "uke"
-#: ../../extend/addon/hzaddons/upload_limits/upload_limits.php:29
-msgid "PHP post_max_size (must be larger than upload_max_filesize): "
-msgstr ""
+#: ../../Zotlabs/Module/Cdav.php:1031
+#, fuzzy
+msgid "Day"
+msgstr "dag"
-#: ../../extend/addon/hzaddons/nsabait/Mod_Nsabait.php:23
-msgid "NSA Bait App"
+#: ../../Zotlabs/Module/Cdav.php:1032
+msgid "List month"
msgstr ""
-#: ../../extend/addon/hzaddons/nsabait/Mod_Nsabait.php:25
-msgid "Make yourself a political target."
+#: ../../Zotlabs/Module/Cdav.php:1033
+msgid "List week"
msgstr ""
-#: ../../extend/addon/hzaddons/content_import/Mod_content_import.php:27
-msgid "No server specified"
+#: ../../Zotlabs/Module/Cdav.php:1034
+msgid "List day"
msgstr ""
-#: ../../extend/addon/hzaddons/content_import/Mod_content_import.php:72
-msgid "Posts imported"
+#: ../../Zotlabs/Module/Cdav.php:1042
+msgid "More"
msgstr ""
-#: ../../extend/addon/hzaddons/content_import/Mod_content_import.php:112
-msgid "Files imported"
+#: ../../Zotlabs/Module/Cdav.php:1043
+msgid "Less"
msgstr ""
-#: ../../extend/addon/hzaddons/content_import/Mod_content_import.php:134
-msgid ""
-"This will import all your conversations and cloud files from a cloned "
-"channel on another server. This may take a while if you have lots of posts "
-"and or files."
+#: ../../Zotlabs/Module/Cdav.php:1045
+msgid "Select calendar"
msgstr ""
-#: ../../extend/addon/hzaddons/content_import/Mod_content_import.php:135
-msgid "Include posts"
+#: ../../Zotlabs/Module/Cdav.php:1048
+msgid "Delete all"
msgstr ""
-#: ../../extend/addon/hzaddons/content_import/Mod_content_import.php:135
-msgid "Conversations, Articles, Cards, and other posted content"
+#: ../../Zotlabs/Module/Cdav.php:1051
+msgid "Sorry! Editing of recurrent events is not yet implemented."
msgstr ""
-#: ../../extend/addon/hzaddons/content_import/Mod_content_import.php:136
-msgid "Include files"
+#: ../../Zotlabs/Module/Cdav.php:1052
+msgid ""
+"Could not fetch calendar resource. The selected calendar might be disabled."
msgstr ""
-#: ../../extend/addon/hzaddons/content_import/Mod_content_import.php:136
-msgid "Files, Photos and other cloud storage"
+#: ../../Zotlabs/Module/Cdav.php:1452
+msgid "Default Calendar"
msgstr ""
-#: ../../extend/addon/hzaddons/content_import/Mod_content_import.php:137
-msgid "Original Server base URL"
+#: ../../Zotlabs/Module/Cdav.php:1463
+msgid "Default Addressbook"
msgstr ""
-#: ../../extend/addon/hzaddons/rtof/Mod_Rtof.php:24
-msgid "Friendica Crosspost Connector Settings saved."
-msgstr ""
+#: ../../Zotlabs/Module/Dirsearch.php:32
+msgid "This directory server requires an access token"
+msgstr "Denne katalogtjeneren krever en tilgangsnøkkel (access token)"
-#: ../../extend/addon/hzaddons/rtof/Mod_Rtof.php:47
-msgid "Send public postings to Friendica by default"
+#: ../../Zotlabs/Module/Oauth2.php:54
+msgid "Name and Secret are required"
msgstr ""
-#: ../../extend/addon/hzaddons/rtof/Mod_Rtof.php:51
-msgid "Friendica API Path"
+#: ../../Zotlabs/Module/Oauth2.php:114
+msgid "Add OAuth2 application"
msgstr ""
-#: ../../extend/addon/hzaddons/rtof/Mod_Rtof.php:51
-#: ../../extend/addon/hzaddons/redred/Mod_Redred.php:65
-msgid "https://{sitename}/api"
+#: ../../Zotlabs/Module/Oauth2.php:120 ../../Zotlabs/Module/Oauth2.php:147
+msgid "Grant Types"
msgstr ""
-#: ../../extend/addon/hzaddons/rtof/Mod_Rtof.php:55
-msgid "Friendica login name"
+#: ../../Zotlabs/Module/Oauth2.php:120 ../../Zotlabs/Module/Oauth2.php:121
+msgid "leave blank unless your application sepcifically requires this"
msgstr ""
-#: ../../extend/addon/hzaddons/rtof/Mod_Rtof.php:59
-msgid "Friendica password"
+#: ../../Zotlabs/Module/Oauth2.php:121 ../../Zotlabs/Module/Oauth2.php:148
+msgid "Authorization scope"
msgstr ""
-#: ../../extend/addon/hzaddons/rtof/Mod_Rtof.php:67
-msgid "Friendica Crosspost Connector"
+#: ../../Zotlabs/Module/Oauth2.php:132
+msgid "OAuth2 Application not found."
msgstr ""
-#: ../../extend/addon/hzaddons/rtof/rtof.php:51
-msgid "Post to Friendica"
+#: ../../Zotlabs/Module/Oauth2.php:147 ../../Zotlabs/Module/Oauth2.php:148
+msgid "leave blank unless your application specifically requires this"
msgstr ""
-#: ../../extend/addon/hzaddons/notifyadmin/notifyadmin.php:34
-msgid "New registration"
+#: ../../Zotlabs/Module/Oauth2.php:190
+msgid "Connected OAuth2 Apps"
msgstr ""
-#: ../../extend/addon/hzaddons/notifyadmin/notifyadmin.php:42
+#: ../../Zotlabs/Module/Settings/Display.php:126
#, php-format
-msgid "Message sent to %s. New account registration: %s"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/redfiles/redfiles.php:119
-msgid "Redmatrix File Storage Import"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/redfiles/redfiles.php:120
-msgid "This will import all your Redmatrix cloud files to this channel."
-msgstr ""
-
-#: ../../extend/addon/hzaddons/mailtest/mailtest.php:19
-msgid "Send test email"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/mailtest/mailtest.php:50
-#: ../../extend/addon/hzaddons/hubwall/hubwall.php:50
-msgid "No recipients found."
-msgstr ""
-
-#: ../../extend/addon/hzaddons/mailtest/mailtest.php:66
-msgid "Mail sent."
-msgstr ""
-
-#: ../../extend/addon/hzaddons/mailtest/mailtest.php:68
-msgid "Sending of mail failed."
-msgstr ""
-
-#: ../../extend/addon/hzaddons/mailtest/mailtest.php:77
-msgid "Mail Test"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/mailtest/mailtest.php:96
-#: ../../extend/addon/hzaddons/hubwall/hubwall.php:93
-msgid "Message subject"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:57
-msgid "Errors encountered deleting database table "
-msgstr ""
+msgid "%s - (Experimental)"
+msgstr "%s - (Eksperimentelt)"
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:96
-msgid "Drop tables when uninstalling?"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Display.php:182
+msgid "Display Settings"
+msgstr "Visningsinnstillinger"
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:96
-msgid ""
-"If checked, the Rendezvous database tables will be deleted when the plugin "
-"is uninstalled."
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Display.php:183
+msgid "Theme Settings"
+msgstr "Temainnstillinger"
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:97
-msgid "Mapbox Access Token"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Display.php:184
+msgid "Custom Theme Settings"
+msgstr "Tilpassede temainnstillinger"
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:97
-msgid ""
-"If you enter a Mapbox access token, it will be used to retrieve map tiles "
-"from Mapbox instead of the default OpenStreetMap tile server."
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Display.php:185
+msgid "Content Settings"
+msgstr "Innholdsinnstillinger"
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:162
-msgid "Rendezvous"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Display.php:191
+msgid "Display Theme:"
+msgstr "Visningstema:"
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:167
-msgid ""
-"This identity has been deleted by another member due to inactivity. Please "
-"press the \"New identity\" button or refresh the page to register a new "
-"identity. You may use the same name."
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Display.php:192
+msgid "Select scheme"
+msgstr "Velg skjema"
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:168
-msgid "Welcome to Rendezvous!"
+#: ../../Zotlabs/Module/Settings/Display.php:194
+msgid "Threaded conversation view"
msgstr ""
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:169
-msgid ""
-"Enter your name to join this rendezvous. To begin sharing your location with "
-"the other members, tap the GPS control. When your location is discovered, a "
-"red dot will appear and others will be able to see you on the map."
+#: ../../Zotlabs/Module/Settings/Display.php:194
+msgid "Display replies below their parent message (default yes)"
msgstr ""
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:171
-msgid "Let's meet here"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Display.php:195
+msgid "Enable user zoom on mobile devices"
+msgstr "Skru på brukerstyrt zoom på mobile enheter"
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:174
-msgid "New marker"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Display.php:196
+msgid "Update browser every xx seconds"
+msgstr "Oppdater nettleser hvert xx sekunder"
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:175
-msgid "Edit marker"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Display.php:196
+msgid "Minimum of 10 seconds, no maximum"
+msgstr "Minimum 10 sekunder, ikke noe maksimum"
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:176
-msgid "New identity"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Display.php:197
+msgid "Maximum number of conversations to load at any time:"
+msgstr "Maksimalt antall samtaler å laste samtidig:"
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:177
-msgid "Delete marker"
+#: ../../Zotlabs/Module/Settings/Display.php:197
+msgid "Maximum of 30 items"
msgstr ""
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:178
-msgid "Delete member"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Display.php:198
+msgid "Show emoticons (smilies) as images"
+msgstr "Vis emoticons (smilefjes) som bilder"
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:179
-msgid "Edit proximity alert"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Display.php:199
+msgid "Link post titles to source"
+msgstr "Lenk innleggets tittel til kilden"
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:180
-msgid ""
-"A proximity alert will be issued when this member is within a certain radius "
-"of you.<br><br>Enter a radius in meters (0 to disable):"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Display.php:201
+msgid "Display new member quick links menu"
+msgstr "Vis hurtiglenker for nye medlemmer"
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:180
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:185
-msgid "distance"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Network.php:42
+#: ../../Zotlabs/Module/Settings/Channel_home.php:46
+msgid "Max height of content (in pixels)"
+msgstr "Maks høyde for innhold (i piksler)"
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:181
-msgid "Proximity alert distance (meters)"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Network.php:44
+#: ../../Zotlabs/Module/Settings/Channel_home.php:48
+msgid "Click to expand content exceeding this height"
+msgstr "Klikk for å vise hele innlegg som overskrider denne grensen"
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:182
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:184
-msgid ""
-"A proximity alert will be issued when you are within a certain radius of the "
-"marker location.<br><br>Enter a radius in meters (0 to disable):"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Network.php:59
+msgid "Stream Settings"
+msgstr "Instillinger for tidslinjen"
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:183
-msgid "Marker proximity alert"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel_home.php:61
+msgid "Personal menu to display in your channel pages"
+msgstr "Personlig meny som kan vises på dine kanalsider"
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:186
-msgid "Reminder note"
+#: ../../Zotlabs/Module/Settings/Channel_home.php:88
+msgid "Channel Home Settings"
msgstr ""
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:187
-msgid ""
-"Enter a note to be displayed when you are within the specified proximity..."
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Account.php:23
+msgid "Not valid email."
+msgstr "Ikke gyldig e-post."
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:199
-msgid "Add new rendezvous"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Account.php:26
+msgid "Protected email address. Cannot change to that email."
+msgstr "Beskyttet e-postadresse. Kan ikke endre til den e-postadressen."
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:200
-msgid ""
-"Create a new rendezvous and share the access link with those you wish to "
-"invite to the group. Those who open the link become members of the "
-"rendezvous. They can view other member locations, add markers to the map, or "
-"share their own locations with the group."
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Account.php:35
+msgid "System failure storing new email. Please try again."
+msgstr "Systemfeil ved lagring av ny e-post. Vennligst prøv igjen."
-#: ../../extend/addon/hzaddons/rendezvous/rendezvous.php:232
-msgid "You have no rendezvous. Press the button above to create a rendezvous!"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Account.php:53
+msgid "Password verification failed."
+msgstr "Passordbekreftelsen mislyktes."
-#: ../../extend/addon/hzaddons/redred/Mod_Redred.php:24
-msgid "Channel is required."
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Account.php:60
+msgid "Passwords do not match. Password unchanged."
+msgstr "Passordene stemmer ikke overens. Passord uforandret."
-#: ../../extend/addon/hzaddons/redred/Mod_Redred.php:38
-msgid "Hubzilla Crosspost Connector Settings saved."
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Account.php:64
+msgid "Empty passwords are not allowed. Password unchanged."
+msgstr "Tomme passord er ikke tillatt. Passord uforandret."
-#: ../../extend/addon/hzaddons/redred/Mod_Redred.php:61
-msgid "Send public postings to Hubzilla channel by default"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Account.php:78
+msgid "Password changed."
+msgstr "Passord endret."
-#: ../../extend/addon/hzaddons/redred/Mod_Redred.php:65
-msgid "Hubzilla API Path"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Account.php:80
+msgid "Password update failed. Please try again."
+msgstr "Passord oppdatering mislyktes. Vennligst prøv igjen."
-#: ../../extend/addon/hzaddons/redred/Mod_Redred.php:69
-msgid "Hubzilla login name"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Account.php:105
+msgid "Account Settings"
+msgstr "Kontoinnstillinger"
-#: ../../extend/addon/hzaddons/redred/Mod_Redred.php:73
-msgid "Hubzilla channel name"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Account.php:106
+msgid "Current Password"
+msgstr "Nåværende passord"
-#: ../../extend/addon/hzaddons/redred/Mod_Redred.php:77
-msgid "Hubzilla password"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Account.php:107
+msgid "Enter New Password"
+msgstr "Skriv nytt passord"
-#: ../../extend/addon/hzaddons/redred/Mod_Redred.php:85
-msgid "Hubzilla Crosspost Connector"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Account.php:108
+msgid "Confirm New Password"
+msgstr "Bekreft nytt passord"
-#: ../../extend/addon/hzaddons/redred/redred.php:50
-msgid "Post to Hubzilla"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Account.php:108
+msgid "Leave password fields blank unless changing"
+msgstr "La passordfeltene stå blanke om det ikke skal endres"
-#: ../../extend/addon/hzaddons/workflow/workflow.php:224
-msgid "Workflow user."
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Account.php:110
+msgid "Multi-Factor Authentication"
+msgstr "Flerfaktorautentisering"
-#: ../../extend/addon/hzaddons/workflow/workflow.php:275
-msgid "This channel"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Account.php:111
+msgid "DId2 or Email Address:"
+msgstr "DId2 eller epostadresse:"
-#: ../../extend/addon/hzaddons/workflow/workflow.php:330
-msgid "Create New Workflow Item"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Account.php:114
+msgid "Remove this account including all its channels"
+msgstr "Slett denne kontoen inkludert alle dens kanaler"
-#: ../../extend/addon/hzaddons/workflow/workflow.php:564
-#: ../../extend/addon/hzaddons/workflow/workflow.php:1466
-#: ../../extend/addon/hzaddons/workflow/workflow.php:1485
-msgid "Workflow"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Editor.php:40
+msgid "Editor Settings"
+msgstr "Instillinger for redigering"
-#: ../../extend/addon/hzaddons/workflow/workflow.php:1454
-msgid "No Workflows Available"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Features.php:45
+msgid "Additional Features"
+msgstr "Ekstra funksjoner"
-#: ../../extend/addon/hzaddons/workflow/workflow.php:1484
-msgid "Add item to which workflow"
+#: ../../Zotlabs/Module/Settings/Directory.php:40
+msgid "Directory Settings"
msgstr ""
-#: ../../extend/addon/hzaddons/workflow/workflow.php:1544
-#: ../../extend/addon/hzaddons/workflow/workflow.php:1663
-msgid "Create Workflow Item"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Featured.php:25
+msgid "No feature settings configured"
+msgstr "Ingen funksjonsinnstillinger er konfigurert"
-#: ../../extend/addon/hzaddons/workflow/workflow.php:2632
-msgid "Link"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Featured.php:34
+msgid "Addon Settings"
+msgstr "Instillinger for tillegg"
-#: ../../extend/addon/hzaddons/workflow/workflow.php:2634
-msgid "Web link."
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Featured.php:35
+msgid "Please save/submit changes to any panel before opening another."
+msgstr "Husk å lagre endringene i ett panel før du åpner andre."
-#: ../../extend/addon/hzaddons/workflow/workflow.php:2655
-#: ../../extend/addon/hzaddons/workflow/workflow.php:2724
-msgid "Brief description or title"
+#: ../../Zotlabs/Module/Settings/Manage.php:41
+msgid "Channel Manager Settings"
msgstr ""
-#: ../../extend/addon/hzaddons/workflow/workflow.php:2663
-#: ../../extend/addon/hzaddons/workflow/workflow.php:2732
-msgid "Notes and Info"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Privacy.php:49
+msgid "Privacy settings updated."
+msgstr "Personverninnstillingene ble oppdatert."
-#: ../../extend/addon/hzaddons/workflow/workflow.php:2670
-msgid "Used to order links"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Privacy.php:67
+msgid "Only those you specifically allow"
+msgstr "Bare de du spesifikt tillater"
-#: ../../extend/addon/hzaddons/workflow/workflow.php:2730
-msgid "Body"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Privacy.php:68
+msgid "Approved connections"
+msgstr "Godkjente forbindelser"
-#: ../../extend/addon/hzaddons/workflow/Settings/Mod_WorkflowSettings.php:101
-msgid "Workflow Settings"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Privacy.php:69
+msgid "Any connections"
+msgstr "Enhver forbindelse"
-#: ../../extend/addon/hzaddons/workflow/Settings/WorkflowSettingsUtil.php:145
-#, fuzzy
-#| msgid "Editor Settings"
-msgid "Workflow settings"
-msgstr "Instillinger for redigering"
+#: ../../Zotlabs/Module/Settings/Privacy.php:70
+msgid "Anybody on this website"
+msgstr "Enhver ved dette nettstedet"
-#: ../../extend/addon/hzaddons/nsfw/nsfw.php:153
-msgid "Possible adult content"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Privacy.php:71
+msgid "Anybody in this network"
+msgstr "Enhver i dette nettverket"
-#: ../../extend/addon/hzaddons/nsfw/nsfw.php:168
-#, php-format
-msgid "%s - view"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Privacy.php:72
+msgid "Anybody authenticated"
+msgstr "Enhver som er autentisert"
-#: ../../extend/addon/hzaddons/nsfw/Mod_Nsfw.php:22
-msgid "NSFW Settings saved."
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Privacy.php:73
+msgid "Anybody on the internet"
+msgstr "Enhver på Internett"
-#: ../../extend/addon/hzaddons/nsfw/Mod_Nsfw.php:42
+#: ../../Zotlabs/Module/Settings/Privacy.php:83
msgid ""
-"This app looks in posts for the words/text you specify below, and collapses "
-"any content containing those keywords so it is not displayed at "
-"inappropriate times, such as sexual innuendo that may be improper in a work "
-"setting. It is polite and recommended to tag any content containing nudity "
-"with #NSFW. This filter can also match any other word/text you specify, and "
-"can thereby be used as a general purpose content filter."
-msgstr ""
-
-#: ../../extend/addon/hzaddons/nsfw/Mod_Nsfw.php:47
-msgid "Comma separated list of keywords to hide"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/nsfw/Mod_Nsfw.php:47
-msgid "Word, /regular-expression/, lang=xx, lang!=xx"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/nsfw/Mod_Nsfw.php:56
-msgid "NSFW"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/tictac/tictac.php:21
-msgid "Three Dimensional Tic-Tac-Toe"
+"Advise: set to \"Anybody on the internet\" and use privacy groups to "
+"restrict access"
msgstr ""
-#: ../../extend/addon/hzaddons/tictac/tictac.php:54
-msgid "3D Tic-Tac-Toe"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Privacy.php:122
+msgid "Privacy Settings"
+msgstr "Personverninnstillinger"
-#: ../../extend/addon/hzaddons/tictac/tictac.php:59
-msgid "New game"
+#: ../../Zotlabs/Module/Settings/Privacy.php:127
+msgid "Advanced configuration"
msgstr ""
-#: ../../extend/addon/hzaddons/tictac/tictac.php:60
-msgid "New game with handicap"
+#: ../../Zotlabs/Module/Settings/Privacy.php:129
+msgid "Proceed with caution"
msgstr ""
-#: ../../extend/addon/hzaddons/tictac/tictac.php:61
+#: ../../Zotlabs/Module/Settings/Privacy.php:130
msgid ""
-"Three dimensional tic-tac-toe is just like the traditional game except that "
-"it is played on multiple levels simultaneously. "
+"Changing advanced configuration settings can impact your, and your contacts "
+"channels functionality and security."
msgstr ""
-#: ../../extend/addon/hzaddons/tictac/tictac.php:62
-msgid ""
-"In this case there are three levels. You win by getting three in a row on "
-"any level, as well as up, down, and diagonally across the different levels."
+#: ../../Zotlabs/Module/Settings/Privacy.php:131
+msgid "Accept the risk and continue"
msgstr ""
-#: ../../extend/addon/hzaddons/tictac/tictac.php:64
-msgid ""
-"The handicap game disables the center position on the middle level because "
-"the player claiming this square often has an unfair advantage."
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Privacy.php:133
+msgid "Automatically approve new contacts"
+msgstr "Automatisk godkjenn nye kontakter"
-#: ../../extend/addon/hzaddons/tictac/tictac.php:183
-msgid "You go first..."
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Privacy.php:134
+msgid "Opt-out of search engine indexing"
+msgstr "Ikke la søkemotorer indeksere denne kanalen"
-#: ../../extend/addon/hzaddons/tictac/tictac.php:188
-msgid "I'm going first this time..."
+#: ../../Zotlabs/Module/Settings/Privacy.php:135
+msgid "Group actor"
msgstr ""
-#: ../../extend/addon/hzaddons/tictac/tictac.php:194
-msgid "You won!"
+#: ../../Zotlabs/Module/Settings/Privacy.php:135
+msgid "Allow this channel to act as a forum"
msgstr ""
-#: ../../extend/addon/hzaddons/tictac/tictac.php:200
-#: ../../extend/addon/hzaddons/tictac/tictac.php:225
-msgid "\"Cat\" game!"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Privacy.php:136
+msgid "Accept all messages which mention you"
+msgstr "Godta alle meldinger som nevner deg"
-#: ../../extend/addon/hzaddons/tictac/tictac.php:223
-msgid "I won!"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Privacy.php:136
+msgid "This setting bypasses normal permissions"
+msgstr "Denne instillingen overstyrer vanlig tilgangskontroll"
-#: ../../extend/addon/hzaddons/photocache/Mod_Photocache.php:27
-msgid "Photo Cache settings saved."
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Privacy.php:137
+msgid "Accept unsolicited comments for moderation"
+msgstr "Godta kommentarer fra fremmede for moderering"
-#: ../../extend/addon/hzaddons/photocache/Mod_Photocache.php:43
-msgid ""
-"Saves a copy of images from external sites locally to increase your "
-"anonymity in the web."
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Privacy.php:137
+msgid "Otherwise they will be silently dropped"
+msgstr "Ellers vil de avvises uten varsel"
-#: ../../extend/addon/hzaddons/photocache/Mod_Photocache.php:49
-msgid "Minimal photo size for caching"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Privacy.php:138
+msgid "Enable OCAP access"
+msgstr "Slå på OCAP tilgangskontroll"
-#: ../../extend/addon/hzaddons/photocache/Mod_Photocache.php:51
-msgid "In pixels. From 1 up to 1024, 0 will be replaced with system default."
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Privacy.php:138
+msgid "Grant limited posts the right to access linked private media"
+msgstr "Gi begrensede innlegg tilgang til private media som er lenket fra dem"
-#: ../../extend/addon/hzaddons/photocache/Mod_Photocache.php:60
-msgid "Photo Cache"
+#: ../../Zotlabs/Module/Settings/Events.php:40
+msgid "Events Settings"
msgstr ""
-#: ../../extend/addon/hzaddons/logrot/logrot.php:36
-msgid "Logfile archive directory"
+#: ../../Zotlabs/Module/Settings/Photos.php:40
+msgid "Photos Settings"
msgstr ""
-#: ../../extend/addon/hzaddons/logrot/logrot.php:36
-msgid "Directory to store rotated logs"
+#: ../../Zotlabs/Module/Settings/Conversation.php:42
+msgid "Conversation Settings"
msgstr ""
-#: ../../extend/addon/hzaddons/logrot/logrot.php:37
-msgid "Logfile size in bytes before rotating"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:106
+#: ../../Zotlabs/Module/Settings/Channel.php:218
+msgid "Please select a channel role"
+msgstr "Velg en kanalrolle"
-#: ../../extend/addon/hzaddons/logrot/logrot.php:38
-msgid "Number of logfiles to retain"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:195
+msgid "Your channel address is"
+msgstr "Din kanaladresse er"
-#: ../../extend/addon/hzaddons/xmpp/Mod_Xmpp.php:23
-msgid "XMPP settings updated."
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:198
+msgid "Your files/photos are accessible via WebDAV at"
+msgstr "Dine filer og foto er tilgjengelig via WebDAV på"
-#: ../../extend/addon/hzaddons/xmpp/Mod_Xmpp.php:35
-msgid "XMPP App"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:229
+msgid "Channel Settings"
+msgstr "Kanalinnstillinger"
-#: ../../extend/addon/hzaddons/xmpp/Mod_Xmpp.php:36
-msgid "Embedded XMPP (Jabber) client"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:236
+msgid "Basic Settings"
+msgstr "Grunninnstillinger"
-#: ../../extend/addon/hzaddons/xmpp/Mod_Xmpp.php:52
-msgid "Individual credentials"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:237
+msgid "Channel timezone:"
+msgstr "Tidssone for kanalen:"
-#: ../../extend/addon/hzaddons/xmpp/Mod_Xmpp.php:58
-msgid "Jabber BOSH server"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:238
+msgid "Default post location:"
+msgstr "Standard plassering for innlegg:"
-#: ../../extend/addon/hzaddons/xmpp/Mod_Xmpp.php:67
-msgid "XMPP Settings"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:238
+msgid "Geographical location to display on your posts"
+msgstr "Geografisk plassering som vises på dine innlegg"
-#: ../../extend/addon/hzaddons/xmpp/xmpp.php:44
-msgid "Jabber BOSH host"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:239
+msgid "Use browser location"
+msgstr "Bruk nettleserplassering"
-#: ../../extend/addon/hzaddons/xmpp/xmpp.php:45
-msgid "Use central userbase"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:240
+msgid "Adult content"
+msgstr "Voksent innhold"
-#: ../../extend/addon/hzaddons/xmpp/xmpp.php:45
-msgid ""
-"If enabled, members will automatically login to an ejabberd server that has "
-"to be installed on this machine with synchronized credentials via the "
-"\"auth_ejabberd.php\" script."
+#: ../../Zotlabs/Module/Settings/Channel.php:240
+msgid "This channel frequently or regularly publishes adult content"
msgstr ""
+"Denne kanalen vil ofte, eller regelmessig poste innlegg med voksent innhold"
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWikiPage.php:41
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWikiPage.php:109
-msgid "(No Title)"
-msgstr "(Ingen tittel)"
-
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWikiPage.php:123
-msgid "Wiki page create failed."
-msgstr "Kunne ikke opprette wikiside."
-
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWikiPage.php:137
-msgid "Wiki not found."
-msgstr "Fant ikke wikien."
-
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWikiPage.php:148
-msgid "Destination name already exists"
-msgstr "Navnet finnes allerede"
-
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWikiPage.php:181
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWikiPage.php:376
-msgid "Page not found"
-msgstr "Fant ikke siden"
-
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWikiPage.php:211
-msgid "Error reading page content"
-msgstr "Det oppstod en feil under lesing av innholder på siden"
+#: ../../Zotlabs/Module/Settings/Channel.php:241
+msgid "Maximum Friend Requests/Day:"
+msgstr "Maksimalt antall venneforespørsler per dag:"
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWikiPage.php:367
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWikiPage.php:425
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWikiPage.php:493
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWikiPage.php:534
-msgid "Error reading wiki"
-msgstr "Det oppstod en feil ved lesing av wikien"
+#: ../../Zotlabs/Module/Settings/Channel.php:241
+msgid "May reduce spam activity"
+msgstr "Kan redusere søppelpostaktivitet"
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWikiPage.php:411
-msgid "Page update failed."
-msgstr "Oppdatering av siden mislyktes."
+#: ../../Zotlabs/Module/Settings/Channel.php:243
+msgid "By default post a status message when:"
+msgstr "Legg inn en statusmelding når du:"
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWikiPage.php:447
-msgid "Nothing deleted"
-msgstr "Ingenting ble slettet"
+#: ../../Zotlabs/Module/Settings/Channel.php:244
+msgid "accepting a friend request"
+msgstr "aksepterer en venneforespørsel"
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWikiPage.php:514
-msgid "Compare: object not found."
-msgstr "Sammanlign: fant ikke objektet."
+#: ../../Zotlabs/Module/Settings/Channel.php:245
+msgid "joining a forum/community"
+msgstr "blir med i et forum/miljø"
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWikiPage.php:521
-msgid "Page updated"
-msgstr "Siden ble oppdatert"
+#: ../../Zotlabs/Module/Settings/Channel.php:246
+msgid "making an <em>interesting</em> profile change"
+msgstr "gjør en <em>interessant</em> profilendring"
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWikiPage.php:529
-msgid "Wiki resource_id required for git commit"
-msgstr "Wiki ressurs_id er nødvendig for å lagre i git"
+#: ../../Zotlabs/Module/Settings/Channel.php:247
+msgid "Send a notification email when:"
+msgstr "Send en varsel-e-post når:"
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWikiPage.php:589
-#: ../../extend/addon/hzaddons/wiki/Widget/Wiki_page_history.php:29
-msgctxt "wiki_history"
-msgid "Message"
-msgstr "Melding"
+#: ../../Zotlabs/Module/Settings/Channel.php:248
+msgid "You receive a connection request"
+msgstr "Du har mottatt en forespørsel om forbindelse"
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWikiPage.php:590
-#: ../../extend/addon/hzaddons/wiki/Widget/Wiki_page_history.php:30
-msgid "Date"
-msgstr "Dato"
+#: ../../Zotlabs/Module/Settings/Channel.php:249
+msgid "Your connections are confirmed"
+msgstr "Dine forbindelser er bekreftet"
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWikiPage.php:591
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:364
-#: ../../extend/addon/hzaddons/wiki/Widget/Wiki_page_history.php:31
-msgid "Revert"
-msgstr "Tilbakestill"
+#: ../../Zotlabs/Module/Settings/Channel.php:250
+msgid "Someone writes on your profile wall"
+msgstr "Noen skriver på din profilvegg"
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWikiPage.php:592
-#: ../../extend/addon/hzaddons/wiki/Widget/Wiki_page_history.php:32
-msgid "Compare"
-msgstr "Sammenlign"
+#: ../../Zotlabs/Module/Settings/Channel.php:251
+msgid "Someone writes a followup comment"
+msgstr "Noen skriver en oppfølgende kommentar"
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWiki.php:144
-msgid "Wiki updated successfully"
-msgstr "Wikien ble oppdatert"
+#: ../../Zotlabs/Module/Settings/Channel.php:252
+msgid "You receive a private message"
+msgstr "Du mottar en privat melding"
-#: ../../extend/addon/hzaddons/wiki/Lib/NativeWiki.php:204
-msgid "Wiki files deleted successfully"
-msgstr "Wikifiler ble slettet"
+#: ../../Zotlabs/Module/Settings/Channel.php:253
+msgid "You receive a friend suggestion"
+msgstr "Du mottok et venneforslag"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:136
-msgid "Error retrieving wiki"
-msgstr "Det oppstod en feil ved henting av wikien"
+#: ../../Zotlabs/Module/Settings/Channel.php:254
+msgid "You are tagged in a post"
+msgstr "Du merkes i et innlegg"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:143
-msgid "Error creating zip file export folder"
-msgstr "Det oppstod en feil ved eksport av mappe til zip fil"
+#: ../../Zotlabs/Module/Settings/Channel.php:255
+msgid "You are poked/prodded/etc. in a post"
+msgstr "Du ble prikket/oppildnet/og så vider i et innlegg"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:194
-msgid "Error downloading wiki: "
+#: ../../Zotlabs/Module/Settings/Channel.php:256
+msgid "Someone likes your post/comment"
msgstr ""
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:209
-#: ../../extend/addon/hzaddons/wiki/wiki.php:45
-#: ../../extend/addon/hzaddons/wiki/wiki.php:98
-#: ../../extend/addon/hzaddons/wiki/Widget/Wiki_list.php:23
-msgid "Wikis"
-msgstr "Wikier"
+#: ../../Zotlabs/Module/Settings/Channel.php:257
+msgid "Show visual notifications including:"
+msgstr "Vis visuelle varslinger om:"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:219
-msgid "Wiki name"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:258
+msgid "Unseen stream activity"
+msgstr "Ny aktivitet i nettverksstrømmen"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:220
-msgid "Content type"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:260
+msgid "Unseen private messages"
+msgstr "Nye private meldinger"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:223
-msgid "Any&nbsp;type"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:260
+#: ../../Zotlabs/Module/Settings/Channel.php:265
+#: ../../Zotlabs/Module/Settings/Channel.php:266
+#: ../../Zotlabs/Module/Settings/Channel.php:267
+msgid "Recommended"
+msgstr "Anbefalt"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:230
-msgid "Lock content type"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:261
+msgid "Upcoming events"
+msgstr "Kommende hendelser"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:231
-msgid "Create a status post for this wiki"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:262
+msgid "Events today"
+msgstr "Hendelser idag"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:232
-msgid "Edit Wiki Name"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:263
+msgid "Upcoming birthdays"
+msgstr "Kommende fødselsdager"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:277
-#, fuzzy
-msgid "Wiki not found"
-msgstr "Fant ikke wikien."
+#: ../../Zotlabs/Module/Settings/Channel.php:263
+msgid "Not available in all themes"
+msgstr "Ikke tilgjengelig i alle temaer"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:303
-msgid "Rename page"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:264
+msgid "System (personal) notifications"
+msgstr "System (personlige) varslinger"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:318
-msgid "Error retrieving page content"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:265
+msgid "System info messages"
+msgstr "System infomeldinger"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:326
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:328
-msgid "New page"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:266
+msgid "System critical alerts"
+msgstr "System kritiske varsel"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:363
-msgid "Revision Comparison"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:268
+msgid "System Registrations"
+msgstr "Systemregistreringer"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:371
-msgid "Short description of your changes (optional)"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:269
+msgid "Unseen shared files"
+msgstr "Nye delte filer"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:391
-msgid "New page name"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:271
+msgid "Unseen likes and dislikes"
+msgstr "Nye liker/ikke liker"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:396
-msgid "Embed image from photo albums"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:272
+msgid "Unseen forum posts"
+msgstr "Ny forumpost"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:407
-msgid "History"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:273
+msgid "Email notification hub (hostname)"
+msgstr "Hub for epostvarsler (tjenernavn)"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:485
-msgid "Error creating wiki. Invalid name."
+#: ../../Zotlabs/Module/Settings/Channel.php:273
+#, php-format
+msgid ""
+"If your channel is mirrored to multiple hubs, set this to your preferred "
+"location. This will prevent duplicate email notifications. Example: %s"
msgstr ""
+"Hvis kanalen din er klonet til flere tjenere kan du sette dette til den du "
+"vil skal sende deg epostvarsler. Dette vil forhindre duplikate epostvarsler. "
+"Eksempel: %s"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:492
-msgid "A wiki with this name already exists."
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:274
+msgid "Show new wall posts, private messages and connections under Notices"
+msgstr "Vis nye veggposter, private meldinger og forbindelser under varsler"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:505
-msgid "Wiki created, but error creating Home page."
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:275
+msgid "Mark all notices of the thread read if a notice is clicked"
+msgstr "Merk alle varsler for en tråd som lest når du klikker på ett varsel"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:512
-msgid "Error creating wiki"
+#: ../../Zotlabs/Module/Settings/Channel.php:275
+msgid "If no, only the clicked notice will be marked read"
msgstr ""
+"Om du velger \"Nei\", blir kun det varslet du klikker på merket som lest"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:536
-msgid "Error updating wiki. Invalid name."
+#: ../../Zotlabs/Module/Settings/Channel.php:276
+msgid ""
+"Desktop notifications are unavailable because the required browser "
+"permission has not been granted"
msgstr ""
+"Skriverbordsvarsler er ikke tilgjengelig fordi nettstedet ikke har de "
+"nødvendige nettlesertillatelsene"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:555
-msgid "Error updating wiki"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:277
+msgid "Grant permission"
+msgstr "Gi tilgang"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:570
-msgid "Wiki delete permission denied."
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:278
+msgid "Notify me of events this many days in advance"
+msgstr "Varsle meg om hendelser dette antall dager på forhånd"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:580
-msgid "Error deleting wiki"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:278
+msgid "Must be greater than 0"
+msgstr "Må være større enn 0"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:613
-msgid "New page created"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:281
+msgid "Default photo upload folder"
+msgstr "Standard mappe for opplasting av bilder"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:739
-msgid "Cannot delete Home"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:281
+#: ../../Zotlabs/Module/Settings/Channel.php:282
+msgid "%Y - current year, %m - current month"
+msgstr "%Y - nåværende år, %m - nåværende måned"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:815
-msgid "Current Revision"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:282
+msgid "Default file upload folder"
+msgstr "Standard mappe for opplasting av filer"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:815
-msgid "Selected Revision"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:284
+msgid "Remove this channel."
+msgstr "Fjern denne kanalen."
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:870
-msgid "You must be authenticated."
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:285
+msgid "Expire other channel content after this many days"
+msgstr "Annet kanal innhold utløper etter så mange dager"
-#: ../../extend/addon/hzaddons/wiki/Mod_Wiki.php:899
-#: ../../extend/addon/hzaddons/wiki/Widget/Wiki_pages.php:64
-msgid "Add new page"
-msgstr "Legg til ny side"
+#: ../../Zotlabs/Module/Settings/Channel.php:285
+msgid "0 or blank to use the website limit."
+msgstr "0 eller la være tomt for å bruke grensen til nettstedet."
-#: ../../extend/addon/hzaddons/wiki/wiki.php:48
-#: ../../extend/addon/hzaddons/cards/cards.php:51
-msgid "View Cards"
+#: ../../Zotlabs/Module/Settings/Channel.php:285
+#, php-format
+msgid "This website expires after %d days."
msgstr ""
-#: ../../extend/addon/hzaddons/wiki/Widget/Wiki_pages.php:58
-msgid "Wiki Pages"
-msgstr "Wikisider"
-
-#: ../../extend/addon/hzaddons/wiki/Widget/Wiki_pages.php:69
-msgid "Page name"
-msgstr "Sidenavn"
+#: ../../Zotlabs/Module/Settings/Channel.php:285
+msgid "This website does not expire imported content."
+msgstr "Dette nettstedet sletter ikke importert innhold."
-#: ../../extend/addon/hzaddons/wholikesme/wholikesme.php:30
-msgid "Who likes me?"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Channel.php:285
+msgid "The website limit takes precedence if lower than your limit."
+msgstr "Nettstedets grense vil gjelde dersom den er lavere enn din grense."
-#: ../../extend/addon/hzaddons/cards/Mod_Card_edit.php:129
-msgid "Edit Card"
+#: ../../Zotlabs/Module/Settings/Channel.php:286
+#: ../../Zotlabs/Module/Settings/Channel.php:287
+msgid ""
+"Words one per line or #tags, $categories, /patterns/, lang=xx, lang!=xx - "
+"leave blank to import all posts"
msgstr ""
+"Ett ord per linje eller #emneknagger, $kategorier, /mønster/, lang=xx, lang!"
+"=xx - la være tomt for å importere alle innlegg"
-#: ../../extend/addon/hzaddons/cards/Mod_Cards.php:115
-msgid "Add Card"
+#: ../../Zotlabs/Module/Settings/Profiles.php:41
+msgid "Default profile for new contacts"
msgstr ""
-#: ../../extend/addon/hzaddons/libertree/Mod_Libertree.php:25
-msgid "Libertree Crosspost Connector Settings saved."
+#: ../../Zotlabs/Module/Settings/Profiles.php:49
+msgid "Profiles Settings"
msgstr ""
-#: ../../extend/addon/hzaddons/libertree/Mod_Libertree.php:49
-msgid "Libertree API token"
+#: ../../Zotlabs/Module/Settings/Connections.php:40
+msgid "Connections Settings"
msgstr ""
-#: ../../extend/addon/hzaddons/libertree/Mod_Libertree.php:53
-msgid "Libertree site URL"
+#: ../../Zotlabs/Module/Settings/Calendar.php:40
+msgid "Calendar Settings"
msgstr ""
-#: ../../extend/addon/hzaddons/libertree/Mod_Libertree.php:57
-msgid "Post to Libertree by default"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Multifactor.php:23
+#, fuzzy
+#| msgid "Name is required"
+msgid "Password is required"
+msgstr "Navn er påkrevd"
-#: ../../extend/addon/hzaddons/libertree/Mod_Libertree.php:65
-msgid "Libertree Crosspost Connector"
+#: ../../Zotlabs/Module/Settings/Multifactor.php:29
+msgid "The provided password is not correct"
msgstr ""
-#: ../../extend/addon/hzaddons/libertree/libertree.php:43
-msgid "Post to Libertree"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Multifactor.php:68
+msgid "Account Multi-Factor Authentication"
+msgstr "Tofaktorautentisering"
-#: ../../extend/addon/hzaddons/hubwall/hubwall.php:19
-msgid "Send email to all members"
+#: ../../Zotlabs/Module/Settings/Multifactor.php:69
+msgid ""
+"This is your generated secret. It may be used in some cases if the QR image "
+"cannot be read. Please store it in a safe place."
msgstr ""
+"Dette er en generert sikkerhetskode. Den kan være nyttig i enkelte tilfeller "
+"hvor QR koden ikke kan leses. Lagre den et sikkert sted."
-#: ../../extend/addon/hzaddons/hubwall/hubwall.php:74
-#, php-format
-msgid "%1$d of %2$d messages sent."
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Multifactor.php:70
+msgid "Please enter the code from your authenticator app"
+msgstr "Skriv inn koden fra din autentiseringsapp"
-#: ../../extend/addon/hzaddons/hubwall/hubwall.php:82
-msgid "Send email to all hub members."
+#: ../../Zotlabs/Module/Settings/Multifactor.php:71
+msgid "You will only be able to enable MFA if the test passes"
msgstr ""
+"Flerfaktorautentisering vil kun bli slått på dersom denne testen lykkes"
-#: ../../extend/addon/hzaddons/hubwall/hubwall.php:94
-msgid "Sender Email address"
+#: ../../Zotlabs/Module/Settings/Multifactor.php:75
+msgid "Congratulations, the provided code was correct"
msgstr ""
-#: ../../extend/addon/hzaddons/hubwall/hubwall.php:95
-msgid "Test mode (only send to hub administrator)"
+#: ../../Zotlabs/Module/Settings/Multifactor.php:76
+msgid "Incorrect code"
msgstr ""
-#: ../../extend/addon/hzaddons/mdpost/mdpost.php:42
-msgid "Use markdown for editing posts"
-msgstr ""
+#: ../../Zotlabs/Module/Settings/Multifactor.php:79
+#, fuzzy
+#| msgid "Failed authentication"
+msgid "Enable Multi-Factor Authentication"
+msgstr "Mislykket autentisering"
-#: ../../extend/addon/hzaddons/openid/openid.php:49
-msgid ""
-"We encountered a problem while logging in with the OpenID you provided. "
-"Please check the correct spelling of the ID."
+#: ../../Zotlabs/Module/Settings/Multifactor.php:81
+msgid "Logging in will require you to be in possession of your smartphone"
msgstr ""
-"Vi støtte på et problem under innloggingen med din OpenID. Vennligst sjekk "
-"at ID-en er stavet riktig."
-
-#: ../../extend/addon/hzaddons/openid/openid.php:49
-msgid "The error message was:"
-msgstr "Feilmeldingen var:"
-
-#: ../../extend/addon/hzaddons/openid/Mod_Openid.php:32
-msgid "OpenID protocol error. No ID returned."
-msgstr "OpenID protokollfeil. Ingen ID ble returnert."
-
-#: ../../extend/addon/hzaddons/openid/Mod_Openid.php:78
-#: ../../extend/addon/hzaddons/openid/Mod_Openid.php:179
-#, php-format
-msgid "Welcome %s. Remote authentication successful."
-msgstr "Velkommen %s. Ekstern autentisering er vellykket."
-
-#: ../../extend/addon/hzaddons/openid/MysqlProvider.php:52
-msgid "First Name"
-msgstr "Fornavn"
-
-#: ../../extend/addon/hzaddons/openid/MysqlProvider.php:53
-msgid "Last Name"
-msgstr "Etternavn"
-
-#: ../../extend/addon/hzaddons/openid/MysqlProvider.php:55
-msgid "Full Name"
-msgstr "Fullt navn"
-
-#: ../../extend/addon/hzaddons/openid/MysqlProvider.php:61
-msgid "Profile Photo 16px"
-msgstr "Profilbilde 16px"
-
-#: ../../extend/addon/hzaddons/openid/MysqlProvider.php:62
-msgid "Profile Photo 32px"
-msgstr "Profilbilde 32px"
-
-#: ../../extend/addon/hzaddons/openid/MysqlProvider.php:63
-msgid "Profile Photo 48px"
-msgstr "Profilbilde 48px"
-
-#: ../../extend/addon/hzaddons/openid/MysqlProvider.php:64
-msgid "Profile Photo 64px"
-msgstr "Profilbilde 64px"
-
-#: ../../extend/addon/hzaddons/openid/MysqlProvider.php:65
-msgid "Profile Photo 80px"
-msgstr "Profilbilde 80px"
-
-#: ../../extend/addon/hzaddons/openid/MysqlProvider.php:66
-msgid "Profile Photo 128px"
-msgstr "Profilbilde 128px"
-#: ../../extend/addon/hzaddons/openid/MysqlProvider.php:70
-msgid "Birth Year"
-msgstr "Fødselsår"
+#: ../../Zotlabs/Module/Settings/Multifactor.php:84
+#, fuzzy
+#| msgid "Your new password is"
+msgid "Your account password"
+msgstr "Ditt nye passord er"
-#: ../../extend/addon/hzaddons/openid/MysqlProvider.php:71
-msgid "Birth Month"
-msgstr "Fødselsmåne"
+#: ../../Zotlabs/Module/Settings/Multifactor.php:86
+msgid "Test"
+msgstr "Sjekk koden"
-#: ../../extend/addon/hzaddons/openid/MysqlProvider.php:72
-msgid "Birth Day"
-msgstr "Fødselsdag"
+#: ../../Zotlabs/Module/Rmagic.php:46
+msgid "Authentication failed."
+msgstr "Autentisering mislyktes."
-#: ../../extend/addon/hzaddons/openid/MysqlProvider.php:73
-msgid "Birthdate"
-msgstr "Fødselsdato"
+#: ../../Zotlabs/Module/Editblock.php:138
+msgid "Edit Block"
+msgstr "Endre byggekloss"
-#: ../../extend/addon/hzaddons/irc/Mod_Irc.php:23
-#: ../../extend/addon/hzaddons/irc/irc.php:41
-msgid "Popular Channels"
+#: ../../Zotlabs/Module/Email_validation.php:37
+msgid "Email Verification Required"
msgstr ""
-#: ../../extend/addon/hzaddons/irc/irc.php:37
-msgid "Channels to auto connect"
+#: ../../Zotlabs/Module/Email_validation.php:38
+#, php-format
+msgid ""
+"A verification token was sent to your email address [%s]. Enter that token "
+"here to complete the account verification step. Please allow a few minutes "
+"for delivery, and check your spam folder if you do not see the message."
msgstr ""
+"En verifikasjonskode ble sendt til epostadressen din [%s]. Skriv inn koden "
+"du mottok her for å fullføre kontoverifikasjonen. Det kan ta noen minutter "
+"før du mottar koden. Sjekk også at eposten ikke har havnet i mappen for "
+"søppelpost om du ikke ser den etter en stund."
-#: ../../extend/addon/hzaddons/irc/irc.php:37
-#: ../../extend/addon/hzaddons/irc/irc.php:41
-msgid "Comma separated list"
+#: ../../Zotlabs/Module/Email_validation.php:39
+msgid "Resend Email"
msgstr ""
-#: ../../extend/addon/hzaddons/irc/irc.php:45
-msgid "IRC Settings"
+#: ../../Zotlabs/Module/Email_validation.php:42
+msgid "Validation token"
msgstr ""
-#: ../../extend/addon/hzaddons/irc/irc.php:54
-msgid "IRC settings saved."
-msgstr ""
+#: ../../Zotlabs/Module/Import.php:71
+msgid "Nothing to import."
+msgstr "Ingenting å importere."
-#: ../../extend/addon/hzaddons/irc/irc.php:58
-msgid "IRC Chatroom"
-msgstr ""
+#: ../../Zotlabs/Module/Import.php:87 ../../Zotlabs/Module/Import.php:101
+msgid "Unable to download data from old server"
+msgstr "Ikke i stand til å laste ned data fra gammel tjener"
-#: ../../extend/addon/hzaddons/opensearch/opensearch.php:26
+#: ../../Zotlabs/Module/Import.php:164
#, php-format
-msgctxt "opensearch"
-msgid "Search %1$s (%2$s)"
-msgstr "Søk %1$s (%2$s)"
-
-#: ../../extend/addon/hzaddons/opensearch/opensearch.php:28
-msgctxt "opensearch"
-msgid "$Projectname"
-msgstr "$Projectname"
+msgid "Your service plan only allows %d channels."
+msgstr "Din tjenesteplan tillater bare %d kanaler."
-#: ../../extend/addon/hzaddons/opensearch/opensearch.php:43
-msgid "Search $Projectname"
-msgstr ""
+#: ../../Zotlabs/Module/Import.php:191
+msgid "No channel. Import failed."
+msgstr "Ingen kanal. Import mislyktes."
-#: ../../extend/addon/hzaddons/pubcrawl/Mod_Pubcrawl.php:28
-msgid "ActivityPub Protocol Settings updated."
+#: ../../Zotlabs/Module/Import.php:197
+msgid "Channel exists but has been marked removed on this hub. Import failed."
msgstr ""
-#: ../../extend/addon/hzaddons/pubcrawl/Mod_Pubcrawl.php:44
+#: ../../Zotlabs/Module/Import.php:569
msgid ""
-"The activitypub protocol does not support location independence. Connections "
-"you make within that network may be unreachable from alternate channel "
-"locations."
-msgstr ""
-
-#: ../../extend/addon/hzaddons/pubcrawl/Mod_Pubcrawl.php:50
-msgid "Send activities of type note instead of article"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/pubcrawl/Mod_Pubcrawl.php:50
-msgid "Microblog services such as Mastodon do not properly support articles"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/pubcrawl/Mod_Pubcrawl.php:58
-msgid "Activitypub Protocol"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/flashcards/Mod_Flashcards.php:225
-msgid "Not allowed."
-msgstr ""
-
-#: ../../extend/addon/hzaddons/ijpost/Mod_Ijpost.php:23
-msgid "Insane Journal Crosspost Connector Settings saved."
+"Automatic content and files import was not possible due to API version "
+"incompatiblity. Please import content and files manually!"
msgstr ""
-#: ../../extend/addon/hzaddons/ijpost/Mod_Ijpost.php:35
-msgid "Insane Journal Crosspost Connector App"
-msgstr ""
+#: ../../Zotlabs/Module/Import.php:597
+msgid "You must be logged in to use this feature."
+msgstr "Du må være innlogget for å bruke denne funksjonen."
-#: ../../extend/addon/hzaddons/ijpost/Mod_Ijpost.php:36
-msgid "Relay public postings to Insane Journal"
+#: ../../Zotlabs/Module/Import.php:604
+msgid "Channel Import"
msgstr ""
-#: ../../extend/addon/hzaddons/ijpost/Mod_Ijpost.php:53
-msgid "InsaneJournal username"
+#: ../../Zotlabs/Module/Import.php:605
+msgid ""
+"Use this form to import an existing channel from a different server/hub. You "
+"may retrieve the channel identity from the old server/hub via the network or "
+"provide an export file."
msgstr ""
+"Bruk dette skjemaet for å importere en eksisterende kanal fra en annen "
+"tjener/hub. Du kan hente inn kanalidentiteten fra den gamle tjeneren/huben "
+"via nettverket eller ved å bruke en eksportfil."
-#: ../../extend/addon/hzaddons/ijpost/Mod_Ijpost.php:57
-msgid "InsaneJournal password"
-msgstr ""
+#: ../../Zotlabs/Module/Import.php:607
+msgid "Or provide the old server/hub details"
+msgstr "Eller oppgi detaljene fra den gamle tjeneren/hub-en"
-#: ../../extend/addon/hzaddons/ijpost/Mod_Ijpost.php:61
-msgid "Post to InsaneJournal by default"
-msgstr ""
+#: ../../Zotlabs/Module/Import.php:609
+msgid "Your old identity address (xyz@example.com)"
+msgstr "Din gamle identitetsadresse (xyz@example.com)"
-#: ../../extend/addon/hzaddons/ijpost/Mod_Ijpost.php:69
-msgid "Insane Journal Crosspost Connector"
-msgstr ""
+#: ../../Zotlabs/Module/Import.php:610
+msgid "Your old login email address"
+msgstr "Din gamle innloggings e-postadresse"
-#: ../../extend/addon/hzaddons/ijpost/ijpost.php:44
-msgid "Post to Insane Journal"
-msgstr ""
+#: ../../Zotlabs/Module/Import.php:611
+msgid "Your old login password"
+msgstr "Ditt gamle innloggingspassord"
-#: ../../extend/addon/hzaddons/planets/Mod_Planets.php:23
-msgid "Random Planet App"
+#: ../../Zotlabs/Module/Import.php:612
+msgid "Import your items and files (limited by available memory)"
msgstr ""
-#: ../../extend/addon/hzaddons/planets/Mod_Planets.php:25
+#: ../../Zotlabs/Module/Import.php:614
msgid ""
-"Set a random planet from the Star Wars Empire as your location when posting"
-msgstr ""
-
-#: ../../extend/addon/hzaddons/donate/donate.php:21
-msgid "Project Servers and Resources"
+"For either option, please choose whether to make this hub your new primary "
+"address, or whether your old location should continue this role. You will be "
+"able to post from either location, but only one can be marked as the primary "
+"location for files, photos, and media."
msgstr ""
+"Enten du tar det ene eller det andre valget, vennligst angi om du vil at "
+"denne hubben skal være din nye primære adresse, eller om din gamle "
+"plassering skal fortsette å ha denne rollen. Du kan lage innlegg fra den ene "
+"eller den andre plasseringen, men bare en av dem kan markeres som den "
+"primære plasseringen for filer, bilder og media."
-#: ../../extend/addon/hzaddons/donate/donate.php:22
-msgid "Project Creator and Tech Lead"
-msgstr ""
+#: ../../Zotlabs/Module/Import.php:616
+msgid "Make this hub my primary location"
+msgstr "Gjør dette nettstedet til min primære plassering"
-#: ../../extend/addon/hzaddons/donate/donate.php:49
-msgid ""
-"And the hundreds of other people and organisations who helped make the "
-"Hubzilla possible."
+#: ../../Zotlabs/Module/Import.php:617
+msgid "Move this channel (disable all previous locations)"
msgstr ""
-#: ../../extend/addon/hzaddons/donate/donate.php:52
-msgid ""
-"The Redmatrix/Hubzilla projects are provided primarily by volunteers giving "
-"their time and expertise - and often paying out of pocket for services they "
-"share with others."
+#: ../../Zotlabs/Module/Import.php:618
+msgid "Use this channel nickname instead of the one provided"
msgstr ""
-#: ../../extend/addon/hzaddons/donate/donate.php:53
+#: ../../Zotlabs/Module/Import.php:618
msgid ""
-"There is no corporate funding and no ads, and we do not collect and sell "
-"your personal information. (We don't control your personal information - "
-"<strong>you do</strong>.)"
+"Leave blank to keep your existing channel nickname. You will be randomly "
+"assigned a similar nickname if either name is already allocated on this site."
msgstr ""
-#: ../../extend/addon/hzaddons/donate/donate.php:54
+#: ../../Zotlabs/Module/Import.php:620
msgid ""
-"Help support our ground-breaking work in decentralisation, web identity, and "
-"privacy."
+"This process may take several minutes to complete. Please submit the form "
+"only once and leave this page open until finished."
msgstr ""
+"Denne prosessen kan ta flere minutter å fullføre. Vennligst send inn dette "
+"skjemaet bare en gang og la siden være åpen inntil den er ferdig."
-#: ../../extend/addon/hzaddons/donate/donate.php:56
-msgid ""
-"Your donations keep servers and services running and also helps us to "
-"provide innovative new features and continued development."
-msgstr ""
+#: ../../Zotlabs/Module/Acl.php:371
+msgid "network"
+msgstr "nettverk"
-#: ../../extend/addon/hzaddons/donate/donate.php:59
-msgid "Donate"
+#: ../../Zotlabs/Module/Filer.php:64
+msgid "Enter a folder name"
msgstr ""
-#: ../../extend/addon/hzaddons/donate/donate.php:61
-msgid ""
-"Choose a project, developer, or public hub to support with a one-time "
-"donation"
+#: ../../Zotlabs/Module/Filer.php:64
+msgid "or select an existing folder (doubleclick)"
msgstr ""
-#: ../../extend/addon/hzaddons/donate/donate.php:62
-msgid "Donate Now"
-msgstr ""
+#: ../../Zotlabs/Module/Vote.php:40
+msgid "Poll not found."
+msgstr "Fant ikke spørreskjema."
-#: ../../extend/addon/hzaddons/donate/donate.php:63
-msgid ""
-"<strong><em>Or</em></strong> become a project sponsor (Hubzilla Project only)"
+#: ../../Zotlabs/Module/Vote.php:69
+msgid "Invalid response."
msgstr ""
-#: ../../extend/addon/hzaddons/donate/donate.php:64
-msgid ""
-"Please indicate if you would like your first name or full name (or nothing) "
-"to appear in our sponsor listing"
+#: ../../Zotlabs/Module/Vote.php:128
+msgid "Response submitted. Updates may not appear instantly."
msgstr ""
-#: ../../extend/addon/hzaddons/donate/donate.php:65
-msgid "Sponsor"
-msgstr ""
+#~ msgid "Please enter the required information."
+#~ msgstr "Vennligst skriv inn nødvendig informasjon."
-#: ../../extend/addon/hzaddons/donate/donate.php:68
-msgid "Special thanks to: "
-msgstr ""
+#~ msgctxt "title"
+#~ msgid "Likes"
+#~ msgstr "Liker"
-#: ../../extend/addon/hzaddons/fediquest/Mod_Fediquest.php:22
-#, fuzzy
-#| msgid "Edit App"
-msgid "Fediquest App"
-msgstr "Endre app"
+#~ msgctxt "title"
+#~ msgid "Dislikes"
+#~ msgstr "Liker ikke"
-#: ../../extend/addon/hzaddons/fediquest/Mod_Fediquest.php:23
-msgid "A distributed quest for a given word (game)."
-msgstr ""
+#~ msgctxt "title"
+#~ msgid "Attending"
+#~ msgstr "Deltar"
-#: ../../extend/addon/hzaddons/fediquest/Mod_Fediquest.php:24
-msgid ""
-"To start a game, enter [fediquest]your_word[/fediquest] somewhere in a "
-"toplevel post."
-msgstr ""
+#~ msgctxt "title"
+#~ msgid "Might attend"
+#~ msgstr "Deltar kanskje"
-#: ../../extend/addon/hzaddons/fediquest/Mod_Fediquest.php:25
-msgid "Your contacts can post their guess in the comments."
-msgstr ""
+#~ msgctxt "title"
+#~ msgid "Repeats"
+#~ msgstr "Videresendinger"
-#: ../../extend/addon/hzaddons/fediquest/Mod_Fediquest.php:26
-msgid ""
-"Your channel will evaluate the guess and automatically post the response."
-msgstr ""
+#~ msgid "Approve this item"
+#~ msgstr "Godkjenn dette elementet"
-#: ../../extend/addon/hzaddons/fediquest/Mod_Fediquest.php:28
-msgid "Correct letters"
-msgstr ""
+#~ msgid "Delete this item"
+#~ msgstr "Slett dette elementet"
-#: ../../extend/addon/hzaddons/fediquest/Mod_Fediquest.php:29
-msgid "Letters contained in the word but at the wrong spot"
-msgstr ""
+#~ msgctxt "noun"
+#~ msgid "Not Attending"
+#~ msgid_plural "Not Attending"
+#~ msgstr[0] "Deltar ikke"
+#~ msgstr[1] "Deltar ikke"
-#: ../../extend/addon/hzaddons/fediquest/Mod_Fediquest.php:30
-msgid "Letters not contained in the word"
-msgstr ""
+#~ msgid "created a new post"
+#~ msgstr "laget et nytt innlegg"
-#: ../../extend/addon/hzaddons/fediquest/fediquest.php:211
-msgid "ERROR: word length is not correct!"
-msgstr ""
+#~ msgid "Stream"
+#~ msgstr "Tidslinje"
-#: ../../extend/addon/hzaddons/wppost/wppost.php:47
-msgid "Post to WordPress"
-msgstr ""
+#~ msgid "Direct message"
+#~ msgstr "Direktemelding"
-#: ../../extend/addon/hzaddons/wppost/Mod_Wppost.php:30
-msgid "Wordpress Settings saved."
-msgstr ""
+#~ msgid "Image"
+#~ msgstr "Bilde"
-#: ../../extend/addon/hzaddons/wppost/Mod_Wppost.php:67
-msgid "WordPress username"
-msgstr ""
+#~ msgid "New direct messages notifications"
+#~ msgstr "Varsel om nye direktemeldinger"
-#: ../../extend/addon/hzaddons/wppost/Mod_Wppost.php:71
-msgid "WordPress password"
-msgstr ""
+#~ msgid "Direct messages stream"
+#~ msgstr "Strøm for direktemeldinger"
-#: ../../extend/addon/hzaddons/wppost/Mod_Wppost.php:75
-msgid "WordPress API URL"
-msgstr ""
+#~ msgid "Mark all events seen"
+#~ msgstr "Merk alle hendelser som sett"
-#: ../../extend/addon/hzaddons/wppost/Mod_Wppost.php:76
-msgid "Typically https://your-blog.tld/xmlrpc.php"
-msgstr ""
+#~ msgid "Notices"
+#~ msgstr "Varsel"
-#: ../../extend/addon/hzaddons/wppost/Mod_Wppost.php:79
-msgid "WordPress blogid"
-msgstr ""
+#~ msgid "Direct messages"
+#~ msgstr "Direktemeldinger"
-#: ../../extend/addon/hzaddons/wppost/Mod_Wppost.php:80
-msgid "For multi-user sites such as wordpress.com, otherwise leave blank"
-msgstr ""
+#~ msgid "No messages"
+#~ msgstr "Ingen meldinger"
-#: ../../extend/addon/hzaddons/wppost/Mod_Wppost.php:84
-msgid "Post to WordPress by default"
-msgstr ""
+#~ msgid "Unseen"
+#~ msgstr "Ulest"
-#: ../../extend/addon/hzaddons/wppost/Mod_Wppost.php:88
-msgid "Forward comments (requires hubzilla_wp plugin)"
-msgstr ""
+#~ msgid "Preload images before rendering the page"
+#~ msgstr "Last inn bildene før gjengivelsen av siden"
-#: ../../extend/addon/hzaddons/wppost/Mod_Wppost.php:104
-msgid "Wordpress Post"
-msgstr ""
+#~ msgid ""
+#~ "The subjective page load time will be longer but the page will be ready "
+#~ "when displayed"
+#~ msgstr ""
+#~ "Den personlige opplevelsen av lastetiden vil være lenger, men siden vil "
+#~ "være klar når den vises"
-#: ../../extend/addon/hzaddons/authchoose/Mod_Authchoose.php:30
-msgid ""
-"Allow magic authentication only to websites of your immediate connections"
-msgstr ""
+#~ msgid "timeago.prefixAgo"
+#~ msgstr "timeago.prefixAgo"
-#: ../../extend/addon/hzaddons/authchoose/Mod_Authchoose.php:36
-msgid "Authchoose"
-msgstr ""
+#~ msgid "timeago.prefixFromNow"
+#~ msgstr "timeago.prefixFromNow"
-#: ../../extend/addon/hzaddons/upgrade_info/upgrade_info.php:48
-msgid "Your channel has been upgraded to $Projectname version"
-msgstr ""
+#~ msgid "timeago.suffixAgo"
+#~ msgstr "siden"
-#: ../../extend/addon/hzaddons/upgrade_info/upgrade_info.php:50
-msgid "Please have a look at the"
-msgstr ""
+#~ msgid "less than a minute"
+#~ msgstr "mindre enn ett minutt"
-#: ../../extend/addon/hzaddons/upgrade_info/upgrade_info.php:52
-msgid "git history"
-msgstr ""
+#~ msgid "about a minute"
+#~ msgstr "omtrent et minutt"
-#: ../../extend/addon/hzaddons/upgrade_info/upgrade_info.php:54
-msgid "change log"
-msgstr ""
+#~ msgid "about an hour"
+#~ msgstr "omtrent en time"
-#: ../../extend/addon/hzaddons/upgrade_info/upgrade_info.php:55
-msgid "for further info."
-msgstr ""
+#~ msgid "a day"
+#~ msgstr "en dag"
-#: ../../extend/addon/hzaddons/upgrade_info/upgrade_info.php:60
-#, fuzzy
-#| msgid "$Projectname"
-msgid "$Projectname Upgrade Info"
-msgstr "$Projectname"
+#~ msgid "about a month"
+#~ msgstr "omtrent en måned"
-#: ../../extend/addon/hzaddons/upgrade_info/upgrade_info.php:64
-msgid "Do not show this again"
-msgstr ""
+#~ msgid "about a year"
+#~ msgstr "omtrent et år"
-#: ../../extend/addon/hzaddons/hideaside/Mod_Hideaside.php:28
-msgid "Hide Aside App"
-msgstr ""
+#~ msgid " "
+#~ msgstr " "
-#: ../../extend/addon/hzaddons/hideaside/Mod_Hideaside.php:29
-msgid "Fade out aside areas after a while when using endless scroll"
-msgstr ""
+#~ msgid "timeago.numbers"
+#~ msgstr "timeago.numbers"
-#: ../../extend/addon/hzaddons/msgfooter/msgfooter.php:47
-msgid "text to include in all outgoing posts from this site"
-msgstr ""
+#, fuzzy
+#~ msgid "(optional)"
+#~ msgstr "Valgfritt"
#~ msgid "Link color"
#~ msgstr "Lenkefarge"
diff --git a/view/nb/hstrings.php b/view/nb/hstrings.php
index e1ab75bef..526f29d09 100644
--- a/view/nb/hstrings.php
+++ b/view/nb/hstrings.php
@@ -6,31 +6,100 @@ function string_plural_select_nb($n){
}}
App::$rtl = 0;
App::$strings["plural_function_code"] = "(n != 1 ? 1 : 0)";
-App::$strings["Image/photo"] = "Bilde/fotografi";
-App::$strings["Encrypted content"] = "Kryptert innhold";
-App::$strings["Install %1\$s element %2\$s"] = "";
-App::$strings["This post contains an installable %s element, however you lack permissions to install it on this site."] = "Dette innlegget inneholder det installerbare elementet %s, men du mangler tillatelse til å installere det på dette nettstedet.";
-App::$strings["webpage"] = "nettside";
-App::$strings["layout"] = "layout";
-App::$strings["block"] = "byggekloss";
-App::$strings["menu"] = "meny";
-App::$strings["card"] = "";
-App::$strings["article"] = "";
-App::$strings["post"] = "innlegg";
-App::$strings["%1\$s wrote the following %2\$s %3\$s"] = "%1\$s skrev følgende %2\$s %3\$s";
-App::$strings["Click to open/close"] = "Klikk for å åpne/lukke";
-App::$strings["spoiler"] = "";
-App::$strings["View article"] = "";
-App::$strings["View summary"] = "";
-App::$strings["Different viewers will see this text differently"] = "Denne teksten vil se forskjellig ut for ulike besøkende";
-App::$strings["$1 wrote:"] = "$1 skrev:";
+App::$strings["Source channel not found."] = "Fant ikke kildekanalen.";
+App::$strings["Default"] = "Standard";
+App::$strings["Focus (Hubzilla default)"] = "Focus (Hubzilla standard)";
+App::$strings["Submit"] = "Lagre";
+App::$strings["Theme settings"] = "Instillinger for utseende";
+App::$strings["Dark style"] = "Mørk drakt";
+App::$strings["Light style"] = "Lys drakt";
+App::$strings["Common settings"] = "Felles innstillinger";
+App::$strings["Primary theme color"] = "Primærfarge for tema";
+App::$strings["Current color, leave empty for default"] = "Gjeldende farge, la feltet stå tomt for å bruke standardfarge";
+App::$strings["Success theme color"] = "Farge for vellykket utførelse";
+App::$strings["Info theme color"] = "Farge for informasjon";
+App::$strings["Warning theme color"] = "Farge for advarsler";
+App::$strings["Danger theme color"] = "Farge for fare";
+App::$strings["Default to dark mode"] = "Bruk mørk drakt som standard";
+App::$strings["No"] = "Nei";
+App::$strings["Yes"] = "Ja";
+App::$strings["Always use light icons for navbar"] = "Alltid bruk lyse ikoner i navigasjonslinjen";
+App::$strings["Enable this option if you use a dark navbar color in light mode"] = "Slå på dette valget om du bruker mørk farge for navigasjonslinjen med lys drakt";
+App::$strings["Narrow navbar"] = "Smal navigasjonslinje";
+App::$strings["Navigation bar background color"] = "Navigasjonslinjens bakgrunnsfarge";
+App::$strings["Dark navigation bar background color"] = "Mørk bakgrunnsfarge for navigasjonslinjen";
+App::$strings["Set the background color"] = "Angi bakgrunnsfargen";
+App::$strings["Set the dark background color"] = "Angi bakgrunnsfargen for mørk drakt";
+App::$strings["Set the background image"] = "Angi bakgrunnsbilde";
+App::$strings["Set the dark background image"] = "Angi bakgrunnsbilde for mørk drakt";
+App::$strings["Set font-size for the entire application"] = "Angi skriftstørrelsen for hele programmet";
+App::$strings["Examples: 1rem, 100%, 16px"] = "For eksempel: 1rem, 100%, 16px";
+App::$strings["Set radius of corners in rem"] = "Angi hjørneradius i rem";
+App::$strings["Leave empty for default radius"] = "La feltet stå tomt for å bruke standard radius";
+App::$strings["Set maximum width of content region in rem"] = "Set maksbredde for hovedregionen i rem";
+App::$strings["Leave empty for default width"] = "La feltet stå tomt for å bruke standard bredde";
+App::$strings["Set size of conversation author photo"] = "Angi størrelsen for samtalens forfatterbilde";
+App::$strings["Leave empty for default size"] = "La feltet stå tomt for å bruke standard størrelse";
+App::$strings["Set size of followup author photos"] = "Angi størrelsen på forfatterbilder ved oppfølging";
+App::$strings["Show advanced settings"] = "Vis avanserte innstillinger";
+App::$strings["%1\$s's bookmarks"] = "%1\$s sine bokmerker";
+App::$strings["This is the home page of %s."] = "Dette er hjemmesiden til %s.";
+App::$strings["Delegation session ended."] = "Delegasjonsøkt avsluttet.";
+App::$strings["Logged out."] = "Logget ut.";
+App::$strings["Email validation is incomplete. Please check your email."] = "Verifisering via epost er ikke fullført. Sjekk eposten din.";
+App::$strings["Failed authentication"] = "Mislykket autentisering";
+App::$strings["Login failed."] = "Innlogging mislyktes.";
+App::$strings["View PDF"] = "Vis PDF";
+App::$strings[" by "] = " av ";
+App::$strings[" on "] = "på";
+App::$strings["Embedded content"] = "Innebygget innhold";
+App::$strings["Embedding disabled"] = "Innbygging avskrudd";
+App::$strings["l F d, Y \\@ g:i A"] = "l F d, Y \\@ g:i A";
+App::$strings["Starts:"] = "Starter:";
+App::$strings["Finishes:"] = "Slutter:";
+App::$strings["Location:"] = "Plassering:";
+App::$strings["l F d, Y"] = "l d. F, Y";
+App::$strings["Start:"] = "Start:";
+App::$strings["End:"] = "Slutt:";
+App::$strings["Timezone"] = "Tidssone";
+App::$strings["This event has been added to your calendar."] = "Denne hendelsen er lagt til i din kalender.";
+App::$strings["event"] = "hendelse";
+App::$strings["Not specified"] = "Ikke spesifisert";
+App::$strings["Needs Action"] = "Trenger handling";
+App::$strings["Completed"] = "Ferdig";
+App::$strings["In Process"] = "Igang";
+App::$strings["Cancelled"] = "Avbrutt";
+App::$strings["Mobile"] = "Mobil";
+App::$strings["Home"] = "Hjem";
+App::$strings["Home, Voice"] = "Hjem, telefon";
+App::$strings["Home, Fax"] = "Hjem, fax";
+App::$strings["Work"] = "Arbeid";
+App::$strings["Work, Voice"] = "Arbeid, telefon";
+App::$strings["Work, Fax"] = "Arbeid, fax";
+App::$strings["Other"] = "Annen";
+App::$strings["unknown"] = "ukjent";
+App::$strings["Permission denied"] = "Tillatelse avvist";
+App::$strings["Visible to anybody on the internet."] = "Synlig for alle.";
+App::$strings["Visible to you only."] = "Synlig bare for deg.";
+App::$strings["Visible to anybody in this network."] = "Synlig for alle i dette nettverket.";
+App::$strings["Visible to anybody authenticated."] = "Synlig for alle som er autentisert.";
+App::$strings["Visible to anybody on %s."] = "Synlig for alle på %s.";
+App::$strings["Visible to all connections."] = "Synlig for alle forbindelser.";
+App::$strings["Visible to approved connections."] = "Synlig for godkjente forbindelser.";
+App::$strings["Visible to specific connections."] = "Synlig for spesifikke forbindelser.";
+App::$strings["&#x1f501; Repeated %1\$s's %2\$s"] = "&#x1f501; videreformidlet %2\$s til %1\$s ";
+App::$strings["Privacy group not found."] = "Personverngruppen ble ikke funnet.";
+App::$strings["Privacy group is empty."] = "Personverngruppen er tom.";
+App::$strings["Privacy group: %s"] = "Personverngruppe: %s";
+App::$strings["Connection: %s"] = "Forbindelse: %s";
+App::$strings["Connection not found."] = "Forbindelsen ble ikke funnet.";
App::$strings["Off"] = "Av";
App::$strings["On"] = "På";
App::$strings["Calendar"] = "Kalender";
-App::$strings["Start calendar week on Monday"] = "";
-App::$strings["Default is Sunday"] = "";
-App::$strings["Event Timezone Selection"] = "";
-App::$strings["Allow event creation in timezones other than your own."] = "";
+App::$strings["Start calendar week on Monday"] = "Mandag er ukestart";
+App::$strings["Default is Sunday"] = "Søndag er standard";
+App::$strings["Event Timezone Selection"] = "Tidssone for arrangementet";
+App::$strings["Allow event creation in timezones other than your own."] = "Tillat arrangementer i andre tidssoner enn din egen.";
App::$strings["Channel Home"] = "Kanalhjem";
App::$strings["Search by Date"] = "Søk etter dato";
App::$strings["Ability to select posts by date ranges"] = "Mulighet for å velge innlegg etter datoområde";
@@ -41,19 +110,21 @@ App::$strings["Comments will be displayed separately"] = "Kommentarer blir vist
App::$strings["Connections"] = "Forbindelser";
App::$strings["Connection Filtering"] = "Filtrer forbindelser";
App::$strings["Filter incoming posts from connections based on keywords/content"] = "Filtrer innkommende innlegg fra forbindelser basert på nøkkelord/innhold";
-App::$strings["Conversation"] = "";
-App::$strings["Emoji Reactions"] = "";
-App::$strings["Add emoji reaction ability to posts"] = "";
+App::$strings["Conversation"] = "Samtale";
+App::$strings["Emoji Reactions"] = "Hurtigsvar med Emojier";
+App::$strings["Add emoji reaction ability to posts"] = "Legger til en knapp for hurtige svar på innlegg med en emoji";
App::$strings["Dislike Posts"] = "Mislik innlegg";
App::$strings["Ability to dislike posts/comments"] = "Mulighet til å mislike innlegg/kommentarer";
-App::$strings["Star Posts"] = "Stjerneinnlegg";
-App::$strings["Ability to mark special posts with a star indicator"] = "Mulighet til å merke spesielle innlegg med en stjerne";
-App::$strings["Reply on comment"] = "";
-App::$strings["Ability to reply on selected comment"] = "";
+App::$strings["Star Posts"] = "Favorittmerk innlegg";
+App::$strings["Ability to mark conversations with a star"] = "Mulighet til å favorittmerke innlegg med en stjerne";
+App::$strings["File Posts"] = "Arkivér innlegg";
+App::$strings["Ability to file posts"] = "Mulighet til å arkivere innlegg i mapper";
+App::$strings["Reply on comment"] = "Svar på kommentar";
+App::$strings["Ability to reply on selected comment"] = "Mulighet for å svare på en valgt kommentar";
App::$strings["Directory"] = "Katalog";
App::$strings["Advanced Directory Search"] = "Avansert katalogsøk";
App::$strings["Allows creation of complex directory search queries"] = "Gjør det mulig å bruke avanserte kriterier ved søk i katalogen";
-App::$strings["Editor"] = "";
+App::$strings["Editor"] = "Innleggsredigering";
App::$strings["Post Categories"] = "Innleggskategorier";
App::$strings["Add categories to your posts"] = "Legg kategorier til dine innlegg";
App::$strings["Large Photos"] = "Store bilder";
@@ -61,7 +132,7 @@ App::$strings["Include large (1024px) photo thumbnails in posts. If not enabled,
App::$strings["Even More Encryption"] = "Enda mer kryptering";
App::$strings["Allow optional encryption of content end-to-end with a shared secret key"] = "Tillat valgfri kryptering av innhold ende-til-ende via en delt hemmelig nøkkel";
App::$strings["Disable Comments"] = "Slå av kommentarer";
-App::$strings["Provide the option to disable comments for a post"] = "Slår på valg for å ikke tillate kommentarer til innlegget";
+App::$strings["Provide the option to disable comments for a post"] = "Gjør det mulig å slå av kommentarer for et innlegg";
App::$strings["Delayed Posting"] = "Tidfest publisering";
App::$strings["Allow posts to be published at a later date"] = "Tillat innlegg å bli publisert på et senere tidspunkt";
App::$strings["Content Expiration"] = "Innholdet utløper";
@@ -70,8 +141,8 @@ App::$strings["Suppress Duplicate Posts/Comments"] = "Forhindre duplikat av innl
App::$strings["Prevent posts with identical content to be published with less than two minutes in between submissions."] = "Forhindre innlegg med identisk innhold fra å bli publisert hvis det er mindre enn to minutter mellom innsendingene.";
App::$strings["Auto-save drafts of posts and comments"] = "Automatisk lagring av kladd for innlegg og kommentarer";
App::$strings["Automatically saves post and comment drafts in local browser storage to help prevent accidental loss of compositions"] = "Lagrer automatisk utkast til innlegg og kommentarer i nettleserens interne lager for å unngå å tape innholet ved et uhell";
-App::$strings["Manage"] = "";
-App::$strings["Navigation Channel Select"] = "Navigasjon kanalvalg";
+App::$strings["Manage"] = "Administrasjon";
+App::$strings["Navigation Channel Select"] = "Enklere kanalvalg";
App::$strings["Change channels directly from within the navigation dropdown menu"] = "Endre kanaler direkte fra navigasjonsmenyen";
App::$strings["Network"] = "Nettverk";
App::$strings["Events Filter"] = "Arrangementsfilter";
@@ -93,251 +164,290 @@ App::$strings["Ability to display only posts that you've interacted on"] = "Muli
App::$strings["Photos"] = "Bilder";
App::$strings["Photo Location"] = "Bildeplassering";
App::$strings["If location data is available on uploaded photos, link this to a map."] = "Hvis plasseringsdata er tilgjengelige i opplastede bilder, plasser dette på et kart.";
-App::$strings["Flag Adult Photos"] = "";
-App::$strings["Provide photo edit option to hide inappropriate photos from default album view"] = "";
-App::$strings["Profiles"] = "";
+App::$strings["Flag Adult Photos"] = "Merk bilder med voksent innhold";
+App::$strings["Provide photo edit option to hide inappropriate photos from default album view"] = "Gir mulighet for å merke bilder med upassende innhold fra standard albumvisning";
+App::$strings["Profiles"] = "Profiler";
App::$strings["Advanced Profiles"] = "Avanserte profiler";
App::$strings["Additional profile sections and selections"] = "Ytterlige seksjoner og utvalg til profilen";
App::$strings["Profile Import/Export"] = "Profil-import/-eksport";
App::$strings["Save and load profile details across sites/channels"] = "Lagre og åpne profildetaljer på tvers av nettsteder/kanaler";
App::$strings["Multiple Profiles"] = "Flere profiler";
App::$strings["Ability to create multiple profiles"] = "Mulig å lage flere profiler";
-App::$strings["%1\$s's bookmarks"] = "%1\$s sine bokmerker";
-App::$strings["unknown"] = "ukjent";
App::$strings["Permission denied."] = "Tillatelse avslått.";
App::$strings["Item was not found."] = "Elementet ble ikke funnet.";
-App::$strings["Unknown error."] = "";
+App::$strings["Unknown error."] = "Ukjent feil.";
App::$strings["No source file."] = "Ingen kildefil.";
App::$strings["Cannot locate file to replace"] = "Kan ikke finne filen som skal byttes ut";
App::$strings["Cannot locate file to revise/update"] = "Finner ikke filen som skal revideres/oppdateres";
+App::$strings["Filename too long"] = "Filnavnet er for langt";
App::$strings["File exceeds size limit of %d"] = "Filens størrelse overgår grensen på %d";
App::$strings["You have reached your limit of %1$.0f Mbytes attachment storage."] = "Du har nådd din lagringsgrense for vedlegg på %1$.0f Mbytes.";
App::$strings["File upload failed. Possible system limit or action terminated."] = "Mislyktes med å laste opp filen. Mulig systemgrense eller handling avbrutt.";
App::$strings["Stored file could not be verified. Upload failed."] = "Lagret fil kunne ikke bekreftes. Opplasting mislyktes.";
App::$strings["Path not available."] = "Stien er ikke tilgjengelig.";
App::$strings["Empty pathname"] = "Tomt sti-navn";
-App::$strings["duplicate filename or path"] = "duplikat av filnavn eller sti";
-App::$strings["Path not found."] = "Stien ble ikke funnet.";
-App::$strings["mkdir failed."] = "mkdir mislyktes.";
+App::$strings["Pathname too long"] = "Filbanen er for lang";
+App::$strings["duplicate filename or path"] = "duplikat av filnavn eller filbane";
+App::$strings["Path not found."] = "Filbanen ble ikke funnet.";
+App::$strings["mkdir failed."] = "mkdir fikk en feil.";
App::$strings["database storage failed."] = "databaselagring mislyktes.";
-App::$strings["Empty path"] = "Tom sti";
-App::$strings["%s shared an %s with you"] = "Filer: delt med meg";
-App::$strings["%s shared a %s with you"] = "";
-App::$strings["image"] = "Bilde";
-App::$strings["file"] = "";
-App::$strings["url: "] = "";
-App::$strings["error_code: "] = "";
-App::$strings["error_string: "] = "";
-App::$strings["content-type: "] = "";
-App::$strings["Friendica"] = "Friendica";
-App::$strings["OStatus"] = "OStatus";
-App::$strings["GNU-Social"] = "";
-App::$strings["RSS/Atom"] = "RSS/Atom";
-App::$strings["ActivityPub"] = "";
-App::$strings["Email"] = "E-post";
-App::$strings["Diaspora"] = "Diaspora";
-App::$strings["Facebook"] = "Facebook";
-App::$strings["Zot"] = "Zot";
-App::$strings["LinkedIn"] = "LinkedIn";
-App::$strings["XMPP/IM"] = "XMPP/IM";
-App::$strings["MySpace"] = "MySpace";
-App::$strings["Delegation session ended."] = "";
-App::$strings["Logged out."] = "Logget ut.";
-App::$strings["Email validation is incomplete. Please check your email."] = "";
-App::$strings["Failed authentication"] = "Mislykket autentisering";
-App::$strings["Login failed."] = "Innlogging mislyktes.";
-App::$strings["This is the home page of %s."] = "";
-App::$strings["Remote authentication"] = "Fjernautentisering";
-App::$strings["Click to authenticate to your home hub"] = "Klikk for å godkjennes mot din hjemme-hub";
-App::$strings["Channels"] = "Kanaler";
-App::$strings["Manage your channels"] = "Behandle kanalene dine";
-App::$strings["Settings"] = "Innstillinger";
-App::$strings["Account/Channel Settings"] = "Konto-/kanalinnstillinger";
-App::$strings["Logout"] = "Logg ut";
-App::$strings["End this session"] = "Avslutt denne økten";
-App::$strings["View Profile"] = "Vis profil";
-App::$strings["Your profile page"] = "Din profilside";
-App::$strings["Edit Profiles"] = "Rediger profiler";
-App::$strings["Manage/Edit profiles"] = "Håndter/endre profiler";
-App::$strings["Edit Profile"] = "Rediger profil";
-App::$strings["Edit your profile"] = "Rediger profil";
-App::$strings["Login"] = "Logg inn";
-App::$strings["Sign in"] = "Logg på";
-App::$strings["Take me home"] = "";
-App::$strings["Log me out of this site"] = "";
-App::$strings["Register"] = "Registrer";
-App::$strings["Create an account"] = "Lag en konto";
-App::$strings["Help"] = "Hjelp";
-App::$strings["Help and documentation"] = "Hjelp og dokumentasjon";
-App::$strings["Search"] = "Søk";
-App::$strings["Search site @name, !forum, #tag, ?docs, content"] = "Søk etter @navn, !forum, #emne, ?dokumentasjon eller innhold";
-App::$strings["Admin"] = "Administrator";
-App::$strings["Site Setup and Configuration"] = "Nettstedsoppsett og -konfigurasjon";
-App::$strings["Loading"] = "Laster...";
-App::$strings["@name, #tag, ?doc, content"] = "@navn, #merkelapp, ?dokumentasjon, innhold";
-App::$strings["Please wait..."] = "Vennligst vent...";
-App::$strings["Apps"] = "Apper";
-App::$strings["Channel Apps"] = "";
-App::$strings["System Apps"] = "";
-App::$strings["Pinned Apps"] = "";
-App::$strings["Featured Apps"] = "Fremhevede apper";
-App::$strings["Channel"] = "Kanal";
-App::$strings["Status Messages and Posts"] = "Statusmeldinger og -innlegg";
-App::$strings["About"] = "Om";
-App::$strings["Profile Details"] = "Profildetaljer";
-App::$strings["Photo Albums"] = "Fotoalbum";
-App::$strings["Files"] = "Filer";
-App::$strings["Files and Storage"] = "Filer og lagring";
-App::$strings["Chatrooms"] = "Chatrom";
-App::$strings["Bookmarks"] = "Bokmerker";
-App::$strings["Saved Bookmarks"] = "Lagrede bokmerker";
-App::$strings["Webpages"] = "Websider";
-App::$strings["View Webpages"] = "";
-App::$strings["l F d, Y \\@ g:i A"] = "l F d, Y \\@ g:i A";
-App::$strings["Starts:"] = "Starter:";
-App::$strings["Finishes:"] = "Slutter:";
-App::$strings["Location:"] = "Plassering:";
-App::$strings["l F d, Y"] = "";
-App::$strings["Start:"] = "";
-App::$strings["End:"] = "";
-App::$strings["Timezone"] = "Tidssone";
-App::$strings["This event has been added to your calendar."] = "Denne hendelsen er lagt til i din kalender.";
-App::$strings["event"] = "hendelse";
-App::$strings["Not specified"] = "Ikke spesifisert";
-App::$strings["Needs Action"] = "Trenger handling";
-App::$strings["Completed"] = "Ferdig";
-App::$strings["In Process"] = "Igang";
-App::$strings["Cancelled"] = "Avbrutt";
-App::$strings["Mobile"] = "mobil";
-App::$strings["Home"] = "Hjem";
-App::$strings["Home, Voice"] = "";
-App::$strings["Home, Fax"] = "";
-App::$strings["Work"] = "";
-App::$strings["Work, Voice"] = "";
-App::$strings["Work, Fax"] = "";
-App::$strings["Other"] = "Annen";
-App::$strings["Image exceeds website size limit of %lu bytes"] = "Bilde overstiger nettstedets størrelsesbegrensning på %lu bytes";
-App::$strings["Image file is empty."] = "Bildefilen er tom.";
-App::$strings["Unable to process image"] = "Kan ikke behandle bildet";
-App::$strings["Photo storage failed."] = "Bildelagring mislyktes.";
-App::$strings["a new photo"] = "et nytt bilde";
-App::$strings["__ctx:photo_upload__ %1\$s posted %2\$s to %3\$s"] = "%1\$s la inn %2\$s til %3\$s";
-App::$strings["Recent Photos"] = "Nye bilder";
-App::$strings["Upload New Photos"] = "Last opp nye bilder";
-App::$strings["Permission denied"] = "Tillatelse avvist";
-App::$strings["Visible to anybody on the internet."] = "Synlig for enhver på Internett.";
-App::$strings["Visible to you only."] = "Synlig bare for deg.";
-App::$strings["Visible to anybody in this network."] = "Synlig for enhver i dette nettverket.";
-App::$strings["Visible to anybody authenticated."] = "Synlig for enhver som er autentisert.";
-App::$strings["Visible to anybody on %s."] = "Synlig for alle på %s.";
-App::$strings["Visible to all connections."] = "Synlig for alle forbindelser.";
-App::$strings["Visible to approved connections."] = "Synlig for godkjente forbindelser.";
-App::$strings["Visible to specific connections."] = "Synlig for spesifikke forbindelser.";
-App::$strings["&#x1f501; Repeated %1\$s's %2\$s"] = "";
-App::$strings["Item not found."] = "Elementet ble ikke funnet.";
-App::$strings["Privacy group not found."] = "Personverngruppen ble ikke funnet.";
-App::$strings["Privacy group is empty."] = "Personverngruppen er tom.";
-App::$strings["Privacy group: %s"] = "Personverngruppe: %s";
-App::$strings["Connection: %s"] = "Forbindelse: %s";
-App::$strings["Connection not found."] = "Forbindelsen ble ikke funnet.";
-App::$strings["The provided email address is not valid"] = "";
-App::$strings["The provided email domain is not among those allowed on this site"] = "";
-App::$strings["The provided email address is already registered at this site"] = "";
-App::$strings["There is a pending registration for this address - click \"Register\" to continue verification"] = "";
-App::$strings["An invitation is required."] = "En invitasjon er påkrevd.";
-App::$strings["Invitation could not be verified."] = "Invitasjon kunne ikke bekreftes.";
-App::$strings["Please enter the required information."] = "Vennligst skriv inn nødvendig informasjon.";
-App::$strings["Failed to store account information."] = "Mislyktes med å lagre kontoinformasjon.";
-App::$strings["Registration confirmation for %s"] = "Registreringsbekreftelse for %s";
-App::$strings["Registration request at %s"] = "Registreringsforespørsel hos %s";
-App::$strings["your registration password"] = "ditt registreringspassord";
-App::$strings["Registration details for %s"] = "Registreringsdetaljer for %s";
-App::$strings["Account approved."] = "Konto godkjent.";
-App::$strings["Registration revoked for %s"] = "Registrering trukket tilbake for %s";
-App::$strings["Could not revoke registration for %s"] = "";
-App::$strings["Click here to upgrade."] = "Klikk her for å oppgradere.";
-App::$strings["This action exceeds the limits set by your subscription plan."] = "Denne handlingen går utenfor grensene satt i din abonnementsplan.";
-App::$strings["This action is not available under your subscription plan."] = "Denne handlingen er ikke tilgjengelig i din abonnementsplan.";
-App::$strings["open"] = "";
-App::$strings["closed"] = "";
-App::$strings["Registration is currently"] = "";
-App::$strings["please come back"] = "";
-App::$strings["Profile Photos"] = "Profilbilder";
+App::$strings["Empty path"] = "Tom filbane";
+App::$strings["%s shared an %s with you"] = "%s delte en %s med deg";
+App::$strings["%s shared a %s with you"] = "%s delte et %s med deg";
+App::$strings["image"] = "bilde";
+App::$strings["file"] = "fil";
+App::$strings["Select a profile to assign to this contact"] = "Velg en profil for denne kontakten";
+App::$strings["Frequently"] = "Ofte";
+App::$strings["Hourly"] = "Hver time";
+App::$strings["Twice daily"] = "To ganger daglig";
+App::$strings["Daily"] = "Daglig";
+App::$strings["Weekly"] = "Ukentlig";
+App::$strings["Monthly"] = "Månedlig";
+App::$strings["Male"] = "Mannlig";
+App::$strings["Female"] = "Kvinnelig";
+App::$strings["Currently Male"] = "For tiden mann";
+App::$strings["Currently Female"] = "For tiden kvinne";
+App::$strings["Mostly Male"] = "For det meste mann";
+App::$strings["Mostly Female"] = "For det meste kvinne";
+App::$strings["Transgender"] = "Transkjønnet";
+App::$strings["Intersex"] = "interkjønnet";
+App::$strings["Transsexual"] = "Transseksuell";
+App::$strings["Hermaphrodite"] = "Hermafroditt";
+App::$strings["Neuter"] = "Intetkjønn";
+App::$strings["Non-specific"] = "Ubestemt";
+App::$strings["Undecided"] = "Ubestemt";
+App::$strings["Males"] = "Menn";
+App::$strings["Females"] = "Kvinner";
+App::$strings["Gay"] = "Homo";
+App::$strings["Lesbian"] = "Lesbisk";
+App::$strings["No Preference"] = "Ingen preferanse";
+App::$strings["Bisexual"] = "Biseksuell";
+App::$strings["Autosexual"] = "Autoseksuell";
+App::$strings["Abstinent"] = "Avholdende";
+App::$strings["Virgin"] = "Jomfru";
+App::$strings["Deviant"] = "Avviker";
+App::$strings["Fetish"] = "Fetisj";
+App::$strings["Oodles"] = "Masse";
+App::$strings["Nonsexual"] = "Ikke-seksuell";
+App::$strings["Single"] = "Enslig";
+App::$strings["Lonely"] = "Ensom";
+App::$strings["Available"] = "Tilgjengelig";
+App::$strings["Unavailable"] = "Ikke tilgjengelig";
+App::$strings["Has crush"] = "Er forelsket";
+App::$strings["Infatuated"] = "Betatt";
+App::$strings["Dating"] = "Sammen med";
+App::$strings["Unfaithful"] = "Utro";
+App::$strings["Sex Addict"] = "Sexavhengig";
+App::$strings["Friends"] = "Venner";
+App::$strings["Friends/Benefits"] = "Venner/Frynsegoder";
+App::$strings["Casual"] = "Tilfeldig";
+App::$strings["Engaged"] = "Forlovet";
+App::$strings["Married"] = "Gift";
+App::$strings["Imaginarily married"] = "Gift i fantasien";
+App::$strings["Partners"] = "Partnere";
+App::$strings["Cohabiting"] = "Samboer";
+App::$strings["Common law"] = "Samboer";
+App::$strings["Happy"] = "Lykkelig";
+App::$strings["Not looking"] = "Ikke på utkikk";
+App::$strings["Swinger"] = "Partnerbytte";
+App::$strings["Betrayed"] = "Bedratt";
+App::$strings["Separated"] = "Separert";
+App::$strings["Unstable"] = "Ustabilt";
+App::$strings["Divorced"] = "Skilt";
+App::$strings["Imaginarily divorced"] = "Skilt i fantasien";
+App::$strings["Widowed"] = "Enke";
+App::$strings["Uncertain"] = "Usikkert";
+App::$strings["It's complicated"] = "Det er komplisert";
+App::$strings["Don't care"] = "Bryr meg ikke";
+App::$strings["Ask me"] = "Spør meg";
App::$strings[" and "] = " og ";
-App::$strings[", "] = " ";
+App::$strings[", "] = ".";
App::$strings["Profile Photo"] = "Profilbilde";
-App::$strings["Cover Photo"] = "Forsidebilder";
+App::$strings["Cover Photo"] = "Forsidebilde";
App::$strings["public profile"] = "offentlig profil";
-App::$strings["%1\$s %2\$s has been updated to %3\$s."] = "%1\$s har oppdatert %2\$s, endret %3\$s.";
+App::$strings["%1\$s %2\$s has been updated to %3\$s."] = "%1\$s %2\$s har blitt oppdatert til %3\$s.";
App::$strings["%1\$s updated the %2\$s. Changed %3\$s."] = "%1\$s har oppdatert %2\$s, endret %3\$s.";
+App::$strings["%d invitation available"] = array(
+ 0 => "%d invitasjon tilgjengelig",
+ 1 => "%d invitasjoner tilgjengelig",
+);
+App::$strings["Advanced"] = "Avansert";
+App::$strings["Find Channels"] = "Finn kanaler";
+App::$strings["Enter name or interest"] = "Skriv navn eller interesse";
+App::$strings["Connect/Follow"] = "Forbindelse/Følg";
+App::$strings["Examples: Robert Morgenstein, Fishing"] = "Eksempler: Ola Nordmann, fisking";
+App::$strings["Find"] = "Finn";
+App::$strings["Channel Suggestions"] = "Kanalforslag";
+App::$strings["Random Profile"] = "Tilfeldig profil";
+App::$strings["Invite Friends"] = "Inviter venner";
+App::$strings["Advanced example: name=fred and country=iceland"] = "Avansert eksempel: navn=fred og land=island";
+App::$strings["Everything"] = "Alt";
+App::$strings["Categories"] = "Kategorier";
+App::$strings["Common Connections"] = "Felles forbindelser";
+App::$strings["View all %d common connections"] = "Vis alle %d felles forbindelser";
+App::$strings["INVALID EVENT DISMISSED!"] = "UGYLDIG HELDELSE AVVIST!";
+App::$strings["Summary: "] = "Sammendrag:";
+App::$strings["Unknown"] = "Ukjent";
+App::$strings["Date: "] = "Dato:";
+App::$strings["Reason: "] = "Grunn:";
+App::$strings["INVALID CARD DISMISSED!"] = "UGYLDIG KORT AVVIST!";
+App::$strings["Name: "] = "Navn:";
App::$strings["A deleted group with this name was revived. Existing item permissions <strong>may</strong> apply to this group and any future members. If this is not what you intended, please create another group with a different name."] = "En slettet gruppe med dette navnet ble gjenopprettet. Eksisterende tillatelser for elementet <strong>kan</strong> gjelde for denne gruppen og fremtidige medlemmer. Hvis du ønsket noe annet, vennligst lag en ny gruppe med et annet navn.";
App::$strings["Add new connections to this privacy group"] = "Legg nye forbindelser i denne personverngruppen";
App::$strings["edit"] = "endre";
App::$strings["Privacy Groups"] = "Personverngrupper";
App::$strings["Edit group"] = "Endre gruppe";
-App::$strings["Manage privacy groups"] = "";
+App::$strings["Manage privacy groups"] = "Administrer personverngrupper";
App::$strings["Channels not in any privacy group"] = "Kanaler uten personverngruppe";
App::$strings["add"] = "legg til";
-App::$strings["default"] = "standard";
-App::$strings["Select an alternate language"] = "Velg et annet språk";
-App::$strings["Unable to import a removed channel."] = "";
-App::$strings["Cannot create a duplicate channel identifier on this system. Import failed."] = "Kan ikke lage en kopi av kanal-identifikatoren på dette systemet. Import mislyktes.";
-App::$strings["Unable to create a unique channel address. Import failed."] = "Klarte ikke å lage en unik kanaladresse. Import mislyktes.";
-App::$strings["Cloned channel not found. Import failed."] = "Klonet kanal ble ikke funnet. Import mislyktes.";
-App::$strings["Miscellaneous"] = "Forskjellig";
-App::$strings["Birthday"] = "Fødselsdag:";
-App::$strings["Age: "] = "Alder: ";
-App::$strings["YYYY-MM-DD or MM-DD"] = "YYYY-MM-DD eller MM-DD";
-App::$strings["Required"] = "Påkrevd";
-App::$strings["never"] = "aldri";
-App::$strings["less than a second ago"] = "for mindre enn ett sekund siden";
-App::$strings["__ctx:e.g. 22 hours ago, 1 minute ago__ %1\$d %2\$s ago"] = "%1\$d %2\$s siden";
-App::$strings["__ctx:relative_date__ year"] = array(
- 0 => "år",
- 1 => "år",
-);
-App::$strings["__ctx:relative_date__ month"] = array(
- 0 => "måned",
- 1 => "måneder",
+App::$strings["Trending"] = "Populært";
+App::$strings["Tags"] = "Merkelapper";
+App::$strings["Keywords"] = "Nøkkelord";
+App::$strings["have"] = "har";
+App::$strings["has"] = "har";
+App::$strings["want"] = "ønsker";
+App::$strings["wants"] = "ønsker";
+App::$strings["like"] = "liker";
+App::$strings["likes"] = "liker";
+App::$strings["dislike"] = "misliker";
+App::$strings["dislikes"] = "misliker";
+App::$strings["__ctx:noun__ Like"] = array(
+ 0 => "Liker",
+ 1 => "Liker",
);
-App::$strings["__ctx:relative_date__ week"] = array(
- 0 => "uke",
- 1 => "uker",
+App::$strings["Profile Photos"] = "Profilbilder";
+App::$strings["The provided email address is not valid"] = "Epostadressen som er oppgitt er ikke gyldig";
+App::$strings["The provided email domain is not among those allowed on this site"] = "Epostadressen som er oppgitt har ikke et domene som er tillatt på dette nettstedet";
+App::$strings["The provided email address is already registered at this site"] = "Epostadressen som ble oppgitt er allerede registrert på dette nettstedet";
+App::$strings["There is a pending registration for this address - click \"Register\" to continue verification"] = "Det er en ventende registrering for denne addressen - klikk på \"Registrér\" for å fortsette verifiseringen";
+App::$strings["An invitation is required."] = "En invitasjon er påkrevd.";
+App::$strings["Invitation could not be verified."] = "Invitasjon kunne ikke bekreftes.";
+App::$strings["Failed to store account information."] = "Mislyktes med å lagre kontoinformasjon.";
+App::$strings["Registration confirmation for %s"] = "Registreringsbekreftelse for %s";
+App::$strings["Registration request at %s"] = "Registreringsforespørsel hos %s";
+App::$strings["your registration password"] = "ditt registreringspassord";
+App::$strings["Registration details for %s"] = "Registreringsdetaljer for %s";
+App::$strings["Account approved."] = "Konto godkjent.";
+App::$strings["Registration revoked for %s"] = "Registrering trukket tilbake for %s";
+App::$strings["Could not revoke registration for %s"] = "Registreringen for %s kunne ikke tilbakekalles";
+App::$strings["Click here to upgrade."] = "Klikk her for å oppgradere.";
+App::$strings["This action exceeds the limits set by your subscription plan."] = "Denne handlingen går utenfor grensene satt i din abonnementsplan.";
+App::$strings["This action is not available under your subscription plan."] = "Denne handlingen er ikke tilgjengelig i din abonnementsplan.";
+App::$strings["open"] = "åpen";
+App::$strings["closed"] = "lukket";
+App::$strings["Registration is currently"] = "Registrering er for øyeblikket";
+App::$strings["please come back"] = "kom tilbake";
+App::$strings["Delete this item?"] = "Slett dette elementet?";
+App::$strings["Item deleted"] = "Element slettet";
+App::$strings["Comment"] = "Kommentar";
+App::$strings["expand"] = "utvid";
+App::$strings["collapse"] = "kollaps";
+App::$strings["Password too short"] = "Passordet er for kort";
+App::$strings["Passwords do not match"] = "Passordene er ikke like";
+App::$strings["everybody"] = "alle";
+App::$strings["Secret Passphrase"] = "Hemmelig passordsetning";
+App::$strings["Passphrase hint"] = "Hint om passordsetning";
+App::$strings["Notice: Permissions have changed but have not yet been submitted."] = "Varsel: Tillatelser har blitt endret, men de har ennå ikke blitt sendt inn.";
+App::$strings["close all"] = "lukk alle";
+App::$strings["Nothing new here"] = "Ikke noe nytt her";
+App::$strings["Rate This Channel (this is public)"] = "Vurder denne kanalen (dette er offentlig)";
+App::$strings["Rating"] = "Vurdering";
+App::$strings["Describe (optional)"] = "Beskriv (valgfritt)";
+App::$strings["Please enter a link URL"] = "Vennligst skriv inn en lenke URL";
+App::$strings["Unsaved changes. Are you sure you wish to leave this page?"] = "Endringene er ikke lagret. Er du sikker på at du ønsker å forlate denne siden?";
+App::$strings["Location"] = "Plassering";
+App::$strings["lovely"] = "fint";
+App::$strings["wonderful"] = "nydelig";
+App::$strings["fantastic"] = "fantastisk";
+App::$strings["great"] = "kjempebra";
+App::$strings["Your chosen nickname was either already taken or not valid. Please use our suggestion ("] = "Kallenavnet du har valgt er enten allerede opptatt, eller ikke gyldig. Bruk vårt forslag (";
+App::$strings[") or enter a new one."] = ") eller velg et annet.";
+App::$strings["Thank you, this nickname is valid."] = "Takk, dette kallenavnet er gyldig.";
+App::$strings["A channel name is required."] = "Et kanalnavn er påkrevet.";
+App::$strings["This is a "] = "Dette er et";
+App::$strings[" channel name"] = "kanalnavn";
+App::$strings["Back to reply"] = "Tilbake til svar";
+App::$strings["Pinned"] = "Festet";
+App::$strings["Pin to the top"] = "Fest til toppen";
+App::$strings["Unpin from the top"] = "Ikke fest til toppen lengre";
+App::$strings["Double click to exit zoom"] = "Dobbelklikk for å gå ut av forstørring";
+App::$strings["%d minutes"] = array(
+ 0 => "%d minutter",
+ 1 => "%d minutter",
);
-App::$strings["__ctx:relative_date__ day"] = array(
- 0 => "dag",
- 1 => "dager",
+App::$strings["about %d hours"] = array(
+ 0 => "omtrent %d timer",
+ 1 => "omtrent %d timer",
);
-App::$strings["__ctx:relative_date__ hour"] = array(
- 0 => "time",
- 1 => "timer",
+App::$strings["%d days"] = array(
+ 0 => "%d dager",
+ 1 => "%d dager",
);
-App::$strings["__ctx:relative_date__ minute"] = array(
- 0 => "minutt",
- 1 => "minutter",
+App::$strings["%d months"] = array(
+ 0 => "%d måneder",
+ 1 => "%d måneder",
);
-App::$strings["__ctx:relative_date__ second"] = array(
- 0 => "sekund",
- 1 => "sekunder",
+App::$strings["%d years"] = array(
+ 0 => "%d år",
+ 1 => "%d år",
);
-App::$strings["%1\$s's birthday"] = "%1\$s sin fødselsdag";
-App::$strings["Happy Birthday %1\$s"] = "Gratulerer med dagen, %1\$s";
+App::$strings["January"] = "januar";
+App::$strings["February"] = "februar";
+App::$strings["March"] = "mars";
+App::$strings["April"] = "april";
+App::$strings["__ctx:long__ May"] = "mai";
+App::$strings["June"] = "juni";
+App::$strings["July"] = "juli";
+App::$strings["August"] = "august";
+App::$strings["September"] = "september";
+App::$strings["October"] = "oktober";
+App::$strings["November"] = "november";
+App::$strings["December"] = "desember";
+App::$strings["Jan"] = "Jan";
+App::$strings["Feb"] = "Feb";
+App::$strings["Mar"] = "Mar";
+App::$strings["Apr"] = "Apr";
+App::$strings["__ctx:short__ May"] = "mai";
+App::$strings["Jun"] = "Jun";
+App::$strings["Jul"] = "Jul";
+App::$strings["Aug"] = "Aug";
+App::$strings["Sep"] = "Sep";
+App::$strings["Oct"] = "Okt";
+App::$strings["Nov"] = "Nov";
+App::$strings["Dec"] = "Des";
+App::$strings["Sunday"] = "søndag";
+App::$strings["Monday"] = "mandag";
+App::$strings["Tuesday"] = "tirsdag";
+App::$strings["Wednesday"] = "onsdag";
+App::$strings["Thursday"] = "torsdag";
+App::$strings["Friday"] = "fredag";
+App::$strings["Saturday"] = "lørdag";
+App::$strings["Sun"] = "Søn";
+App::$strings["Mon"] = "Man";
+App::$strings["Tue"] = "Tirs";
+App::$strings["Wed"] = "Ons";
+App::$strings["Thu"] = "Tors";
+App::$strings["Fri"] = "Fre";
+App::$strings["Sat"] = "Lør";
+App::$strings["__ctx:calendar__ today"] = "idag";
+App::$strings["__ctx:calendar__ month"] = "måned";
+App::$strings["__ctx:calendar__ week"] = "uke";
+App::$strings["__ctx:calendar__ day"] = "dag";
+App::$strings["__ctx:calendar__ All day"] = "Hele dagen";
+App::$strings["Please stand by while your download is being prepared."] = "Nedlastingen din blir klargjort.";
+App::$strings["Email address not valid"] = "Epostadressen er ikke gyldig";
+App::$strings["Required"] = "Påkrevd";
+App::$strings["OpenWebAuth: %1\$s welcomes %2\$s"] = "OpenWebAuth: %1\$s ønsker %2\$s velkommen";
App::$strings["photo"] = "foto";
App::$strings["channel"] = "kanal";
-App::$strings["comment"] = "kommentar";
+App::$strings["message"] = "melding";
App::$strings["%1\$s likes %2\$s's %3\$s"] = "%1\$s liker %2\$s sin %3\$s";
-App::$strings["likes %1\$s's %2\$s"] = "";
+App::$strings["likes %1\$s's %2\$s"] = "liker %2\$s til %1\$s";
App::$strings["%1\$s doesn't like %2\$s's %3\$s"] = "%1\$s liker ikke %2\$s sin %3\$s";
-App::$strings["doesn't like %1\$s's %2\$s"] = "";
-App::$strings["%1\$s repeated %2\$s's %3\$s"] = "%1\$s deltar kanskje på %2\$ss %3\$s";
-App::$strings["repeated %1\$s's %2\$s"] = "Besøk %1\$s sitt %2\$s";
-App::$strings["This is an unsaved preview"] = "";
-App::$strings["__ctx:title__ Likes"] = "Liker";
-App::$strings["__ctx:title__ Dislikes"] = "Liker ikke";
-App::$strings["__ctx:title__ Attending"] = "Deltar";
-App::$strings["__ctx:title__ Not attending"] = "Deltar ikke";
-App::$strings["__ctx:title__ Might attend"] = "Deltar kanskje";
-App::$strings["__ctx:title__ Repeats"] = "";
+App::$strings["doesn't like %1\$s's %2\$s"] = "liker ikke %2\$s til %1\$s";
+App::$strings["%1\$s repeated %2\$s's %3\$s"] = "%1\$s videreformidlet %3\$s til %2\$s";
+App::$strings["repeated %1\$s's %2\$s"] = "videreformidlet %2\$s til %1\$s";
+App::$strings["This is an unsaved preview"] = "Dette er en forhåndsvisning som ikke er lagret";
App::$strings["Select"] = "Velg";
App::$strings["Delete"] = "Slett";
App::$strings["Toggle Star Status"] = "Skru av og på stjernestatus";
@@ -360,12 +470,10 @@ App::$strings["Delete Selected Items"] = "Slett valgte elementer";
App::$strings["View Source"] = "Vis kilde";
App::$strings["Follow Thread"] = "Følg tråd";
App::$strings["Unfollow Thread"] = "Ikke følg tråd";
+App::$strings["View Profile"] = "Vis profil";
App::$strings["Recent Activity"] = "Nylig aktivitet";
App::$strings["Connect"] = "Koble";
App::$strings["Edit Connection"] = "Endre forbindelse";
-App::$strings["Unknown"] = "Ukjent";
-App::$strings["Approve this item"] = "Fjern denne filen";
-App::$strings["Delete this item"] = "Slett dette elementet?";
App::$strings["%s likes this."] = "%s liker dette.";
App::$strings["%s doesn't like this."] = "%s liker ikke dette.";
App::$strings["<span %1\$s>%2\$d people</span> like this."] = array(
@@ -393,23 +501,24 @@ App::$strings["Where are you right now?"] = "Hvor er du akkurat nå?";
App::$strings["Choose images to embed"] = "Velg bilder";
App::$strings["Choose an album"] = "Velg et album";
App::$strings["Choose a different album..."] = "Velg et annet album...";
-App::$strings["Error getting album list"] = "";
-App::$strings["Error getting photo link"] = "";
-App::$strings["Error getting album"] = "";
-App::$strings["Comments enabled"] = "";
-App::$strings["Comments disabled"] = "";
-App::$strings["Confirm delete"] = "Profilen er slettet.";
+App::$strings["Error getting album list"] = "Klarte ikke å hente albumlisten";
+App::$strings["Error getting photo link"] = "Klarte ikke å hente fotolenken";
+App::$strings["Error getting album"] = "Klarte ikke å hente album";
+App::$strings["Comments enabled"] = "Kommentarer er tillatt";
+App::$strings["Comments disabled"] = "Komentarer er ikke tillatt";
+App::$strings["Confirm delete"] = "Bekreft sletting";
App::$strings["Preview"] = "Forhåndsvisning";
-App::$strings["Share"] = "Del";
+App::$strings["Start a conversation"] = "Start en samtale";
App::$strings["Page link name"] = "Sidens lenkenavn";
App::$strings["Post as"] = "Lag innlegg som";
App::$strings["Bold"] = "Uthevet";
App::$strings["Italic"] = "Kursiv";
+App::$strings["Highlight selected text"] = "Fremhev valgt tekst";
App::$strings["Underline"] = "Understreket";
App::$strings["Quote"] = "Sitat";
App::$strings["Code"] = "Kode";
App::$strings["Attach/Upload file"] = "Last opp fil/vedlegg";
-App::$strings["Embed an image from your albums"] = "";
+App::$strings["Embed an image from your albums"] = "Sett inn et bilde fra albumene dine";
App::$strings["Cancel"] = "Avbryt";
App::$strings["OK"] = "OK";
App::$strings["Toggle voting"] = "Skru av eller på stemming";
@@ -417,13 +526,11 @@ App::$strings["Toggle poll"] = "Spørreskjema (på/av)";
App::$strings["Option"] = "Valg";
App::$strings["Add option"] = "Legg til valg";
App::$strings["Minutes"] = "Minutter";
-App::$strings["Hours"] = "timer";
-App::$strings["Days"] = "dager";
+App::$strings["Hours"] = "Timer";
+App::$strings["Days"] = "Dager";
App::$strings["Allow multiple answers"] = "Tillat flere svar";
-App::$strings["No"] = "Nei";
-App::$strings["Yes"] = "Ja";
App::$strings["Disable comments"] = "Slå av kommentarer";
-App::$strings["Toggle comments"] = "";
+App::$strings["Toggle comments"] = "Slå av/på kommentarer";
App::$strings["Title (optional)"] = "Tittel (valgfri)";
App::$strings["Summary (optional)"] = "Sammendrag (valgfritt)";
App::$strings["Categories (optional, comma-separated list)"] = "Kategorier (valgfri, kommaseparert liste)";
@@ -432,23 +539,27 @@ App::$strings["Other networks and post services"] = "Andre nettverk og innleggst
App::$strings["Set expiration date"] = "Angi utløpsdato";
App::$strings["Set publish date"] = "Angi publiseringsdato";
App::$strings["Encrypt text"] = "Krypter tekst";
-App::$strings["__ctx:noun__ Like"] = array(
- 0 => "Liker",
- 1 => "Liker",
-);
App::$strings["__ctx:noun__ Repeat"] = array(
- 0 => "",
- 1 => "",
+ 0 => "Videresending",
+ 1 => "Videresendinger",
);
App::$strings["__ctx:noun__ Dislike"] = array(
0 => "Liker ikke",
1 => "Liker ikke",
);
+App::$strings["__ctx:noun__ Comment"] = array(
+ 0 => "Kommentar",
+ 1 => "Kommentarer",
+);
+App::$strings["__ctx:noun__ Reply"] = array(
+ 0 => "Svar",
+ 1 => "Svar",
+);
App::$strings["__ctx:noun__ Attending"] = array(
0 => "Deltar",
1 => "Deltar",
);
-App::$strings["__ctx:noun__ Not Attending"] = array(
+App::$strings["__ctx:noun__ Not attending"] = array(
0 => "Deltar ikke",
1 => "Deltar ikke",
);
@@ -456,190 +567,202 @@ App::$strings["__ctx:noun__ Undecided"] = array(
0 => "Ikke bestemt",
1 => "Ikke bestemt",
);
+App::$strings["Remote authentication"] = "Fjernautentisering";
+App::$strings["Click to authenticate to your home hub"] = "Klikk for å godkjennes mot din hjemme-hub";
+App::$strings["Channels"] = "Kanaler";
+App::$strings["Manage your channels"] = "Behandle kanalene dine";
+App::$strings["Settings"] = "Innstillinger";
+App::$strings["Account/Channel Settings"] = "Konto-/kanalinnstillinger";
+App::$strings["Logout"] = "Logg ut";
+App::$strings["End this session"] = "Avslutt denne økten";
+App::$strings["Your profile page"] = "Din profilside";
+App::$strings["Edit Profiles"] = "Rediger profiler";
+App::$strings["Manage/Edit profiles"] = "Håndter/endre profiler";
+App::$strings["Edit Profile"] = "Rediger profil";
+App::$strings["Edit your profile"] = "Rediger profil";
+App::$strings["Login"] = "Logg inn";
+App::$strings["Sign in"] = "Logg på";
+App::$strings["Take me home"] = "Ta meg hjem";
+App::$strings["Log me out of this site"] = "Logg meg ut fra dette nettstedet";
+App::$strings["Register"] = "Registrer";
+App::$strings["Create an account"] = "Lag en konto";
+App::$strings["Help"] = "Hjelp";
+App::$strings["Help and documentation"] = "Hjelp og dokumentasjon";
+App::$strings["Search"] = "Søk";
+App::$strings["Search site @name, !forum, #tag, ?docs, content"] = "Søk etter @navn, !forum, #emne, ?dokumentasjon eller innhold";
+App::$strings["Admin"] = "Administrator";
+App::$strings["Site Setup and Configuration"] = "Nettstedsoppsett og -konfigurasjon";
+App::$strings["Loading"] = "Laster";
+App::$strings["@name, #tag, ?doc, content"] = "@navn, #merkelapp, ?dokumentasjon, innhold";
+App::$strings["Please wait..."] = "Vennligst vent...";
+App::$strings["Apps"] = "Apper";
+App::$strings["Channel Apps"] = "Kanalapper";
+App::$strings["System Apps"] = "Systemapper";
+App::$strings["Pinned Apps"] = "Festede apper";
+App::$strings["Featured Apps"] = "Fremhevede apper";
+App::$strings["Channel"] = "Kanal";
+App::$strings["Status Messages and Posts"] = "Statusmeldinger og -innlegg";
+App::$strings["About"] = "Om";
+App::$strings["Profile Details"] = "Profildetaljer";
+App::$strings["Photo Albums"] = "Fotoalbum";
+App::$strings["Files"] = "Filer";
+App::$strings["Files and Storage"] = "Filer og lagring";
+App::$strings["Chatrooms"] = "Chatrom";
+App::$strings["Bookmarks"] = "Bokmerker";
+App::$strings["Saved Bookmarks"] = "Lagrede bokmerker";
+App::$strings["Webpages"] = "Websider";
+App::$strings["View Webpages"] = "Vis websider";
+App::$strings["The form security token was not correct. This probably happened because the form has been opened for too long (>3 hours) before submitting it."] = "Skjemaets sikkerhetspollett var ikke gyldig. Dette skjedde antakelig fordi skjemaet har vært åpnet for lenge (>3 timer) før det ble sendt inn.";
+App::$strings["Miscellaneous"] = "Forskjellig";
+App::$strings["Birthday"] = "Fødselsdag";
+App::$strings["Age: "] = "Alder: ";
+App::$strings["YYYY-MM-DD or MM-DD"] = "YYYY-MM-DD eller MM-DD";
+App::$strings["never"] = "aldri";
+App::$strings["less than a second ago"] = "for mindre enn ett sekund siden";
+App::$strings["__ctx:e.g. 22 hours ago, 1 minute ago__ %1\$d %2\$s ago"] = "%1\$d %2\$s siden";
+App::$strings["ago"] = "siden";
+App::$strings["in"] = "i";
+App::$strings["now"] = "nå";
+App::$strings["__ctx:relative_date__ year"] = array(
+ 0 => "år",
+ 1 => "år",
+);
+App::$strings["__ctx:relative_date__ month"] = array(
+ 0 => "måned",
+ 1 => "måneder",
+);
+App::$strings["__ctx:relative_date__ week"] = array(
+ 0 => "uke",
+ 1 => "uker",
+);
+App::$strings["__ctx:relative_date__ day"] = array(
+ 0 => "dag",
+ 1 => "dager",
+);
+App::$strings["__ctx:relative_date__ hour"] = array(
+ 0 => "time",
+ 1 => "timer",
+);
+App::$strings["__ctx:relative_date__ minute"] = array(
+ 0 => "minutt",
+ 1 => "minutter",
+);
+App::$strings["__ctx:relative_date__ second"] = array(
+ 0 => "sekund",
+ 1 => "sekunder",
+);
+App::$strings["%1\$s's birthday"] = "%1\$s sin fødselsdag";
+App::$strings["Happy Birthday %1\$s"] = "Gratulerer med dagen, %1\$s";
+App::$strings["Image exceeds website size limit of %lu bytes"] = "Bilde overstiger nettstedets størrelsesbegrensning på %lu bytes";
+App::$strings["Image file is empty."] = "Bildefilen er tom.";
+App::$strings["Unable to process image"] = "Kan ikke behandle bildet";
+App::$strings["Photo storage failed."] = "Bildelagring mislyktes.";
+App::$strings["a new photo"] = "et nytt bilde";
+App::$strings["__ctx:photo_upload__ %1\$s posted %2\$s to %3\$s"] = "%1\$s la inn %2\$s til %3\$s";
+App::$strings["Recent Photos"] = "Nye bilder";
+App::$strings["Upload New Photos"] = "Last opp nye bilder";
+App::$strings["New window"] = "Nytt vindu";
+App::$strings["Open the selected location in a different window or browser tab"] = "Åpne det valgte stedet i et annet vindu eller nettleser-fane";
+App::$strings["url: "] = "url:";
+App::$strings["error_code: "] = "feilkode:";
+App::$strings["error_string: "] = "feilmelding";
+App::$strings["content-type: "] = "";
+App::$strings["Friendica"] = "Friendica";
+App::$strings["OStatus"] = "OStatus";
+App::$strings["GNU-Social"] = "";
+App::$strings["RSS/Atom"] = "RSS/Atom";
+App::$strings["ActivityPub"] = "";
+App::$strings["Email"] = "E-post";
+App::$strings["Diaspora"] = "Diaspora";
+App::$strings["Facebook"] = "Facebook";
+App::$strings["Zot"] = "Zot";
+App::$strings["LinkedIn"] = "LinkedIn";
+App::$strings["XMPP/IM"] = "XMPP/IM";
+App::$strings["MySpace"] = "MySpace";
+App::$strings["%1\$s wrote the following %2\$s %3\$s"] = "%1\$s skrev følgende %2\$s %3\$s";
+App::$strings["post"] = "innlegg";
+App::$strings["spoiler"] = "røpealarm";
+App::$strings["Edit"] = "Rediger";
+App::$strings["Visible to your default audience"] = "Synlig for ditt standard publikum";
+App::$strings["Profile-Based Privacy Groups"] = "Profilbaserte personverngrupper";
+App::$strings["Private Forum"] = "Privat forum";
+App::$strings["Forums"] = "Forum";
+App::$strings["Only me"] = "Kun meg";
+App::$strings["Share with"] = "Del med";
+App::$strings["Custom selection"] = "Tilpasset utvalg";
+App::$strings["Select \"Allow\" to allow viewing. \"Don't allow\" lets you override and limit the scope of \"Allow\"."] = "Velg \"Tillat\" for å tillate visning. \"Ikke tillat\" lar deg overstyre og begrense gyldigheten av \"Tillat\".";
+App::$strings["Allow"] = "Tillat";
+App::$strings["Don't allow"] = "Ikke tillat";
+App::$strings["Permissions"] = "Tillatelser";
+App::$strings["Close"] = "Lukk";
+App::$strings["Post permissions %s cannot be changed %s after a post is shared.</br />These permissions set who is allowed to view the post."] = "Tillatelsene til innlegget %s kan ikke endres %s etter at innlegget er delt.</br />Disse tillatelsene avgjør hvem som kan se innlegget.";
App::$strings["prev"] = "forrige";
App::$strings["first"] = "første";
App::$strings["last"] = "siste";
App::$strings["next"] = "neste";
App::$strings["older"] = "eldre";
App::$strings["newer"] = "nyere";
-App::$strings["Accepts"] = "";
-App::$strings["Comments"] = "";
-App::$strings["Stream items"] = "";
-App::$strings["Wall posts"] = "";
-App::$strings["Nothing"] = "";
+App::$strings["Accepts"] = "Godtar";
+App::$strings["Comments"] = "Kommentarer";
+App::$strings["Stream items"] = "Elementer fra tidslinjen";
+App::$strings["Wall posts"] = "Veggposter";
+App::$strings["Nothing"] = "Ingenting";
App::$strings["View all %s connections"] = "Vis alle %s forbindelser";
-App::$strings["Network: %s"] = "";
+App::$strings["Network: %s"] = "Nettverk: %s";
App::$strings["Save"] = "Lagre";
-App::$strings["Monday"] = "mandag";
-App::$strings["Tuesday"] = "tirsdag";
-App::$strings["Wednesday"] = "onsdag";
-App::$strings["Thursday"] = "torsdag";
-App::$strings["Friday"] = "fredag";
-App::$strings["Saturday"] = "lørdag";
-App::$strings["Sunday"] = "søndag";
-App::$strings["January"] = "januar";
-App::$strings["February"] = "februar";
-App::$strings["March"] = "mars";
-App::$strings["April"] = "april";
App::$strings["May"] = "mai";
-App::$strings["June"] = "juni";
-App::$strings["July"] = "juli";
-App::$strings["August"] = "august";
-App::$strings["September"] = "september";
-App::$strings["October"] = "oktober";
-App::$strings["November"] = "november";
-App::$strings["December"] = "desember";
App::$strings["Unknown attachment"] = "Ukjent vedlegg";
App::$strings["Size"] = "Størrelse";
App::$strings["remove category"] = "fjern kategori";
App::$strings["remove from file"] = "fjern fra fil";
-App::$strings["Download binary/encrypted content"] = "";
+App::$strings["Download binary/encrypted content"] = "Last ned binært/kryptert innhold";
App::$strings["__ctx:noun__ %d Vote"] = array(
- 0 => "",
- 1 => "",
+ 0 => "%d stemme",
+ 1 => "%d stemmer",
);
App::$strings["__ctx:noun__ %d Vote in total"] = array(
- 0 => "",
- 1 => "",
+ 0 => "%d stemme totalt",
+ 1 => "%d stemmer totalt",
);
App::$strings["Poll has ended"] = "Spørreskjema har utløpt";
-App::$strings["Poll ends in %s"] = "Spørreskjema utløper om %s";
-App::$strings["Vote"] = "";
+App::$strings["Poll ends %s"] = "Spørreskjema utløper %s";
+App::$strings["Vote"] = "Stem";
App::$strings["Link to Source"] = "Lenke til kilde";
+App::$strings["default"] = "standard";
App::$strings["Page layout"] = "Sidens layout";
App::$strings["You can create your own with the layouts tool"] = "Du kan lage din egen med layout-verktøyet";
App::$strings["BBcode"] = "BBcode";
App::$strings["HTML"] = "";
App::$strings["Markdown"] = "Markdown";
App::$strings["Text"] = "Tekst";
-App::$strings["Comanche Layout"] = "";
+App::$strings["Comanche Layout"] = "Comanche Utlegg";
App::$strings["PHP"] = "";
App::$strings["Page content type"] = "Sidens innholdstype";
+App::$strings["conversation"] = "samtale";
App::$strings["activity"] = "aktivitet";
App::$strings["poll"] = "spørreskjema";
-App::$strings["a-z, 0-9, -, and _ only"] = "";
+App::$strings["a-z, 0-9, -, and _ only"] = "kun a-z, 0-9, - og _";
App::$strings["Design Tools"] = "Designverktøy";
App::$strings["Blocks"] = "Byggeklosser";
App::$strings["Menus"] = "Menyer";
App::$strings["Layouts"] = "Layout";
App::$strings["Pages"] = "Sider";
App::$strings["Import"] = "Importer";
-App::$strings["Import website..."] = "";
-App::$strings["Select folder to import"] = "";
-App::$strings["Import from a zipped folder:"] = "";
-App::$strings["Import from cloud files:"] = "";
-App::$strings["/cloud/channel/path/to/folder"] = "";
-App::$strings["Enter path to website files"] = "";
-App::$strings["Select folder"] = "";
-App::$strings["Export website..."] = "";
-App::$strings["Export to a zip file"] = "";
+App::$strings["Import website..."] = "Importer vevside";
+App::$strings["Select folder to import"] = "Velg mappe som skal importeres";
+App::$strings["Import from a zipped folder:"] = "Importer fra en zippet mappe:";
+App::$strings["Import from cloud files:"] = "Importer fra filer i skyen:";
+App::$strings["/cloud/channel/path/to/folder"] = "/cloud/channel/bane/til/mappe";
+App::$strings["Enter path to website files"] = "Oppgi bane til vesidens filer";
+App::$strings["Select folder"] = "Velg mappe";
+App::$strings["Export website..."] = "Eksporter vevside…";
+App::$strings["Export to a zip file"] = "Eksporter til zip-fil";
App::$strings["website.zip"] = "";
-App::$strings["Enter a name for the zip file."] = "";
-App::$strings["Export to cloud files"] = "";
-App::$strings["/path/to/export/folder"] = "";
-App::$strings["Enter a path to a cloud files destination."] = "";
-App::$strings["Specify folder"] = "";
-App::$strings["Default"] = "Standard";
-App::$strings["View PDF"] = "";
-App::$strings[" by "] = " av ";
-App::$strings[" on "] = "På";
-App::$strings["Embedded content"] = "Innebygget innhold";
-App::$strings["Embedding disabled"] = "Innbygging avskrudd";
-App::$strings["Delete this item?"] = "Slett dette elementet?";
-App::$strings["Item deleted"] = "";
-App::$strings["Comment"] = "Kommentar";
-App::$strings["show all"] = "Vis alle";
-App::$strings["show less"] = "Vis eldste først";
-App::$strings["expand"] = "";
-App::$strings["collapse"] = "";
-App::$strings["Password too short"] = "Passordet er for kort";
-App::$strings["Passwords do not match"] = "Passordene er ikke like";
-App::$strings["everybody"] = "alle";
-App::$strings["Secret Passphrase"] = "Hemmelig passordsetning";
-App::$strings["Passphrase hint"] = "Hint om passordsetning";
-App::$strings["Notice: Permissions have changed but have not yet been submitted."] = "Varsel: Tillatelser har blitt endret, men de har ennå ikke blitt sendt inn.";
-App::$strings["close all"] = "lukk alle";
-App::$strings["Nothing new here"] = "Ikke noe nytt her";
-App::$strings["Rate This Channel (this is public)"] = "Vurder denne kanalen (dette er offentlig)";
-App::$strings["Rating"] = "Vurdering";
-App::$strings["Describe (optional)"] = "Beskriv (valgfritt)";
-App::$strings["Submit"] = "Lagre";
-App::$strings["Please enter a link URL"] = "Vennligst skriv inn en lenke URL";
-App::$strings["Unsaved changes. Are you sure you wish to leave this page?"] = "Endringene er ikke lagret. Er du sikker på at du ønsker å forlate denne siden?";
-App::$strings["Location"] = "Plassering";
-App::$strings["lovely"] = "";
-App::$strings["wonderful"] = "";
-App::$strings["fantastic"] = "";
-App::$strings["great"] = "";
-App::$strings["Your chosen nickname was either already taken or not valid. Please use our suggestion ("] = "";
-App::$strings[") or enter a new one."] = "";
-App::$strings["Thank you, this nickname is valid."] = "";
-App::$strings["A channel name is required."] = "";
-App::$strings["This is a "] = "";
-App::$strings[" channel name"] = "Kanalnavn";
-App::$strings["Back to reply"] = "";
-App::$strings["Pinned"] = "";
-App::$strings["Pin to the top"] = "";
-App::$strings["Unpin from the top"] = "";
-App::$strings["%d minutes"] = array(
- 0 => "%d minutter",
- 1 => "%d minutter",
-);
-App::$strings["about %d hours"] = array(
- 0 => "omtrent %d timer",
- 1 => "omtrent %d timer",
-);
-App::$strings["%d days"] = array(
- 0 => "%d dager",
- 1 => "%d dager",
-);
-App::$strings["%d months"] = array(
- 0 => "%d måneder",
- 1 => "%d måneder",
-);
-App::$strings["%d years"] = array(
- 0 => "%d år",
- 1 => "%d år",
-);
-App::$strings["timeago.prefixAgo"] = "timeago.prefixAgo";
-App::$strings["timeago.prefixFromNow"] = "timeago.prefixFromNow";
-App::$strings["timeago.suffixAgo"] = "siden";
-App::$strings["timeago.suffixFromNow"] = "";
-App::$strings["less than a minute"] = "mindre enn ett minutt";
-App::$strings["about a minute"] = "omtrent et minutt";
-App::$strings["about an hour"] = "omtrent en time";
-App::$strings["a day"] = "en dag";
-App::$strings["about a month"] = "omtrent en måned";
-App::$strings["about a year"] = "omtrent et år";
-App::$strings[" "] = " ";
-App::$strings["timeago.numbers"] = "timeago.numbers";
-App::$strings["__ctx:long__ May"] = "mai";
-App::$strings["Jan"] = "Jan";
-App::$strings["Feb"] = "Feb";
-App::$strings["Mar"] = "Mar";
-App::$strings["Apr"] = "Apr";
-App::$strings["__ctx:short__ May"] = "mai";
-App::$strings["Jun"] = "Jun";
-App::$strings["Jul"] = "Jul";
-App::$strings["Aug"] = "Aug";
-App::$strings["Sep"] = "Sep";
-App::$strings["Oct"] = "Okt";
-App::$strings["Nov"] = "Nov";
-App::$strings["Dec"] = "Des";
-App::$strings["Sun"] = "Søn";
-App::$strings["Mon"] = "Man";
-App::$strings["Tue"] = "Tirs";
-App::$strings["Wed"] = "Ons";
-App::$strings["Thu"] = "Tors";
-App::$strings["Fri"] = "Fre";
-App::$strings["Sat"] = "Lør";
-App::$strings["__ctx:calendar__ today"] = "idag";
-App::$strings["__ctx:calendar__ month"] = "måned";
-App::$strings["__ctx:calendar__ week"] = "uke";
-App::$strings["__ctx:calendar__ day"] = "dag";
-App::$strings["__ctx:calendar__ All day"] = "Hele dagen";
-App::$strings["Please stand by while your download is being prepared."] = "";
-App::$strings["Email address not valid"] = "";
+App::$strings["Enter a name for the zip file."] = "Oppgi et navn til zip-filen.";
+App::$strings["Export to cloud files"] = "Eksporter til filen i skyen";
+App::$strings["/path/to/export/folder"] = "/bane/til/eksport/mappe";
+App::$strings["Enter a path to a cloud files destination."] = "Oppgi bane i skyen hvor filene skal lagres";
+App::$strings["Specify folder"] = "Velg mappe";
App::$strings["Unable to obtain identity information from database"] = "Klarer ikke å få tak i identitetsinformasjon fra databasen";
App::$strings["Empty name"] = "Mangler navn";
App::$strings["Name too long"] = "Navnet er for langt";
@@ -649,15 +772,13 @@ App::$strings["Reserved nickname. Please choose another."] = "Reservert kallenav
App::$strings["Nickname has unsupported characters or is already being used on this site."] = "Kallenavnet inneholder tegn som ikke er støttet eller det er allerede i bruk på dette nettstedet.";
App::$strings["Unable to retrieve created identity"] = "Klarer ikke å hente den lagede identiteten";
App::$strings["Default Profile"] = "Standardprofil";
-App::$strings["Friends"] = "Venner";
-App::$strings["Unable to retrieve modified identity"] = "";
-App::$strings["Requested channel is not available"] = "";
+App::$strings["Unable to retrieve modified identity"] = "Ikke i stand til å hente endret identitet";
+App::$strings["Requested channel is not available"] = "Kanalen du prøver å nå er ikke tilgjengelig";
App::$strings["Requested profile is not available."] = "Forespurt profil er ikke tilgjengelig.";
App::$strings["Change profile photo"] = "Endre profilbilde";
-App::$strings["Edit"] = "Endre";
App::$strings["Create New Profile"] = "Lag ny profil";
App::$strings["Profile Image"] = "Profilbilde";
-App::$strings["Visible to everybody"] = "";
+App::$strings["Visible to everybody"] = "Synlig for alle";
App::$strings["Edit visibility"] = "Endre synlighet";
App::$strings["Gender:"] = "Kjønn:";
App::$strings["Status:"] = "Status:";
@@ -665,12 +786,8 @@ App::$strings["Homepage:"] = "Hjemmeside:";
App::$strings["Hometown:"] = "Hjemby:";
App::$strings["Online Now"] = "Online nå";
App::$strings["This channel has not added a profile description yet"] = "Denne kanalen har ikke lagt til en profilbeskrivelse enda";
-App::$strings["Change your profile photo"] = "";
-App::$strings["Female"] = "Kvinnelig";
-App::$strings["Male"] = "Mannlig";
+App::$strings["Change your profile photo"] = "Endre profilbildet ditt";
App::$strings["Trans"] = "";
-App::$strings["Neuter"] = "Intetkjønn";
-App::$strings["Non-specific"] = "Ubestemt";
App::$strings["Full Name:"] = "Fullt navn:";
App::$strings["j F, Y"] = "j F, Y";
App::$strings["j F"] = "j F";
@@ -702,151 +819,655 @@ App::$strings["Remote Authentication"] = "Fjernautentisering";
App::$strings["Enter your channel address (e.g. channel@example.com)"] = "Skriv din kanaladresse (for eksempel channel@exampel.com)";
App::$strings["Authenticate"] = "Autentiser";
App::$strings["Account '%s' deleted"] = "Kontoen '%s' slettet";
-App::$strings["%d invitation available"] = array(
- 0 => "%d invitasjon tilgjengelig",
- 1 => "%d invitasjoner tilgjengelig",
-);
-App::$strings["Advanced"] = "Avansert";
-App::$strings["Find Channels"] = "Finn kanaler";
-App::$strings["Enter name or interest"] = "Skriv navn eller interesse";
-App::$strings["Connect/Follow"] = "Forbindelse/Følg";
-App::$strings["Examples: Robert Morgenstein, Fishing"] = "Eksempler: Ola Nordmann, fisking";
-App::$strings["Find"] = "Finn";
-App::$strings["Channel Suggestions"] = "Kanalforslag";
-App::$strings["Random Profile"] = "Tilfeldig profil";
-App::$strings["Invite Friends"] = "Inviter venner";
-App::$strings["Advanced example: name=fred and country=iceland"] = "Avansert eksempel: navn=fred og land=island";
-App::$strings["Everything"] = "Alt";
-App::$strings["Categories"] = "Kategorier";
-App::$strings["Common Connections"] = "";
-App::$strings["View all %d common connections"] = "";
-App::$strings["The form security token was not correct. This probably happened because the form has been opened for too long (>3 hours) before submitting it."] = "Skjemaets sikkerhetspollett var ikke gyldig. Dette skjedde antakelig fordi skjemaet har vært åpnet for lenge (>3 timer) før det ble sendt inn.";
-App::$strings["Trending"] = "";
-App::$strings["Tags"] = "Merkelapper";
-App::$strings["Keywords"] = "Nøkkelord";
-App::$strings["have"] = "har";
-App::$strings["has"] = "har";
-App::$strings["want"] = "ønsker";
-App::$strings["wants"] = "ønsker";
-App::$strings["like"] = "liker";
-App::$strings["likes"] = "liker";
-App::$strings["dislike"] = "misliker";
-App::$strings["dislikes"] = "misliker";
-App::$strings["New window"] = "Nytt vindu";
-App::$strings["Open the selected location in a different window or browser tab"] = "Åpne det valgte stedet i et annet vindu eller nettleser-fane";
-App::$strings["Visible to your default audience"] = "Synlig for ditt standard publikum";
-App::$strings["Profile-Based Privacy Groups"] = "";
-App::$strings["Private Forum"] = "";
-App::$strings["Forums"] = "Forum";
-App::$strings["Only me"] = "Kun meg";
-App::$strings["Share with"] = "Del med";
-App::$strings["Custom selection"] = "Tilpasset utvalg";
-App::$strings["Select \"Allow\" to allow viewing. \"Don't allow\" lets you override and limit the scope of \"Allow\"."] = "";
-App::$strings["Allow"] = "";
-App::$strings["Don't allow"] = "";
-App::$strings["Permissions"] = "Tillatelser";
-App::$strings["Close"] = "Lukk";
-App::$strings["Post permissions %s cannot be changed %s after a post is shared.</br />These permissions set who is allowed to view the post."] = "Tillatelsene til innlegget %s kan ikke endres %s etter at innlegget er delt.</br />Disse tillatelsene avgjør hvem som kan se innlegget.";
-App::$strings["INVALID EVENT DISMISSED!"] = "";
-App::$strings["Summary: "] = "Sammendrag";
-App::$strings["Date: "] = "Dato";
-App::$strings["Reason: "] = "";
-App::$strings["INVALID CARD DISMISSED!"] = "";
-App::$strings["Name: "] = "Navn";
-App::$strings["Select a profile to assign to this contact"] = "Velg en profil for denne kontakten";
-App::$strings["Frequently"] = "Ofte";
-App::$strings["Hourly"] = "Hver time";
-App::$strings["Twice daily"] = "To ganger daglig";
-App::$strings["Daily"] = "Daglig";
-App::$strings["Weekly"] = "Ukentlig";
-App::$strings["Monthly"] = "Månedlig";
-App::$strings["Currently Male"] = "For tiden mann";
-App::$strings["Currently Female"] = "For tiden kvinne";
-App::$strings["Mostly Male"] = "For det meste mann";
-App::$strings["Mostly Female"] = "For det meste kvinne";
-App::$strings["Transgender"] = "Transkjønnet";
-App::$strings["Intersex"] = "interkjønnet";
-App::$strings["Transsexual"] = "Transseksuell";
-App::$strings["Hermaphrodite"] = "Hermafroditt";
-App::$strings["Undecided"] = "Ubestemt";
-App::$strings["Males"] = "Menn";
-App::$strings["Females"] = "Kvinner";
-App::$strings["Gay"] = "Homo";
-App::$strings["Lesbian"] = "Lesbisk";
-App::$strings["No Preference"] = "Ingen preferanse";
-App::$strings["Bisexual"] = "Biseksuell";
-App::$strings["Autosexual"] = "Autoseksuell";
-App::$strings["Abstinent"] = "Avholdende";
-App::$strings["Virgin"] = "Jomfru";
-App::$strings["Deviant"] = "Avviker";
-App::$strings["Fetish"] = "Fetisj";
-App::$strings["Oodles"] = "Masse";
-App::$strings["Nonsexual"] = "Ikke-seksuell";
-App::$strings["Single"] = "Enslig";
-App::$strings["Lonely"] = "Ensom";
-App::$strings["Available"] = "Tilgjengelig";
-App::$strings["Unavailable"] = "Ikke tilgjengelig";
-App::$strings["Has crush"] = "Er forelsket";
-App::$strings["Infatuated"] = "Betatt";
-App::$strings["Dating"] = "Sammen med";
-App::$strings["Unfaithful"] = "Utro";
-App::$strings["Sex Addict"] = "Sexavhengig";
-App::$strings["Friends/Benefits"] = "Venner/Frynsegoder";
-App::$strings["Casual"] = "Tilfeldig";
-App::$strings["Engaged"] = "Forlovet";
-App::$strings["Married"] = "Gift";
-App::$strings["Imaginarily married"] = "Gift i fantasien";
-App::$strings["Partners"] = "Partnere";
-App::$strings["Cohabiting"] = "Samboer";
-App::$strings["Common law"] = "Samboer";
-App::$strings["Happy"] = "Lykkelig";
-App::$strings["Not looking"] = "Ikke på utkikk";
-App::$strings["Swinger"] = "Partnerbytte";
-App::$strings["Betrayed"] = "Bedratt";
-App::$strings["Separated"] = "Separert";
-App::$strings["Unstable"] = "Ustabilt";
-App::$strings["Divorced"] = "Skilt";
-App::$strings["Imaginarily divorced"] = "Skilt i fantasien";
-App::$strings["Widowed"] = "Enke";
-App::$strings["Uncertain"] = "Usikkert";
-App::$strings["It's complicated"] = "Det er komplisert";
-App::$strings["Don't care"] = "Bryr meg ikke";
-App::$strings["Ask me"] = "Spør meg";
-App::$strings["OpenWebAuth: %1\$s welcomes %2\$s"] = "OpenWebAuth: %1\$s ønsker %2\$s velkommen";
-App::$strings["Focus (Hubzilla default)"] = "Focus (Hubzilla standard)";
-App::$strings["Theme settings"] = "Instillinger for utseende";
-App::$strings["Dark style"] = "";
-App::$strings["Light style"] = "";
-App::$strings["Common settings"] = "";
-App::$strings["Primary theme color"] = "";
-App::$strings["Current color, leave empty for default"] = "La feltet stå tomt for å bruke standard bredde";
-App::$strings["Success theme color"] = "";
-App::$strings["Info theme color"] = "Lenkefarge når musepekeren er over";
-App::$strings["Warning theme color"] = "Lenkefarge når musepekeren er over";
-App::$strings["Danger theme color"] = "Lenkefarge når musepekeren er over";
-App::$strings["Default to dark mode"] = "Standard mappe for opplasting av bilder";
-App::$strings["Always use light icons for navbar"] = "";
-App::$strings["Enable this option if you use a dark navbar color in light mode"] = "";
-App::$strings["Narrow navbar"] = "Smal navigasjonslinje";
-App::$strings["Navigation bar background color"] = "Navigasjonslinjens bakgrunnsfarge";
-App::$strings["Dark navigation bar background color"] = "Navigasjonslinjens bakgrunnsfarge";
-App::$strings["Set the background color"] = "Angi bakgrunnsfargen";
-App::$strings["Set the dark background color"] = "Angi bakgrunnsfargen";
-App::$strings["Set the background image"] = "Angi bakgrunnsbilde";
-App::$strings["Set the dark background image"] = "Angi bakgrunnsbilde";
-App::$strings["Set font-size for the entire application"] = "Angi skriftstørrelsen for hele programmet";
-App::$strings["Examples: 1rem, 100%, 16px"] = "For eksempel: 1rem, 100%, 16px";
-App::$strings["Set radius of corners in rem"] = "Angi hjørneradius";
-App::$strings["Leave empty for default radius"] = "La feltet stå tomt for å bruke standard bredde";
-App::$strings["Set maximum width of content region in rem"] = "Set maksbredde for hovedregionen i rem";
-App::$strings["Leave empty for default width"] = "La feltet stå tomt for å bruke standard bredde";
-App::$strings["Set size of conversation author photo"] = "Angi størrelsen for samtalens forfatterbilde";
-App::$strings["Leave empty for default size"] = "La feltet stå tomt for å bruke standard bredde";
-App::$strings["Set size of followup author photos"] = "Angi størrelsen på forfatterbilder ved oppfølging";
-App::$strings["Show advanced settings"] = "Vis avanserte innstillinger";
+App::$strings["Image/photo"] = "Bilde/fotografi";
+App::$strings["Encrypted content"] = "Kryptert innhold";
+App::$strings["Install %1\$s element %2\$s"] = "Installer %1\$s element %2\$s";
+App::$strings["This post contains an installable %s element, however you lack permissions to install it on this site."] = "Dette innlegget inneholder det installerbare elementet %s, men du mangler tillatelse til å installere det på dette nettstedet.";
+App::$strings["webpage"] = "nettside";
+App::$strings["layout"] = "layout";
+App::$strings["block"] = "byggekloss";
+App::$strings["menu"] = "meny";
+App::$strings["card"] = "kort";
+App::$strings["article"] = "artikkel";
+App::$strings["Click to open/close"] = "Klikk for å åpne/lukke";
+App::$strings["View article"] = "Vis artikkel";
+App::$strings["View summary"] = "Vis sammendrag";
+App::$strings["Different viewers will see this text differently"] = "Denne teksten vil se forskjellig ut for ulike besøkende";
+App::$strings["$1 wrote:"] = "$1 skrev:";
+App::$strings["Unable to import a removed channel."] = "Ikke i stand til å importere en kanal som er slettet.";
+App::$strings["Cannot create a duplicate channel identifier on this system. Import failed."] = "Kan ikke lage en kopi av kanal-identifikatoren på dette systemet. Import mislyktes.";
+App::$strings["Unable to create a unique channel address. Import failed."] = "Klarte ikke å lage en unik kanaladresse. Import mislyktes.";
+App::$strings["Cloned channel not found. Import failed."] = "Klonet kanal ble ikke funnet. Import mislyktes.";
+App::$strings["Popular Channels"] = "Populære kanaler";
+App::$strings["Channels to auto connect"] = "Kanaler som skal kobles automatisk";
+App::$strings["Comma separated list"] = "Kommaseparert liste";
+App::$strings["IRC Settings"] = "Innstillinger for IRC";
+App::$strings["IRC settings saved."] = "Innstillinger for IRC lagret.";
+App::$strings["IRC Chatroom"] = "IRC-kanal";
+App::$strings["ERROR: word length is not correct!"] = "FEIL: lengden på ordet er ikke riktig!";
+App::$strings["Fediquest App"] = "Fediquest";
+App::$strings["A distributed quest for a given word (game)."] = "En distribuert gjettelek om ord (spill)";
+App::$strings["To start a game, enter [fediquest]your_word[/fediquest] somewhere in a toplevel post."] = "For å starte et spill, skriv [fediquest]ditt_ord[/fediquest] et sted i et nytt innlegg.";
+App::$strings["Your contacts can post their guess in the comments."] = "Forbindelsene dine kan poste sine gjetninger i kommentarene til innlegget.";
+App::$strings["Your channel will evaluate the guess and automatically post the response."] = "Kanalen din vil automatisk vurdere svarene og poste resultatet i tråden.";
+App::$strings["Correct letters"] = "Riktige bokstaver";
+App::$strings["Letters contained in the word but at the wrong spot"] = "Bokstaver som er riktige, men på feil plass";
+App::$strings["Letters not contained in the word"] = "Bokstaver som ikke finnes i ordet";
+App::$strings["Network error"] = "Nettverksfeil";
+App::$strings["API error"] = "API-feil";
+App::$strings["Unknown issue"] = "Ukjent problem";
+App::$strings["Unable to retrieve email address from remote identity provider"] = "Fikk ikke til å hente epostadressen din fra identitetstilbyderen";
+App::$strings["Unable to login using email address "] = "Fikk ikke til å logge inn ved bruk av epostadresse";
+App::$strings["Social Authentication using your social media account"] = "Sosial Autentisering ved bruk av din sosiale nettverkskonto";
+App::$strings["This app enables one or more social provider sign-in buttons on the login page."] = "Denne appen legger til en eller flere login-knapper fra sosiale nettverk på innloggingssiden.";
+App::$strings["Add an identity provider"] = "Legg til en identitetstilbyder";
+App::$strings["Enable "] = "Skru på ";
+App::$strings["Key"] = "Nøkkel";
+App::$strings["Word"] = "Ord";
+App::$strings["Secret"] = "Hemmelighet";
+App::$strings["Add a custom provider"] = "Legg til en egendefinert tilbyder";
+App::$strings["Remove an identity provider"] = "Fjern en identitetstilbyder";
+App::$strings["Social authentication"] = "Sosial autentisering";
+App::$strings["Error while saving provider settings"] = "Det oppsto en feil ved lagring av innstillingene for tilbyderen";
+App::$strings["Custom provider already exists"] = "Den egendefinerte tilbyderen finnes allerede";
+App::$strings["Social authentication settings saved."] = "Innstillingene for sosial autentisering ble lagret.";
+App::$strings["Show Upload Limits"] = "Vis opplastingsgrenser";
+App::$strings["Hubzilla configured maximum size: "] = "Maksimum størrelse definert av Hubzilla:";
+App::$strings["PHP upload_max_filesize: "] = "";
+App::$strings["PHP post_max_size (must be larger than upload_max_filesize): "] = "PHP post_max_size (må være større enn upload_max_filesize):";
+App::$strings["Send your identity to all websites"] = "Send din identitet til alle nettsteder";
+App::$strings["Send ZID"] = "";
+App::$strings["Send email to all members"] = "Send epost til alle medlemmer";
+App::$strings["%s Administrator"] = "%s administrator";
+App::$strings["No recipients found."] = "Fant ingen mottakere";
+App::$strings["%1\$d of %2\$d messages sent."] = "%1\$d av %2\$d meldinger sendt.";
+App::$strings["Send email to all hub members."] = "Send epost til alle medlemmer av hub'en";
+App::$strings["Message subject"] = "Emne for meldingen";
+App::$strings["Sender Email address"] = "Avsenderens epostadresse";
+App::$strings["Test mode (only send to hub administrator)"] = "Testmodus (send kun til hubens administrator)";
+App::$strings["Rainbow Tag App"] = "";
+App::$strings["Add some colour to tag clouds"] = "";
+App::$strings["Rainbow Tag"] = "";
+App::$strings["We encountered a problem while logging in with the OpenID you provided. Please check the correct spelling of the ID."] = "Vi støtte på et problem under innloggingen med din OpenID. Vennligst sjekk at ID-en er stavet riktig.";
+App::$strings["The error message was:"] = "Feilmeldingen var:";
+App::$strings["OpenID protocol error. No ID returned."] = "OpenID protokollfeil. Ingen ID ble returnert.";
+App::$strings["Welcome %s. Remote authentication successful."] = "Velkommen %s. Ekstern autentisering er vellykket.";
+App::$strings["First Name"] = "Fornavn";
+App::$strings["Last Name"] = "Etternavn";
+App::$strings["Nickname"] = "Kallenavn";
+App::$strings["Full Name"] = "Fullt navn";
+App::$strings["Profile Photo 16px"] = "Profilbilde 16px";
+App::$strings["Profile Photo 32px"] = "Profilbilde 32px";
+App::$strings["Profile Photo 48px"] = "Profilbilde 48px";
+App::$strings["Profile Photo 64px"] = "Profilbilde 64px";
+App::$strings["Profile Photo 80px"] = "Profilbilde 80px";
+App::$strings["Profile Photo 128px"] = "Profilbilde 128px";
+App::$strings["Homepage URL"] = "Hjemmeside URL";
+App::$strings["Language"] = "Språk";
+App::$strings["Birth Year"] = "Fødselsår";
+App::$strings["Birth Month"] = "Fødselsmåne";
+App::$strings["Birth Day"] = "Fødselsdag";
+App::$strings["Birthdate"] = "Fødselsdato";
+App::$strings["Gender"] = "Kjønn";
+App::$strings["System defaults:"] = "";
+App::$strings["Preferred Clipart IDs"] = "";
+App::$strings["List of preferred clipart ids. These will be shown first."] = "";
+App::$strings["Default Search Term"] = "Standard søkeord";
+App::$strings["The default search term. These will be shown second."] = "Standard søkeord. Disse vil vises som nummer to.";
+App::$strings["Return After"] = "";
+App::$strings["Page to load after image selection."] = "";
+App::$strings["Profile List"] = "";
+App::$strings["Order of Preferred"] = "";
+App::$strings["Sort order of preferred clipart ids."] = "";
+App::$strings["Newest first"] = "";
+App::$strings["As entered"] = "";
+App::$strings["Order of other"] = "";
+App::$strings["Sort order of other clipart ids."] = "";
+App::$strings["Most downloaded first"] = "";
+App::$strings["Most liked first"] = "";
+App::$strings["Preferred IDs Message"] = "";
+App::$strings["Message to display above preferred results."] = "";
+App::$strings["Uploaded by: "] = "";
+App::$strings["Drawn by: "] = "";
+App::$strings["Use this image"] = "";
+App::$strings["Or select from a free OpenClipart.org image:"] = "";
+App::$strings["Search Term"] = "Søkeord";
+App::$strings["Unknown error. Please try again later."] = "";
+App::$strings["Shift-reload the page or clear browser cache if the new photo does not display immediately."] = "Hold nede Shift-knappen og last siden på nytt eller tøm nettleserens mellomlager hvis det nye bildet ikke vises umiddelbart.";
+App::$strings["Profile photo updated successfully."] = "";
+App::$strings["NSA Bait App"] = "";
+App::$strings["Make yourself a political target."] = "";
+App::$strings["Recent Channel/Profile Viewers"] = "";
+App::$strings["No entries."] = "";
+App::$strings["Hubzilla File Storage Import"] = "";
+App::$strings["This will import all your cloud files from another server."] = "";
+App::$strings["Hubzilla Server base URL"] = "";
+App::$strings["Since modified date yyyy-mm-dd"] = "";
+App::$strings["Until modified date yyyy-mm-dd"] = "";
+App::$strings["Your channel has been upgraded to \$Projectname version"] = "";
+App::$strings["Please have a look at the"] = "";
+App::$strings["git history"] = "";
+App::$strings["change log"] = "";
+App::$strings["for further info."] = "";
+App::$strings["\$Projectname Upgrade Info"] = "\$Projectname";
+App::$strings["Do not show this again"] = "";
+App::$strings["__ctx:opensearch__ Search %1\$s (%2\$s)"] = "Søk %1\$s (%2\$s)";
+App::$strings["__ctx:opensearch__ \$Projectname"] = "\$Projectname";
+App::$strings["\$Projectname"] = "\$Projectname";
+App::$strings["Search \$Projectname"] = "";
+App::$strings["Hubzilla Directory Stats"] = "";
+App::$strings["Total Hubs"] = "";
+App::$strings["Hubzilla Hubs"] = "";
+App::$strings["Friendica Hubs"] = "";
+App::$strings["Diaspora Pods"] = "";
+App::$strings["Hubzilla Channels"] = "";
+App::$strings["Friendica Channels"] = "";
+App::$strings["Diaspora Channels"] = "";
+App::$strings["Aged 35 and above"] = "";
+App::$strings["Aged 34 and under"] = "";
+App::$strings["Average Age"] = "";
+App::$strings["Known Chatrooms"] = "";
+App::$strings["Known Tags"] = "";
+App::$strings["Please note Diaspora and Friendica statistics are merely those **this directory** is aware of, and not all those known in the network. This also applies to chatrooms,"] = "";
+App::$strings["This website is tracked using the <a href='http://www.piwik.org'>Piwik</a> analytics tool."] = "";
+App::$strings["If you do not want that your visits are logged this way you <a href='%s'>can set a cookie to prevent Piwik from tracking further visits of the site</a> (opt-out)."] = "";
+App::$strings["Piwik Base URL"] = "";
+App::$strings["Absolute path to your Piwik installation. (without protocol (http/s), with trailing slash)"] = "";
+App::$strings["Site ID"] = "";
+App::$strings["Show opt-out cookie link?"] = "";
+App::$strings["Asynchronous tracking"] = "";
+App::$strings["Enable frontend JavaScript error tracking"] = "";
+App::$strings["This feature requires Piwik >= 2.2.0"] = "";
+App::$strings["Settings updated."] = "Innstillinger oppdatert.";
+App::$strings["Photos imported"] = "";
+App::$strings["Redmatrix Photo Album Import"] = "";
+App::$strings["This will import all your Redmatrix photo albums to this channel."] = "";
+App::$strings["Redmatrix Server base URL"] = "";
+App::$strings["Redmatrix Login Username"] = "";
+App::$strings["Redmatrix Login Password"] = "";
+App::$strings["Import just this album"] = "";
+App::$strings["Leave blank to import all albums"] = "";
+App::$strings["Maximum count to import"] = "";
+App::$strings["0 or blank to import all available"] = "";
+App::$strings["pageheader Settings saved."] = "";
+App::$strings["Message to display on every page on this server"] = "";
+App::$strings["Page Header"] = "";
+App::$strings["Report Bug"] = "";
+App::$strings["Hide Aside App"] = "";
+App::$strings["Fade out aside areas after a while when using endless scroll"] = "";
+App::$strings["Post to Insane Journal"] = "";
+App::$strings["Insane Journal Crosspost Connector Settings saved."] = "";
+App::$strings["Insane Journal Crosspost Connector App"] = "";
+App::$strings["Not Installed"] = "";
+App::$strings["Relay public postings to Insane Journal"] = "";
+App::$strings["InsaneJournal username"] = "";
+App::$strings["InsaneJournal password"] = "";
+App::$strings["Post to InsaneJournal by default"] = "";
+App::$strings["Insane Journal Crosspost Connector"] = "";
+App::$strings["Who likes me?"] = "";
+App::$strings["Post to Hubzilla"] = "";
+App::$strings["Channel is required."] = "";
+App::$strings["Invalid channel."] = "Ugyldig kanal";
+App::$strings["Hubzilla Crosspost Connector Settings saved."] = "";
+App::$strings["Send public postings to Hubzilla channel by default"] = "";
+App::$strings["Hubzilla API Path"] = "";
+App::$strings["https://{sitename}/api"] = "";
+App::$strings["Hubzilla login name"] = "";
+App::$strings["Hubzilla channel name"] = "";
+App::$strings["Hubzilla password"] = "";
+App::$strings["Hubzilla Crosspost Connector"] = "";
+App::$strings["Cards"] = "";
+App::$strings["View Cards"] = "";
+App::$strings["Page not found."] = "Siden ikke funnet.";
+App::$strings["Add Card"] = "";
+App::$strings["Item not found"] = "Elementet ble ikke funnet.";
+App::$strings["Channel not found."] = "Kanalen ble ikke funnet.";
+App::$strings["Edit Card"] = "";
+App::$strings["Page to load after login"] = "";
+App::$strings["Examples: \"apps\", \"network?f=&gid=37\" (privacy collection), \"channel\" or \"notifications/system\" (leave blank for default.)"] = "";
+App::$strings["Startpage"] = "";
+App::$strings["Post to Libertree"] = "";
+App::$strings["Libertree Crosspost Connector Settings saved."] = "";
+App::$strings["Libertree API token"] = "";
+App::$strings["Libertree site URL"] = "";
+App::$strings["Post to Libertree by default"] = "";
+App::$strings["Libertree Crosspost Connector"] = "";
+App::$strings["Logfile archive directory"] = "";
+App::$strings["Directory to store rotated logs"] = "";
+App::$strings["Logfile size in bytes before rotating"] = "";
+App::$strings["Number of logfiles to retain"] = "";
+App::$strings["Your account on %s will expire in a few days."] = "";
+App::$strings["Your test account is about to expire."] = "";
+App::$strings["The activitypub protocol does not support location independence. Connections you make within that network may be unreachable from alternate channel locations."] = "";
+App::$strings["Activitypub Protocol"] = "";
+App::$strings["Refresh failed"] = "Forny";
+App::$strings["Refresh succeeded"] = "";
+App::$strings["Save Bookmarks"] = "Lagre bokmerker";
+App::$strings["Random Planet App"] = "";
+App::$strings["Set a random planet from the Star Wars Empire as your location when posting"] = "";
+App::$strings["Post to Dreamwidth"] = "";
+App::$strings["Source"] = "";
+App::$strings["Dreamwidth Crosspost Connector Settings saved."] = "";
+App::$strings["Dreamwidth username"] = "";
+App::$strings["Dreamwidth password"] = "";
+App::$strings["Post to Dreamwidth by default"] = "";
+App::$strings["Add link to original post"] = "";
+App::$strings["Link description (default:"] = "";
+App::$strings["Dreamwidth Crosspost Connector"] = "";
+App::$strings["Please install the statistics addon to be able to configure a diaspora relay"] = "";
+App::$strings["Diaspora Relay Handle"] = "";
+App::$strings["Address of a diaspora relay. Example: relay@diasporarelay.tld"] = "";
+App::$strings["Diaspora relay could not be imported"] = "";
+App::$strings["No subject"] = "";
+App::$strings["No account to import to."] = "Ingenting å importere.";
+App::$strings["Incompatible data version - aborting"] = "";
+App::$strings["No username found in import file."] = "Ingen brukernavn ble funnet i importfilen.";
+App::$strings["Import completed."] = "Import ferdig.";
+App::$strings["\$projectname"] = "\$projectname";
+App::$strings["%1\$s dislikes %2\$s's %3\$s"] = "";
+App::$strings["status"] = "status";
+App::$strings["%1\$s is attending %2\$s's %3\$s"] = "%1\$s deltar på %2\$ss %3\$s";
+App::$strings["%1\$s is not attending %2\$s's %3\$s"] = "%1\$s deltar ikke på %2\$ss %3\$s";
+App::$strings["%1\$s may attend %2\$s's %3\$s"] = "%1\$s deltar kanskje på %2\$ss %3\$s";
+App::$strings["Diaspora Protocol Settings updated."] = "";
+App::$strings["The diaspora protocol does not support location independence. Connections you make within that network may be unreachable from alternate channel locations."] = "";
+App::$strings["Prevent your hashtags from being redirected to other sites"] = "";
+App::$strings["Sign and forward posts and comments with no existing Diaspora signature"] = "";
+App::$strings["Followed hashtags (comma separated, do not include the #)"] = "";
+App::$strings["Diaspora Protocol"] = "";
+App::$strings["Fuzzloc Settings updated."] = "";
+App::$strings["Minimum offset in meters"] = "";
+App::$strings["Maximum offset in meters"] = "";
+App::$strings["Fuzzy Location"] = "";
+App::$strings["QR code"] = "";
+App::$strings["QR Generator"] = "";
+App::$strings["Enter some text"] = "";
+App::$strings["superblock settings updated"] = "innstillingene til superblokk ble oppdatert";
+App::$strings["Currently blocked"] = "Blokkert for øyeblikket";
+App::$strings["No channels currently blocked"] = "Ingen kanaler er blokkert i øyeblikket";
+App::$strings["Remove"] = "Fjern";
+App::$strings["Block Completely"] = "Blokker helt";
+App::$strings["Block from site"] = "Byggeklossens tittel";
+App::$strings["You're welcome."] = "";
+App::$strings["Ah shucks..."] = "";
+App::$strings["Don't mention it."] = "";
+App::$strings["&lt;blush&gt;"] = "";
+App::$strings["Save Settings"] = "";
+App::$strings["text to include in all outgoing posts from this site"] = "";
+App::$strings["Send test email"] = "";
+App::$strings["Mail sent."] = "";
+App::$strings["Sending of mail failed."] = "";
+App::$strings["Mail Test"] = "";
+App::$strings["NSFW Settings saved."] = "";
+App::$strings["This app looks in posts for the words/text you specify below, and collapses any content containing those keywords so it is not displayed at inappropriate times, such as sexual innuendo that may be improper in a work setting. It is polite and recommended to tag any content containing nudity with #NSFW. This filter can also match any other word/text you specify, and can thereby be used as a general purpose content filter."] = "";
+App::$strings["Comma separated list of keywords to hide"] = "";
+App::$strings["Word, /regular-expression/, lang=xx, lang!=xx"] = "";
+App::$strings["NSFW"] = "";
+App::$strings["Possible adult content"] = "";
+App::$strings["%s - view"] = "";
+App::$strings["Errors encountered deleting database table "] = "";
+App::$strings["Submit Settings"] = "";
+App::$strings["Drop tables when uninstalling?"] = "";
+App::$strings["If checked, the Rendezvous database tables will be deleted when the plugin is uninstalled."] = "";
+App::$strings["Mapbox Access Token"] = "";
+App::$strings["If you enter a Mapbox access token, it will be used to retrieve map tiles from Mapbox instead of the default OpenStreetMap tile server."] = "";
+App::$strings["Rendezvous"] = "";
+App::$strings["This identity has been deleted by another member due to inactivity. Please press the \"New identity\" button or refresh the page to register a new identity. You may use the same name."] = "";
+App::$strings["Welcome to Rendezvous!"] = "";
+App::$strings["Enter your name to join this rendezvous. To begin sharing your location with the other members, tap the GPS control. When your location is discovered, a red dot will appear and others will be able to see you on the map."] = "";
+App::$strings["Let's meet here"] = "";
+App::$strings["Name"] = "Navn";
+App::$strings["Description"] = "Beskrivelse";
+App::$strings["New marker"] = "";
+App::$strings["Edit marker"] = "";
+App::$strings["New identity"] = "";
+App::$strings["Delete marker"] = "";
+App::$strings["Delete member"] = "";
+App::$strings["Edit proximity alert"] = "";
+App::$strings["A proximity alert will be issued when this member is within a certain radius of you.<br><br>Enter a radius in meters (0 to disable):"] = "";
+App::$strings["distance"] = "";
+App::$strings["Proximity alert distance (meters)"] = "";
+App::$strings["A proximity alert will be issued when you are within a certain radius of the marker location.<br><br>Enter a radius in meters (0 to disable):"] = "";
+App::$strings["Marker proximity alert"] = "";
+App::$strings["Reminder note"] = "";
+App::$strings["Enter a note to be displayed when you are within the specified proximity..."] = "";
+App::$strings["Add new rendezvous"] = "";
+App::$strings["Create a new rendezvous and share the access link with those you wish to invite to the group. Those who open the link become members of the rendezvous. They can view other member locations, add markers to the map, or share their own locations with the group."] = "";
+App::$strings["You have no rendezvous. Press the button above to create a rendezvous!"] = "";
+App::$strings["Errors encountered creating database tables."] = "Feil oppstod under opprettelsen av databasetabeller.";
+App::$strings["You are now authenticated to pumpio."] = "";
+App::$strings["return to the featured settings page"] = "tilbake til funksjonsinnstillinger";
+App::$strings["Post to Pump.io"] = "";
+App::$strings["Pump.io Settings saved."] = "";
+App::$strings["Pump.io servername"] = "";
+App::$strings["Without \"http://\" or \"https://\""] = "";
+App::$strings["Pump.io username"] = "";
+App::$strings["Without the servername"] = "";
+App::$strings["You are not authenticated to pumpio"] = "";
+App::$strings["(Re-)Authenticate your pump.io connection"] = "";
+App::$strings["Post to pump.io by default"] = "";
+App::$strings["Should posts be public"] = "";
+App::$strings["Mirror all public posts"] = "";
+App::$strings["Pump.io Crosspost Connector"] = "";
+App::$strings["Post to WordPress"] = "";
+App::$strings["Wordpress Settings saved."] = "";
+App::$strings["WordPress username"] = "";
+App::$strings["WordPress password"] = "";
+App::$strings["WordPress API URL"] = "";
+App::$strings["Typically https://your-blog.tld/xmlrpc.php"] = "";
+App::$strings["WordPress blogid"] = "";
+App::$strings["For multi-user sites such as wordpress.com, otherwise leave blank"] = "";
+App::$strings["Post to WordPress by default"] = "";
+App::$strings["Forward comments (requires hubzilla_wp plugin)"] = "";
+App::$strings["Wordpress Post"] = "";
+App::$strings["Edit Article"] = "";
+App::$strings["Articles"] = "";
+App::$strings["View Articles"] = "";
+App::$strings["Add Article"] = "";
+App::$strings["Profile Unavailable."] = "Profilen er ikke tilgjengelig.";
+App::$strings["Invalid channel"] = "Ugyldig kanal";
+App::$strings["Error retrieving wiki"] = "Det oppstod en feil ved henting av wikien";
+App::$strings["Error creating zip file export folder"] = "Det oppstod en feil ved eksport av mappe til zip fil";
+App::$strings["Error downloading wiki: "] = "";
+App::$strings["Wikis"] = "Wikier";
+App::$strings["Download"] = "";
+App::$strings["View"] = "Vis";
+App::$strings["Create New"] = "Lag ny profil";
+App::$strings["Wiki name"] = "";
+App::$strings["Content type"] = "";
+App::$strings["Type"] = "Type";
+App::$strings["Any&nbsp;type"] = "";
+App::$strings["Lock content type"] = "";
+App::$strings["Create a status post for this wiki"] = "";
+App::$strings["Edit Wiki Name"] = "";
+App::$strings["Wiki not found"] = "Fant ikke wikien.";
+App::$strings["Rename page"] = "";
+App::$strings["Share"] = "Del";
+App::$strings["Error retrieving page content"] = "";
+App::$strings["New page"] = "";
+App::$strings["Revision Comparison"] = "";
+App::$strings["Revert"] = "Tilbakestill";
+App::$strings["Short description of your changes (optional)"] = "";
+App::$strings["New page name"] = "";
+App::$strings["Embed image from photo albums"] = "";
+App::$strings["Choose a different album"] = "Velg et annet album";
+App::$strings["History"] = "";
+App::$strings["Error creating wiki. Invalid name."] = "";
+App::$strings["A wiki with this name already exists."] = "";
+App::$strings["Wiki created, but error creating Home page."] = "";
+App::$strings["Error creating wiki"] = "";
+App::$strings["Error updating wiki. Invalid name."] = "";
+App::$strings["Error updating wiki"] = "";
+App::$strings["Wiki delete permission denied."] = "";
+App::$strings["Error deleting wiki"] = "";
+App::$strings["New page created"] = "";
+App::$strings["Cannot delete Home"] = "";
+App::$strings["Current Revision"] = "";
+App::$strings["Selected Revision"] = "";
+App::$strings["You must be authenticated."] = "";
+App::$strings["Add new page"] = "Legg til ny side";
+App::$strings["Options"] = "Valg";
+App::$strings["Wiki updated successfully"] = "Wikien ble oppdatert";
+App::$strings["Wiki files deleted successfully"] = "Wikifiler ble slettet";
+App::$strings["(No Title)"] = "(Ingen tittel)";
+App::$strings["Wiki page create failed."] = "Kunne ikke opprette wikiside.";
+App::$strings["Wiki not found."] = "Fant ikke wikien.";
+App::$strings["Destination name already exists"] = "Navnet finnes allerede";
+App::$strings["Page not found"] = "Fant ikke siden";
+App::$strings["Error reading page content"] = "Det oppstod en feil under lesing av innholder på siden";
+App::$strings["Error reading wiki"] = "Det oppstod en feil ved lesing av wikien";
+App::$strings["Page update failed."] = "Oppdatering av siden mislyktes.";
+App::$strings["Nothing deleted"] = "Ingenting ble slettet";
+App::$strings["Compare: object not found."] = "Sammanlign: fant ikke objektet.";
+App::$strings["Page updated"] = "Siden ble oppdatert";
+App::$strings["Wiki resource_id required for git commit"] = "Wiki ressurs_id er nødvendig for å lagre i git";
+App::$strings["__ctx:wiki_history__ Message"] = "Melding";
+App::$strings["Date"] = "Dato";
+App::$strings["Compare"] = "Sammenlign";
+App::$strings["Wiki Pages"] = "Wikisider";
+App::$strings["Page name"] = "Sidenavn";
+App::$strings["Project Servers and Resources"] = "";
+App::$strings["Project Creator and Tech Lead"] = "";
+App::$strings["And the hundreds of other people and organisations who helped make the Hubzilla possible."] = "";
+App::$strings["The Redmatrix/Hubzilla projects are provided primarily by volunteers giving their time and expertise - and often paying out of pocket for services they share with others."] = "";
+App::$strings["There is no corporate funding and no ads, and we do not collect and sell your personal information. (We don't control your personal information - <strong>you do</strong>.)"] = "";
+App::$strings["Help support our ground-breaking work in decentralisation, web identity, and privacy."] = "";
+App::$strings["Your donations keep servers and services running and also helps us to provide innovative new features and continued development."] = "";
+App::$strings["Donate"] = "";
+App::$strings["Choose a project, developer, or public hub to support with a one-time donation"] = "";
+App::$strings["Donate Now"] = "";
+App::$strings["<strong><em>Or</em></strong> become a project sponsor (Hubzilla Project only)"] = "";
+App::$strings["Please indicate if you would like your first name or full name (or nothing) to appear in our sponsor listing"] = "";
+App::$strings["Sponsor"] = "";
+App::$strings["Special thanks to: "] = "";
+App::$strings["Jabber BOSH host"] = "";
+App::$strings["Use central userbase"] = "";
+App::$strings["If enabled, members will automatically login to an ejabberd server that has to be installed on this machine with synchronized credentials via the \"auth_ejabberd.php\" script."] = "";
+App::$strings["XMPP settings updated."] = "";
+App::$strings["XMPP App"] = "";
+App::$strings["Embedded XMPP (Jabber) client"] = "";
+App::$strings["Individual credentials"] = "";
+App::$strings["Jabber BOSH server"] = "";
+App::$strings["XMPP Settings"] = "";
+App::$strings["New registration"] = "";
+App::$strings["%s : Message delivery failed."] = "%s : meldingslevering feilet.";
+App::$strings["Message sent to %s. New account registration: %s"] = "";
+App::$strings["Post to GNU social"] = "";
+App::$strings["Site name"] = "Nettstedets navn";
+App::$strings["API URL"] = "";
+App::$strings["Consumer Secret"] = "Consumer Secret";
+App::$strings["Consumer Key"] = "Consumer Key";
+App::$strings["Application name"] = "";
+App::$strings["Please contact your site administrator.<br />The provided API URL is not valid."] = "Den oppgitte URLen for APIet er ikke gyldig. Kontakt serverens administrator.";
+App::$strings["We could not contact the GNU social API with the Path you entered."] = "";
+App::$strings["GNU social settings updated."] = "";
+App::$strings["Globally Available GNU social OAuthKeys"] = "";
+App::$strings["There are preconfigured OAuth key pairs for some GNU social servers available. If you are using one of them, please use these credentials.<br />If not feel free to connect to any other GNU social instance (see below)."] = "";
+App::$strings["Provide your own OAuth Credentials"] = "";
+App::$strings["No consumer key pair for GNU social found. Register your Hubzilla Account as an desktop client on your GNU social account, copy the consumer key pair here and enter the API base root.<br />Before you register your own OAuth key pair ask the administrator if there is already a key pair for this Hubzilla installation at your favourite GNU social installation."] = "";
+App::$strings["OAuth Consumer Key"] = "";
+App::$strings["OAuth Consumer Secret"] = "";
+App::$strings["Base API Path"] = "";
+App::$strings["Remember the trailing /"] = "";
+App::$strings["GNU social application name"] = "";
+App::$strings["To connect to your GNU social account click the button below to get a security code from GNU social which you have to copy into the input box below and submit the form. Only your <strong>public</strong> posts will be posted to GNU social."] = "";
+App::$strings["Log in with GNU social"] = "";
+App::$strings["Copy the security code from GNU social here"] = "";
+App::$strings["Cancel Connection Process"] = "";
+App::$strings["Current GNU social API is"] = "";
+App::$strings["Cancel GNU social Connection"] = "";
+App::$strings["Currently connected to: "] = "";
+App::$strings["<strong>Note</strong>: Due your privacy settings (<em>Hide your profile details from unknown viewers?</em>) the link potentially included in public postings relayed to GNU social will lead the visitor to a blank page informing the visitor that the access to your profile has been restricted."] = "";
+App::$strings["Post to GNU social by default"] = "";
+App::$strings["If enabled your public postings will be posted to the associated GNU-social account by default"] = "";
+App::$strings["Clear OAuth configuration"] = "";
+App::$strings["GNU-Social Crosspost Connector"] = "";
+App::$strings["Affinity"] = "";
+App::$strings["Post to Livejournal"] = "";
+App::$strings["Posted by"] = "";
+App::$strings["Livejournal username"] = "";
+App::$strings["Livejournal password"] = "";
+App::$strings["Post to Livejournal by default"] = "";
+App::$strings["Send wall-to-wall posts to Livejournal"] = "";
+App::$strings["Livejournal Crosspost Connector"] = "";
+App::$strings["Error: order mismatch. Please try again."] = "";
+App::$strings["Manual payments are not enabled."] = "";
+App::$strings["Order not found."] = "";
+App::$strings["Finished"] = "";
+App::$strings["Enable Hubzilla Services Module"] = "";
+App::$strings["New Sku"] = "";
+App::$strings["Cannot save edits to locked item."] = "";
+App::$strings["SKU not found."] = "";
+App::$strings["Invalid Activation Directive."] = "";
+App::$strings["Invalid Deactivation Directive."] = "";
+App::$strings["Add to this privacy group"] = "";
+App::$strings["Set user service class"] = "";
+App::$strings["You must be using a local account to purchase this service."] = "";
+App::$strings["Changes Locked"] = "";
+App::$strings["Item available for purchase."] = "";
+App::$strings["Price"] = "";
+App::$strings["Photo URL"] = "";
+App::$strings["Add buyer to privacy group"] = "";
+App::$strings["Add buyer as connection"] = "";
+App::$strings["Set Service Class"] = "";
+App::$strings["Enable Subscription Management Module"] = "";
+App::$strings["Cannot include subscription items with different terms in the same order."] = "";
+App::$strings["Select Subscription to Edit"] = "";
+App::$strings["Edit Subscriptions"] = "";
+App::$strings["Subscription SKU"] = "";
+App::$strings["Catalog Description"] = "";
+App::$strings["Subscription available for purchase."] = "";
+App::$strings["Maximum active subscriptions to this item per account."] = "";
+App::$strings["Subscription price."] = "";
+App::$strings["Quantity"] = "";
+App::$strings["Term"] = "";
+App::$strings["Enable Paypal Button Module (API-v2)"] = "";
+App::$strings["Use Production Key"] = "";
+App::$strings["Paypal Sandbox Client Key"] = "";
+App::$strings["Paypal Sandbox Secret Key"] = "";
+App::$strings["Paypal Production Client Key"] = "";
+App::$strings["Paypal Production Secret Key"] = "";
+App::$strings["Paypal button payments are not enabled."] = "";
+App::$strings["Paypal button payments are not properly configured. Please choose another payment option."] = "";
+App::$strings["Enable Paypal Button Module"] = "";
+App::$strings["Enable Manual Cart Module"] = "";
+App::$strings["Enable Order/Item Options"] = "";
+App::$strings["Label"] = "";
+App::$strings["Instructions"] = "";
+App::$strings["Access Denied."] = "Ingen tilgang";
+App::$strings["Order Not Found"] = "";
+App::$strings["Access Denied"] = "Ingen tilgang";
+App::$strings["Invalid Item"] = "Ugyldig element.";
+App::$strings["DB Cleanup Failure"] = "";
+App::$strings["[cart] Item Added"] = "";
+App::$strings["Order already checked out."] = "";
+App::$strings["Drop database tables when uninstalling."] = "";
+App::$strings["Cart Settings"] = "";
+App::$strings["Shop"] = "";
+App::$strings["You must be logged into the Grid to shop."] = "";
+App::$strings["Access denied."] = "Ingen tilgang";
+App::$strings["No Order Found"] = "";
+App::$strings["An unknown error has occurred Please start again."] = "";
+App::$strings["Requirements not met."] = "";
+App::$strings["Review your order and complete any needed requirements."] = "";
+App::$strings["Invalid Payment Type. Please start again."] = "";
+App::$strings["Order not found"] = "";
+App::$strings["Enable Test Catalog"] = "";
+App::$strings["Enable Manual Payments"] = "";
+App::$strings["Base Merchant Currency"] = "";
+App::$strings["Post to Twitter"] = "";
+App::$strings["Twitter settings updated."] = "";
+App::$strings["No consumer key pair for Twitter found. Please contact your site administrator."] = "";
+App::$strings["At this Hubzilla instance the Twitter plugin was enabled but you have not yet connected your account to your Twitter account. To do so click the button below to get a PIN from Twitter which you have to copy into the input box below and submit the form. Only your <strong>public</strong> posts will be posted to Twitter."] = "";
+App::$strings["Log in with Twitter"] = "";
+App::$strings["Copy the PIN from Twitter here"] = "";
+App::$strings["<strong>Note:</strong> Due your privacy settings (<em>Hide your profile details from unknown viewers?</em>) the link potentially included in public postings relayed to Twitter will lead the visitor to a blank page informing the visitor that the access to your profile has been restricted."] = "";
+App::$strings["Twitter post length"] = "";
+App::$strings["Maximum tweet length"] = "";
+App::$strings["Send public postings to Twitter by default"] = "";
+App::$strings["If enabled your public postings will be posted to the associated Twitter account by default"] = "";
+App::$strings["Twitter Crosspost Connector"] = "";
+App::$strings["generic profile image"] = "";
+App::$strings["random geometric pattern"] = "";
+App::$strings["monster face"] = "";
+App::$strings["computer generated face"] = "";
+App::$strings["retro arcade style face"] = "";
+App::$strings["Hub default profile photo"] = "";
+App::$strings["Information"] = "";
+App::$strings["Libravatar addon is installed, too. Please disable Libravatar addon or this Gravatar addon.<br>The Libravatar addon will fall back to Gravatar if nothing was found at Libravatar."] = "";
+App::$strings["Default avatar image"] = "";
+App::$strings["Select default avatar image if none was found at Gravatar. See README"] = "";
+App::$strings["Rating of images"] = "";
+App::$strings["Select the appropriate avatar rating for your site. See README"] = "";
+App::$strings["Gravatar settings updated."] = "";
+App::$strings["WYSIWYG status editor"] = "";
+App::$strings["WYSIWYG Status App"] = "";
+App::$strings["WYSIWYG Status"] = "";
+App::$strings["Redmatrix File Storage Import"] = "";
+App::$strings["This will import all your Redmatrix cloud files to this channel."] = "";
+App::$strings["View Larger"] = "";
+App::$strings["Tile Server URL"] = "";
+App::$strings["A list of <a href=\"http://wiki.openstreetmap.org/wiki/TMS\" target=\"_blank\">public tile servers</a>"] = "";
+App::$strings["Nominatim (reverse geocoding) Server URL"] = "";
+App::$strings["A list of <a href=\"http://wiki.openstreetmap.org/wiki/Nominatim\" target=\"_blank\">Nominatim servers</a>"] = "";
+App::$strings["Default zoom"] = "";
+App::$strings["The default zoom level. (1:world, 18:highest, also depends on tile server)"] = "";
+App::$strings["Include marker on map"] = "";
+App::$strings["Include a marker on the map."] = "";
+App::$strings["Workflow user."] = "";
+App::$strings["This channel"] = "";
+App::$strings["Primary"] = "Primær";
+App::$strings["Create New Workflow Item"] = "";
+App::$strings["Workflow"] = "";
+App::$strings["No Workflows Available"] = "";
+App::$strings["Add item to which workflow"] = "";
+App::$strings["Create Workflow Item"] = "";
+App::$strings["Link"] = "";
+App::$strings["Web link."] = "";
+App::$strings["Title"] = "Tittel";
+App::$strings["Brief description or title"] = "";
+App::$strings["Notes"] = "Merknader";
+App::$strings["Notes and Info"] = "";
+App::$strings["Priority"] = "Prioritet";
+App::$strings["Used to order links"] = "";
+App::$strings["Body"] = "";
+App::$strings["Workflow Settings"] = "";
+App::$strings["Workflow settings"] = "Instillinger for redigering";
+App::$strings["Some setting"] = "";
+App::$strings["A setting"] = "";
+App::$strings["Skeleton Settings"] = "";
+App::$strings["Allow magic authentication only to websites of your immediate connections"] = "";
+App::$strings["Authchoose"] = "";
+App::$strings["No server specified"] = "";
+App::$strings["Posts imported"] = "";
+App::$strings["Files imported"] = "";
+App::$strings["Content Import"] = "";
+App::$strings["This will import all your conversations and cloud files from a cloned channel on another server. This may take a while if you have lots of posts and or files."] = "";
+App::$strings["Include posts"] = "";
+App::$strings["Conversations, Articles, Cards, and other posted content"] = "";
+App::$strings["Include files"] = "";
+App::$strings["Files, Photos and other cloud storage"] = "";
+App::$strings["Original Server base URL"] = "";
+App::$strings["Use markdown for editing posts"] = "";
+App::$strings["Friendica Crosspost Connector Settings saved."] = "";
+App::$strings["Send public postings to Friendica by default"] = "";
+App::$strings["Friendica API Path"] = "";
+App::$strings["Friendica login name"] = "";
+App::$strings["Friendica password"] = "";
+App::$strings["Friendica Crosspost Connector"] = "";
+App::$strings["Post to Friendica"] = "";
+App::$strings["An account has been created for you."] = "";
+App::$strings["Authentication successful but rejected: account creation is disabled."] = "";
+App::$strings["Federate"] = "";
+App::$strings["nofed Settings saved."] = "";
+App::$strings["Federate posts by default"] = "";
+App::$strings["No Federation"] = "";
+App::$strings["Photo Cache settings saved."] = "";
+App::$strings["Saves a copy of images from external sites locally to increase your anonymity in the web."] = "";
+App::$strings["Minimal photo size for caching"] = "";
+App::$strings["In pixels. From 1 up to 1024, 0 will be replaced with system default."] = "";
+App::$strings["Photo Cache"] = "";
+App::$strings["Gallery"] = "";
+App::$strings["Photo Gallery"] = "";
+App::$strings["Your Webbie:"] = "";
+App::$strings["Fontsize (px):"] = "";
+App::$strings["Link:"] = "";
+App::$strings["Like us on Hubzilla"] = "";
+App::$strings["Embed:"] = "";
+App::$strings["Three Dimensional Tic-Tac-Toe"] = "";
+App::$strings["3D Tic-Tac-Toe"] = "";
+App::$strings["New game"] = "";
+App::$strings["New game with handicap"] = "";
+App::$strings["Three dimensional tic-tac-toe is just like the traditional game except that it is played on multiple levels simultaneously. "] = "";
+App::$strings["In this case there are three levels. You win by getting three in a row on any level, as well as up, down, and diagonally across the different levels."] = "";
+App::$strings["The handicap game disables the center position on the middle level because the player claiming this square often has an unfair advantage."] = "";
+App::$strings["You go first..."] = "";
+App::$strings["I'm going first this time..."] = "";
+App::$strings["You won!"] = "";
+App::$strings["\"Cat\" game!"] = "";
+App::$strings["I won!"] = "";
App::$strings["Create an account to access services and applications"] = "";
App::$strings["Email or nickname"] = "Epost eller brukernavn";
-App::$strings["Nickname"] = "Kallenavn";
App::$strings["Password"] = "Passord";
App::$strings["Remember me"] = "Husk meg";
App::$strings["Forgot your password?"] = "Glemt passordet ditt?";
@@ -855,23 +1476,79 @@ App::$strings["[\$Projectname] Website SSL error for %s"] = "";
App::$strings["Website SSL certificate is not valid. Please correct."] = "Nettstedets SSL-sertifikat er ikke gyldig. Vennligst fiks dette.";
App::$strings["[\$Projectname] Cron tasks not running on %s"] = "";
App::$strings["Cron/Scheduled tasks not running."] = "Cron/planlagte oppgaver kjører ikke.";
-App::$strings["0. Beginner/Basic"] = "";
-App::$strings["1. Novice - not skilled but willing to learn"] = "";
-App::$strings["2. Intermediate - somewhat comfortable"] = "";
-App::$strings["3. Advanced - very comfortable"] = "";
-App::$strings["4. Expert - I can write computer code"] = "";
-App::$strings["5. Wizard - I probably know more than you do"] = "";
+App::$strings["\$Projectname Notification"] = "\$Projectname varsling";
+App::$strings["Thank You,"] = "Tusen takk,";
+App::$strings["This email was sent by %1\$s at %2\$s."] = "";
+App::$strings["To stop receiving these messages, please adjust your Notification Settings at %s"] = "";
+App::$strings["To stop receiving these messages, please adjust your %s."] = "";
+App::$strings["Notification Settings"] = "Varslingsinnstillinger";
+App::$strings["%s <!item_type!>"] = "%s <!item_type!>";
+App::$strings["[\$Projectname:Notify] New direct message received at %s"] = "[\$Projectname:Notify] Ny direktemelding mottatt kl. %s";
+App::$strings["%1\$s sent you a new private message at %2\$s"] = "%1\$s sendte deg en ny direktemelding kl. %2\$s";
+App::$strings["%1\$s sent you %2\$s."] = "%1\$s sendte deg %2\$s.";
+App::$strings["a direct message"] = "en direktemelding";
+App::$strings["Please visit %s to view and/or reply to your private messages."] = "Gå til %s for å vise og/eller svare på dine direktemeldinger.";
+App::$strings["requested to post in"] = "vil like";
+App::$strings["posted in"] = "lagt inn";
+App::$strings["requested to like"] = "vil like";
+App::$strings["liked"] = "";
+App::$strings["requested to dislike"] = "";
+App::$strings["disliked"] = "";
+App::$strings["requested to repeat"] = "vil like";
+App::$strings["repeated"] = "videresendte";
+App::$strings["voted on"] = "";
+App::$strings["%1\$s %2\$s [zrl=%3\$s]a %4\$s[/zrl]"] = "";
+App::$strings["%1\$s %2\$s [zrl=%3\$s]%4\$s's %5\$s[/zrl]"] = "";
+App::$strings["%1\$s %2\$s [zrl=%3\$s]your %4\$s[/zrl]"] = "";
+App::$strings["[\$Projectname:Notify] Moderated Comment to conversation #%1\$d by %2\$s"] = "";
+App::$strings["[\$Projectname:Notify] Comment to conversation #%1\$d by %2\$s"] = "";
+App::$strings["%1\$s commented on an item/conversation you have been following"] = "";
+App::$strings["Please visit %s to view and/or reply to the conversation."] = "Vennligst besøk %s for å se og/eller svare i samtalen.";
+App::$strings["Please visit %s to approve or reject this comment."] = "";
+App::$strings["[\$Projectname:Notify] Like received to conversation #%1\$d by %2\$s"] = "";
+App::$strings["%1\$s liked an item/conversation you created"] = "";
+App::$strings["[\$Projectname:Notify] %s posted to your profile wall"] = "";
+App::$strings["%1\$s posted to your profile wall at %2\$s"] = "";
+App::$strings["%1\$s posted to [zrl=%2\$s]your wall[/zrl]"] = "";
+App::$strings["[\$Projectname:Notify] %s tagged you"] = "";
+App::$strings["%1\$s tagged you at %2\$s"] = "";
+App::$strings["%1\$s [zrl=%2\$s]tagged you[/zrl]."] = "";
+App::$strings["[\$Projectname:Notify] %1\$s poked you"] = "";
+App::$strings["%1\$s poked you at %2\$s"] = "";
+App::$strings["%1\$s [zrl=%2\$s]poked you[/zrl]."] = "";
+App::$strings["[\$Projectname:Notify] %s tagged your post"] = "";
+App::$strings["%1\$s tagged your post at %2\$s"] = "";
+App::$strings["%1\$s tagged [zrl=%2\$s]your post[/zrl]"] = "";
+App::$strings["[\$Projectname:Notify] Introduction received"] = "";
+App::$strings["You've received an new connection request from '%1\$s' at %2\$s"] = "";
+App::$strings["You've received [zrl=%1\$s]a new connection request[/zrl] from %2\$s."] = "";
+App::$strings["You may visit their profile at %s"] = "Du kan besøke profilen deres på %s";
+App::$strings["Please visit %s to approve or reject the connection request."] = "Vennligst besøk %s for å godkjenne eller avslå forespørselen om forbindelse.";
+App::$strings["[\$Projectname:Notify] Friend suggestion received"] = "";
+App::$strings["You've received a friend suggestion from '%1\$s' at %2\$s"] = "";
+App::$strings["You've received [zrl=%1\$s]a friend suggestion[/zrl] for %2\$s from %3\$s."] = "";
+App::$strings["Name:"] = "Navn:";
+App::$strings["Photo:"] = "Bilde:";
+App::$strings["Please visit %s to approve or reject the suggestion."] = "Vennligst besøk %s for å godkjenne eller avslå dette forslaget.";
+App::$strings["[\$Projectname:Notify]"] = "";
+App::$strings["started a poll"] = "opprettet spørreskjema";
+App::$strings["started a conversation"] = "started en samtale";
+App::$strings["voted on %s's poll"] = "stemte på %s sitt spørreskjema";
+App::$strings["posted in %s's conversation"] = "postet i %s sin samtale";
+App::$strings["shared a file with you"] = "delte en fil med deg";
+App::$strings["edited a message dated %s"] = "redigerte en melding datert %s";
+App::$strings["added your channel"] = "la til din kanal";
+App::$strings["sent you a direct message"] = "sendte deg en direktemelding";
+App::$strings["g A l F d"] = "g A l F d";
+App::$strings["[today]"] = "[idag]";
+App::$strings["created an event"] = "opprettet et arrangement";
+App::$strings["status verified"] = "status verifisert";
App::$strings["Affinity Tool"] = "Nærhetsverktøy";
-App::$strings["Articles"] = "";
-App::$strings["Cards"] = "";
App::$strings["Site Admin"] = "Nettstedsadministrator";
-App::$strings["Report Bug"] = "";
-App::$strings["Content Filter"] = "";
-App::$strings["Content Import"] = "";
-App::$strings["Remote Diagnostics"] = "";
+App::$strings["Content Filter"] = "Innholdsfilter";
+App::$strings["Remote Diagnostics"] = "Fjerndiagnostikk";
App::$strings["Suggest Channels"] = "Foreslå kanaler";
App::$strings["Channel Manager"] = "Kanalstyring";
-App::$strings["Stream"] = "Tidslinje";
App::$strings["Wiki"] = "";
App::$strings["Mail"] = "Melding";
App::$strings["Chat"] = "Chat";
@@ -880,37 +1557,52 @@ App::$strings["Suggest"] = "Forreslå";
App::$strings["Random Channel"] = "Tilfeldig kanal";
App::$strings["Invite"] = "Inviter";
App::$strings["Features"] = "Funksjoner";
-App::$strings["Language"] = "Språk";
App::$strings["Post"] = "Innlegg";
App::$strings["Notifications"] = "Varsler";
-App::$strings["Order Apps"] = "";
+App::$strings["Order Apps"] = "Ordne apper";
App::$strings["CardDAV"] = "";
App::$strings["Channel Sources"] = "Kanalkilder";
-App::$strings["Guest Access"] = "";
-App::$strings["Notes"] = "Merknader";
+App::$strings["Guest Access"] = "Gjestetilgang";
App::$strings["OAuth Apps Manager"] = "";
App::$strings["OAuth2 Apps Manager"] = "";
-App::$strings["PDL Editor"] = "";
-App::$strings["Contact Roles"] = "";
-App::$strings["Public Stream"] = "";
-App::$strings["My Chatrooms"] = "";
-App::$strings["Channel Export"] = "";
+App::$strings["PDL Editor"] = "PDL Redigering";
+App::$strings["Contact Roles"] = "Kontaktroller";
+App::$strings["Public Stream"] = "Offentlig strøm";
+App::$strings["My Chatrooms"] = "Mine chattekanaler";
+App::$strings["Channel Export"] = "Kanaleksport";
App::$strings["Update"] = "Oppdater";
App::$strings["Install"] = "Installer";
App::$strings["Purchase"] = "Kjøp";
-App::$strings["Undelete"] = "";
+App::$strings["Undelete"] = "Hent tilbake";
App::$strings["Add to app-tray"] = "Legg til i meny";
App::$strings["Remove from app-tray"] = "Fjern fra meny";
App::$strings["Pin to navbar"] = "Fest til navigasjonslinjen";
App::$strings["Unpin from navbar"] = "Fjern fra navigasjonslinjen";
-App::$strings["Directory Options"] = "Kataloginnstillinger";
-App::$strings["Safe Mode"] = "Trygg modus";
-App::$strings["Public Forums Only"] = "Bare offentlige forum";
-App::$strings["This Website Only"] = "Kun dette nettstedet";
-App::$strings["A deleted privacy group with this name was revived. Existing item permissions <strong>may</strong> apply to this privacy group and any future members. If this is not what you intended, please create another privacy group with a different name."] = "";
-App::$strings["Select a privacy group"] = "";
+App::$strings["Public"] = "Offentlig";
+App::$strings["Anybody in the \$Projectname network"] = "Alle i \$Projectname-nettverket";
+App::$strings["Any account on %s"] = "Brukerkontoer på %s";
+App::$strings["Any of my connections"] = "Alle mine forbindelser";
+App::$strings["Only connections I specifically allow"] = "Kun forbindelser jeg gir tilgang";
+App::$strings["Anybody authenticated (could include visitors from other networks)"] = "Alle som er autentisert (det kan inkludere besøkende fra andre nettverk)";
+App::$strings["Any connections including those who haven't yet been approved"] = "Alle forbindelser inkludert forbindelser som ikke enda har blitt godkjent";
+App::$strings["This is your default setting for the audience of your normal stream, and posts."] = "Dette er standardinnstillingene for hvem som vil motta din vanlige strøm og innlegg.";
+App::$strings["This is your default setting for who can view your default channel profile"] = "Dette er standardinnstillingene for hvem som kan se kanalens standardprofil";
+App::$strings["This is your default setting for who can view your connections"] = "Dette er standardinnstillingene for hvem som kan se dine forbindelser";
+App::$strings["This is your default setting for who can view your file storage and photos"] = "Dette er standardinnstillingene for hvem som kan se dine filer og bilder";
+App::$strings["This is your default setting for the audience of your webpages"] = "Dette er standardinnstillingene for hvem som kan se dine websider";
+App::$strings["__ctx:permcat__ Default"] = "Standard";
+App::$strings["Source code of failed update: "] = "Kildekoden til oppdateringen som mislyktes:";
+App::$strings["Update Error at %s"] = "Oppdateringsfeil ved %s";
+App::$strings["Update %s failed. See error logs."] = "Oppdatering %s mislyktes. Se feilloggen.";
+App::$strings["Channel is blocked on this site."] = "Kanalen er blokkert på dette nettstedet.";
+App::$strings["Channel location missing."] = "Kanalplassering mangler.";
+App::$strings["Remote channel or protocol unavailable."] = "Kanalen eller protokollen er ikke tilgjengelig på den andre serveren.";
+App::$strings["Channel discovery failed."] = "Kanaloppdagelse mislyktes.";
+App::$strings["Protocol disabled."] = "Protokollen er avskrudd.";
+App::$strings["Cannot connect to yourself."] = "Kan ikke lage forbindelse med deg selv.";
+App::$strings["error saving data"] = "feil ved lagring av data";
App::$strings["Restricted message"] = "Begrenset melding";
-App::$strings["Direct message"] = "Direktemelding";
+App::$strings["Private message"] = "Privat melding";
App::$strings["Public Policy"] = "";
App::$strings["Privacy conflict. Discretion advised."] = "";
App::$strings["Admin Delete"] = "";
@@ -918,9 +1610,7 @@ App::$strings["Save to Folder"] = "Lagre i mappe";
App::$strings["I will attend"] = "Jeg vil delta";
App::$strings["I will not attend"] = "Jeg deltar ikke";
App::$strings["I might attend"] = "Jeg vil kanskje delta";
-App::$strings["I like this (toggle)"] = "Jeg liker dette (skru av og på)";
-App::$strings["I don't like this (toggle)"] = "Jeg liker ikke dette (skru av og på)";
-App::$strings["Reply to this comment"] = "Slett denne menyen";
+App::$strings["Reply to this message"] = "Svar på denne meldingen";
App::$strings["reply"] = "";
App::$strings["Reply to"] = "";
App::$strings["share"] = "del";
@@ -932,136 +1622,61 @@ App::$strings["%d comment"] = array(
1 => "%d kommentarer",
);
App::$strings["%d unseen"] = "%d uleste";
+App::$strings["Load the next few of total %d comments"] = "Last inn noen flere av ialt %d kommentarer";
+App::$strings["Expand Replies"] = "";
App::$strings["Forum"] = "";
App::$strings["to"] = "til";
App::$strings["via"] = "via";
App::$strings["Wall-to-Wall"] = "Vegg-til-vegg";
App::$strings["via Wall-To-Wall:"] = "via vegg-til-vegg:";
-App::$strings["Attend"] = "";
-App::$strings["Attendance Options"] = "";
-App::$strings["Voting Options"] = "";
-App::$strings["Go to previous comment"] = "";
-App::$strings["Pinned post"] = "";
-App::$strings["Save Bookmarks"] = "Lagre bokmerker";
+App::$strings["Last edited %s"] = "Sist endret: %s";
+App::$strings["Expires %s"] = "Utløper %s";
+App::$strings["Published %s"] = "Publisert %s";
+App::$strings["Attend"] = "Delta";
+App::$strings["Attendance Options"] = "Valg for deltagelse";
+App::$strings["Voting Options"] = "Valg for avstemning";
+App::$strings["Go to previous comment"] = "Gå til forrige kommentar";
+App::$strings["Pinned post"] = "Festet innlegg";
App::$strings["Add to Calendar"] = "Legg til i kalender";
-App::$strings["Mark all comments seen"] = "Merk alle hendelser som sett";
-App::$strings["Add yours"] = "Legg til bilder";
-App::$strings["Remove yours"] = "Fjern begrep";
+App::$strings["Mark all comments seen"] = "Merk alle kommentarer som sett";
+App::$strings["Add yours"] = "Legg til din";
+App::$strings["Remove yours"] = "Fjern din";
+App::$strings["show less"] = "vis færre";
+App::$strings["show more"] = "vis flere";
+App::$strings["show all"] = "vis alle";
App::$strings["This is you"] = "Dette er deg";
-App::$strings["Image"] = "Bilde";
App::$strings["Insert Link"] = "Sett inn lenke";
App::$strings["Video"] = "Video";
-App::$strings["Your full name (required)"] = "";
-App::$strings["Your email address (required)"] = "";
-App::$strings["Your website URL (optional)"] = "";
+App::$strings["Your full name (required)"] = "Fullt navn (påkrevd)";
+App::$strings["Your email address (required)"] = "Epostaddresse (påkrevd)";
+App::$strings["Your website URL (optional)"] = "Webside (valgfritt)";
+App::$strings["Unable to verify channel signature"] = "Ikke i stand til å sjekke kanalsignaturen";
App::$strings["Likes %1\$s's %2\$s"] = "";
App::$strings["Doesn't like %1\$s's %2\$s"] = "";
App::$strings["Will attend %s's event"] = "";
App::$strings["Will not attend %s's event"] = "";
App::$strings["May attend %s's event"] = "";
App::$strings["May not attend %s's event"] = "";
+App::$strings["Unable to verify site signature for %s"] = "Ikke i stand til å bekrefte signaturen til %s";
+App::$strings["Directory Options"] = "Kataloginnstillinger";
+App::$strings["Safe Mode"] = "Trygg modus";
+App::$strings["Public Forums Only"] = "Bare offentlige forum";
+App::$strings["This Website Only"] = "Kun dette nettstedet";
App::$strings["Missing room name"] = "Mangler romnavn";
App::$strings["Duplicate room name"] = "Duplikat romnavn";
App::$strings["Invalid room specifier."] = "Ugyldig rom-spesifisering.";
App::$strings["Room not found."] = "Rommet ble ikke funnet.";
App::$strings["Room is full"] = "Rommet er fullt";
-App::$strings["\$Projectname Notification"] = "\$Projectname varsling";
-App::$strings["\$projectname"] = "\$projectname";
-App::$strings["Thank You,"] = "Tusen takk,";
-App::$strings["%s Administrator"] = "%s administrator";
-App::$strings["This email was sent by %1\$s at %2\$s."] = "";
-App::$strings["\$Projectname"] = "\$Projectname";
-App::$strings["To stop receiving these messages, please adjust your Notification Settings at %s"] = "";
-App::$strings["To stop receiving these messages, please adjust your %s."] = "";
-App::$strings["Notification Settings"] = "Varslingsinnstillinger";
-App::$strings["%s <!item_type!>"] = "%s <!item_type!>";
-App::$strings["[\$Projectname:Notify] New direct message received at %s"] = "[\$Projectname:Notify] Ny direktemelding mottatt kl. %s";
-App::$strings["%1\$s sent you a new direct message at %2\$s"] = "%1\$s sendte deg en ny direktemelding kl. %2\$s";
-App::$strings["%1\$s sent you %2\$s."] = "%1\$s sendte deg %2\$s.";
-App::$strings["a direct message"] = "en direktemelding";
-App::$strings["Please visit %s to view and/or reply to your direct messages."] = "Gå til %s for å vise og/eller svare på dine direktemeldinger.";
-App::$strings["requested to comment on"] = "";
-App::$strings["commented on"] = "";
-App::$strings["requested to like"] = "Dato for forespørsel";
-App::$strings["liked"] = "";
-App::$strings["requested to dislike"] = "";
-App::$strings["disliked"] = "";
-App::$strings["repeated"] = "Laget";
-App::$strings["voted on"] = "";
-App::$strings["%1\$s %2\$s [zrl=%3\$s]a %4\$s[/zrl]"] = "";
-App::$strings["%1\$s %2\$s [zrl=%3\$s]%4\$s's %5\$s[/zrl]"] = "";
-App::$strings["%1\$s %2\$s [zrl=%3\$s]your %4\$s[/zrl]"] = "";
-App::$strings["[\$Projectname:Notify] Moderated Comment to conversation #%1\$d by %2\$s"] = "";
-App::$strings["[\$Projectname:Notify] Comment to conversation #%1\$d by %2\$s"] = "";
-App::$strings["%1\$s commented on an item/conversation you have been following"] = "";
-App::$strings["Please visit %s to view and/or reply to the conversation."] = "Vennligst besøk %s for å se og/eller svare i samtalen.";
-App::$strings["Please visit %s to approve or reject this comment."] = "";
-App::$strings["[\$Projectname:Notify] Like received to conversation #%1\$d by %2\$s"] = "";
-App::$strings["%1\$s liked an item/conversation you created"] = "";
-App::$strings["[\$Projectname:Notify] %s posted to your profile wall"] = "";
-App::$strings["%1\$s posted to your profile wall at %2\$s"] = "";
-App::$strings["%1\$s posted to [zrl=%2\$s]your wall[/zrl]"] = "";
-App::$strings["[\$Projectname:Notify] %s tagged you"] = "";
-App::$strings["%1\$s tagged you at %2\$s"] = "";
-App::$strings["%1\$s [zrl=%2\$s]tagged you[/zrl]."] = "";
-App::$strings["[\$Projectname:Notify] %1\$s poked you"] = "";
-App::$strings["%1\$s poked you at %2\$s"] = "";
-App::$strings["%1\$s [zrl=%2\$s]poked you[/zrl]."] = "";
-App::$strings["[\$Projectname:Notify] %s tagged your post"] = "";
-App::$strings["%1\$s tagged your post at %2\$s"] = "";
-App::$strings["%1\$s tagged [zrl=%2\$s]your post[/zrl]"] = "";
-App::$strings["[\$Projectname:Notify] Introduction received"] = "";
-App::$strings["You've received an new connection request from '%1\$s' at %2\$s"] = "";
-App::$strings["You've received [zrl=%1\$s]a new connection request[/zrl] from %2\$s."] = "";
-App::$strings["You may visit their profile at %s"] = "Du kan besøke profilen deres på %s";
-App::$strings["Please visit %s to approve or reject the connection request."] = "Vennligst besøk %s for å godkjenne eller avslå forespørselen om forbindelse.";
-App::$strings["[\$Projectname:Notify] Friend suggestion received"] = "";
-App::$strings["You've received a friend suggestion from '%1\$s' at %2\$s"] = "";
-App::$strings["You've received [zrl=%1\$s]a friend suggestion[/zrl] for %2\$s from %3\$s."] = "";
-App::$strings["Name:"] = "Navn:";
-App::$strings["Photo:"] = "Bilde:";
-App::$strings["Please visit %s to approve or reject the suggestion."] = "Vennligst besøk %s for å godkjenne eller avslå dette forslaget.";
-App::$strings["[\$Projectname:Notify]"] = "";
-App::$strings["created a new poll"] = "opprettet spørreskjema";
-App::$strings["created a new post"] = "laget et nytt innlegg";
-App::$strings["voted on %s's poll"] = "stemte på %s sitt spørreskjema";
-App::$strings["commented on %s's post"] = "kommenterte på %s sitt innlegg";
-App::$strings["shared a file with you"] = "";
-App::$strings["edited a post dated %s"] = "";
-App::$strings["edited a comment dated %s"] = "";
-App::$strings["added your channel"] = "la til din kanal";
-App::$strings["sent you a direct message"] = "sendte deg en direktemelding";
-App::$strings["g A l F d"] = "g A l F d";
-App::$strings["[today]"] = "[idag]";
-App::$strings["created an event"] = "";
-App::$strings["status verified"] = "";
-App::$strings["Source code of failed update: "] = "";
-App::$strings["Update Error at %s"] = "Oppdateringsfeil ved %s";
-App::$strings["Update %s failed. See error logs."] = "Oppdatering %s mislyktes. Se feilloggen.";
-App::$strings["Channel is blocked on this site."] = "Kanalen er blokkert på dette nettstedet.";
-App::$strings["Channel location missing."] = "Kanalplassering mangler.";
-App::$strings["Remote channel or protocol unavailable."] = "";
-App::$strings["Channel discovery failed."] = "Kanaloppdagelse mislyktes.";
-App::$strings["Protocol disabled."] = "Protokollen er avskrudd.";
-App::$strings["Cannot connect to yourself."] = "Kan ikke lage forbindelse med deg selv.";
-App::$strings["error saving data"] = "";
-App::$strings["Unable to verify channel signature"] = "Ikke i stand til å sjekke kanalsignaturen";
-App::$strings["Public"] = "Offentlig";
-App::$strings["Anybody in the \$Projectname network"] = "";
-App::$strings["Any account on %s"] = "";
-App::$strings["Any of my connections"] = "";
-App::$strings["Only connections I specifically allow"] = "";
-App::$strings["Anybody authenticated (could include visitors from other networks)"] = "";
-App::$strings["Any connections including those who haven't yet been approved"] = "";
-App::$strings["This is your default setting for the audience of your normal stream, and posts."] = "";
-App::$strings["This is your default setting for who can view your default channel profile"] = "";
-App::$strings["This is your default setting for who can view your connections"] = "";
-App::$strings["This is your default setting for who can view your file storage and photos"] = "";
-App::$strings["This is your default setting for the audience of your webpages"] = "";
-App::$strings["Unable to verify site signature for %s"] = "Ikke i stand til å bekrefte signaturen til %s";
-App::$strings["__ctx:permcat__ Default"] = "Standard";
+App::$strings["0. Beginner/Basic"] = "";
+App::$strings["1. Novice - not skilled but willing to learn"] = "";
+App::$strings["2. Intermediate - somewhat comfortable"] = "";
+App::$strings["3. Advanced - very comfortable"] = "";
+App::$strings["4. Expert - I can write computer code"] = "";
+App::$strings["5. Wizard - I probably know more than you do"] = "";
+App::$strings["A deleted privacy group with this name was revived. Existing item permissions <strong>may</strong> apply to this privacy group and any future members. If this is not what you intended, please create another privacy group with a different name."] = "";
+App::$strings["Select a privacy group"] = "";
App::$strings["Social Networking"] = "Sosialt nettverk";
-App::$strings["Social - Federation"] = "";
+App::$strings["Social - Federation"] = "Sosial - føderert";
App::$strings["Social - Mostly Public"] = "Sosial - ganske offentlig";
App::$strings["Social - Restricted"] = "Sosial - begrenset";
App::$strings["Social - Private"] = "Sosial - privat";
@@ -1077,26 +1692,198 @@ App::$strings["Special - Celebrity/Soapbox"] = "Spesiell - kjendis/talerstol";
App::$strings["Special - Group Repository"] = "Spesiell - gruppelager";
App::$strings["Custom/Expert Mode"] = "Tilpasset/Ekspertmodus";
App::$strings["Personal"] = "Personlig";
-App::$strings["Community forum"] = "Forum for fellesskap";
+App::$strings["Community forum"] = "Forum";
App::$strings["Custom"] = "";
-App::$strings["Can view my channel stream and posts"] = "";
+App::$strings["Can view my channel stream and posts"] = "Kan se min kanalstrøm og innlegg";
App::$strings["Can send me their channel stream and posts"] = "Kan sende meg deres kanalstrøm og innlegg";
App::$strings["Can view my default channel profile"] = "Kan se min standard kanalprofil";
App::$strings["Can view my connections"] = "Kan se mine forbindelser";
App::$strings["Can view my file storage and photos"] = "Kan se mine filer og bilder";
-App::$strings["Can upload/modify my file storage and photos"] = "";
-App::$strings["Can view my channel webpages"] = "";
-App::$strings["Can view my wiki pages"] = "";
-App::$strings["Can create/edit my channel webpages"] = "";
-App::$strings["Can write to my wiki pages"] = "";
-App::$strings["Can post on my channel (wall) page"] = "";
+App::$strings["Can upload/modify my file storage and photos"] = "Kan laste opp/redigere mine filer og bilder";
+App::$strings["Can view my channel webpages"] = "Kan se min kanal sine websider";
+App::$strings["Can view my wiki pages"] = "Kan se mine wiki-sider";
+App::$strings["Can create/edit my channel webpages"] = "Kan opprette/redigere websider på min kanal";
+App::$strings["Can write to my wiki pages"] = "Kan skrive på mine wiki-sider";
+App::$strings["Can post on my channel (wall) page"] = "Kan poste på min kanal (vegg)";
App::$strings["Can comment on or like my posts"] = "Kan kommentere på eller like mine innlegg";
App::$strings["Can send me direct messages"] = "Kan sende meg direktemeldinger";
-App::$strings["Can like/dislike profiles and profile things"] = "";
-App::$strings["Can chat with me"] = "";
-App::$strings["Can source/mirror my public posts in derived channels"] = "";
-App::$strings["Can administer my channel"] = "";
-App::$strings["Page not found."] = "Siden ikke funnet.";
+App::$strings["Can like/dislike profiles and profile things"] = "Kan like/ikke like profiler og profilting";
+App::$strings["Can chat with me"] = "Kan chatte med meg";
+App::$strings["Can source/mirror my public posts in derived channels"] = "Kan speile eller bruke mine offentlige innlegg i avledede kanaler";
+App::$strings["Can administer my channel"] = "Kan administrere min kanal";
+App::$strings["Tasks"] = "Oppgaver";
+App::$strings["Click to show more"] = "";
+App::$strings["App Collections"] = "Appsamlinger";
+App::$strings["Installed apps"] = "Installerte apper";
+App::$strings["Available Apps"] = "Tilgjengelige apper";
+App::$strings["Rating Tools"] = "Vurderingsverktøy";
+App::$strings["Rate Me"] = "Vurder meg";
+App::$strings["View Ratings"] = "Vis vurderinger";
+App::$strings["Unseen network activity"] = "Ny aktivitet i nettverksstrømmen";
+App::$strings["Network stream"] = "";
+App::$strings["Mark all read"] = "Merk alle som sett";
+App::$strings["Conversation starters"] = "Samtaleinnstillinger";
+App::$strings["Filter by name or address"] = "Filtrer etter navn eller adresse";
+App::$strings["Unseen channel activity"] = "Ny kanalaktivitet";
+App::$strings["Channel stream"] = "Kanalnavn";
+App::$strings["Mark all seen"] = "Merk alle som sett";
+App::$strings["Private"] = "Privat forum";
+App::$strings["Unseen private activity"] = "Ny aktivitet i nettverksstrømmen";
+App::$strings["Private stream"] = "Privat forum";
+App::$strings["Events"] = "Hendelser";
+App::$strings["Unseen events activity"] = "Ny aktivitet i nettverksstrømmen";
+App::$strings["View events"] = "";
+App::$strings["New Connections"] = "Nye forbindelser";
+App::$strings["New connections"] = "Nye forbindelser";
+App::$strings["View all"] = "Vis alle";
+App::$strings["Useen files activity"] = "Ny aktivitet i nettverksstrømmen";
+App::$strings["Unseen notifications"] = "Systemvarsler";
+App::$strings["Unseen forums activity"] = "Ny aktivitet i nettverksstrømmen";
+App::$strings["Registrations"] = "";
+App::$strings["Unseen registration activity"] = "Ny aktivitet i nettverksstrømmen";
+App::$strings["Unseen public stream activity"] = "Ny aktivitet i den offentlige strømmen";
+App::$strings["Public stream"] = "";
+App::$strings["Sorry, you have got no notifications at the moment"] = "";
+App::$strings["Bookmarked Chatrooms"] = "Bokmerkede chatrom";
+App::$strings["Overview"] = "";
+App::$strings["Add new guest"] = "";
+App::$strings["Guest access"] = "";
+App::$strings["Profile Creation"] = "Oppretting av profil";
+App::$strings["Upload profile photo"] = "Last opp profilbilde";
+App::$strings["Upload cover photo"] = "Last opp omslagsbilde";
+App::$strings["Find and Connect with others"] = "Finn andre";
+App::$strings["View the directory"] = "Se i katalogen";
+App::$strings["View friend suggestions"] = "Vis venneforslag";
+App::$strings["Manage your connections"] = "Behandle forbindelser";
+App::$strings["Communicate"] = "Kommuniser";
+App::$strings["View your channel homepage"] = "Vis kanalens hjemmeside";
+App::$strings["View your network stream"] = "Vis nettverksstrømmen";
+App::$strings["Documentation"] = "Dokumentasjon";
+App::$strings["Missing Features?"] = "Noe som mangler?";
+App::$strings["Pin apps to navigation bar"] = "Fest apper til navigasjonslinjen";
+App::$strings["Install more apps"] = "Legg til flere apper";
+App::$strings["View public stream"] = "";
+App::$strings["New Member Links"] = "Lenker for nye medlemmer";
+App::$strings["Ignore/Hide"] = "Ignorer/Skjul";
+App::$strings["Suggestions"] = "Forslag";
+App::$strings["See more..."] = "Se mer...";
+App::$strings["Public and restricted conversations"] = "Offentlige og begrensede samtaler";
+App::$strings["Private conversations"] = "Private samtaler";
+App::$strings["Starred conversations"] = "Stjernemerkede samtaler";
+App::$strings["Filed messages"] = "Lagrede meldinger";
+App::$strings["No conversations"] = "Ingen samtaler";
+App::$strings["Unseen reactions"] = "Usette reaksjoner";
+App::$strings["Filter by file name"] = "Filtrer etter navn";
+App::$strings["Toggle post editor"] = "Vis redigering av innlegg";
+App::$strings["Toggle personal notes"] = "";
+App::$strings["Channel activities"] = "";
+App::$strings["View Photo"] = "Vis foto";
+App::$strings["Edit Album"] = "Endre album";
+App::$strings["Upload"] = "Last opp";
+App::$strings["Share This"] = "Del dette";
+App::$strings["View %s's profile - %s"] = "Vis %s sin profil - %s";
+App::$strings["Don't show"] = "Ikke vis";
+App::$strings["You have %1$.0f of %2$.0f allowed connections."] = "Du har %1$.0f av %2$.0f tillate forbindelser.";
+App::$strings["Add New Connection"] = "Legg til ny forbindelse";
+App::$strings["Enter channel address"] = "Skriv kanaladressen";
+App::$strings["Examples: bob@example.com, https://example.com/barbara"] = "Eksempel: ola@eksempel.no, https://eksempel.no/kari";
+App::$strings["Read mode"] = "";
+App::$strings["Edit mode"] = "";
+App::$strings["Editing"] = "";
+App::$strings["Saving"] = "";
+App::$strings["Saved"] = "";
+App::$strings["Posted Date"] = "Innleggsdato";
+App::$strings["Order by last posted date"] = "Sorter etter dato innlegg ble postet";
+App::$strings["Commented Date"] = "Sist kommentert";
+App::$strings["Order by last commented date"] = "Sorter etter dato for siste kommentar";
+App::$strings["Date Unthreaded"] = "Utrådet";
+App::$strings["Order unthreaded by date"] = "Sorter innlegg og kommentarer uavhengig av hverandre etter dato";
+App::$strings["Stream Order"] = "Sortering av innlegg";
+App::$strings["Direct Messages"] = "Direktemeldinger";
+App::$strings["Show direct (private) messages"] = "Vis direktemeldinger (private meldinger)";
+App::$strings["Show posts that include events"] = "";
+App::$strings["Polls"] = "Spørreskjema";
+App::$strings["Show posts that include polls"] = "Vis innlegg som inneholder spørreskjema";
+App::$strings["Show posts related to the %s privacy group"] = "";
+App::$strings["Show my privacy groups"] = "";
+App::$strings["Show posts to this forum"] = "";
+App::$strings["Show forums"] = "";
+App::$strings["Starred Posts"] = "";
+App::$strings["Show posts that I have starred"] = "";
+App::$strings["Personal Posts"] = "";
+App::$strings["Show posts that mention or involve me"] = "";
+App::$strings["Show posts that I have filed to %s"] = "";
+App::$strings["Show filed post categories"] = "";
+App::$strings["Panel search"] = "";
+App::$strings["Filter by name"] = "Filtrer etter navn";
+App::$strings["Remove active filter"] = "";
+App::$strings["Stream Filters"] = "Filtere for tidslinjen";
+App::$strings["Welcome"] = "Velkommen";
+App::$strings["No recent activities"] = "Nylig aktivitet";
+App::$strings["__ctx:noun__ new connection"] = array(
+ 0 => "ny forbindelse",
+ 1 => "nye forbindelser",
+);
+App::$strings["__ctx:noun__ notice"] = array(
+ 0 => "varsel",
+ 1 => "varsel",
+);
+App::$strings["Me"] = "Meg";
+App::$strings["Family"] = "Familie";
+App::$strings["Acquaintances"] = "Bekjente";
+App::$strings["All"] = "Alle";
+App::$strings["Refresh"] = "Forny";
+App::$strings["Site"] = "Nettsted";
+App::$strings["Accounts"] = "Kontoer";
+App::$strings["Member registrations waiting for confirmation"] = "";
+App::$strings["Security"] = "Sikkerhet";
+App::$strings["Addons"] = "Tillegg";
+App::$strings["Themes"] = "Utseende";
+App::$strings["Inspect queue"] = "Inspiser kø";
+App::$strings["Queueworker"] = "";
+App::$strings["Profile Fields"] = "Profilfelter";
+App::$strings["DB updates"] = "Databaseoppdateringer";
+App::$strings["Logs"] = "Logger";
+App::$strings["Addon Features"] = "";
+App::$strings["Add new role"] = "Ny rolle";
+App::$strings["Contact roles"] = "Kontaktroller";
+App::$strings["Role members"] = "Medlemmer";
+App::$strings["__ctx:widget__ Activity"] = "Aktivitet";
+App::$strings["photo/image"] = "foto/bilde";
+App::$strings["Remove term"] = "Fjern begrep";
+App::$strings["Chat Members"] = "Medlemmer";
+App::$strings["Suggested Chatrooms"] = "Foreslåtte chatrom";
+App::$strings["App Categories"] = "Appkategorier";
+App::$strings["Account settings"] = "Kontoinnstillinger";
+App::$strings["Channel settings"] = "Kanalinnstillinger";
+App::$strings["Privacy settings"] = "Personverninnstillinger";
+App::$strings["Display settings"] = "Visningsinnstillinger";
+App::$strings["Manage locations"] = "";
+App::$strings["Archives"] = "Arkiv";
+App::$strings["Select Channel"] = "";
+App::$strings["Read-write"] = "";
+App::$strings["Read-only"] = "";
+App::$strings["Channel Calendar"] = "";
+App::$strings["CalDAV Calendars"] = "";
+App::$strings["Shared CalDAV Calendars"] = "";
+App::$strings["Share this calendar"] = "";
+App::$strings["Calendar name and color"] = "";
+App::$strings["Create new CalDAV calendar"] = "";
+App::$strings["Create"] = "Lag";
+App::$strings["Calendar Name"] = "";
+App::$strings["Calendar Tools"] = "";
+App::$strings["Channel Calendars"] = "";
+App::$strings["Import calendar"] = "";
+App::$strings["Select a calendar to import to"] = "";
+App::$strings["Addressbooks"] = "";
+App::$strings["Addressbook name"] = "";
+App::$strings["Create new addressbook"] = "";
+App::$strings["Addressbook Name"] = "";
+App::$strings["Addressbook Tools"] = "";
+App::$strings["Import addressbook"] = "";
+App::$strings["Select an addressbook to import to"] = "";
+App::$strings["Add new group"] = "";
+App::$strings["Privacy groups"] = "Personverngrupper";
App::$strings["Change filename to"] = "";
App::$strings["Select a target location"] = "";
App::$strings["Copy to target location"] = "";
@@ -1105,19 +1892,15 @@ App::$strings["Notify your contacts about this file"] = "Varsle dine kontakter o
App::$strings["File category"] = "";
App::$strings["Total"] = "Totalt";
App::$strings["Shared"] = "Delt";
-App::$strings["Create"] = "Lag";
App::$strings["Add Files"] = "";
-App::$strings["Name"] = "Navn";
-App::$strings["Type"] = "Type";
App::$strings["Last Modified"] = "Sist endret";
App::$strings["parent"] = "opp et nivå";
App::$strings["Copy/paste this code to attach file to a post"] = "Kopier og lim inn denne koden for å legge til filen i et innlegg";
App::$strings["Copy/paste this URL to link file from a web page"] = "Kopier og lim inn denne URL-en for å lenke til filen fra en webside";
-App::$strings["Select All"] = "velg alle";
+App::$strings["Select All"] = "Velg alle";
App::$strings["Bulk Actions"] = "";
App::$strings["Adjust Permissions"] = "";
App::$strings["Move or Copy"] = "";
-App::$strings["Download"] = "";
App::$strings["Info"] = "";
App::$strings["Rename"] = "";
App::$strings["Attachment BBcode"] = "";
@@ -1128,298 +1911,69 @@ App::$strings["You are using %1\$s of %2\$s available file storage. (%3\$s&#37;)
App::$strings["WARNING:"] = "ADVARSEL:";
App::$strings["Create new folder"] = "Lag ny mappe";
App::$strings["Upload file"] = "Last opp fil";
-App::$strings["Upload"] = "Last opp";
App::$strings["Drop files here to immediately upload"] = "";
App::$strings["Show in your contacts shared folder"] = "";
App::$strings["You can select files via the upload button or drop them right here or into an existing folder."] = "";
-App::$strings["Unable to update menu."] = "Ikke i stand til å oppdatere meny.";
-App::$strings["Unable to create menu."] = "Ikke i stand til å lage meny.";
-App::$strings["Menu Name"] = "Menynavn";
-App::$strings["Unique name (not visible on webpage) - required"] = "Unikt navn (ikke synlig på websiden) - påkrevet";
-App::$strings["Menu Title"] = "Menytittel";
-App::$strings["Visible on webpage - leave empty for no title"] = "Synlig på websiden - la stå tomt for ingen tittel";
-App::$strings["Allow Bookmarks"] = "Tillat bokmerker";
-App::$strings["Menu may be used to store saved bookmarks"] = "Menyen kan brukes til å lagre lagrede bokmerker";
-App::$strings["Submit and proceed"] = "Send inn og fortsett";
-App::$strings["Drop"] = "Slett";
-App::$strings["Created"] = "Laget";
-App::$strings["Edited"] = "Endret";
-App::$strings["New"] = "Nye";
-App::$strings["Bookmarks allowed"] = "Bokmerker tillatt";
-App::$strings["Delete this menu"] = "Slett denne menyen";
-App::$strings["Edit menu contents"] = "Endre menyinnholdet";
-App::$strings["Edit this menu"] = "Endre denne menyen";
-App::$strings["Menu could not be deleted."] = "Menyen kunne ikke bli slettet.";
-App::$strings["Menu not found."] = "Menyen ble ikke funnet.";
-App::$strings["Edit Menu"] = "Endre meny";
-App::$strings["Add or remove entries to this menu"] = "Legg til eller fjern punkter i denne menyen";
-App::$strings["Menu name"] = "Menynavn";
-App::$strings["Must be unique, only seen by you"] = "Må være unik, ses bare av deg";
-App::$strings["Menu title"] = "Menytittel";
-App::$strings["Menu title as seen by others"] = "Menytittelen andre ser";
-App::$strings["Allow bookmarks"] = "Tillat bokmerker";
-App::$strings["Not found."] = "Ikke funnet.";
-App::$strings["Documentation Search"] = "Søk i dokumentasjon";
-App::$strings["Not Found"] = "Ikke funnet";
-App::$strings["\$Projectname Documentation"] = "\$Projectname dokumentasjon";
-App::$strings["Contents"] = "";
-App::$strings["Members"] = "Medlemmer";
-App::$strings["Administrators"] = "";
-App::$strings["Developers"] = "";
-App::$strings["Tutorials"] = "";
-App::$strings["Help:"] = "Hjelp:";
-App::$strings["Item not found"] = "Elementet ble ikke funnet.";
-App::$strings["Block Name"] = "Byggeklossens navn";
-App::$strings["Edit Block"] = "Endre byggekloss";
-App::$strings["Posts and comments"] = "Innlegg og kommentarer";
-App::$strings["Only posts"] = "Kun innlegg";
+App::$strings["Invalid item."] = "Ugyldig element.";
+App::$strings["Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
+App::$strings["Item sync completed!"] = "";
+App::$strings["Import host does not seem to be online or compatible"] = "";
+App::$strings["Item sync completed but no items were found!"] = "";
+App::$strings["File sync completed!"] = "";
+App::$strings["File sync completed but no files were found!"] = "";
+App::$strings["Channel clone status"] = "";
+App::$strings["Item sync status"] = "";
+App::$strings["File sync status"] = "";
+App::$strings["Channel cloning completed!"] = "";
+App::$strings["Resume"] = "";
+App::$strings["Only resume if sync stalled!"] = "";
+App::$strings["Public access denied."] = "Offentlig tilgang avvist.";
App::$strings["Malformed message id."] = "";
-App::$strings["Insufficient permissions. Request redirected to profile page."] = "Utilstrekkelig tillatelse. Forespørsel omdirigert til profilsiden.";
-App::$strings["Search Results For:"] = "Søkeresultat for:";
+App::$strings["Item not found."] = "Elementet ble ikke funnet.";
App::$strings["Reset form"] = "Nullstill skjema";
App::$strings["You must enable javascript for your browser to be able to view this content."] = "";
-App::$strings["You have created %1$.0f of %2$.0f allowed channels."] = "Du har laget %1$.0f av %2$.0f tillatte kanaler.";
-App::$strings["Your real name is recommended."] = "";
-App::$strings["Examples: \"Bob Jameson\", \"Lisa and her Horses\", \"Soccer\", \"Aviation Group\""] = "Eksempel: \"Ola Nordmann\", \"Lisa og hestene hennes\", \"Fotball\", \"Sykkelgruppa\"";
-App::$strings["This will be used to create a unique network address (like an email address)."] = "";
-App::$strings["Allowed characters are a-z 0-9, - and _"] = "";
-App::$strings["Channel name"] = "Kanalnavn";
-App::$strings["Choose a short nickname"] = "Velg et kort kallenavn";
-App::$strings["Channel role"] = "Kanalrolle";
-App::$strings["Create a Channel"] = "";
-App::$strings["A channel is a unique network identity. It can represent a person (social network profile), a forum (group), a business or celebrity page, a newsfeed, and many other things."] = "";
-App::$strings["or <a href=\"import\">import an existing channel</a> from another location."] = "eller <a href=\"import\">importer en eksisterende kanal</a> fra et annet sted.";
-App::$strings["Validate"] = "";
-App::$strings["Unable to find your hub."] = "Ikke i stand til å finne hubben din.";
-App::$strings["Post successful."] = "Innlegg vellykket.";
-App::$strings["Not a zip file or zip file corrupted."] = "";
-App::$strings["Import Items"] = "Importer elementer";
-App::$strings["Use this form to import existing posts and content from an export file."] = "Bruk dette skjemaet for å importere eksisterende innlegg og innhold fra en eksportfil.";
-App::$strings["File to Upload"] = "Fil som skal lastes opp";
-App::$strings["Imported file is empty."] = "Importert fil er tom.";
-App::$strings["Content import completed"] = "";
-App::$strings["Chatroom import completed"] = "";
-App::$strings["Channel calendar import 1/2 completed"] = "";
-App::$strings["Channel calendar import 2/2 completed"] = "";
-App::$strings["Menu import completed"] = "";
-App::$strings["Wiki import completed"] = "";
-App::$strings["Webpages import completed"] = "";
-App::$strings["Edit post"] = "Endre innlegg";
-App::$strings["Invalid item."] = "Ugyldig element.";
-App::$strings["Channel not found."] = "Kanalen ble ikke funnet.";
-App::$strings["Language App"] = "";
-App::$strings["Not Installed"] = "";
-App::$strings["Token verification failed."] = "";
-App::$strings["Email verification resent"] = "";
-App::$strings["Unable to resend email verification message."] = "";
-App::$strings["\$Projectname Server - Setup"] = "\$Projectname-tjener - oppsett";
-App::$strings["Could not connect to database."] = "Fikk ikke kontakt med databasen.";
-App::$strings["Could not connect to specified site URL. Possible SSL certificate or DNS issue."] = "Fikk ikke kontakt med det angitte nettstedets URL. Problemet kan muligens skyldes SSL-sertifikatet eller DNS.";
-App::$strings["Could not create table."] = "Kunne ikke lage tabellen.";
-App::$strings["Your site database has been installed."] = "Databasen til ditt nettsted har blitt installert.";
-App::$strings["You may need to import the file \"install/schema_xxx.sql\" manually using a database client."] = "Du må kanskje importere filen \"install/schmea_xxx.sql\" manuelt ved å bruke en databaseklient.";
-App::$strings["Please see the file \"install/INSTALL.txt\"."] = "Vennligst les filen \"install/INSTALL.txt\".";
-App::$strings["System check"] = "Systemsjekk";
-App::$strings["Next"] = "Neste";
-App::$strings["Check again"] = "Sjekk igjen";
-App::$strings["Database connection"] = "Databaseforbindelse";
-App::$strings["In order to install \$Projectname we need to know how to connect to your database."] = "For å installere \$Projectname må du oppgi hvordan din database kan kontaktes.";
-App::$strings["Please contact your hosting provider or site administrator if you have questions about these settings."] = "Vennligst kontakt din nettstedstilbyder eller nettstedsadministrator hvis du har spørsmål om disse innstillingene.";
-App::$strings["The database you specify below should already exist. If it does not, please create it before continuing."] = "Databasen du oppgir nedenfor må finnes på forhånd. Hvis den ikke finnes, vennligst lag den før du fortsetter.";
-App::$strings["Database Server Name"] = "Navn på databasetjener";
-App::$strings["Default is 127.0.0.1"] = "Standard er 127.0.0.1";
-App::$strings["Database Port"] = "Databaseport";
-App::$strings["Communication port number - use 0 for default"] = "Kommunikasjonsportnummer - bruk 0 for standard";
-App::$strings["Database Login Name"] = "Database innloggingsnavn";
-App::$strings["Database Login Password"] = "Database innloggingspassord";
-App::$strings["Database Name"] = "Databasenavn";
-App::$strings["Database Type"] = "Databasetype";
-App::$strings["Site administrator email address"] = "E-postadressen til administrator ved nettstedet";
-App::$strings["Your account email address must match this in order to use the web admin panel."] = "Din konto sin e-postadresse må være lik denne for å kunne bruke web-administrasjonspanelet.";
-App::$strings["Website URL"] = "Nettstedets URL";
-App::$strings["Please use SSL (https) URL if available."] = "Vennligst bruk SSL (https) URL hvis tilgjengelig.";
-App::$strings["Please select a default timezone for your website"] = "Vennligst velg en standard tidssone for ditt nettsted";
-App::$strings["Site settings"] = "Nettstedets innstillinger";
-App::$strings["PHP version 8.0 or greater is required."] = "";
-App::$strings["PHP version"] = "";
-App::$strings["Could not find a command line version of PHP in the web server PATH."] = "Fant ikke en kommandolinjeversjon av PHP i webtjenerens sti (PATH).";
-App::$strings["If you don't have a command line version of PHP installed on server, you will not be able to run background polling via cron."] = "Hvis du ikke har en kommandolinjeversjon av PHP installert på tjeneren, så vil du ikke kunne kjøre bakgrunnshenting via cron.";
-App::$strings["PHP executable path"] = "PHP-kjørefilens sti";
-App::$strings["Enter full path to php executable. You can leave this blank to continue the installation."] = "Skriv full sti til kjørefilen for PHP. Du kan la denne stå blank for å fortsette installasjonen.";
-App::$strings["Command line PHP"] = "Kommandolinje PHP";
-App::$strings["Unable to check command line PHP, as shell_exec() is disabled. This is required."] = "";
-App::$strings["The command line version of PHP on your system does not have \"register_argc_argv\" enabled."] = "Kommandolinjeversjonen av PHP på ditt system har ikke \"register_argc_argv\" påskrudd.";
-App::$strings["This is required for message delivery to work."] = "Dette er påkrevd for at meldingslevering skal virke.";
-App::$strings["PHP register_argc_argv"] = "PHP register_argc_argv";
-App::$strings["This is not sufficient to upload larger images or files. You should be able to upload at least 4 MB at once."] = "";
-App::$strings["Your max allowed total upload size is set to %s. Maximum size of one file to upload is set to %s. You are allowed to upload up to %d files at once."] = "Den største totale opplastingsstørrelsen du er tillatt er satt til %s. Filstørrelsen på en enkelt fil er satt til å maksimalt være %s. Du har lov til å laste opp inntil %d filer samtidig.";
-App::$strings["You can adjust these settings in the server php.ini file."] = "";
-App::$strings["PHP upload limits"] = "PHP opplastingsgrenser";
-App::$strings["Error: the \"openssl_pkey_new\" function on this system is not able to generate encryption keys"] = "Feil: \"openssl_pkey_new\"-funksjonen på dette systemet er ikke i stand til å lage krypteringsnøkler";
-App::$strings["If running under Windows, please see \"http://www.php.net/manual/en/openssl.installation.php\"."] = "Ved kjøring på Windows, vennligst se \"http://www.php.net/manual/en/openssl.installation.php\".";
-App::$strings["Generate encryption keys"] = "Lag krypteringsnøkler";
-App::$strings["Error: the sodium encryption library is not installed."] = "";
-App::$strings["Generate ed25519 encryption keys"] = "Lag krypteringsnøkler";
-App::$strings["Error: one of \"bcmath\" or \"gmp\" (bigmath library) extensions are required."] = "";
-App::$strings["Bigmath library (either bcmath or gmp)"] = "";
-App::$strings["libCurl PHP module"] = "libCurl PHP-modul";
-App::$strings["GD graphics PHP module"] = "GD graphics PHP-modul";
-App::$strings["OpenSSL PHP module"] = "OpenSSL PHP-modul";
-App::$strings["PDO database PHP module"] = "";
-App::$strings["mb_string PHP module"] = "mb_string PHP-modul";
-App::$strings["xml PHP module"] = "xml PHP modul";
-App::$strings["zip PHP module"] = "";
-App::$strings["intl PHP module"] = "xml PHP modul";
-App::$strings["Apache mod_rewrite module"] = "Apache mod_rewrite-modul";
-App::$strings["Error: Apache webserver mod-rewrite module is required but not installed."] = "Feil: Apache web-tjenerens mod-rewrite-modul er påkrevd, men ikke installert.";
-App::$strings["exec"] = "";
-App::$strings["Error: exec is required but is either not installed or has been disabled in php.ini"] = "";
-App::$strings["shell_exec"] = "";
-App::$strings["Error: shell_exec is required but is either not installed or has been disabled in php.ini"] = "";
-App::$strings["Error: libCURL PHP module required but not installed."] = "Feil: libCURL PHP-modul er påkrevd, men er ikke installert.";
-App::$strings["Error: GD PHP module with JPEG support or ImageMagick graphics library required but not installed."] = "";
-App::$strings["Error: openssl PHP module required but not installed."] = "Feil: openssl PHP-modul er påkrevd, men er ikke installert.";
-App::$strings["Error: PDO database PHP module missing a driver for either mysql or pgsql."] = "";
-App::$strings["Error: PDO database PHP module required but not installed."] = "";
-App::$strings["Error: mb_string PHP module required but not installed."] = "Feil: mb_string PHP-modul er påkrevd, men er ikke installert.";
-App::$strings["Error: xml PHP module required for DAV but not installed."] = "Feil: XML PHP modul er påkrevet for DAV, men den er ikke installert.";
-App::$strings["Error: zip PHP module required but not installed."] = "";
-App::$strings["Error: intl PHP module required but not installed."] = "Feil: openssl PHP-modul er påkrevd, men er ikke installert.";
-App::$strings[".htconfig.php is writable"] = ".htconfig.php kan skrives til";
-App::$strings["The web installer needs to be able to create a file called \".htconfig.php\" in the top folder of your web server and it is unable to do so."] = "Web-installasjonen må kunne lage en fil kalt \".htconfig.php\" i toppkatalogen til web-tjeneren din, men dette får den ikke til.";
-App::$strings["This is most often a permission setting, as the web server may not be able to write files in your folder - even if you can."] = "Dette er oftest tillatelsesinnstilling, ettersom webtjeneren kanskje kan skrive til filer i din mappe - selv om du kan.";
-App::$strings["Please see install/INSTALL.txt for additional information."] = "";
-App::$strings["This software uses the Smarty3 template engine to render its web views. Smarty3 compiles templates to PHP to speed up rendering."] = "";
-App::$strings["In order to store these compiled templates, the web server needs to have write access to the directory %s under the top level web folder."] = "";
-App::$strings["Please ensure that the user that your web server runs as (e.g. www-data) has write access to this folder."] = "Vennligst sikre at brukeren som din web-tjeneste kjører som (for eksempel www-data) har skrivetilgang til denne katalogen.";
-App::$strings["Note: as a security measure, you should give the web server write access to %s only--not the template files (.tpl) that it contains."] = "Merknad: som et sikkerhetstiltak bør du bare gi webtjerenn skrivetilgang til %s - ikke til malfilene (.tpl) som den inneholder.";
-App::$strings["%s is writable"] = "%s kan skrives til";
-App::$strings["This software uses the store directory to save uploaded files. The web server needs to have write access to the store directory under the top level web folder"] = "";
-App::$strings["store is writable"] = "lageret kan skrives til";
-App::$strings["SSL certificate cannot be validated. Fix certificate or disable https access to this site."] = "SSL-sertifikatet kan ikke kontrolleres. Fiks sertifikatet eller skru av https tilgang til dette nettstedet.";
-App::$strings["If you have https access to your website or allow connections to TCP port 443 (the https: port), you MUST use a browser-valid certificate. You MUST NOT use self-signed certificates!"] = "Hvis du har HTTPS-tilgang til ditt nettsted eller tillater forbindelser til TCP port 443 (HTTPS-porten), så MÅ du bruke nettlesergodkjent sertifkater. Du MÅ IKKE bruke egensignert sertifikater!";
-App::$strings["This restriction is incorporated because public posts from you may for example contain references to images on your own hub."] = "Denne begrensningen er tatt inn fordi offentlige innlegg fra deg kan for eksempel inneholde referanser til bilder på din egen hub.";
-App::$strings["If your certificate is not recognized, members of other sites (who may themselves have valid certificates) will get a warning message on their own site complaining about security issues."] = "Hvis sertifikatet ditt ikke gjenkjennes, så vil medlemmer på andre nettsteder (som selv kan ha godkjente sertifikater) få en beskjed med en advarsel på deres eget nettsted som klager over sikkerhetsproblemer.";
-App::$strings["This can cause usability issues elsewhere (not just on your own site) so we must insist on this requirement."] = "Dette kan gi problemer med brukervennlighet (ikke bare på ditt eget nettsted), så vi må insistere på dette kravet.";
-App::$strings["Providers are available that issue free certificates which are browser-valid."] = "Det finnes tilbydere som utsteder gratis sertifikater som er gyldige i nettlesere.";
-App::$strings["If you are confident that the certificate is valid and signed by a trusted authority, check to see if you have failed to install an intermediate cert. These are not normally required by browsers, but are required for server-to-server communications."] = "";
-App::$strings["SSL certificate validation"] = "SSL sertifikat-kontroll";
-App::$strings["Url rewrite in .htaccess is not working. Check your server configuration.Test: "] = "URL omskriving (rewrite) i .htaccess virker ikke. Sjekk konfigurasjonen til tjeneren din. Test: ";
-App::$strings["Url rewrite is working"] = "URL rewrite virker";
-App::$strings["The database configuration file \".htconfig.php\" could not be written. Please use the enclosed text to create a configuration file in your web server root."] = "Databasekonfigurasjonsfilen \".htconfig.php\" kunne ikke skrives. Vennligst bruk den medfølgende teksten for å lage en konfigurasjonsfil i toppkatalogen av din web-tjener.";
-App::$strings["Errors encountered creating database tables."] = "Feil oppstod under opprettelsen av databasetabeller.";
-App::$strings["<h1>What next?</h1>"] = "";
-App::$strings["IMPORTANT: You will need to [manually] setup a scheduled task for the poller."] = "VIKTIG: Du må [manuelt] sette opp en automatisert tidfestet oppgave til bakgrunnshenteren.";
-App::$strings["Bookmark added"] = "Bokmerke lagt til";
-App::$strings["My Connections Bookmarks"] = "Mine forbindelsers bokmerker";
-App::$strings["Update to Hubzilla 5.0 step 2"] = "";
-App::$strings["To complete the update please run"] = "";
-App::$strings["php util/z6convert.php"] = "";
-App::$strings["from the terminal."] = "";
-App::$strings["Email resent"] = "";
-App::$strings["Email resend failed"] = "";
-App::$strings["Verification successful"] = "";
-App::$strings["Account successfull created"] = "";
-App::$strings["Channel successfull created"] = "";
-App::$strings["Automatic channel creation failed. Please create a channel."] = "";
-App::$strings["Account creation error"] = "";
-App::$strings["Verify failed"] = "";
-App::$strings["Token verification failed"] = "";
-App::$strings["Request not inside time frame"] = "";
-App::$strings["Identity unknown"] = "";
-App::$strings["dId2 mistaken"] = "";
-App::$strings["Your Registration ID"] = "";
-App::$strings["Registration verification"] = "";
-App::$strings["Hold on, you can start verification in"] = "";
-App::$strings["Please remember your verification token for ID"] = "";
-App::$strings["Token validity"] = "";
-App::$strings["Resend email"] = "";
-App::$strings["Registration status"] = "";
-App::$strings["Verification successful!"] = "";
-App::$strings["Your login ID is"] = "";
-App::$strings["After your account has been approved by our administrator you will be able to login with your login ID and your provided password."] = "";
-App::$strings["Registration request revoked"] = "";
-App::$strings["Sorry for any inconvience. Thank you for your response."] = "";
-App::$strings["Please enter your verification token for ID"] = "";
-App::$strings["Please check your email!"] = "";
-App::$strings["Verification token"] = "";
-App::$strings["ID expired"] = "";
-App::$strings["You will require the verification token for ID"] = "";
-App::$strings["Unknown or expired ID"] = "";
-App::$strings["dId2 malformed"] = "";
-App::$strings["Authorize application connection"] = "Tillat programforbindelse";
-App::$strings["Return to your app and insert this Security Code:"] = "";
-App::$strings["Please login to continue."] = "Vennligst logg inn for å fortsette.";
-App::$strings["Do you want to authorize this application to access your posts and contacts, and/or create new posts for you?"] = "Vil du tillate dette programmet å få tilgang til dine innlegg og kontakter, og/eller lage nye innlegg for deg?";
-App::$strings["Event can not end before it has started."] = "Hendelsen kan ikke slutte før den starter.";
-App::$strings["Unable to generate preview."] = "Klarer ikke å lage forhåndsvisning.";
-App::$strings["Event title and start time are required."] = "Hendelsestittel og starttidspunkt er påkrevd.";
-App::$strings["Event not found."] = "Hendelsen ble ikke funnet.";
-App::$strings["Edit event"] = "Endre hendelse";
-App::$strings["Delete event"] = "Slett hendelse";
-App::$strings["Link to source"] = "Lenke til kilde";
-App::$strings["calendar"] = "kalender";
-App::$strings["Failed to remove event"] = "Mislyktes med å slette hendelse";
-App::$strings["Remote privacy information not available"] = "";
-App::$strings["__ctx:acl__ Profile"] = "Profil";
-App::$strings["Privacy group"] = "Personverngruppe:";
-App::$strings["Item"] = "";
-App::$strings["Click to copy link to this ressource for guest %s to clipboard"] = "";
-App::$strings["Link copied"] = "";
-App::$strings["Access"] = "Tilgang";
-App::$strings["Guest access"] = "";
-App::$strings["OCAP access"] = "";
-App::$strings["Layout Name"] = "Layout-navn";
-App::$strings["Layout Description (Optional)"] = "Layoutens beskrivelse (valgfritt)";
-App::$strings["Edit Layout"] = "Endre layout";
-App::$strings["Album not found."] = "Albumet ble ikke funnet.";
-App::$strings["Delete Album"] = "Slett album";
-App::$strings["Delete Photo"] = "Slett bilde";
-App::$strings["Public access denied."] = "Offentlig tilgang avvist.";
-App::$strings["No photos selected"] = "Ingen bilder valgt";
-App::$strings["Access to this item is restricted."] = "Tilgang til dette elementet er begrenset.";
-App::$strings["%1$.2f MB photo storage used."] = "%1$.2f MB lagringsplass til bilder er brukt.";
-App::$strings["%1$.2f MB of %2$.2f MB photo storage used."] = "%1$.2f MB av %2$.2f MB lagringsplass til bilder er brukt.";
-App::$strings["Upload Photos"] = "Last opp bilder";
-App::$strings["Enter an album name"] = "Skriv et albumnavn";
-App::$strings["or select an existing album (doubleclick)"] = "eller velg et eksisterende album (dobbeltklikk)";
-App::$strings["Create a status post for this upload"] = "Lag et statusinnlegg for denne opplastingen";
-App::$strings["Description (optional)"] = "Beskrivelse (valgritt)";
-App::$strings["Show Newest First"] = "Vis nyeste først";
-App::$strings["Show Oldest First"] = "Vis eldste først";
-App::$strings["View Photo"] = "Vis foto";
-App::$strings["Edit Album"] = "Endre album";
-App::$strings["Add Photos"] = "Legg til bilder";
-App::$strings["Permission denied. Access to this item may be restricted."] = "Tillatelse avvist. Tilgang til dette elementet kan være begrenset.";
-App::$strings["Photo not available"] = "Bilde er utilgjengelig";
-App::$strings["Use as profile photo"] = "Bruk som profilbilde";
-App::$strings["Use as cover photo"] = "Bruk som omslagsbilde";
-App::$strings["Private Photo"] = "Privat bilde";
-App::$strings["Previous"] = "Forrige";
-App::$strings["View Full Size"] = "Vis i full størrelse";
-App::$strings["Remove"] = "Fjern";
-App::$strings["Edit photo"] = "Endre bilde";
-App::$strings["Rotate CW (right)"] = "Roter med klokka (mot høyre)";
-App::$strings["Rotate CCW (left)"] = "Roter mot klokka (venstre)";
-App::$strings["Move photo to album"] = "";
-App::$strings["Enter a new album name"] = "Skriv et nytt albumnavn";
-App::$strings["or select an existing one (doubleclick)"] = "eller velg et eksisterende album (dobbeltklikk)";
-App::$strings["Add a Tag"] = "Legg til merkelapp";
-App::$strings["Example: @bob, @Barbara_Jensen, @jim@example.com"] = "Eksempel: @bob, @Barbara_Jensen, @jim@example.com";
-App::$strings["Flag as adult in album view"] = "Flag som voksent i albumvisning";
-App::$strings["View all"] = "Vis alle";
-App::$strings["Photo Tools"] = "Fotoverktøy";
-App::$strings["In This Photo:"] = "I dette bildet:";
-App::$strings["Map"] = "Kart";
-App::$strings["__ctx:noun__ Likes"] = "Liker";
-App::$strings["__ctx:noun__ Dislikes"] = "Liker ikke";
+App::$strings["Article"] = "";
+App::$strings["Item has been removed."] = "";
+App::$strings["Image uploaded but image cropping failed."] = "Bildet ble lastet opp, men beskjæring av bildet mislyktes.";
+App::$strings["Cover Photos"] = "Forsidebilder";
+App::$strings["Image resize failed."] = "Endring av bildestørrelse mislyktes.";
+App::$strings["Image upload failed."] = "Opplasting av bildet mislyktes.";
+App::$strings["Unable to process image."] = "Kan ikke behandle bildet.";
+App::$strings["Photo not available."] = "Bildet er ikke tilgjengelig.";
+App::$strings["Your cover photo may be visible to anybody on the internet"] = "Omslagsbildet ditt vil være synlig for alle på internett";
+App::$strings["Upload File:"] = "Last opp fil:";
+App::$strings["Select a profile:"] = "Velg en profil:";
+App::$strings["Change Cover Photo"] = "Endre omslagsbilde";
+App::$strings["Use a photo from your albums"] = "";
+App::$strings["Select existing photo"] = "Velg eksisterende bilde";
+App::$strings["Crop Image"] = "Beskjær bildet";
+App::$strings["Please adjust the image cropping for optimum viewing."] = "Vennligst juster bildebeskjæringen for optimal visning.";
+App::$strings["Done Editing"] = "Lagre endringer";
+App::$strings["Page link"] = "Sidelenke";
+App::$strings["Edit Webpage"] = "Endre webside";
+App::$strings["Private forum"] = "";
+App::$strings["Public forum"] = "Offentlig forum:";
+App::$strings["Import Webpage Elements"] = "";
+App::$strings["Import selected"] = "";
+App::$strings["Export Webpage Elements"] = "";
+App::$strings["Export selected"] = "";
+App::$strings["Actions"] = "Handlinger";
+App::$strings["Page Link"] = "Sidelenke";
+App::$strings["Page Title"] = "Sidetittel";
+App::$strings["Created"] = "Laget";
+App::$strings["Edited"] = "Endret";
+App::$strings["Invalid file type."] = "";
+App::$strings["Error opening zip file"] = "";
+App::$strings["Invalid folder path."] = "";
+App::$strings["No webpage elements detected."] = "";
+App::$strings["Import complete."] = "";
+App::$strings["%s element installed"] = "%s element installert";
+App::$strings["%s element installation failed"] = "Installasjon av %s-element mislyktes";
App::$strings["Name is required"] = "Navn er påkrevd";
App::$strings["Key and Secret are required"] = "Nøkkel og hemmelighet er påkrevd";
App::$strings["Add application"] = "Legg til program";
App::$strings["Name of application"] = "Navn på program";
-App::$strings["Consumer Key"] = "Consumer Key";
App::$strings["Automatically generated - change if desired. Max length 20"] = "Automatisk laget - kan endres om du vil. Største lengde 20";
-App::$strings["Consumer Secret"] = "Consumer Secret";
App::$strings["Redirect"] = "Omdirigering";
App::$strings["Redirect URI - leave blank unless your application specifically requires this"] = "Omdirigerings-URI - la stå tomt hvis ikke ditt program spesifikt krever dette";
App::$strings["Icon url"] = "Ikon-URL";
@@ -1429,228 +1983,210 @@ App::$strings["Connected OAuth Apps"] = "";
App::$strings["Client key starts with"] = "Klientnøkkel starter med";
App::$strings["No name"] = "Ikke noe navn";
App::$strings["Remove authorization"] = "Fjern tillatelse";
-App::$strings["Available Apps"] = "Tilgjengelige apper";
-App::$strings["Installed Apps"] = "Installerte apper";
-App::$strings["Manage Apps"] = "Behandle apper";
-App::$strings["Create Custom App"] = "";
-App::$strings["No connections."] = "Ingen forbindelser.";
-App::$strings["Visit %s's profile [%s]"] = "Besøk %s sin profil [%s]";
-App::$strings["View Connections"] = "Vis forbindelser";
-App::$strings["toggle full screen mode"] = "";
-App::$strings["Item is not editable"] = "Elementet kan ikke endres";
-App::$strings["Post not found."] = "";
-App::$strings["%1\$s tagged %2\$s's %3\$s with %4\$s"] = "%1\$s merket %3\$s til %2\$s med %4\$s";
-App::$strings["No channel."] = "Ingen kanal.";
-App::$strings["No connections in common."] = "Ingen forbindelser felles.";
-App::$strings["View Common Connections"] = "";
-App::$strings["Profile not found."] = "Profilen ble ikke funnet.";
-App::$strings["Profile deleted."] = "Profilen er slettet.";
-App::$strings["Profile-"] = "Profil-";
-App::$strings["New profile created."] = "Ny profil opprettet.";
-App::$strings["Profile unavailable to clone."] = "Profilen er utilgjengelig for klonen.";
-App::$strings["Profile unavailable to export."] = "Profilen er utilgjengelig for eksport.";
-App::$strings["Profile Name is required."] = "Profilnavn er påkrevd.";
-App::$strings["Marital Status"] = "Sivilstand";
-App::$strings["Romantic Partner"] = "Romantisk partner";
-App::$strings["Likes"] = "Liker";
-App::$strings["Dislikes"] = "Liker ikke";
-App::$strings["Work/Employment"] = "Arbeid/sysselsetting";
-App::$strings["Religion"] = "Religion";
-App::$strings["Political Views"] = "Politiske synspunkter";
-App::$strings["Gender"] = "Kjønn";
-App::$strings["Sexual Preference"] = "Seksuelle preferanser";
-App::$strings["Homepage"] = "Hjemmeside";
-App::$strings["Interests"] = "Interesser";
-App::$strings["Address"] = "Adresse";
-App::$strings["Profile updated."] = "Profilen er oppdatert.";
-App::$strings["Hide my connections from viewers of this profile"] = "Skjul mine forbindelser fra besøkende som ser denne profilen";
-App::$strings["Publish my default profile in the network directory"] = "La standardprofilen min vises i nettverkskatalonen";
-App::$strings["Suggest me as a potential contact to new members"] = "Foreslå meg som mulig kontakt til nye medlemmer";
-App::$strings["Reveal my online status"] = "La andre se om jeg er pålogget eller ikke";
-App::$strings["Edit Profile Details"] = "Endre profildetaljer";
-App::$strings["View this profile"] = "Vis denne profilen";
-App::$strings["Profile Tools"] = "Profilverktøy";
-App::$strings["Change cover photo"] = "Endre omslagsbilde";
-App::$strings["Create a new profile using these settings"] = "Lag en ny profil ved å bruke disse innstillingene";
-App::$strings["Clone this profile"] = "Klon denne profilen";
-App::$strings["Delete this profile"] = "Slett denne profilen";
-App::$strings["Add profile things"] = "Legg til profilting";
-App::$strings["Basic"] = "Grunnleggende";
-App::$strings["Relationship"] = "Forhold";
-App::$strings["Import profile from file"] = "Importer profil fra fil";
-App::$strings["Export profile to file"] = "Eksporter profil til fil";
-App::$strings["Your gender"] = "Kjønn";
-App::$strings["Marital status"] = "Sivilstatus";
-App::$strings["Sexual preference"] = "Legning";
-App::$strings["Profile name"] = "Profilnavn";
-App::$strings["This is your default profile."] = "Dette er din standardprofil.";
-App::$strings["Your full name"] = "Fullt navn";
-App::$strings["Short title/description"] = "Kort tittel/beskrivelse";
-App::$strings["Maximal 190 characters"] = "Maksimum 190 tegn";
-App::$strings["Street address"] = "Gateadresse";
-App::$strings["Locality/City"] = "Sted/by";
-App::$strings["Region/State"] = "Region";
-App::$strings["Postal/Zip code"] = "Postnummer";
-App::$strings["Country"] = "Land";
-App::$strings["Who (if applicable)"] = "Hvem (om relevant)";
-App::$strings["Examples: cathy123, Cathy Williams, cathy@example.com"] = "Eksempler: kari123, Kari Villiamsen, kari@example.com";
-App::$strings["Since (date)"] = "Fra (dato)";
-App::$strings["Tell us about yourself"] = "Fortell oss om deg selv";
-App::$strings["Homepage URL"] = "Hjemmeside URL";
-App::$strings["Hometown"] = "Hjemsted";
-App::$strings["Political views"] = "Politiske holdninger";
-App::$strings["Religious views"] = "Religiøse holdninger";
-App::$strings["Keywords used in directory listings"] = "Nøkkelord for bruk i katalogoppføringen";
-App::$strings["Example: fishing photography software"] = "Eksempel: fisking fotografering programvare";
-App::$strings["Musical interests"] = "Musikkinteresser";
-App::$strings["Books, literature"] = "Bøker, litteratur";
-App::$strings["Television"] = "TV/fjernsyn";
-App::$strings["Film/Dance/Culture/Entertainment"] = "Film/dans/kultur/underholdning";
-App::$strings["Hobbies/Interests"] = "Hobbier/Interesser";
-App::$strings["Love/Romance"] = "Kjærlighet/romantikk";
-App::$strings["School/Education"] = "Skolle/utdanning";
-App::$strings["Contact information and social networks"] = "Kontaktinformasjon og andre sosiale nettverk";
-App::$strings["My other channels"] = "Mine andre kanaler";
-App::$strings["Create New"] = "Lag ny profil";
App::$strings["No such group"] = "Gruppen finnes ikke";
App::$strings["No such channel"] = "Ingen slik kanal";
+App::$strings["Search Results For:"] = "Søkeresultat for:";
App::$strings["Privacy group is empty"] = "Personverngruppen er tom";
App::$strings["Privacy group: "] = "Personverngruppe: ";
-App::$strings["Invalid channel."] = "Ugyldig kanal";
-App::$strings["Invite App"] = "";
-App::$strings["Register is closed"] = "";
-App::$strings["Note, the invitation code is valid up to"] = "";
-App::$strings["Too many recipients for one invitation (max %d)"] = "";
-App::$strings["No recipients for this invitation"] = "";
-App::$strings["(%s) : Not a real email address"] = "(%s): Ikke en virkelig epostadresse";
-App::$strings["(%s) : Not allowed email address"] = "(%s): Ikke en tillatt epostadresse";
-App::$strings["(%s) : email address already in use"] = "(%s): epostadressen er allerede i bruk";
-App::$strings["(%s) : Accepted email address"] = "(%s): Godkjent epostadresse";
-App::$strings["%s : Message delivery failed."] = "%s : meldingslevering feilet.";
-App::$strings["To %s : Message delivery success."] = "";
-App::$strings["%1\$d mail(s) sent, %2\$d mail error(s)"] = "";
-App::$strings["Invites not proposed by configuration"] = "";
-App::$strings["Contact the site admin"] = "";
-App::$strings["Invites by users not enabled"] = "";
-App::$strings["You have no more invitations available"] = "Du har ikke flere invitasjoner tilgjengelig";
-App::$strings["Not on xchan"] = "";
-App::$strings["All users invitation limit exceeded."] = "";
-App::$strings["Minute(s)"] = "";
-App::$strings["Hour(s)"] = "";
-App::$strings["Day(s)"] = "";
-App::$strings["Invitation expires after"] = "";
-App::$strings["duration up from now"] = "";
-App::$strings["Invitation"] = "";
-App::$strings["Send invitations"] = "Send invitasjoner";
-App::$strings["Invitations I am using"] = "Invitasjoner jeg bruker";
-App::$strings["Invitations we are using"] = "";
-App::$strings["§ Note, the email(s) sent will be recorded in the system logs"] = "";
-App::$strings["Enter email addresses, one per line:"] = "Skriv e-postadresser, en per linje:";
-App::$strings["Your message:"] = "Din melding:";
-App::$strings["Invite template"] = "";
-App::$strings["Subject:"] = "Emne:";
-App::$strings["Here you may enter personal notes to the recipient(s)"] = "";
-App::$strings["Failed to create source. No channel selected."] = "Mislyktes med å lage kilde. Ingen kanal er valgt.";
-App::$strings["Source created."] = "Kilden er laget.";
-App::$strings["Source updated."] = "Kilden er oppdatert.";
-App::$strings["*"] = "*";
-App::$strings["Manage remote sources of content for your channel."] = "Håndtere eksterne innholdskilder til din kanal.";
-App::$strings["New Source"] = "Ny kilde";
-App::$strings["Import all or selected content from the following channel into this channel and distribute it according to your channel settings."] = "Importer alt eller et utvalgt av innhold fra følgende kanal inn i denne kanalen og distribuer det i henhold til dine egne kanalinnstillinger.";
-App::$strings["Only import content with these words (one per line)"] = "Bare importer innhold med disse ordene (ett ord per linje)";
-App::$strings["Leave blank to import all public content"] = "La stå tomt for å importere alt offentlig innhold";
-App::$strings["Channel Name"] = "Kanalnavn";
-App::$strings["Add the following categories to posts imported from this source (comma separated)"] = "";
-App::$strings["Resend posts with this channel as author"] = "";
-App::$strings["Copyrights may apply"] = "";
-App::$strings["Source not found."] = "Kilden ble ikke funnet.";
-App::$strings["Edit Source"] = "Endre kilde";
-App::$strings["Delete Source"] = "Slett kilde";
-App::$strings["Source removed"] = "Kilden er fjernet";
-App::$strings["Unable to remove source."] = "Ikke i stand til å fjerne kilde.";
-App::$strings["No suggestions available. If this is a new site, please try again in 24 hours."] = "Ingen forslag tilgjengelige. Hvis dette er et nytt nettsted, vennligst prøv igjen om 24 timer.";
-App::$strings["Ignore/Hide"] = "Ignorer/Skjul";
-App::$strings["%s element installed"] = "%s element installert";
-App::$strings["%s element installation failed"] = "Installasjon av %s-element mislyktes";
-App::$strings["Privacy group created."] = "Personverngruppen er opprettet.";
-App::$strings["Could not create privacy group."] = "Kunne ikke opprette personverngruppen.";
-App::$strings["Privacy group updated."] = "Personverngruppen er oppdatert.";
-App::$strings["Post to this group by default"] = "";
-App::$strings["Add new contacts to this group by default"] = "";
-App::$strings["Privacy group name"] = "Personverngruppens navn:";
-App::$strings["Members are visible to other channels"] = "Medlemmer er synlig for andre kanaler";
-App::$strings["Privacy group removed."] = "Personverngruppen er fjernet.";
-App::$strings["Unable to remove privacy group."] = "Ikke i stand til å fjerne personverngruppen.";
-App::$strings["Privacy Group: %s"] = "Personverngruppe: %s";
-App::$strings["Privacy group name: "] = "Personverngruppens navn: ";
-App::$strings["Group members"] = "";
-App::$strings["Not in this group"] = "";
-App::$strings["Click a channel to toggle membership"] = "";
-App::$strings["Comanche page description language help"] = "Hjelp med Comanche sidebeskrivelsesspråk";
-App::$strings["Layout Description"] = "Layout-beskrivelse";
-App::$strings["Download PDL file"] = "Last ned PDL-fil";
-App::$strings["View"] = "Vis";
-App::$strings["Name and Secret are required"] = "";
-App::$strings["Add OAuth2 application"] = "";
-App::$strings["Grant Types"] = "";
-App::$strings["leave blank unless your application sepcifically requires this"] = "";
-App::$strings["Authorization scope"] = "";
-App::$strings["OAuth2 Application not found."] = "";
-App::$strings["leave blank unless your application specifically requires this"] = "";
-App::$strings["Connected OAuth2 Apps"] = "";
-App::$strings["item"] = "";
-App::$strings["Image uploaded but image cropping failed."] = "Bildet ble lastet opp, men beskjæring av bildet mislyktes.";
-App::$strings["Cover Photos"] = "Forsidebilder";
-App::$strings["Image resize failed."] = "Endring av bildestørrelse mislyktes.";
-App::$strings["Image upload failed."] = "Opplasting av bildet mislyktes.";
-App::$strings["Unable to process image."] = "Kan ikke behandle bildet.";
-App::$strings["Photo not available."] = "Bildet er ikke tilgjengelig.";
-App::$strings["Your cover photo may be visible to anybody on the internet"] = "Omslagsbildet ditt vil være synlig for enhver på internett";
-App::$strings["Upload File:"] = "Last opp fil:";
-App::$strings["Select a profile:"] = "Velg en profil:";
-App::$strings["Change Cover Photo"] = "Endre omslagsbilde";
-App::$strings["Use a photo from your albums"] = "";
-App::$strings["Choose a different album"] = "Velg et annet album";
-App::$strings["Select existing photo"] = "Velg eksisterende bilde";
-App::$strings["Crop Image"] = "Beskjær bildet";
-App::$strings["Please adjust the image cropping for optimum viewing."] = "Vennligst juster bildebeskjæringen for optimal visning.";
-App::$strings["Done Editing"] = "Lagre endringer";
-App::$strings["You must be logged in to see this page."] = "Du må være innloegget for å se denne siden.";
-App::$strings["Room not found"] = "Rommet ble ikke funnet";
-App::$strings["Leave Room"] = "Forlat rom";
-App::$strings["Delete Room"] = "Slett rom";
-App::$strings["I am away right now"] = "Jeg er borte akkurat nå";
-App::$strings["I am online"] = "Jeg er pålogget";
-App::$strings["Bookmark this room"] = "Bokmerk dette rommet";
-App::$strings["New Chatroom"] = "Nytt chatrom";
-App::$strings["Chatroom name"] = "Romnavn";
-App::$strings["Expiration of chats (minutes)"] = "Chat utgår (antall minutter)";
-App::$strings["%1\$s's Chatrooms"] = "%1\$s sine chatrom";
-App::$strings["No chatrooms available"] = "Ingen rom tilgjengelige";
-App::$strings["Add Room"] = "";
-App::$strings["Expiration"] = "Utløp";
-App::$strings["min"] = "min";
-App::$strings["Invalid message"] = "Ugyldig melding";
-App::$strings["no results"] = "ingen resultater";
-App::$strings["channel sync processed"] = "kanalsynkronisering er behandlet";
-App::$strings["queued"] = "lagt i kø";
-App::$strings["posted"] = "lagt inn";
-App::$strings["accepted for delivery"] = "akseptert for levering";
-App::$strings["updated"] = "oppdatert";
-App::$strings["update ignored"] = "oppdatering ignorert";
-App::$strings["permission denied"] = "tillatelse avvist";
-App::$strings["recipient not found"] = "mottaker ble ikke funnet";
-App::$strings["Delivery report for %1\$s"] = "Leveringsrapport for %1\$s";
-App::$strings["Options"] = "Valg";
-App::$strings["Redeliver"] = "";
-App::$strings["Select a bookmark folder"] = "Velg en bokmerkemappe";
-App::$strings["Save Bookmark"] = "Lagre bokmerke";
-App::$strings["URL of bookmark"] = "URL-en til bokmerket";
-App::$strings["Description"] = "Beskrivelse";
-App::$strings["Or enter new bookmark folder name"] = "Eller skriv nytt navn på bokmerkemappe";
-App::$strings["Some blurb about what to do when you're new here"] = "En standardtekst om hva du bør gjøre som ny her";
-App::$strings["Private forum"] = "";
-App::$strings["Public forum"] = "Offentlig forum:";
+App::$strings["Installed Apps"] = "Installerte apper";
+App::$strings["Manage Apps"] = "Behandle apper";
+App::$strings["Create Custom App"] = "";
+App::$strings["No service class restrictions found."] = "Ingen restriksjoner er funnet i tjenesteklasse.";
+App::$strings["No valid account found."] = "Ingen gyldig konto funnet.";
+App::$strings["Password reset request issued. Check your email."] = "Forespørsel om å tilbakestille passord er mottatt. Sjekk e-posten din.";
+App::$strings["Site Member (%s)"] = "Nettstedsmedlem (%s)";
+App::$strings["Password reset requested at %s"] = "Forespurt om å tilbakestille passord hos %s";
+App::$strings["Request could not be verified. (You may have previously submitted it.) Password reset failed."] = "Forespørsel kunne ikke bekreftes. (Du kan ha sendt den inn tidligere.) Tilbakestilling av passord mislyktes.";
+App::$strings["Your password has been reset as requested."] = "Ditt passord har blitt tilbakestilt som forespurt.";
+App::$strings["Your new password is"] = "Ditt nye passord er";
+App::$strings["Save or copy your new password - and then"] = "Lagre eller kopier ditt nye passord, og deretter kan du";
+App::$strings["click here to login"] = "klikke her for å logge inn";
+App::$strings["Your password may be changed from the <em>Settings</em> page after successful login."] = "Ditt passord kan endres på siden <em>Innstillinger</em> etter vellykket innlogging.";
+App::$strings["Your password has changed at %s"] = "Ditt passord er endret hos %s";
+App::$strings["Forgot your Password?"] = "Glemt passord ditt?";
+App::$strings["Enter your email address and submit to have your password reset. Then check your email for further instructions."] = "Skriv e-postadressen din og send inn for å tilbakestille passordet ditt. Sjekk deretter din e-post for videre instruksjoner.";
+App::$strings["Email Address"] = "E-postadresse";
+App::$strings["Reset"] = "Tilbakestill";
+App::$strings["Channel removals are not allowed within 48 hours of changing the account password."] = "Fjerning av kanaler er ikke tillatt innen 48 timer etter endring av kontopassordet.";
+App::$strings["Remove Channel"] = "Fjern kanal";
+App::$strings["WARNING: "] = "ADVARSEL: ";
+App::$strings["This channel will be permanently removed. "] = "";
+App::$strings["This action can not be undone!"] = "";
+App::$strings["Please enter your password for verification:"] = "Vennligst skriv ditt passord for å få bekreftelse:";
+App::$strings["Not a zip file or zip file corrupted."] = "";
+App::$strings["Import Items"] = "Importer elementer";
+App::$strings["Use this form to import existing posts and content from an export file."] = "Bruk dette skjemaet for å importere eksisterende innlegg og innhold fra en eksportfil.";
+App::$strings["File to Upload"] = "Fil som skal lastes opp";
+App::$strings["Imported file is empty."] = "Importert fil er tom.";
+App::$strings["Content import completed"] = "";
+App::$strings["Chatroom import completed"] = "";
+App::$strings["Channel calendar import 1/2 completed"] = "";
+App::$strings["Channel calendar import 2/2 completed"] = "";
+App::$strings["Menu import completed"] = "";
+App::$strings["Wiki import completed"] = "";
+App::$strings["Webpages import completed"] = "";
+App::$strings["This site is not a directory server"] = "Dette nettstedet er ikke en katalogtjener";
+App::$strings["New"] = "Nye";
+App::$strings["No more system notifications."] = "Ingen flere systemvarsler.";
+App::$strings["System Notifications"] = "Systemvarsler";
+App::$strings["Update to Hubzilla 5.0 step 2"] = "";
+App::$strings["To complete the update please run"] = "";
+App::$strings["php util/z6convert.php"] = "";
+App::$strings["from the terminal."] = "";
+App::$strings["+ Repeat again"] = "";
+App::$strings["- Remove yours"] = "Fjern begrep";
+App::$strings["+ Add yours"] = "+ Legg til";
+App::$strings["Thing updated"] = "Tingen er oppdatert";
+App::$strings["Object store: failed"] = "Objektlagring: mislyktes";
+App::$strings["Thing added"] = "Ting lagt til";
+App::$strings["OBJ: %1\$s %2\$s %3\$s"] = "OBJ: %1\$s %2\$s %3\$s";
+App::$strings["item not found."] = "element ble ikke funnet.";
+App::$strings["Edit Thing"] = "Endre ting";
+App::$strings["Select a profile"] = "Velg en profil";
+App::$strings["Post an activity"] = "Legg inn en aktivitet";
+App::$strings["Only sends to viewers of the applicable profile"] = "Sender bare til seere av den aktuelle profilen";
+App::$strings["Name of thing e.g. something"] = "Navn på ting for eksempel noe";
+App::$strings["URL of thing (optional)"] = "URL til ting (valgfritt)";
+App::$strings["URL for photo of thing (optional)"] = "URL til bilde av ting (valgfritt)";
+App::$strings["Add Thing to your Profile"] = "Legg til ting i din profil";
+App::$strings["Account removals are not allowed within 48 hours of changing the account password."] = "Sletting av kontoer er ikke tillatt innen 48 timer etter endring av kontopassordet.";
+App::$strings["Remove This Account"] = "Slett denne kontoen";
+App::$strings["This account and all its channels will be completely removed from the network. "] = "Denne kontoen og alle dens kanaler vil bli fullstendig fjernet fra nettverket. ";
+App::$strings["This action is permanent and can not be undone!"] = "Denne handlingen er permanent og kan ikke angres!";
+App::$strings["Remove Account"] = "Slett konto";
+App::$strings["This channel is limited to %d tokens"] = "";
+App::$strings["Name and Password are required."] = "";
+App::$strings["Token saved."] = "";
+App::$strings["Use this form to create temporary access identifiers to share things with non-members. These identities may be used in privacy groups and visitors may login using these credentials to access private content."] = "";
+App::$strings["Please select a role for this guest!"] = "";
+App::$strings["Select a role for this guest"] = "";
+App::$strings["Login Name"] = "";
+App::$strings["Login Password"] = "";
+App::$strings["Expires (yyyy-mm-dd)"] = "";
+App::$strings["Layout updated."] = "Layout er oppdatert.";
+App::$strings["Edit System Page Description"] = "Endre beskrivelsen av systemsiden";
+App::$strings["(modified)"] = "(endret)";
+App::$strings["Layout not found."] = "Layouten ble ikke funnet.";
+App::$strings["Module Name:"] = "Modulnavn:";
+App::$strings["Layout Help"] = "Layout-hjelp";
+App::$strings["Edit another layout"] = "";
+App::$strings["System layout"] = "";
+App::$strings["Continue"] = "Fortsett";
+App::$strings["Premium Channel Setup"] = "Premiumkanal-oppsett";
+App::$strings["Enable premium channel connection restrictions"] = "Slå på restriksjoner for forbindelse med premiumkanal";
+App::$strings["Please enter your restrictions or conditions, such as paypal receipt, usage guidelines, etc."] = "Vennligst skriv dine restriksjoner og betingelser, slik som PayPal-kvittering, retningslinjer for bruk, og så videre.";
+App::$strings["This channel may require additional steps or acknowledgement of the following conditions prior to connecting:"] = "Denne kanalen kan kreve ytterligere steg og bekreftelse av følgende betingelser før tilkobling:";
+App::$strings["Potential connections will then see the following text before proceeding:"] = "Potensielle forbindelser vil da se følgende tekst før de går videre:";
+App::$strings["By continuing, I certify that I have complied with any instructions provided on this page."] = "Ved å fortsette bekrefter jeg at jeg har oppfylt alle instruksjoner gitt på denne siden.";
+App::$strings["(No specific instructions have been provided by the channel owner.)"] = "(Ingen spesifikke instruksjoner er gitt av kanaleieren.)";
+App::$strings["Restricted or Premium Channel"] = "Begrenset kanal eller premiumkanal";
+App::$strings["Deprecated!"] = "";
+App::$strings["File not found."] = "Filen ble ikke funnet.";
+App::$strings["Permission Denied."] = "Tillatelse avvist.";
+App::$strings["Edit file permissions"] = "Endre filtillatelser";
+App::$strings["Set/edit permissions"] = "Angi/endre tillatelser";
+App::$strings["Include all files and sub folders"] = "Inkluder alle filer og undermapper";
+App::$strings["Return to file list"] = "Gå tilbake til filoversikten";
+App::$strings["Share this file"] = "Del denne filen";
+App::$strings["Show URL to this file"] = "Vis URLen til denne filen";
+App::$strings["Account not found."] = "Kontoen ble ikke funnet.";
+App::$strings["Multifactor Verification"] = "";
+App::$strings["Please enter the verification key from your authenticator app"] = "";
+App::$strings["Verify"] = "";
+App::$strings["Invalid abook_id"] = "";
+App::$strings["Could not access contact record."] = "Fikk ikke tilgang til kontaktinformasjonen.";
+App::$strings["Could not locate selected profile."] = "Fant ikke valgt profil.";
+App::$strings["is now connected to"] = "er nå forbundet til";
+App::$strings["Contact Tools"] = "Kontaktverktøy";
+App::$strings["Approve this contact"] = "";
+App::$strings["Accept contact to allow communication"] = "";
+App::$strings["Please select a role for this contact!"] = "";
+App::$strings["This contact is unreachable from this location."] = "";
+App::$strings["This contact may be unreachable from other channel locations."] = "";
+App::$strings["Location independence is not supported by their network."] = "";
+App::$strings["View profile"] = "Vis profil";
+App::$strings["This is a group/forum channel"] = "";
+App::$strings["Select a role for this contact"] = "Velg en rolle for denne forbindelsen";
+App::$strings["Slide to adjust your degree of friendship"] = "Flytt for å justere din grad av vennskap";
+App::$strings["Custom Filter"] = "Tilpasset filter";
+App::$strings["Only import posts with this text"] = "Bare importer innlegg med disse ordene";
+App::$strings["words one per line or #tags or /patterns/ or lang=xx, leave blank to import all posts"] = "ord per linje eller #merkelapper eller /mønster/ eller språk lang=xx, la stå blankt for å importere alle innlegg";
+App::$strings["Do not import posts with this text"] = "Ikke importer innlegg med denne teksten";
+App::$strings["Approve contact"] = "";
+App::$strings["Their"] = "Deres";
+App::$strings["My"] = "Mine";
+App::$strings["Roles"] = "Roller";
+App::$strings["Compare permissions"] = "Sammenlign tillatelser";
+App::$strings["Permission"] = "Tillatelse";
+App::$strings["Content filter"] = "";
+App::$strings["Contact updated"] = "";
+App::$strings["Contact update failed"] = "";
+App::$strings["Approve connection"] = "Godkjenn forbindelse";
+App::$strings["Block status updated"] = "";
+App::$strings["Block failed"] = "";
+App::$strings["Ignore status updated"] = "";
+App::$strings["Ignore failed"] = "";
+App::$strings["Archive status updated"] = "";
+App::$strings["Archive failed"] = "";
+App::$strings["Hide status updated"] = "";
+App::$strings["Hide failed"] = "";
+App::$strings["Contact removed"] = "";
+App::$strings["Delete failed"] = "";
+App::$strings["Refetch contact info"] = "";
+App::$strings["Unblock"] = "Ikke blokker lenger";
+App::$strings["Block"] = "Blokker";
+App::$strings["Block (or Unblock) all communications with this connection"] = "Blokker eller fjern blokkering av all kommunikasjon med denne forbindelsen";
+App::$strings["This connection is blocked!"] = "Denne forbindelsen er blokkert!";
+App::$strings["Unignore"] = "Ikke ignorer lenger";
+App::$strings["Ignore"] = "Ignorer";
+App::$strings["Ignore (or Unignore) all inbound communications from this connection"] = "Ignorer eller fjern ignorering av all inngående kommunikasjon fra denne forbindelsen";
+App::$strings["This connection is ignored!"] = "Denne forbindelsen er ignorert!";
+App::$strings["Unarchive"] = "Ikke arkiver lenger";
+App::$strings["Archive"] = "Arkiver";
+App::$strings["Archive (or Unarchive) this connection - mark channel dead but keep content"] = "Arkiver eller fjern arkivering av denne forbindelsen - marker kanal som død, men behold innhold";
+App::$strings["This connection is archived!"] = "Denne forbindelsen er arkivert!";
+App::$strings["Unhide"] = "Ikke skjul lenger";
+App::$strings["Hide"] = "Skjul";
+App::$strings["Hide or Unhide this connection from your other connections"] = "Skjul eller fjern skjuling av denne forbindelsen fra dine andre forbindelser";
+App::$strings["This connection is hidden!"] = "Denne forbindelsen er skjult!";
+App::$strings["Delete this connection"] = "Slett denne forbindelsen";
+App::$strings["Items tagged with: %s"] = "Elementer merket med: %s";
+App::$strings["Search results for: %s"] = "Søkeresultater for: %s";
+App::$strings["Email address required"] = "";
+App::$strings["No password provided"] = "";
+App::$strings["Terms of Service not accepted"] = "";
+App::$strings["Invitation code succesfully applied"] = "";
+App::$strings["Invitation not in time or too late"] = "";
+App::$strings["Invitation email failed"] = "";
+App::$strings["Invitation code failed"] = "";
+App::$strings["Invitations are not available"] = "";
+App::$strings["Registration on this hub is by invitation only"] = "";
+App::$strings["New register request"] = "";
+App::$strings["Error creating dId A"] = "";
+App::$strings["Registration on this hub is disabled."] = "Registrering ved dette nettstedet er skrudd av.";
+App::$strings["Why do you want to join this hub?"] = "";
+App::$strings["This will help to review your registration"] = "";
+App::$strings["Registration on this hub is by approval only."] = "Registrering ved dette nettstedet skjer på godkjenning.";
+App::$strings["Register at another affiliated hub in case when prefered"] = "";
+App::$strings["Registration on this hub is by invitation only."] = "";
+App::$strings["Register at another affiliated hub"] = "";
+App::$strings["Terms of Service"] = "Tjenesteavtale";
+App::$strings["I accept the %s for this website"] = "Jeg godtar %s for dette nettstedet";
+App::$strings["I am over %s years of age and accept the %s for this website"] = "Jeg er mer enn %s år gammel, og godtar %s for dette nettstedet";
+App::$strings["Your email address"] = "Din e-postadresse";
+App::$strings["Choose a password"] = "Velg et passord";
+App::$strings["Please re-enter your password"] = "Vennligst skriv ditt passord en gang til";
+App::$strings["Please enter your invitation code"] = "Vennligst skriv din invitasjonskode";
+App::$strings["Your name"] = "Navn";
+App::$strings["Real name is preferred"] = "";
+App::$strings["Choose a short nickname"] = "Velg et kort kallenavn";
+App::$strings["Your nickname will be used to create an easy to remember channel address"] = "";
+App::$strings["Registration"] = "Registrering";
+App::$strings["I have an invite code"] = "";
+App::$strings["This site has exceeded the number of allowed daily account registrations."] = "";
+App::$strings["This setting requires special processing and editing has been blocked."] = "Denne innstillingen krever spesiell behandling og redigering har blitt blokkert.";
+App::$strings["Configuration Editor"] = "Konfigurasjonsbehandler";
+App::$strings["Warning: Changing some settings could render your channel inoperable. Please leave this page unless you are comfortable with and knowledgeable about how to correctly use this feature."] = "Advarsel: kanalen din kan slutte å virke ved endring av enkelte innstillinger. Vennligst forlat denne siden med mindre du er komfortabel med dette og vet hvordan du bruker denne funksjonen riktig.";
App::$strings["App installed."] = "App installert.";
App::$strings["Malformed app."] = "Feil oppsett for app-en.";
App::$strings["Embed code"] = "Innbyggingskode";
@@ -1664,37 +2200,20 @@ App::$strings["Categories (optional, comma separated list)"] = "Kategorier (valg
App::$strings["Version ID"] = "Versjons-ID";
App::$strings["Price of app"] = "Pris på app";
App::$strings["Location (URL) to purchase app"] = "Plassering (URL) for å kjøpe app";
-App::$strings["No default suggestions were found."] = "";
-App::$strings["Gender: "] = "Kjønn: ";
-App::$strings["Status: "] = "Status: ";
-App::$strings["Homepage: "] = "Hjemmeside: ";
-App::$strings["Description:"] = "Beskrivelse:";
-App::$strings["Unsafe"] = "";
-App::$strings["Spam"] = "";
-App::$strings["Public Forum:"] = "Offentlig forum:";
-App::$strings["Keywords: "] = "Nøkkelord: ";
-App::$strings["Don't suggest"] = "Ikke foreslå";
-App::$strings["Common connections (estimated):"] = "";
-App::$strings["Global Directory"] = "Global katalog";
-App::$strings["Local Directory"] = "Lokal katalog";
-App::$strings["Finding:"] = "Finner:";
-App::$strings["next page"] = "neste side";
-App::$strings["previous page"] = "forrige side";
-App::$strings["Sort options"] = "Sorteringsvalg";
-App::$strings["Alphabetic"] = "Alfabetisk";
-App::$strings["Reverse Alphabetic"] = "Omvendt alfabetisk";
-App::$strings["Newest to Oldest"] = "Nyest til eldst";
-App::$strings["Oldest to Newest"] = "Eldst til nyest";
-App::$strings["No entries (some entries may be hidden)."] = "Ingen oppføringer (noen oppføringer kan være skjult).";
-App::$strings["No service class restrictions found."] = "Ingen restriksjoner er funnet i tjenesteklasse.";
-App::$strings["Tag removed"] = "Merkelapp fjernet";
-App::$strings["Remove Item Tag"] = "Fjern merkelapp fra element";
-App::$strings["Select a tag to remove: "] = "Velg merkelapp å fjerne: ";
-App::$strings["Could not access contact record."] = "Fikk ikke tilgang til kontaktinformasjonen.";
-App::$strings["Could not locate selected profile."] = "Fant ikke valgt profil.";
+App::$strings["item"] = "";
+App::$strings["Unable to locate original post."] = "Ikke i stand til å finne opprinnelig innlegg.";
+App::$strings["Empty post discarded."] = "Tomt innlegg forkastet.";
+App::$strings["Duplicate post suppressed."] = "Duplikat av innlegg forhindret.";
+App::$strings["System error. Post not saved."] = "Systemfeil. Innlegg ble ikke lagret.";
+App::$strings["Your comment is awaiting approval."] = "";
+App::$strings["Unable to obtain post information from database."] = "Ikke i stand til å få tak i informasjon om innlegg fra databasen.";
+App::$strings["You have reached your limit of %1$.0f top level posts."] = "Du har nådd din grense på %1$.0f startinnlegg.";
+App::$strings["You have reached your limit of %1$.0f webpages."] = "Du har nådd din grense på %1$.0f websider.";
+App::$strings["Some blurb about what to do when you're new here"] = "En standardtekst om hva du bør gjøre som ny her";
+App::$strings["Unable to find your hub."] = "Ikke i stand til å finne hubben din.";
+App::$strings["Post successful."] = "Innlegg vellykket.";
App::$strings["Connection updated."] = "Forbindelsen er oppdatert.";
App::$strings["Failed to update connection record."] = "Mislyktes med å oppdatere forbindelsesinformasjonen.";
-App::$strings["is now connected to"] = "er nå forbundet til";
App::$strings["Could not access address book record."] = "Fikk ikke tilgang til informasjonen i adresseboken.";
App::$strings["Refresh failed - channel is currently unavailable."] = "Oppfrisking mislyktes - kanalen er for øyeblikket utilgjengelig.";
App::$strings["Unable to set address book parameters."] = "Ikke i stand til å angi parametre for adresseboken.";
@@ -1705,70 +2224,33 @@ App::$strings["Fetch updated permissions"] = "Hent oppdaterte tillatelser";
App::$strings["Refresh Photo"] = "";
App::$strings["Fetch updated photo"] = "";
App::$strings["View recent posts and comments"] = "Vis nylige innlegg og kommentarer";
-App::$strings["Unblock"] = "Ikke blokker lenger";
-App::$strings["Block"] = "Blokker";
-App::$strings["Block (or Unblock) all communications with this connection"] = "Blokker eller fjern blokkering av all kommunikasjon med denne forbindelsen";
-App::$strings["This connection is blocked!"] = "Denne forbindelsen er blokkert!";
-App::$strings["Unignore"] = "Ikke ignorer lenger";
-App::$strings["Ignore"] = "Ignorer";
-App::$strings["Ignore (or Unignore) all inbound communications from this connection"] = "Ignorer eller fjern ignorering av all inngående kommunikasjon fra denne forbindelsen";
-App::$strings["This connection is ignored!"] = "Denne forbindelsen er ignorert!";
-App::$strings["Unarchive"] = "Ikke arkiver lenger";
-App::$strings["Archive"] = "Arkiver";
-App::$strings["Archive (or Unarchive) this connection - mark channel dead but keep content"] = "Arkiver eller fjern arkivering av denne forbindelsen - marker kanal som død, men behold innhold";
-App::$strings["This connection is archived!"] = "Denne forbindelsen er arkivert!";
-App::$strings["Unhide"] = "Ikke skjul lenger";
-App::$strings["Hide"] = "Skjul";
-App::$strings["Hide or Unhide this connection from your other connections"] = "Skjul eller fjern skjuling av denne forbindelsen fra dine andre forbindelser";
-App::$strings["This connection is hidden!"] = "Denne forbindelsen er skjult!";
-App::$strings["Delete this connection"] = "Slett denne forbindelsen";
App::$strings["Fetch Vcard"] = "";
App::$strings["Fetch electronic calling card for this connection"] = "";
-App::$strings["Affinity"] = "";
App::$strings["Open Set Affinity section by default"] = "";
-App::$strings["Me"] = "Meg";
-App::$strings["Family"] = "Familie";
-App::$strings["Acquaintances"] = "Bekjente";
-App::$strings["All"] = "Alle";
App::$strings["Filter"] = "";
App::$strings["Open Custom Filter section by default"] = "";
-App::$strings["Approve this contact"] = "";
-App::$strings["Accept contact to allow communication"] = "";
App::$strings["Set Affinity"] = "Angi nærhet";
App::$strings["Set Profile"] = "Angi profil";
App::$strings["Set Affinity & Profile"] = "Angi nærhet og profil";
-App::$strings["Please select a role for this contact!"] = "";
-App::$strings["This contact is unreachable from this location."] = "";
-App::$strings["This contact may be unreachable from other channel locations."] = "";
-App::$strings["Location independence is not supported by their network."] = "";
App::$strings["Contact: %s"] = "";
App::$strings["Contact role"] = "";
App::$strings["Manage contact roles"] = "";
App::$strings["This contacts's primary address is"] = "";
App::$strings["Available locations:"] = "Tilgjengelige plasseringer:";
App::$strings["The permissions indicated on this page will be applied to all new connections."] = "Tillatelsene angitt på denne siden gjøres gjeldende for alle nye forbindelser.";
-App::$strings["Contact Tools"] = "";
-App::$strings["Slide to adjust your degree of friendship"] = "Flytt for å justere din grad av vennskap";
-App::$strings["Custom Filter"] = "Tilpasset filter";
-App::$strings["Only import posts with this text"] = "Bare importer innlegg med disse ordene";
-App::$strings["words one per line or #tags or /patterns/ or lang=xx, leave blank to import all posts"] = "ord per linje eller #merkelapper eller /mønster/ eller språk lang=xx, la stå blankt for å importere alle innlegg";
-App::$strings["Do not import posts with this text"] = "Ikke importer innlegg med denne teksten";
App::$strings["Contact Pending Approval"] = "";
App::$strings["inherited"] = "arvet";
-App::$strings["Approve contact"] = "";
App::$strings["Please choose the profile you would like to display to %s when viewing your profile securely."] = "Vennligst velg profilen du ønsker å vise %s når profilen din ses på en sikret måte.";
-App::$strings["Their"] = "";
-App::$strings["My"] = "";
App::$strings["Individual Permissions"] = "Individuelle tillatelser";
App::$strings["Some permissions may be inherited from your channel's <a href=\"settings\"><strong>privacy settings</strong></a>, which have higher priority than individual settings. You can <strong>not</strong> change those settings here."] = "Noen tillatelser kan være arvet fra din kanals <a ref=\"settings\"><strong>personverninnstillinger</strong></a>, som har høyere prioritet enn individuelle innstillinger. Du kan <strong>ikke</strong> endre arvede innstillingene her.";
App::$strings["Some permissions may be inherited from your channel's <a href=\"settings\"><strong>privacy settings</strong></a>, which have higher priority than individual settings. You can change those settings here but they wont have any impact unless the inherited setting changes."] = "Noen tillatelser kan være arvet fra din kanals <a ref=\"settings\"><strong>personverninnstillinger</strong></a>, som har høyere prioritet enn individuelle innstillinger. Du kan endre disse innstillingene her, men de vil ikke få noen effekt før de arvede innstillingene endres.";
App::$strings["Last update:"] = "Siste oppdatering:";
App::$strings["Details"] = "";
App::$strings["Organisation"] = "";
-App::$strings["Title"] = "Tittel";
App::$strings["Phone"] = "";
App::$strings["Instant messenger"] = "";
App::$strings["Website"] = "";
+App::$strings["Address"] = "Adresse";
App::$strings["Note"] = "";
App::$strings["Add Contact"] = "";
App::$strings["Add Field"] = "";
@@ -1778,101 +2260,7 @@ App::$strings["Street"] = "";
App::$strings["Locality"] = "";
App::$strings["Region"] = "";
App::$strings["ZIP Code"] = "";
-App::$strings["Nothing to import."] = "Ingenting å importere.";
-App::$strings["Unable to download data from old server"] = "Ikke i stand til å laste ned data fra gammel tjener";
-App::$strings["Your service plan only allows %d channels."] = "Din tjenesteplan tillater bare %d kanaler.";
-App::$strings["No channel. Import failed."] = "Ingen kanal. Import mislyktes.";
-App::$strings["Channel exists but has been marked removed on this hub. Import failed."] = "";
-App::$strings["Automatic content and files import was not possible due to API version incompatiblity. Please import content and files manually!"] = "";
-App::$strings["You must be logged in to use this feature."] = "Du må være innlogget for å bruke denne funksjonen.";
-App::$strings["Channel Import"] = "";
-App::$strings["Use this form to import an existing channel from a different server/hub. You may retrieve the channel identity from the old server/hub via the network or provide an export file."] = "Bruk dette skjemaet for å importere en eksisterende kanal fra en annen tjener/hub. Du kan hente inn kanalidentiteten fra den gamle tjeneren/huben via nettverket eller ved å bruke en eksportfil.";
-App::$strings["Or provide the old server/hub details"] = "Eller oppgi detaljene fra den gamle tjeneren/hub-en";
-App::$strings["Your old identity address (xyz@example.com)"] = "Din gamle identitetsadresse (xyz@example.com)";
-App::$strings["Your old login email address"] = "Din gamle innloggings e-postadresse";
-App::$strings["Your old login password"] = "Ditt gamle innloggingspassord";
-App::$strings["Import your items and files (limited by available memory)"] = "";
-App::$strings["For either option, please choose whether to make this hub your new primary address, or whether your old location should continue this role. You will be able to post from either location, but only one can be marked as the primary location for files, photos, and media."] = "Enten du tar det ene eller det andre valget, vennligst angi om du vil at denne hubben skal være din nye primære adresse, eller om din gamle plassering skal fortsette å ha denne rollen. Du kan lage innlegg fra den ene eller den andre plasseringen, men bare en av dem kan markeres som den primære plasseringen for filer, bilder og media.";
-App::$strings["Make this hub my primary location"] = "Gjør dette nettstedet til min primære plassering";
-App::$strings["Move this channel (disable all previous locations)"] = "";
-App::$strings["Use this channel nickname instead of the one provided"] = "";
-App::$strings["Leave blank to keep your existing channel nickname. You will be randomly assigned a similar nickname if either name is already allocated on this site."] = "";
-App::$strings["This process may take several minutes to complete. Please submit the form only once and leave this page open until finished."] = "Denne prosessen kan ta flere minutter å fullføre. Vennligst send inn dette skjemaet bare en gang og la siden være åpen inntil den er ferdig.";
-App::$strings["Page link"] = "Sidelenke";
-App::$strings["Edit Webpage"] = "Endre webside";
-App::$strings["Enter a folder name"] = "";
-App::$strings["or select an existing folder (doubleclick)"] = "";
-App::$strings["Invalid profile identifier."] = "Ugyldig profil-identifikator.";
-App::$strings["Profile Visibility Editor"] = "Endre profilsynlighet";
-App::$strings["Click on a contact to add or remove."] = "Klikk på en kontakt for å legge til eller fjerne.";
-App::$strings["Visible To"] = "Synlig for";
-App::$strings["All Connections"] = "Alle forbindelser";
-App::$strings["Permissions denied."] = "Tillatelse avvist.";
-App::$strings["Today"] = "Idag";
-App::$strings["Entry censored"] = "";
-App::$strings["Entry OK"] = "";
-App::$strings["network"] = "nettverk";
-App::$strings["This setting requires special processing and editing has been blocked."] = "Denne innstillingen krever spesiell behandling og redigering har blitt blokkert.";
-App::$strings["Configuration Editor"] = "Konfigurasjonsbehandler";
-App::$strings["Warning: Changing some settings could render your channel inoperable. Please leave this page unless you are comfortable with and knowledgeable about how to correctly use this feature."] = "Advarsel: kanalen din kan slutte å virke ved endring av enkelte innstillinger. Vennligst forlat denne siden med mindre du er komfortabel med dette og vet hvordan du bruker denne funksjonen riktig.";
-App::$strings["Xchan Lookup"] = "Xchan oppslag";
-App::$strings["Lookup xchan beginning with (or webbie): "] = "Slå opp xchan som begynner med (eller webbie): ";
-App::$strings["Email address required"] = "";
-App::$strings["No password provided"] = "";
-App::$strings["Terms of Service not accepted"] = "";
-App::$strings["Invitation code succesfully applied"] = "";
-App::$strings["Invitation not in time or too late"] = "";
-App::$strings["Invitation email failed"] = "";
-App::$strings["Invitation code failed"] = "";
-App::$strings["Invitations are not available"] = "";
-App::$strings["Registration on this hub is by invitation only"] = "";
-App::$strings["New register request"] = "";
-App::$strings["Error creating dId A"] = "";
-App::$strings["Registration on this hub is disabled."] = "Registrering ved dette nettstedet er skrudd av.";
-App::$strings["Why do you want to join this hub?"] = "";
-App::$strings["This will help to review your registration"] = "";
-App::$strings["Registration on this hub is by approval only."] = "Registrering ved dette nettstedet skjer på godkjenning.";
-App::$strings["Register at another affiliated hub in case when prefered"] = "";
-App::$strings["Registration on this hub is by invitation only."] = "";
-App::$strings["Register at another affiliated hub"] = "";
-App::$strings["Terms of Service"] = "Tjenesteavtale";
-App::$strings["I accept the %s for this website"] = "Jeg godtar %s for dette nettstedet";
-App::$strings["I am over %s years of age and accept the %s for this website"] = "Jeg er mer enn %s år gammel, og godtar %s for dette nettstedet";
-App::$strings["Your email address"] = "Din e-postadresse";
-App::$strings["Choose a password"] = "Velg et passord";
-App::$strings["Please re-enter your password"] = "Vennligst skriv ditt passord en gang til";
-App::$strings["Please enter your invitation code"] = "Vennligst skriv din invitasjonskode";
-App::$strings["Your name"] = "Navn";
-App::$strings["Real name is preferred"] = "";
-App::$strings["Your nickname will be used to create an easy to remember channel address"] = "";
-App::$strings["Registration"] = "Registrering";
-App::$strings["I have an invite code"] = "";
-App::$strings["This site has exceeded the number of allowed daily account registrations."] = "";
-App::$strings["Public Hubs"] = "Offentlige huber";
-App::$strings["The listed hubs allow public registration for the \$Projectname network. All hubs in the network are interlinked so membership on any of them conveys membership in the network as a whole. Some hubs may require subscription or provide tiered service plans. The hub itself <strong>may</strong> provide additional details."] = "Nettstedene på listen tillater offentlig registrering i \$Projectname-nettverket. Alle nettsteder i nettverket er forbundet så medlemskap på enhver av dem formidler medlemskap i nettverket som helhet. Noen nettsteder kan kreve abonnement eller tilby lagdelte tjenesteavtaler. Nettstedene selv <strong>kan</strong> gi tilleggsopplysninger.";
-App::$strings["Hub URL"] = "Nettstedets URL";
-App::$strings["Access Type"] = "Tilgangstype";
-App::$strings["Registration Policy"] = "Retningslinjer for registrering";
-App::$strings["Stats"] = "Statistikk";
-App::$strings["Software"] = "Programvare";
-App::$strings["Ratings"] = "Vurderinger";
-App::$strings["Rate"] = "Vurder";
-App::$strings["Thing updated"] = "Tingen er oppdatert";
-App::$strings["Object store: failed"] = "Objektlagring: mislyktes";
-App::$strings["Thing added"] = "Ting lagt til";
-App::$strings["OBJ: %1\$s %2\$s %3\$s"] = "OBJ: %1\$s %2\$s %3\$s";
-App::$strings["item not found."] = "element ble ikke funnet.";
-App::$strings["Edit Thing"] = "Endre ting";
-App::$strings["Select a profile"] = "Velg en profil";
-App::$strings["Post an activity"] = "Legg inn en aktivitet";
-App::$strings["Only sends to viewers of the applicable profile"] = "Sender bare til seere av den aktuelle profilen";
-App::$strings["Name of thing e.g. something"] = "Navn på ting for eksempel noe";
-App::$strings["URL of thing (optional)"] = "URL til ting (valgfritt)";
-App::$strings["URL for photo of thing (optional)"] = "URL til bilde av ting (valgfritt)";
-App::$strings["Add Thing to your Profile"] = "Legg til ting i din profil";
-App::$strings["Welcome to %s"] = "Velkommen til %s";
-App::$strings["Block Title"] = "Byggeklossens tittel";
-App::$strings["Settings updated."] = "Innstillinger oppdatert.";
+App::$strings["Country"] = "Land";
App::$strings["Connection Default Permissions"] = "Forbindelsens standard tillatelser";
App::$strings["Apply these permissions automatically"] = "Bruk disse tillatelsene automatisk";
App::$strings["If enabled, connection requests will be approved without your interaction"] = "";
@@ -1881,166 +2269,251 @@ App::$strings["Add permission role"] = "";
App::$strings["Automatic approval settings"] = "";
App::$strings["My Settings"] = "Mine innstillinger";
App::$strings["Some individual permissions may have been preset or locked based on your channel type and privacy settings."] = "";
-App::$strings["No valid account found."] = "Ingen gyldig konto funnet.";
-App::$strings["Password reset request issued. Check your email."] = "Forespørsel om å tilbakestille passord er mottatt. Sjekk e-posten din.";
-App::$strings["Site Member (%s)"] = "Nettstedsmedlem (%s)";
-App::$strings["Password reset requested at %s"] = "Forespurt om å tilbakestille passord hos %s";
-App::$strings["Request could not be verified. (You may have previously submitted it.) Password reset failed."] = "Forespørsel kunne ikke bekreftes. (Du kan ha sendt den inn tidligere.) Tilbakestilling av passord mislyktes.";
-App::$strings["Your password has been reset as requested."] = "Ditt passord har blitt tilbakestilt som forespurt.";
-App::$strings["Your new password is"] = "Ditt nye passord er";
-App::$strings["Save or copy your new password - and then"] = "Lagre eller kopier ditt nye passord, og deretter kan du";
-App::$strings["click here to login"] = "klikke her for å logge inn";
-App::$strings["Your password may be changed from the <em>Settings</em> page after successful login."] = "Ditt passord kan endres på siden <em>Innstillinger</em> etter vellykket innlogging.";
-App::$strings["Your password has changed at %s"] = "Ditt passord er endret hos %s";
-App::$strings["Forgot your Password?"] = "Glemt passord ditt?";
-App::$strings["Enter your email address and submit to have your password reset. Then check your email for further instructions."] = "Skriv e-postadressen din og send inn for å tilbakestille passordet ditt. Sjekk deretter din e-post for videre instruksjoner.";
-App::$strings["Email Address"] = "E-postadresse";
-App::$strings["Reset"] = "Tilbakestill";
+App::$strings["No suggestions available. If this is a new site, please try again in 24 hours."] = "Ingen forslag tilgjengelige. Hvis dette er et nytt nettsted, vennligst prøv igjen om 24 timer.";
+App::$strings["Connection added."] = "";
+App::$strings["Please login."] = "Vennligst logg inn.";
+App::$strings["No default suggestions were found."] = "";
+App::$strings["Gender: "] = "Kjønn: ";
+App::$strings["Status: "] = "Status: ";
+App::$strings["Homepage: "] = "Hjemmeside: ";
+App::$strings["Description:"] = "Beskrivelse:";
+App::$strings["Unsafe"] = "";
+App::$strings["Spam"] = "";
+App::$strings["Public Forum:"] = "Offentlig forum:";
+App::$strings["Keywords: "] = "Nøkkelord: ";
+App::$strings["Don't suggest"] = "Ikke foreslå";
+App::$strings["Common connections (estimated):"] = "";
+App::$strings["Global Directory"] = "Global katalog";
+App::$strings["Local Directory"] = "Lokal katalog";
+App::$strings["Finding:"] = "Finner:";
+App::$strings["next page"] = "neste side";
+App::$strings["previous page"] = "forrige side";
+App::$strings["Sort options"] = "Sorteringsvalg";
+App::$strings["Alphabetic"] = "Alfabetisk";
+App::$strings["Reverse Alphabetic"] = "Omvendt alfabetisk";
+App::$strings["Newest to Oldest"] = "Nyest til eldst";
+App::$strings["Oldest to Newest"] = "Eldst til nyest";
+App::$strings["No entries (some entries may be hidden)."] = "Ingen oppføringer (noen oppføringer kan være skjult).";
+App::$strings["This profile photo will be visible to anybody on the internet and may be distributed to other websites."] = "Dette profilbildet vil være synlig for alle besøkende, og kan bli gjort tilgjengelig på andre nettsteder.";
+App::$strings["This profile photo will be visible only to channels with permission to view this profile."] = "";
+App::$strings["Use Photo for Profile"] = "";
+App::$strings["Change Profile Photo"] = "Endre profilbilde";
+App::$strings["Reset to default"] = "Tilbakestill til standard";
+App::$strings["Select existing"] = "Velg eksisterende bilde";
+App::$strings["Done editing"] = "Lagre endringer";
+App::$strings["Unknown App"] = "";
+App::$strings["Authorize"] = "";
+App::$strings["Do you authorize the app %s to access your channel data?"] = "";
+App::$strings["Deny"] = "Avslå";
+App::$strings["No connections."] = "Ingen forbindelser.";
+App::$strings["Visit %s's profile [%s]"] = "Besøk %s sin profil [%s]";
+App::$strings["View Connections"] = "Vis forbindelser";
+App::$strings["Authorize application connection"] = "Tillat programforbindelse";
+App::$strings["Return to your app and insert this Security Code:"] = "";
+App::$strings["Please login to continue."] = "Vennligst logg inn for å fortsette.";
+App::$strings["Do you want to authorize this application to access your posts and contacts, and/or create new posts for you?"] = "Vil du tillate dette programmet å få tilgang til dine innlegg og kontakter, og/eller lage nye innlegg for deg?";
+App::$strings["Layout not found"] = "Layouten ble ikke funnet.";
+App::$strings["This template does not support pdledi_gui (no content regions defined)"] = "";
+App::$strings["Main page content"] = "";
+App::$strings["The main page content can not be edited!"] = "";
+App::$strings["Welcome to %s"] = "Velkommen til %s";
+App::$strings["Tag removed"] = "Merkelapp fjernet";
+App::$strings["Remove Item Tag"] = "Fjern merkelapp fra element";
+App::$strings["Select a tag to remove: "] = "Velg merkelapp å fjerne: ";
+App::$strings["Layout Name"] = "Layout-navn";
+App::$strings["Layout Description (Optional)"] = "Layoutens beskrivelse (valgfritt)";
+App::$strings["Comanche page description language help"] = "Hjelp med Comanche sidebeskrivelsesspråk";
+App::$strings["Layout Description"] = "Layout-beskrivelse";
+App::$strings["Download PDL file"] = "Last ned PDL-fil";
+App::$strings["No channel."] = "Ingen kanal.";
+App::$strings["No connections in common."] = "Ingen forbindelser felles.";
+App::$strings["View Common Connections"] = "";
App::$strings["Location not found."] = "Plassering er ikke funnet.";
App::$strings["Location lookup failed."] = "Oppslag på plassering mislyktes.";
App::$strings["Please select another location to become primary before removing the primary location."] = "Vennligst velg en annen plassering som primær før du sletter gjeldende primære plassering.";
App::$strings["Syncing locations"] = "Synkroniserer plasseringer";
App::$strings["No locations found."] = "Ingen plasseringer ble funnet.";
App::$strings["Manage Channel Locations"] = "Håndter kanalplasseringer";
-App::$strings["Primary"] = "Primær";
+App::$strings["Drop"] = "Slett";
App::$strings["Sync Now"] = "Synkroniser nå";
App::$strings["Please wait several minutes between consecutive operations."] = "Vennligst vent flere minutter mellom hver etterfølgende operasjon.";
App::$strings["When possible, drop a location by logging into that website/hub and removing your channel."] = "Når mulig, fjern en plassering ved å logge inn på det nettstedet eller den hub-en og fjern din kanal.";
App::$strings["Use this form to drop the location if the hub is no longer operating."] = "Bruk dette skjemaet for å fjerne plasseringen hvis huben ikke er i drift lenger.";
-App::$strings["Account not found."] = "Kontoen ble ikke funnet";
-App::$strings["Multifactor Verification"] = "";
-App::$strings["Please enter the verification key from your authenticator app"] = "";
-App::$strings["Verify"] = "";
-App::$strings["Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
-App::$strings["Poll not found."] = "Fant ikke spørreskjema.";
-App::$strings["Invalid response."] = "";
-App::$strings["Response submitted. Updates may not appear instantly."] = "";
-App::$strings["Shift-reload the page or clear browser cache if the new photo does not display immediately."] = "Hold nede Shift-knappen og last siden på nytt eller tøm nettleserens mellomlager hvis det nye bildet ikke vises umiddelbart.";
-App::$strings["This profile photo will be visible to anybody on the internet and may be distributed to other websites."] = "Dette profilbildet vil være synlig for enhver besøkende, og kan bli gjort tilgjengelig på andre nettsteder.";
-App::$strings["This profile photo will be visible only to channels with permission to view this profile."] = "";
-App::$strings["Use Photo for Profile"] = "";
-App::$strings["Change Profile Photo"] = "Endre profilbilde";
-App::$strings["Reset to default"] = "Sett tilbake til standard";
-App::$strings["Select existing"] = "Velg eksisterende bilde";
-App::$strings["Done editing"] = "Lagre endringer";
-App::$strings["Import Webpage Elements"] = "";
-App::$strings["Import selected"] = "";
-App::$strings["Export Webpage Elements"] = "";
-App::$strings["Export selected"] = "";
-App::$strings["Actions"] = "Handlinger";
-App::$strings["Page Link"] = "Sidelenke";
-App::$strings["Page Title"] = "Sidetittel";
-App::$strings["Invalid file type."] = "";
-App::$strings["Error opening zip file"] = "";
-App::$strings["Invalid folder path."] = "";
-App::$strings["No webpage elements detected."] = "";
-App::$strings["Import complete."] = "";
-App::$strings["Active"] = "Aktiv";
-App::$strings["Blocked"] = "Blokkert";
-App::$strings["Ignored"] = "Ignorert";
-App::$strings["Hidden"] = "Skjult";
-App::$strings["Archived/Unreachable"] = "Arkivert/utilgjengelig";
-App::$strings["Active Connections"] = "Aktive forbindelser";
-App::$strings["Show active connections"] = "";
-App::$strings["New Connections"] = "Nye forbindelser";
-App::$strings["Show pending (new) connections"] = "Vis ventende (nye) forbindelser";
-App::$strings["Only show blocked connections"] = "Vis bare forbindelser som er blokkert";
-App::$strings["Only show ignored connections"] = "Vis bare ignorerte forbindelser";
-App::$strings["Only show archived/unreachable connections"] = "";
-App::$strings["Only show hidden connections"] = "Vis bare skjulte forbindelser";
-App::$strings["Show all connections"] = "Vis alle forbindelser";
-App::$strings["Pending approval"] = "Venter på godkjenning";
-App::$strings["Archived"] = "Arkivert";
-App::$strings["Not connected at this location"] = "";
-App::$strings["%1\$s [%2\$s]"] = "%1\$s [%2\$s]";
-App::$strings["Edit connection"] = "Endre forbindelse";
-App::$strings["Delete connection"] = "Slett forbindelse";
-App::$strings["Channel address"] = "Kanaladresse";
-App::$strings["Call"] = "";
-App::$strings["Status"] = "Status";
-App::$strings["Connected"] = "Forbundet";
-App::$strings["Approve connection"] = "Godkjenn forbindelse";
-App::$strings["Ignore connection"] = "Ignorer forbindelse";
-App::$strings["Recent activity"] = "Nylig aktivitet";
-App::$strings["Connect at this location"] = "";
-App::$strings["You have %1$.0f of %2$.0f allowed connections."] = "Du har %1$.0f av %2$.0f tillate forbindelser.";
-App::$strings["Search your connections"] = "Søk blant dine forbindelser";
-App::$strings["Contact search"] = "";
-App::$strings["This is a group/forum channel"] = "";
-App::$strings["Continue"] = "Fortsett";
-App::$strings["Premium Channel Setup"] = "Premiumkanal-oppsett";
-App::$strings["Enable premium channel connection restrictions"] = "Slå på restriksjoner for forbindelse med premiumkanal";
-App::$strings["Please enter your restrictions or conditions, such as paypal receipt, usage guidelines, etc."] = "Vennligst skriv dine restriksjoner og betingelser, slik som PayPal-kvittering, retningslinjer for bruk, og så videre.";
-App::$strings["This channel may require additional steps or acknowledgement of the following conditions prior to connecting:"] = "Denne kanalen kan kreve ytterligere steg og bekreftelse av følgende betingelser før tilkobling:";
-App::$strings["Potential connections will then see the following text before proceeding:"] = "Potensielle forbindelser vil da se følgende tekst før de går videre:";
-App::$strings["By continuing, I certify that I have complied with any instructions provided on this page."] = "Ved å fortsette bekrefter jeg at jeg har oppfylt alle instruksjoner gitt på denne siden.";
-App::$strings["(No specific instructions have been provided by the channel owner.)"] = "(Ingen spesifikke instruksjoner er gitt av kanaleieren.)";
-App::$strings["Restricted or Premium Channel"] = "Begrenset kanal eller premiumkanal";
-App::$strings["Affinity Tool settings updated."] = "";
-App::$strings["The numbers below represent the minimum and maximum slider default positions for your network/stream page as a percentage."] = "";
-App::$strings["Default maximum affinity level"] = "";
-App::$strings["0-99 default 99"] = "Standard";
-App::$strings["Default minimum affinity level"] = "";
-App::$strings["0-99 - default 0"] = "Standard";
-App::$strings["Persistent affinity levels"] = "";
-App::$strings["If disabled the max and min levels will be reset to default after page reload"] = "";
-App::$strings["Affinity Tool Settings"] = "";
-App::$strings["About this site"] = "Om dette nettstedet ";
-App::$strings["Site Name"] = "Nettstedets navn";
-App::$strings["Site Information"] = "Nettstedsinformasjon";
-App::$strings["Administrator"] = "Administrator";
-App::$strings["Software and Project information"] = "Program- og prosjektinformasjon";
-App::$strings["This site is powered by \$Projectname"] = "Dette nettstedet drives av \$Projectname";
-App::$strings["Federated and decentralised networking and identity services provided by"] = "Fødererte og desentraliserte nettverks- og identitetstjenester via Zot";
-App::$strings["Additional federated transport protocols:"] = "Øvrige fødererte transportprotokoller:";
-App::$strings["Version %s"] = "Versjon %s";
-App::$strings["Project homepage"] = "Prosjektets hjemmeside";
-App::$strings["Developer homepage"] = "Utviklers hjemmeside";
-App::$strings["Active addons"] = "Aktive tillegg";
-App::$strings["Blocked sites"] = "Byggeklossens tittel";
-App::$strings["Item sync completed!"] = "";
-App::$strings["Import host does not seem to be online or compatible"] = "";
-App::$strings["Item sync completed but no items were found!"] = "";
-App::$strings["File sync completed!"] = "";
-App::$strings["File sync completed but no files were found!"] = "";
-App::$strings["Channel clone status"] = "";
-App::$strings["Item sync status"] = "";
-App::$strings["File sync status"] = "";
-App::$strings["Channel cloning completed!"] = "";
-App::$strings["Resume"] = "";
-App::$strings["Only resume if sync stalled!"] = "";
-App::$strings["This site is not a directory server"] = "Dette nettstedet er ikke en katalogtjener";
-App::$strings["This directory server requires an access token"] = "Denne katalogtjeneren krever en tilgangsnøkkel (access token)";
-App::$strings["Deprecated!"] = "";
-App::$strings["File not found."] = "Filen ble ikke funnet.";
-App::$strings["Permission Denied."] = "Tillatelse avvist.";
-App::$strings["Edit file permissions"] = "Endre filtillatelser";
-App::$strings["Set/edit permissions"] = "Angi/endre tillatelser";
-App::$strings["Include all files and sub folders"] = "Inkluder alle filer og undermapper";
-App::$strings["Return to file list"] = "Gå tilbake til filoversikten";
-App::$strings["Share this file"] = "Del denne filen";
-App::$strings["Show URL to this file"] = "Vis URLen til denne filen";
-App::$strings["Layout not found"] = "Layouten ble ikke funnet.";
-App::$strings["This template does not support pdledi_gui (no content regions defined)"] = "";
-App::$strings["Main page content"] = "";
-App::$strings["The main page content can not be edited!"] = "";
-App::$strings["Layout updated."] = "Layout er oppdatert.";
-App::$strings["Edit System Page Description"] = "Endre beskrivelsen av systemsiden";
-App::$strings["(modified)"] = "(endret)";
-App::$strings["Layout not found."] = "Layouten ble ikke funnet.";
-App::$strings["Module Name:"] = "Modulnavn:";
-App::$strings["Layout Help"] = "Layout-hjelp";
-App::$strings["Edit another layout"] = "";
-App::$strings["System layout"] = "";
-App::$strings["Item approved"] = "Konto godkjent.";
-App::$strings["Account removals are not allowed within 48 hours of changing the account password."] = "Sletting av kontoer er ikke tillatt innen 48 timer etter endring av kontopassordet.";
-App::$strings["Remove This Account"] = "Slett denne kontoen";
-App::$strings["WARNING: "] = "ADVARSEL: ";
-App::$strings["This account and all its channels will be completely removed from the network. "] = "Denne kontoen og alle dens kanaler vil bli fullstendig fjernet fra nettverket. ";
-App::$strings["This action is permanent and can not be undone!"] = "Denne handlingen er permanent og kan ikke angres!";
-App::$strings["Please enter your password for verification:"] = "Vennligst skriv ditt passord for å få bekreftelse:";
-App::$strings["Remove Account"] = "Slett konto";
-App::$strings["Please login."] = "Vennligst logg inn.";
+App::$strings["Public Hubs"] = "Offentlige huber";
+App::$strings["The listed hubs allow public registration for the \$Projectname network. All hubs in the network are interlinked so membership on any of them conveys membership in the network as a whole. Some hubs may require subscription or provide tiered service plans. The hub itself <strong>may</strong> provide additional details."] = "Nettstedene på listen tillater offentlig registrering i \$Projectname-nettverket. Alle nettsteder i nettverket er forbundet så medlemskap på enhver av dem formidler medlemskap i nettverket som helhet. Noen nettsteder kan kreve abonnement eller tilby lagdelte tjenesteavtaler. Nettstedene selv <strong>kan</strong> gi tilleggsopplysninger.";
+App::$strings["Hub URL"] = "Nettstedets URL";
+App::$strings["Access Type"] = "Tilgangstype";
+App::$strings["Registration Policy"] = "Retningslinjer for registrering";
+App::$strings["Stats"] = "Statistikk";
+App::$strings["Software"] = "Programvare";
+App::$strings["Ratings"] = "Vurderinger";
+App::$strings["Rate"] = "Vurder";
+App::$strings["Channel name changes are not allowed within 48 hours of changing the account password."] = "";
+App::$strings["Change channel nickname/address"] = "";
+App::$strings["Any/all connections on other networks will be lost!"] = "";
+App::$strings["New channel address"] = "";
+App::$strings["Rename Channel"] = "";
+App::$strings["Unable to update menu."] = "Ikke i stand til å oppdatere meny.";
+App::$strings["Unable to create menu."] = "Ikke i stand til å lage meny.";
+App::$strings["Menu Name"] = "Menynavn";
+App::$strings["Unique name (not visible on webpage) - required"] = "Unikt navn (ikke synlig på websiden) - påkrevet";
+App::$strings["Menu Title"] = "Menytittel";
+App::$strings["Visible on webpage - leave empty for no title"] = "Synlig på websiden - la stå tomt for ingen tittel";
+App::$strings["Allow Bookmarks"] = "Tillat bokmerker";
+App::$strings["Menu may be used to store saved bookmarks"] = "Menyen kan brukes til å lagre lagrede bokmerker";
+App::$strings["Submit and proceed"] = "Send inn og fortsett";
+App::$strings["Bookmarks allowed"] = "Bokmerker tillatt";
+App::$strings["Delete this menu"] = "Slett denne menyen";
+App::$strings["Edit menu contents"] = "Endre menyinnholdet";
+App::$strings["Edit this menu"] = "Endre denne menyen";
+App::$strings["Menu could not be deleted."] = "Menyen kunne ikke bli slettet.";
+App::$strings["Menu not found."] = "Menyen ble ikke funnet.";
+App::$strings["Edit Menu"] = "Endre meny";
+App::$strings["Add or remove entries to this menu"] = "Legg til eller fjern punkter i denne menyen";
+App::$strings["Menu name"] = "Menynavn";
+App::$strings["Must be unique, only seen by you"] = "Må være unik, ses bare av deg";
+App::$strings["Menu title"] = "Menytittel";
+App::$strings["Menu title as seen by others"] = "Menytittelen andre ser";
+App::$strings["Allow bookmarks"] = "Tillat bokmerker";
+App::$strings["Not found."] = "Ikke funnet.";
+App::$strings["Account not found"] = "Kontoen ble ikke funnet";
+App::$strings["Account '%s' blocked"] = "Kontoen '%s' blokkert";
+App::$strings["Account '%s' unblocked"] = "Kontoen '%s' er ikke blokkert lenger";
+App::$strings["Unverified"] = "";
+App::$strings["Expired"] = "";
+App::$strings["Administration"] = "Administrasjon";
+App::$strings["Show verified registrations"] = "";
+App::$strings["Show all registrations"] = "";
+App::$strings["Select toggle"] = "";
+App::$strings["Deny selected"] = "";
+App::$strings["Approve selected"] = "";
+App::$strings["All registrations"] = "";
+App::$strings["Verified registrations waiting for approval"] = "";
+App::$strings["Request date"] = "Dato for forespørsel";
+App::$strings["Requests"] = "";
+App::$strings["No registrations available"] = "";
+App::$strings["No verified registrations available"] = "";
+App::$strings["Verified"] = "";
+App::$strings["Not yet verified"] = "";
+App::$strings["ID"] = "ID";
+App::$strings["All channels"] = "";
+App::$strings["Register date"] = "Registreringsdato";
+App::$strings["Last login"] = "Siste innlogging";
+App::$strings["Expires"] = "Utløper";
+App::$strings["Service class"] = "";
+App::$strings["Selected accounts will be deleted!\\n\\nEverything these accounts had posted on this site will be permanently deleted!\\n\\nAre you sure?"] = "Valgte kontoer vil bli slettet!\\n\\nAlt disse kontoene har lagt inn på dette nettstedet vil bli slettet permanent!\\n\\nEr du sikker på at du vil slette disse valgte kontoene?";
+App::$strings["The account {0} will be deleted!\\n\\nEverything this account has posted on this site will be permanently deleted!\\n\\nAre you sure?"] = "Kontoen {0} vl bli slettet!\\n\\nAlt denne kontoen har lagt inn på dette nettstedet vil bli slettet permanent!\\n\\nEr du sikker på at du vil slette denne kontoen?";
+App::$strings["Message"] = "Melding";
+App::$strings["%s account blocked/unblocked"] = array(
+ 0 => "%s konto blokkert/ikke blokkert lenger",
+ 1 => "%s kontoer blokkert/ikke blokkert lenger",
+);
+App::$strings["%s account deleted"] = array(
+ 0 => "%s konto slettet",
+ 1 => "%s kontoer slettet",
+);
+App::$strings["Max queueworker threads"] = "";
+App::$strings["Minimum 4, default 4"] = "";
+App::$strings["Assume workers dead after"] = "";
+App::$strings["Minimum 120, default 300 seconds"] = "";
+App::$strings["Pause before starting next task"] = "";
+App::$strings["Minimum 100, default 100 microseconds"] = "";
+App::$strings["Automatically adjust pause before starting next task"] = "";
+App::$strings["Queueworker Settings"] = "";
+App::$strings["Theme settings updated."] = "Temainnstillinger er oppdatert.";
+App::$strings["No themes found."] = "Ingen temaer er funnet.";
+App::$strings["Disable"] = "Skru av";
+App::$strings["Enable"] = "Skru på";
+App::$strings["Screenshot"] = "Skjermbilde";
+App::$strings["Toggle"] = "Skru av og på";
+App::$strings["Author: "] = "Forfatter: ";
+App::$strings["Maintainer: "] = "Vedlikeholder: ";
+App::$strings["[Experimental]"] = "[Eksperimentelt]";
+App::$strings["[Unsupported]"] = "[Ingen støtte]";
+App::$strings["Plugin %s disabled."] = "Tilleggsfunksjonen %s er avskrudd.";
+App::$strings["Plugin %s enabled."] = "Tilleggsfunksjonen %s er påskrudd.";
+App::$strings["Minimum project version: "] = "Minimum prosjektversjon: ";
+App::$strings["Maximum project version: "] = "Maksimum prosjektversjon: ";
+App::$strings["Minimum PHP version: "] = "Minimum PHP-versjon: ";
+App::$strings["Compatible Server Roles: "] = "";
+App::$strings["Requires: "] = "Krever: ";
+App::$strings["Disabled - version incompatibility"] = "Skrudd av - versjonsinkompatibilitet";
+App::$strings["Lock feature %s"] = "Lås funksjon %s";
+App::$strings["Manage Additional Features"] = "Håndter tilleggsfunksjoner";
+App::$strings["New Profile Field"] = "Nytt profilfelt";
+App::$strings["Field nickname"] = "Feltets kallenavn";
+App::$strings["System name of field"] = "Systemnavnet til feltet";
+App::$strings["Input type"] = "Inndata-type";
+App::$strings["Field Name"] = "Feltnavn";
+App::$strings["Label on profile pages"] = "Merkelapp på profilsider";
+App::$strings["Help text"] = "Hjelpetekst";
+App::$strings["Additional info (optional)"] = "Tilleggsinformasjon (valgfritt)";
+App::$strings["Field definition not found"] = "Feltdefinisjonen ble ikke funnet";
+App::$strings["Edit Profile Field"] = "Endre profilfelt";
+App::$strings["Basic Profile Fields"] = "Grunnleggende profilfelter";
+App::$strings["Advanced Profile Fields"] = "Utvidede profilfelter";
+App::$strings["(In addition to basic fields)"] = "(I tillegg til grunnleggende felt)";
+App::$strings["All available fields"] = "Alle tilgjengelige felt";
+App::$strings["Custom Fields"] = "";
+App::$strings["Create Custom Field"] = "Legg til egendefinert felt";
+App::$strings["Channel not found"] = "Kanalen ble ikke funnet";
+App::$strings["%s channel censored/uncensored"] = array(
+ 0 => "%s kanal er sensurert/ikke sensurert lenger",
+ 1 => "%s kanaler er sensurert/ikke sensurert lenger",
+);
+App::$strings["%s channel code allowed/disallowed"] = array(
+ 0 => "%s kanal med kode tillatt/ikke tillatt",
+ 1 => "%s kanaler med kode tillatt/ikke tillatt",
+);
+App::$strings["%s channel deleted"] = array(
+ 0 => "%s kanal slettet",
+ 1 => "%s kanaler slettet",
+);
+App::$strings["Channel '%s' deleted"] = "Kanalen '%s' er slettet";
+App::$strings["Channel '%s' censored"] = "Kanalen '%s' er sensurert";
+App::$strings["Channel '%s' uncensored"] = "Kanalen '%s' er ikke sensurert lenger";
+App::$strings["Channel '%s' code allowed"] = "Kanal '%s' kode tillatt";
+App::$strings["Channel '%s' code disallowed"] = "Kanal '%s' kode ikke tillatt";
+App::$strings["select all"] = "velg alle";
+App::$strings["Censor"] = "Sensurer";
+App::$strings["Uncensor"] = "Ikke sensurer lenger";
+App::$strings["Allow Code"] = "Tillat kode";
+App::$strings["Disallow Code"] = "Ikke tillat kode";
+App::$strings["UID"] = "UID";
+App::$strings["Selected channels will be deleted!\\n\\nEverything that was posted in these channels on this site will be permanently deleted!\\n\\nAre you sure?"] = "Valgte kanaler vil bli slettet!\\n\\nAlt innhold som er lagt inn i disse kanalene på dette nettstedet vil bli slettet for alltid!\\n\\nEr du sikker på at du vil slette disse kanalene med alt innhold?";
+App::$strings["The channel {0} will be deleted!\\n\\nEverything that was posted in this channel on this site will be permanently deleted!\\n\\nAre you sure?"] = "Kanalen {0} vil bli slettet!\\n\\nAlt innhold som er lagt inn i denne kanalen på dettet nettstedet vil bli slettet for alltid!\\n\\nEr du sikker på at du vil slette denne kanalen med alt innhold?";
+App::$strings["Password changed for account %d."] = "";
+App::$strings["Account settings updated."] = "";
+App::$strings["Account Edit"] = "";
+App::$strings["New Password"] = "";
+App::$strings["New Password again"] = "";
+App::$strings["Account language (for emails)"] = "";
+App::$strings["Update has been marked successful"] = "Oppdateringen har blitt merket som en suksess";
+App::$strings["Verification of update %s failed. Check system logs."] = "";
+App::$strings["Update %s was successfully applied."] = "Oppdatering %s ble gjennomført med suksess.";
+App::$strings["Verifying update %s did not return a status. Unknown if it succeeded."] = "";
+App::$strings["Update %s does not contain a verification function."] = "";
+App::$strings["Update function %s could not be found."] = "Oppdatering av funksjon %s kunne ikke finnes.";
+App::$strings["Executing update procedure %s failed. Check system logs."] = "";
+App::$strings["Update %s did not return a status. It cannot be determined if it was successful."] = "";
+App::$strings["Failed Updates"] = "Mislykkede oppdateringer";
+App::$strings["Mark success (if update was manually applied)"] = "Marker suksess (hvis oppdateringen ble gjennomført manuelt)";
+App::$strings["Attempt to verify this update if a verification procedure exists"] = "";
+App::$strings["Attempt to execute this update step automatically"] = "Prøv å gjennomføre dette oppdateringstrinnet automatisk";
+App::$strings["No failed updates."] = "Ingen mislykkede oppdateringer.";
+App::$strings["Log settings updated."] = "Logginnstillinger er oppdatert.";
+App::$strings["Clear"] = "Tøm";
+App::$strings["Debugging"] = "Feilsøking";
+App::$strings["Log file"] = "Loggfil";
+App::$strings["Must be writable by web server. Relative to your top-level webserver directory."] = "";
+App::$strings["Log level"] = "Loggnivå";
+App::$strings["Queue Statistics"] = "Køstatistikk";
+App::$strings["Total Entries"] = "Totalt antall oppføringer";
+App::$strings["Destination URL"] = "Mål-URL";
+App::$strings["Mark hub permanently offline"] = "Merk hub som permanent offline";
+App::$strings["Retry delivery to this hub"] = "";
+App::$strings["Empty queue for this hub"] = "Tøm køen for denne hubben";
+App::$strings["Last known contact"] = "Siste kjente kontakt";
App::$strings["Invalid input"] = "";
App::$strings["Errors"] = "";
App::$strings["Site settings updated."] = "Nettstedsinnstillinger er oppdatert.";
@@ -2055,22 +2528,24 @@ App::$strings["My site has free access only"] = "Mitt nettsted har kun gratis ti
App::$strings["My site offers free accounts with optional paid upgrades"] = "Mitt nettsted tilbyr gratis konto med valgfri oppgradering til betalt tjeneste";
App::$strings["Default permission role for new accounts"] = "";
App::$strings["This role will be used for the first channel created after registration."] = "";
+App::$strings["Minute(s)"] = "";
+App::$strings["Hour(s)"] = "";
+App::$strings["Day(s)"] = "";
App::$strings["Week(s)"] = "";
App::$strings["Month(s)"] = "";
App::$strings["Year(s)"] = "";
App::$strings["Register verification delay"] = "";
App::$strings["Time to wait before a registration can be verified"] = "";
+App::$strings["duration up from now"] = "";
App::$strings["Register verification expiration time"] = "";
App::$strings["Time before an unverified registration will expire"] = "";
-App::$strings["Administration"] = "Administrasjon";
-App::$strings["Site"] = "Nettsted";
App::$strings["File upload"] = "Last opp fil";
App::$strings["Policies"] = "Retningslinjer";
-App::$strings["Site name"] = "Nettstedets navn";
App::$strings["Banner/Logo"] = "Banner/Logo";
App::$strings["Unfiltered HTML/CSS/JS is allowed"] = "";
App::$strings["Administrator Information"] = "Administratorinformasjon";
App::$strings["Contact information for site administrators. Displayed on siteinfo page. BBCode can be used here"] = "Kontaktinformasjon til nettstedsadministratorer. Vises på siteinfo-siden. BBCode kan brukes her";
+App::$strings["Site Information"] = "Nettstedsinformasjon";
App::$strings["Publicly visible description of this site. Displayed on siteinfo page. BBCode can be used here"] = "";
App::$strings["System theme"] = "Systemtema";
App::$strings["Default system theme - may be over-ridden by user profiles - <a href='#' id='cnftheme'>change theme settings</a>"] = "Standard systemtema - kan overstyres av brukerprofiler - <a href='#' id='cnftheme'>endre temainnstillinger</a>";
@@ -2112,10 +2587,10 @@ App::$strings["Present the site homepage in a frame at the original location ins
App::$strings["Allowed friend domains"] = "Tillatte vennedomener";
App::$strings["Comma separated list of domains which are allowed to establish friendships with this site. Wildcards are accepted. Empty to allow any domains"] = "Kommaseparert liste over domener som har lov til å etablere vennskap med dette nettstedet. Jokertegn er akseptert. Tøm for å tillate alle domener";
App::$strings["Force publish"] = "Tving publisering";
-App::$strings["Check to force all profiles on this site to be listed in the site directory"] = "Kryss av for å tvinge alle profiler på dette nettstedet til å bli oppført i nettstedet sin katalog.";
+App::$strings["Check to force all profiles on this site to be listed in the site directory"] = "Kryss av for å tvinge alle profiler på dette nettstedet til å bli oppført i nettstedet sin katalog";
App::$strings["Enable public stream"] = "";
-App::$strings["Enable the public stream. Warning: this content is unmoderated"] = "Importer og gi tilgang til offentlig innhold trukket inn fra andre nettsteder. Advarsel: dette innholdet er ikke moderert.";
-App::$strings["Site only public stream"] = "Mitt nettsted er ikke en offentlig tjeneste";
+App::$strings["Enable the public stream. Warning: this content is unmoderated"] = "Importer og gi tilgang til offentlig innhold trukket inn fra andre nettsteder. Advarsel: dette innholdet er ikke moderert";
+App::$strings["Site only public stream"] = "Offentlig strøm kun fra nettstedet";
App::$strings["Restrict the public stream to content originating at this site"] = "";
App::$strings["Allow anybody on the internet to access the public streams"] = "";
App::$strings["Disable to require authentication before viewing"] = "";
@@ -2156,75 +2631,10 @@ App::$strings["Default: profiles"] = "";
App::$strings["Optional: site location"] = "";
App::$strings["Region or country"] = "";
App::$strings["Invalid 24h time value (hhmm/hmm)"] = "";
-App::$strings["Update has been marked successful"] = "Oppdateringen har blitt merket som en suksess";
-App::$strings["Verification of update %s failed. Check system logs."] = "";
-App::$strings["Update %s was successfully applied."] = "Oppdatering %s ble gjennomført med suksess.";
-App::$strings["Verifying update %s did not return a status. Unknown if it succeeded."] = "";
-App::$strings["Update %s does not contain a verification function."] = "";
-App::$strings["Update function %s could not be found."] = "Oppdatering av funksjon %s kunne ikke finnes.";
-App::$strings["Executing update procedure %s failed. Check system logs."] = "";
-App::$strings["Update %s did not return a status. It cannot be determined if it was successful."] = "";
-App::$strings["Failed Updates"] = "Mislykkede oppdateringer";
-App::$strings["Mark success (if update was manually applied)"] = "Marker suksess (hvis oppdateringen ble gjennomført manuelt)";
-App::$strings["Attempt to verify this update if a verification procedure exists"] = "";
-App::$strings["Attempt to execute this update step automatically"] = "Prøv å gjennomføre dette oppdateringstrinnet automatisk";
-App::$strings["No failed updates."] = "Ingen mislykkede oppdateringer.";
-App::$strings["%s account blocked/unblocked"] = array(
- 0 => "%s konto blokkert/ikke blokkert lenger",
- 1 => "%s kontoer blokkert/ikke blokkert lenger",
-);
-App::$strings["%s account deleted"] = array(
- 0 => "%s konto slettet",
- 1 => "%s kontoer slettet",
-);
-App::$strings["Account not found"] = "Kontoen ble ikke funnet";
-App::$strings["Account '%s' blocked"] = "Kontoen '%s' blokkert";
-App::$strings["Account '%s' unblocked"] = "Kontoen '%s' er ikke blokkert lenger";
-App::$strings["Unverified"] = "";
-App::$strings["Expired"] = "";
-App::$strings["Accounts"] = "Kontoer";
-App::$strings["Show verified registrations"] = "";
-App::$strings["Show all registrations"] = "";
-App::$strings["Select toggle"] = "";
-App::$strings["Deny selected"] = "";
-App::$strings["Approve selected"] = "";
-App::$strings["All registrations"] = "";
-App::$strings["Verified registrations waiting for approval"] = "";
-App::$strings["Request date"] = "Dato for forespørsel";
-App::$strings["Requests"] = "";
-App::$strings["No registrations available"] = "";
-App::$strings["No verified registrations available"] = "";
-App::$strings["Deny"] = "Avslå";
-App::$strings["Verified"] = "";
-App::$strings["Not yet verified"] = "";
-App::$strings["ID"] = "ID";
-App::$strings["All channels"] = "";
-App::$strings["Register date"] = "Registreringsdato";
-App::$strings["Last login"] = "Siste innlogging";
-App::$strings["Expires"] = "Utløper";
-App::$strings["Service class"] = "";
-App::$strings["Selected accounts will be deleted!\\n\\nEverything these accounts had posted on this site will be permanently deleted!\\n\\nAre you sure?"] = "Valgte kontoer vil bli slettet!\\n\\nAlt disse kontoene har lagt inn på dette nettstedet vil bli slettet permanent!\\n\\nEr du sikker på at du vil slette disse valgte kontoene?";
-App::$strings["The account {0} will be deleted!\\n\\nEverything this account has posted on this site will be permanently deleted!\\n\\nAre you sure?"] = "Kontoen {0} vl bli slettet!\\n\\nAlt denne kontoen har lagt inn på dette nettstedet vil bli slettet permanent!\\n\\nEr du sikker på at du vil slette denne kontoen?";
-App::$strings["Message"] = "Melding";
-App::$strings["Max queueworker threads"] = "";
-App::$strings["Minimum 4, default 4"] = "";
-App::$strings["Assume workers dead after"] = "";
-App::$strings["Minimum 120, default 300 seconds"] = "";
-App::$strings["Pause before starting next task"] = "";
-App::$strings["Minimum 100, default 100 microseconds"] = "";
-App::$strings["Automatically adjust pause before starting next task"] = "";
-App::$strings["Queueworker Settings"] = "";
-App::$strings["Password changed for account %d."] = "";
-App::$strings["Account settings updated."] = "";
-App::$strings["Account Edit"] = "";
-App::$strings["New Password"] = "";
-App::$strings["New Password again"] = "";
-App::$strings["Account language (for emails)"] = "";
App::$strings["By default, unfiltered HTML is allowed in embedded media. This is inherently insecure."] = "Ufiltrert HTML er i utgangspunktet tillatt i innebygde media. Dette er en sikkerhetsrisiko.";
App::$strings["The recommended setting is to only allow unfiltered HTML from the following sites:"] = "Det anbefales at man kun tillater ufiltrert HTML fra følgende nettsteder:";
App::$strings["https://youtube.com/<br />https://www.youtube.com/<br />https://youtu.be/<br />https://vimeo.com/<br />https://soundcloud.com/<br />"] = "https://youtube.com/<br />https://www.youtube.com/<br />https://youtu.be/<br />https://vimeo.com/<br />https://soundcloud.com/<br />";
App::$strings["All other embedded content will be filtered, <strong>unless</strong> embedded content from that site is explicitly blocked."] = "Alt annet innebygget innhold vil bli filtrert, <strong>med mindre</strong> innebygget innhold fra den aktuelle siden eksplisitt er blokkert.";
-App::$strings["Security"] = "Sikkerhet";
App::$strings["Block public"] = "Blokker offentlig tilgang";
App::$strings["Check to block public access to all otherwise public personal pages on this site unless you are currently authenticated."] = "Kryss av for å blokkere tilgang til alle personlige sider som ellers ville vært offentlig tilgjengelige på dette nettstedet med mindre du er logget inn.";
App::$strings["Provide a cloud root directory"] = "";
@@ -2251,117 +2661,171 @@ App::$strings["WARNING: SVG images may contain malicious code."] = "";
App::$strings["Allow embedded (inline) PDF files"] = "";
App::$strings["Additional trusted directory server URLs"] = "Standard katalogtjener";
App::$strings["Accept directory flags (spam, nsfw) from those servers. One per line like https://example.tld"] = "";
-App::$strings["Channel not found"] = "Kanalen ble ikke funnet";
-App::$strings["%s channel censored/uncensored"] = array(
- 0 => "%s kanal er sensurert/ikke sensurert lenger",
- 1 => "%s kanaler er sensurert/ikke sensurert lenger",
-);
-App::$strings["%s channel code allowed/disallowed"] = array(
- 0 => "%s kanal med kode tillatt/ikke tillatt",
- 1 => "%s kanaler med kode tillatt/ikke tillatt",
-);
-App::$strings["%s channel deleted"] = array(
- 0 => "%s kanal slettet",
- 1 => "%s kanaler slettet",
-);
-App::$strings["Channel '%s' deleted"] = "Kanalen '%s' er slettet";
-App::$strings["Channel '%s' censored"] = "Kanalen '%s' er sensurert";
-App::$strings["Channel '%s' uncensored"] = "Kanalen '%s' er ikke sensurert lenger";
-App::$strings["Channel '%s' code allowed"] = "Kanal '%s' kode tillatt";
-App::$strings["Channel '%s' code disallowed"] = "Kanal '%s' kode ikke tillatt";
-App::$strings["select all"] = "velg alle";
-App::$strings["Censor"] = "Sensurer";
-App::$strings["Uncensor"] = "Ikke sensurer lenger";
-App::$strings["Allow Code"] = "Tillat kode";
-App::$strings["Disallow Code"] = "Ikke tillat kode";
-App::$strings["UID"] = "UID";
-App::$strings["Selected channels will be deleted!\\n\\nEverything that was posted in these channels on this site will be permanently deleted!\\n\\nAre you sure?"] = "Valgte kanaler vil bli slettet!\\n\\nAlt innhold som er lagt inn i disse kanalene på dette nettstedet vil bli slettet for alltid!\\n\\nEr du sikker på at du vil slette disse kanalene med alt innhold?";
-App::$strings["The channel {0} will be deleted!\\n\\nEverything that was posted in this channel on this site will be permanently deleted!\\n\\nAre you sure?"] = "Kanalen {0} vil bli slettet!\\n\\nAlt innhold som er lagt inn i denne kanalen på dettet nettstedet vil bli slettet for alltid!\\n\\nEr du sikker på at du vil slette denne kanalen med alt innhold?";
-App::$strings["Lock feature %s"] = "Lås funksjon %s";
-App::$strings["Manage Additional Features"] = "Håndter tilleggsfunksjoner";
-App::$strings["Plugin %s disabled."] = "Tilleggsfunksjonen %s er avskrudd.";
-App::$strings["Plugin %s enabled."] = "Tilleggsfunksjonen %s er påskrudd.";
-App::$strings["Disable"] = "Skru av";
-App::$strings["Enable"] = "Skru på";
-App::$strings["Addons"] = "Tillegg";
-App::$strings["Toggle"] = "Skru av og på";
-App::$strings["Author: "] = "Forfatter: ";
-App::$strings["Maintainer: "] = "Vedlikeholder: ";
-App::$strings["Minimum project version: "] = "Minimum prosjektversjon: ";
-App::$strings["Maximum project version: "] = "Maksimum prosjektversjon: ";
-App::$strings["Minimum PHP version: "] = "Minimum PHP-versjon: ";
-App::$strings["Compatible Server Roles: "] = "";
-App::$strings["Requires: "] = "Krever: ";
-App::$strings["Disabled - version incompatibility"] = "Skrudd av - versjonsinkompatibilitet";
-App::$strings["Enter the public git repository URL of the addon repo."] = "";
-App::$strings["Addon repo git URL"] = "";
-App::$strings["Custom repo name"] = "";
-App::$strings["(optional)"] = "Valgfritt";
-App::$strings["Download Addon Repo"] = "";
-App::$strings["Install new repo"] = "";
-App::$strings["Manage Repos"] = "";
-App::$strings["Installed Addon Repositories"] = "";
-App::$strings["Install a New Addon Repository"] = "";
-App::$strings["Switch branch"] = "";
-App::$strings["Queue Statistics"] = "Køstatistikk";
-App::$strings["Total Entries"] = "Totalt antall oppføringer";
-App::$strings["Priority"] = "Prioritet";
-App::$strings["Destination URL"] = "Mål-URL";
-App::$strings["Mark hub permanently offline"] = "Merk hub som permanent offline";
-App::$strings["Retry delivery to this hub"] = "";
-App::$strings["Empty queue for this hub"] = "Tøm køen for denne hubben";
-App::$strings["Last known contact"] = "Siste kjente kontakt";
-App::$strings["New Profile Field"] = "Nytt profilfelt";
-App::$strings["Field nickname"] = "Feltets kallenavn";
-App::$strings["System name of field"] = "Systemnavnet til feltet";
-App::$strings["Input type"] = "Inndata-type";
-App::$strings["Field Name"] = "Feltnavn";
-App::$strings["Label on profile pages"] = "Merkelapp på profilsider";
-App::$strings["Help text"] = "Hjelpetekst";
-App::$strings["Additional info (optional)"] = "Tilleggsinformasjon (valgfritt)";
-App::$strings["Field definition not found"] = "Feltdefinisjonen ble ikke funnet";
-App::$strings["Edit Profile Field"] = "Endre profilfelt";
-App::$strings["Profile Fields"] = "Profilfelter";
-App::$strings["Basic Profile Fields"] = "Grunnleggende profilfelter";
-App::$strings["Advanced Profile Fields"] = "Utvidede profilfelter";
-App::$strings["(In addition to basic fields)"] = "(I tillegg til grunnleggende felt)";
-App::$strings["All available fields"] = "Alle tilgjengelige felt";
-App::$strings["Custom Fields"] = "";
-App::$strings["Create Custom Field"] = "Legg til egendefinert felt";
-App::$strings["Theme settings updated."] = "Temainnstillinger er oppdatert.";
-App::$strings["No themes found."] = "Ingen temaer er funnet.";
-App::$strings["Screenshot"] = "Skjermbilde";
-App::$strings["Themes"] = "Utseende";
-App::$strings["[Experimental]"] = "[Eksperimentelt]";
-App::$strings["[Unsupported]"] = "[Ingen støtte]";
-App::$strings["Log settings updated."] = "Logginnstillinger er oppdatert.";
-App::$strings["Logs"] = "Logger";
-App::$strings["Clear"] = "Tøm";
-App::$strings["Debugging"] = "Feilsøking";
-App::$strings["Log file"] = "Loggfil";
-App::$strings["Must be writable by web server. Relative to your top-level webserver directory."] = "";
-App::$strings["Log level"] = "Loggnivå";
-App::$strings["Can not copy folder into itself."] = "";
-App::$strings["Can not move folder \"%s\" into itself."] = "";
-App::$strings["Unable to locate original post."] = "Ikke i stand til å finne opprinnelig innlegg.";
-App::$strings["status"] = "status";
+App::$strings["You have created %1$.0f of %2$.0f allowed channels."] = "Du har laget %1$.0f av %2$.0f tillatte kanaler.";
+App::$strings["Create a new channel"] = "Lag en ny kanal";
+App::$strings["Current Channel"] = "Gjeldende kanal";
+App::$strings["Switch to one of your channels by selecting it."] = "Bytt til en av dine kanaler ved å velge den.";
+App::$strings["Default Channel"] = "Standardkanal";
+App::$strings["Make Default"] = "Gjør til standard";
+App::$strings["%d new introductions"] = "%d nye introduksjoner";
+App::$strings["Delegated Channel"] = "Delegert kanal";
+App::$strings["Token verification failed."] = "";
+App::$strings["Email verification resent"] = "";
+App::$strings["Unable to resend email verification message."] = "";
+App::$strings["Invalid message"] = "Ugyldig melding";
+App::$strings["no results"] = "ingen resultater";
+App::$strings["channel sync processed"] = "kanalsynkronisering er behandlet";
+App::$strings["queued"] = "lagt i kø";
+App::$strings["posted"] = "lagt inn";
+App::$strings["accepted for delivery"] = "akseptert for levering";
+App::$strings["updated"] = "oppdatert";
+App::$strings["update ignored"] = "oppdatering ignorert";
+App::$strings["permission denied"] = "tillatelse avvist";
+App::$strings["recipient not found"] = "mottaker ble ikke funnet";
+App::$strings["Delivery report for %1\$s"] = "Leveringsrapport for %1\$s";
+App::$strings["Redeliver"] = "";
+App::$strings["Away"] = "Borte";
+App::$strings["Online"] = "Online";
App::$strings["%1\$s is following %2\$s's %3\$s"] = "%1\$s følger %2\$s sin %3\$s";
App::$strings["%1\$s stopped following %2\$s's %3\$s"] = "%1\$s stopped å følge %2\$s sin %3\$s";
+App::$strings["Permissions denied."] = "Tillatelse avvist.";
+App::$strings["Link to source"] = "Lenke til kilde";
+App::$strings["Previous"] = "Forrige";
+App::$strings["Next"] = "Neste";
+App::$strings["Today"] = "Idag";
+App::$strings["Bookmark added"] = "Bokmerke lagt til";
+App::$strings["My Connections Bookmarks"] = "Mine forbindelsers bokmerker";
+App::$strings["Affinity Tool settings updated."] = "";
+App::$strings["The numbers below represent the minimum and maximum slider default positions for your network/stream page as a percentage."] = "";
+App::$strings["Default maximum affinity level"] = "";
+App::$strings["0-99 default 99"] = "Standard";
+App::$strings["Default minimum affinity level"] = "";
+App::$strings["0-99 - default 0"] = "Standard";
+App::$strings["Persistent affinity levels"] = "";
+App::$strings["If disabled the max and min levels will be reset to default after page reload"] = "";
+App::$strings["Affinity Tool Settings"] = "";
+App::$strings["Blocked accounts"] = "";
+App::$strings["Expired accounts"] = "";
+App::$strings["Expiring accounts"] = "";
+App::$strings["Message queues"] = "Meldingskøer";
+App::$strings["Your software should be updated"] = "Programvaren bør oppdateres";
+App::$strings["Summary"] = "Sammendrag";
+App::$strings["Registered accounts"] = "Registrerte kontoer";
+App::$strings["Pending registrations"] = "Ventende registreringer";
+App::$strings["Registered channels"] = "Registrerte kanaler";
+App::$strings["Active addons"] = "Aktive tillegg";
+App::$strings["Version"] = "Versjon";
+App::$strings["Repository version (master)"] = "";
+App::$strings["Repository version (dev)"] = "";
+App::$strings["Contact role deleted."] = "Kontaktrollen ble slettet.";
+App::$strings["Permission category name is required."] = "";
+App::$strings["Contact role saved."] = "Kontaktrollen ble lagret.";
+App::$strings["Role to assign affected contacts and default role to"] = "Ny rolle for berørte forbindelser og standardrolle";
+App::$strings["Role to assign affected contacts to"] = "Ny rolle for berørte forbindelser";
+App::$strings["Assign this role to"] = "Bruk denne rollen for";
+App::$strings["All my contacts"] = "Alle mine forbindelser";
+App::$strings["Automatically assign this role to new contacts"] = "Bruk denne rollen for nye kontakter automatisk";
+App::$strings["Role name"] = "Navn";
+App::$strings["System role - not editable"] = "Systemrolle - kan ikke redigeres";
+App::$strings["Deleting"] = "Sletter kanalrolle";
+App::$strings["Role Permissions"] = "Tillatelser";
+App::$strings["Some permissions may be inherited from your <a href=\"settings\">channel role</a>, which have higher priority than contact role settings."] = "Noen tillatelser kan være arvet fra din <a href=\"settings\">kanalrole</a>, som vil ha høyere prioritet enn innstillingene for kontaktrollen.";
+App::$strings["Can not copy folder into itself."] = "Kan ikke kopiere mappe inn i seg selv.";
+App::$strings["Can not move folder \"%s\" into itself."] = "";
+App::$strings["Xchan Lookup"] = "Xchan oppslag";
+App::$strings["Lookup xchan beginning with (or webbie): "] = "Slå opp xchan som begynner med (eller webbie): ";
+App::$strings["Privacy group created."] = "Personverngruppen er opprettet.";
+App::$strings["Could not create privacy group."] = "Kunne ikke opprette personverngruppen.";
+App::$strings["Privacy group updated."] = "Personverngruppen er oppdatert.";
+App::$strings["Post to this group by default"] = "";
+App::$strings["Add new contacts to this group by default"] = "";
+App::$strings["Privacy group name"] = "Personverngruppens navn";
+App::$strings["Members are visible to other channels"] = "Medlemmer er synlig for andre kanaler";
+App::$strings["Privacy group removed."] = "Personverngruppen er fjernet.";
+App::$strings["Unable to remove privacy group."] = "Ikke i stand til å fjerne personverngruppen.";
+App::$strings["Privacy Group: %s"] = "Personverngruppe: %s";
+App::$strings["Privacy group name: "] = "Personverngruppens navn: ";
+App::$strings["Group members"] = "";
+App::$strings["Not in this group"] = "";
+App::$strings["Click a channel to toggle membership"] = "";
+App::$strings["Documentation Search"] = "Søk i dokumentasjon";
+App::$strings["Not Found"] = "Ikke funnet";
+App::$strings["\$Projectname Documentation"] = "\$Projectname dokumentasjon";
+App::$strings["Contents"] = "";
+App::$strings["Members"] = "Medlemmer";
+App::$strings["Administrators"] = "";
+App::$strings["Developers"] = "";
+App::$strings["Tutorials"] = "";
+App::$strings["Help:"] = "Hjelp:";
+App::$strings["Post not found."] = "";
+App::$strings["comment"] = "kommentar";
+App::$strings["%1\$s tagged %2\$s's %3\$s with %4\$s"] = "%1\$s merket %3\$s til %2\$s med %4\$s";
+App::$strings["Invite App"] = "";
+App::$strings["Register is closed"] = "";
+App::$strings["Note, the invitation code is valid up to"] = "";
+App::$strings["Too many recipients for one invitation (max %d)"] = "";
+App::$strings["No recipients for this invitation"] = "";
+App::$strings["(%s) : Not a real email address"] = "(%s): Ikke en virkelig epostadresse";
+App::$strings["(%s) : Not allowed email address"] = "(%s): Ikke en tillatt epostadresse";
+App::$strings["(%s) : email address already in use"] = "(%s): epostadressen er allerede i bruk";
+App::$strings["(%s) : Accepted email address"] = "(%s): Godkjent epostadresse";
+App::$strings["To %s : Message delivery success."] = "";
+App::$strings["%1\$d mail(s) sent, %2\$d mail error(s)"] = "";
+App::$strings["Invites not proposed by configuration"] = "";
+App::$strings["Contact the site admin"] = "";
+App::$strings["Invites by users not enabled"] = "";
+App::$strings["You have no more invitations available"] = "Du har ikke flere invitasjoner tilgjengelig";
+App::$strings["Not on xchan"] = "";
+App::$strings["Invitation expires after"] = "";
+App::$strings["Invitation"] = "";
+App::$strings["Send invitations"] = "Send invitasjoner";
+App::$strings["Invitations I am using"] = "Invitasjoner jeg bruker";
+App::$strings["Invitations we are using"] = "";
+App::$strings["§ Note, the email(s) sent will be recorded in the system logs"] = "";
+App::$strings["Enter email addresses, one per line:"] = "Skriv e-postadresser, en per linje:";
+App::$strings["Your message:"] = "Din melding:";
+App::$strings["Invite template"] = "";
+App::$strings["Subject:"] = "Emne:";
+App::$strings["Here you may enter personal notes to the recipient(s)"] = "";
+App::$strings["About this site"] = "Om dette nettstedet ";
+App::$strings["Site Name"] = "Nettstedets navn";
+App::$strings["Administrator"] = "Administrator";
+App::$strings["Software and Project information"] = "Program- og prosjektinformasjon";
+App::$strings["This site is powered by \$Projectname"] = "Dette nettstedet drives av \$Projectname";
+App::$strings["Federated and decentralised networking and identity services provided by"] = "Fødererte og desentraliserte nettverks- og identitetstjenester via Zot";
+App::$strings["Additional federated transport protocols:"] = "Øvrige fødererte transportprotokoller:";
+App::$strings["Version %s"] = "Versjon %s";
+App::$strings["Project homepage"] = "Prosjektets hjemmeside";
+App::$strings["Developer homepage"] = "Utviklers hjemmeside";
+App::$strings["Blocked sites"] = "Byggeklossens tittel";
App::$strings["Item not available."] = "Elementet er ikke tilgjengelig.";
-App::$strings["Channel removals are not allowed within 48 hours of changing the account password."] = "Fjerning av kanaler er ikke tillatt innen 48 timer etter endring av kontopassordet.";
-App::$strings["Remove Channel"] = "Fjern kanal";
-App::$strings["This channel will be permanently removed. "] = "";
-App::$strings["This action can not be undone!"] = "";
-App::$strings["Authentication failed."] = "Autentisering mislyktes.";
-App::$strings["Empty post discarded."] = "Tomt innlegg forkastet.";
-App::$strings["Duplicate post suppressed."] = "Duplikat av innlegg forhindret.";
-App::$strings["System error. Post not saved."] = "Systemfeil. Innlegg ble ikke lagret.";
-App::$strings["Your comment is awaiting approval."] = "";
-App::$strings["Unable to obtain post information from database."] = "Ikke i stand til å få tak i informasjon om innlegg fra databasen.";
-App::$strings["You have reached your limit of %1$.0f top level posts."] = "Du har nådd din grense på %1$.0f startinnlegg.";
-App::$strings["You have reached your limit of %1$.0f webpages."] = "Du har nådd din grense på %1$.0f websider.";
-App::$strings["Article"] = "";
-App::$strings["Item has been removed."] = "";
+App::$strings["No content available for year"] = "";
+App::$strings["Export Channel"] = "Eksporter kanal";
+App::$strings["Export channel"] = "Eksporter kanal";
+App::$strings["This will export your identity and social graph into a file which can be used to import your channel to a new hub."] = "";
+App::$strings["Export content"] = "";
+App::$strings["This will export your posts, direct messages, articles and cards per month stored into a zip file per year. Months with no posts will be dismissed."] = "Dette vil eksportere dine innlegg, direktemeldinger, artikler og kort, en fil for hver måned, pakket i en zip fil for hvert år. Måneder uten innhold blir ignorert.";
+App::$strings["Export wikis"] = "";
+App::$strings["This will export your wikis and wiki pages."] = "";
+App::$strings["Export webpages"] = "";
+App::$strings["This will export your webpages and menus."] = "";
+App::$strings["Export channel calendar"] = "";
+App::$strings["This will export your channel calendar events and associated items. CalDAV calendars are not included."] = "";
+App::$strings["Export chatrooms"] = "";
+App::$strings["This will export your chatrooms. Chat history is dismissed."] = "";
+App::$strings["This export can be imported or restored by visiting <a href=\"%1\$s\">%2\$s</a> on any site containing your channel."] = "";
+App::$strings["Like/Dislike"] = "Liker/Liker ikke";
+App::$strings["This action is restricted to members."] = "Denne handlingen er begrenset til medlemmer.";
+App::$strings["Please <a href=\"rmagic\">login with your \$Projectname ID</a> or <a href=\"register\">register as a new \$Projectname member</a> to continue."] = "Vennligst <a href=\"rmagic\">logg inn med din \$Projectname ID</a> eller <a href=\"register\">registrer deg som et nytt \$Projectname-medlem</a> for å fortsette.";
+App::$strings["Invalid request."] = "Ugyldig forespørsel.";
+App::$strings["thing"] = "ting";
+App::$strings["Channel unavailable."] = "Kanalen er utilgjengelig.";
+App::$strings["Previous action reversed."] = "Forrige handling er omgjort.";
+App::$strings["profile"] = "Profil";
+App::$strings["Action completed."] = "Handling ferdig.";
+App::$strings["Thank you."] = "Tusen takk.";
+App::$strings["Block Name"] = "Byggeklossens navn";
+App::$strings["Block Title"] = "Byggeklossens tittel";
App::$strings["Unable to create element."] = "Klarer ikke å lage element.";
App::$strings["Unable to update menu element."] = "Ikke i stand til å oppdatere menyelement.";
App::$strings["Unable to add menu element."] = "Ikke i stand til å legge til menyelement.";
@@ -2391,54 +2855,465 @@ App::$strings["Menu item deleted."] = "Menyelement slettet.";
App::$strings["Menu item could not be deleted."] = "Menyelement kunne ikke bli slettet.";
App::$strings["Edit Menu Element"] = "Endre menyelement";
App::$strings["Link text"] = "Lenketekst";
-App::$strings["Email Verification Required"] = "";
-App::$strings["A verification token was sent to your email address [%s]. Enter that token here to complete the account verification step. Please allow a few minutes for delivery, and check your spam folder if you do not see the message."] = "En verifikasjonskode ble sendt til epostadressen din [%s]. Skriv inn koden du mottok her for å fullføre kontoverifikasjonen. Det kan ta noen minutter før du mottar koden. Sjekk også at eposten ikke har havnet i mappen for søppelpost om du ikke ser den etter en stund.";
-App::$strings["Resend Email"] = "";
-App::$strings["Validation token"] = "";
-App::$strings["No content available for year"] = "";
-App::$strings["Export Channel"] = "Eksporter kanal";
-App::$strings["Export channel"] = "Eksporter kanal";
-App::$strings["This will export your identity and social graph into a file which can be used to import your channel to a new hub."] = "";
-App::$strings["Export content"] = "";
-App::$strings["This will export your posts, direct messages, articles and cards per month stored into a zip file per year. Months with no posts will be dismissed."] = "Dette vil eksportere dine innlegg, direktemeldinger, artikler og kort, en fil for hver måned, pakket i en zip fil for hvert år. Måneder uten innhold blir ignorert.";
-App::$strings["Export wikis"] = "";
-App::$strings["This will export your wikis and wiki pages."] = "";
-App::$strings["Export webpages"] = "";
-App::$strings["This will export your webpages and menus."] = "";
-App::$strings["Export channel calendar"] = "";
-App::$strings["This will export your channel calendar events and associated items. CalDAV calendars are not included."] = "";
-App::$strings["Export chatrooms"] = "";
-App::$strings["This will export your chatrooms. Chat history is dismissed."] = "";
-App::$strings["This export can be imported or restored by visiting <a href=\"%1\$s\">%2\$s</a> on any site containing your channel."] = "";
-App::$strings["No more system notifications."] = "Ingen flere systemvarsler.";
-App::$strings["System Notifications"] = "Systemvarsler";
-App::$strings["Mark all seen"] = "Merk alle som sett";
+App::$strings["Entry censored"] = "";
+App::$strings["Entry OK"] = "";
+App::$strings["Change Order of Pinned Navbar Apps"] = "";
+App::$strings["Change Order of App Tray Apps"] = "";
+App::$strings["Use arrows to move the corresponding app left (top) or right (bottom) in the navbar"] = "";
+App::$strings["Use arrows to move the corresponding app up or down in the app tray"] = "";
+App::$strings["Your real name is recommended."] = "";
+App::$strings["Examples: \"Bob Jameson\", \"Lisa and her Horses\", \"Soccer\", \"Aviation Group\""] = "Eksempel: \"Ola Nordmann\", \"Lisa og hestene hennes\", \"Fotball\", \"Sykkelgruppa\"";
+App::$strings["This will be used to create a unique network address (like an email address)."] = "";
+App::$strings["Allowed characters are a-z 0-9, - and _"] = "";
+App::$strings["Channel name"] = "Kanalnavn";
+App::$strings["Channel role"] = "Kanalrolle";
+App::$strings["Create a Channel"] = "";
+App::$strings["A channel is a unique network identity. It can represent a person (social network profile), a forum (group), a business or celebrity page, a newsfeed, and many other things."] = "";
+App::$strings["or <a href=\"import\">import an existing channel</a> from another location."] = "eller <a href=\"import\">importer en eksisterende kanal</a> fra et annet sted.";
+App::$strings["Validate"] = "";
+App::$strings["Album not found."] = "Albumet ble ikke funnet.";
+App::$strings["Delete Album"] = "Slett album";
+App::$strings["Delete Photo"] = "Slett bilde";
+App::$strings["No photos selected"] = "Ingen bilder valgt";
+App::$strings["Access to this item is restricted."] = "Tilgang til dette elementet er begrenset.";
+App::$strings["%1$.2f MB photo storage used."] = "%1$.2f MB lagringsplass til bilder er brukt.";
+App::$strings["%1$.2f MB of %2$.2f MB photo storage used."] = "%1$.2f MB av %2$.2f MB lagringsplass til bilder er brukt.";
+App::$strings["Upload Photos"] = "Last opp bilder";
+App::$strings["Enter an album name"] = "Skriv et albumnavn";
+App::$strings["or select an existing album (doubleclick)"] = "eller velg et eksisterende album (dobbeltklikk)";
+App::$strings["Create a status post for this upload"] = "Lag et statusinnlegg for denne opplastingen";
+App::$strings["Description (optional)"] = "Beskrivelse (valgritt)";
+App::$strings["Show Newest First"] = "Vis nyeste først";
+App::$strings["Show Oldest First"] = "Vis eldste først";
+App::$strings["Add Photos"] = "Legg til bilder";
+App::$strings["Permission denied. Access to this item may be restricted."] = "Tillatelse avvist. Tilgang til dette elementet kan være begrenset.";
+App::$strings["Photo not available"] = "Bilde er utilgjengelig";
+App::$strings["Use as profile photo"] = "Bruk som profilbilde";
+App::$strings["Use as cover photo"] = "Bruk som omslagsbilde";
+App::$strings["Private Photo"] = "Privat bilde";
+App::$strings["View Full Size"] = "Vis i full størrelse";
+App::$strings["Edit photo"] = "Endre bilde";
+App::$strings["Rotate CW (right)"] = "Roter med klokka (mot høyre)";
+App::$strings["Rotate CCW (left)"] = "Roter mot klokka (venstre)";
+App::$strings["Move photo to album"] = "";
+App::$strings["Enter a new album name"] = "Skriv et nytt albumnavn";
+App::$strings["or select an existing one (doubleclick)"] = "eller velg et eksisterende album (dobbeltklikk)";
+App::$strings["Add a Tag"] = "Legg til merkelapp";
+App::$strings["Example: @bob, @Barbara_Jensen, @jim@example.com"] = "Eksempel: @bob, @Barbara_Jensen, @jim@example.com";
+App::$strings["Flag as adult in album view"] = "Flag som voksent i albumvisning";
+App::$strings["I like this (toggle)"] = "Jeg liker dette (skru av og på)";
+App::$strings["I don't like this (toggle)"] = "Jeg liker ikke dette (skru av og på)";
+App::$strings["Photo Tools"] = "Fotoverktøy";
+App::$strings["In This Photo:"] = "I dette bildet:";
+App::$strings["Map"] = "Kart";
+App::$strings["__ctx:noun__ Likes"] = "Liker";
+App::$strings["__ctx:noun__ Dislikes"] = "Liker ikke";
+App::$strings["You must be logged in to see this page."] = "Du må være innloegget for å se denne siden.";
+App::$strings["Posts and comments"] = "Innlegg og kommentarer";
+App::$strings["Only posts"] = "Kun innlegg";
+App::$strings["Insufficient permissions. Request redirected to profile page."] = "Utilstrekkelig tillatelse. Forespørsel omdirigert til profilsiden.";
+App::$strings["vcard"] = "";
+App::$strings["Item approved"] = "Konto godkjent.";
+App::$strings["Failed to create source. No channel selected."] = "Mislyktes med å lage kilde. Ingen kanal er valgt.";
+App::$strings["Source created."] = "Kilden er laget.";
+App::$strings["Source updated."] = "Kilden er oppdatert.";
+App::$strings["*"] = "*";
+App::$strings["Manage remote sources of content for your channel."] = "Håndtere eksterne innholdskilder til din kanal.";
+App::$strings["New Source"] = "Ny kilde";
+App::$strings["Import all or selected content from the following channel into this channel and distribute it according to your channel settings."] = "Importer alt eller et utvalgt av innhold fra følgende kanal inn i denne kanalen og distribuer det i henhold til dine egne kanalinnstillinger.";
+App::$strings["Only import content with these words (one per line)"] = "Bare importer innhold med disse ordene (ett ord per linje)";
+App::$strings["Leave blank to import all public content"] = "La stå tomt for å importere alt offentlig innhold";
+App::$strings["Channel Name"] = "Kanalnavn";
+App::$strings["Add the following categories to posts imported from this source (comma separated)"] = "";
+App::$strings["Resend posts with this channel as author"] = "";
+App::$strings["Copyrights may apply"] = "";
+App::$strings["Source not found."] = "Kilden ble ikke funnet.";
+App::$strings["Edit Source"] = "Endre kilde";
+App::$strings["Delete Source"] = "Slett kilde";
+App::$strings["Source removed"] = "Kilden er fjernet";
+App::$strings["Unable to remove source."] = "Ikke i stand til å fjerne kilde.";
+App::$strings["Profile not found."] = "Profilen ble ikke funnet.";
+App::$strings["Profile deleted."] = "Profilen er slettet.";
+App::$strings["Profile-"] = "Profil-";
+App::$strings["New profile created."] = "Ny profil opprettet.";
+App::$strings["Profile unavailable to clone."] = "Profilen er utilgjengelig for klonen.";
+App::$strings["Profile unavailable to export."] = "Profilen er utilgjengelig for eksport.";
+App::$strings["Profile Name is required."] = "Profilnavn er påkrevd.";
+App::$strings["Marital Status"] = "Sivilstand";
+App::$strings["Romantic Partner"] = "Romantisk partner";
+App::$strings["Likes"] = "Liker";
+App::$strings["Dislikes"] = "Liker ikke";
+App::$strings["Work/Employment"] = "Arbeid/sysselsetting";
+App::$strings["Religion"] = "Religion";
+App::$strings["Political Views"] = "Politiske synspunkter";
+App::$strings["Sexual Preference"] = "Seksuelle preferanser";
+App::$strings["Homepage"] = "Hjemmeside";
+App::$strings["Interests"] = "Interesser";
+App::$strings["Profile updated."] = "Profilen er oppdatert.";
+App::$strings["Hide my connections from viewers of this profile"] = "Skjul mine forbindelser fra besøkende som ser denne profilen";
+App::$strings["Publish my default profile in the network directory"] = "La standardprofilen min vises i nettverkskatalogen";
+App::$strings["Suggest me as a potential contact to new members"] = "Foreslå meg som mulig kontakt til nye medlemmer";
+App::$strings["Reveal my online status"] = "La andre se om jeg er pålogget eller ikke";
+App::$strings["Edit Profile Details"] = "Endre profildetaljer";
+App::$strings["View this profile"] = "Vis denne profilen";
+App::$strings["Profile Tools"] = "Profilverktøy";
+App::$strings["Change cover photo"] = "Endre omslagsbilde";
+App::$strings["Create a new profile using these settings"] = "Lag en ny profil ved å bruke disse innstillingene";
+App::$strings["Clone this profile"] = "Klon denne profilen";
+App::$strings["Delete this profile"] = "Slett denne profilen";
+App::$strings["Add profile things"] = "Legg til profilting";
+App::$strings["Basic"] = "Grunnleggende";
+App::$strings["Relationship"] = "Forhold";
+App::$strings["Import profile from file"] = "Importer profil fra fil";
+App::$strings["Export profile to file"] = "Eksporter profil til fil";
+App::$strings["Your gender"] = "Kjønn";
+App::$strings["Marital status"] = "Sivilstatus";
+App::$strings["Sexual preference"] = "Legning";
+App::$strings["Profile name"] = "Profilnavn";
+App::$strings["This is your default profile."] = "Dette er din standardprofil.";
+App::$strings["Your full name"] = "Fullt navn";
+App::$strings["Short title/description"] = "Kort tittel/beskrivelse";
+App::$strings["Maximal 190 characters"] = "Maksimum 190 tegn";
+App::$strings["Street address"] = "Gateadresse";
+App::$strings["Locality/City"] = "Sted/by";
+App::$strings["Region/State"] = "Region";
+App::$strings["Postal/Zip code"] = "Postnummer";
+App::$strings["Who (if applicable)"] = "Hvem (om relevant)";
+App::$strings["Examples: cathy123, Cathy Williams, cathy@example.com"] = "Eksempler: kari123, Kari Villiamsen, kari@example.com";
+App::$strings["Since (date)"] = "Fra (dato)";
+App::$strings["Tell us about yourself"] = "Fortell oss om deg selv";
+App::$strings["Hometown"] = "Hjemsted";
+App::$strings["Political views"] = "Politiske holdninger";
+App::$strings["Religious views"] = "Religiøse holdninger";
+App::$strings["Keywords used in directory listings"] = "Nøkkelord for bruk i katalogoppføringen";
+App::$strings["Example: fishing photography software"] = "Eksempel: fisking fotografering programvare";
+App::$strings["Musical interests"] = "Musikkinteresser";
+App::$strings["Books, literature"] = "Bøker, litteratur";
+App::$strings["Television"] = "TV/fjernsyn";
+App::$strings["Film/Dance/Culture/Entertainment"] = "Film/dans/kultur/underholdning";
+App::$strings["Hobbies/Interests"] = "Hobbier/Interesser";
+App::$strings["Love/Romance"] = "Kjærlighet/romantikk";
+App::$strings["School/Education"] = "Skolle/utdanning";
+App::$strings["Contact information and social networks"] = "Kontaktinformasjon og andre sosiale nettverk";
+App::$strings["My other channels"] = "Mine andre kanaler";
App::$strings["Files: shared with me"] = "Filer: delt med meg";
App::$strings["NEW"] = "NY";
App::$strings["Remove all files"] = "Fjern alle filer";
App::$strings["Remove this file"] = "Fjern denne filen";
-App::$strings["This channel is limited to %d tokens"] = "";
-App::$strings["Name and Password are required."] = "";
-App::$strings["Token saved."] = "";
-App::$strings["Use this form to create temporary access identifiers to share things with non-members. These identities may be used in privacy groups and visitors may login using these credentials to access private content."] = "";
-App::$strings["Please select a role for this guest!"] = "";
-App::$strings["Select a role for this guest"] = "";
-App::$strings["Login Name"] = "";
-App::$strings["Login Password"] = "";
-App::$strings["Expires (yyyy-mm-dd)"] = "";
-App::$strings["Contact role deleted."] = "";
-App::$strings["Permission category name is required."] = "";
-App::$strings["Contact role saved."] = "";
-App::$strings["Role to assign affected contacts and default role to"] = "";
-App::$strings["Role to assign affected contacts to"] = "";
-App::$strings["Assign this role to"] = "";
-App::$strings["All my contacts"] = "";
-App::$strings["Automatically assign this role to new contacts"] = "";
-App::$strings["Role name"] = "";
-App::$strings["System role - not editable"] = "";
-App::$strings["Deleting"] = "";
-App::$strings["Role Permissions"] = "";
-App::$strings["Some permissions may be inherited from your <a href=\"settings\">channel role</a>, which have higher priority than contact role settings."] = "";
+App::$strings["Select a bookmark folder"] = "Velg en bokmerkemappe";
+App::$strings["Save Bookmark"] = "Lagre bokmerke";
+App::$strings["URL of bookmark"] = "URL-en til bokmerket";
+App::$strings["Or enter new bookmark folder name"] = "Eller skriv nytt navn på bokmerkemappe";
+App::$strings["Remote privacy information not available"] = "";
+App::$strings["__ctx:acl__ Profile"] = "Profil";
+App::$strings["Privacy group"] = "Personverngruppe:";
+App::$strings["Item"] = "";
+App::$strings["Click to copy link to this ressource for guest %s to clipboard"] = "";
+App::$strings["Link copied"] = "";
+App::$strings["Access"] = "Tilgang";
+App::$strings["OCAP access"] = "";
+App::$strings["Event can not end before it has started."] = "Hendelsen kan ikke slutte før den starter.";
+App::$strings["Unable to generate preview."] = "Klarer ikke å lage forhåndsvisning.";
+App::$strings["Event title and start time are required."] = "Hendelsestittel og starttidspunkt er påkrevd.";
+App::$strings["Event not found."] = "Hendelsen ble ikke funnet.";
+App::$strings["Edit event"] = "Endre hendelse";
+App::$strings["Delete event"] = "Slett hendelse";
+App::$strings["calendar"] = "kalender";
+App::$strings["Failed to remove event"] = "Mislyktes med å slette hendelse";
+App::$strings["This page is available only to site members"] = "";
+App::$strings["What would you like to do?"] = "";
+App::$strings["Please bookmark this page if you would like to return to it in the future"] = "";
+App::$strings["Upload a profile photo"] = "";
+App::$strings["Upload a cover photo"] = "";
+App::$strings["Edit your default profile"] = "";
+App::$strings["View the channel directory"] = "";
+App::$strings["View/edit your channel settings"] = "";
+App::$strings["View the site or project documentation"] = "";
+App::$strings["Visit your channel homepage"] = "";
+App::$strings["View your connections and/or add somebody whose address you already know"] = "";
+App::$strings["View your personal stream (this may be empty until you add some connections)"] = "";
+App::$strings["View the public stream. Warning: this content is not moderated"] = "";
+App::$strings["Room not found"] = "Rommet ble ikke funnet";
+App::$strings["Leave Room"] = "Forlat rom";
+App::$strings["Delete Room"] = "Slett rom";
+App::$strings["I am away right now"] = "Jeg er borte akkurat nå";
+App::$strings["I am online"] = "Jeg er pålogget";
+App::$strings["Bookmark this room"] = "Bokmerk dette rommet";
+App::$strings["New Chatroom"] = "Nytt chatrom";
+App::$strings["Chatroom name"] = "Romnavn";
+App::$strings["Expiration of chats (minutes)"] = "Chat utgår (antall minutter)";
+App::$strings["%1\$s's Chatrooms"] = "%1\$s sine chatrom";
+App::$strings["No chatrooms available"] = "Ingen rom tilgjengelige";
+App::$strings["Add Room"] = "";
+App::$strings["Expiration"] = "Utløp";
+App::$strings["min"] = "min";
+App::$strings["Email resent"] = "";
+App::$strings["Email resend failed"] = "";
+App::$strings["Verification successful"] = "";
+App::$strings["Account successfull created"] = "";
+App::$strings["Channel successfull created"] = "";
+App::$strings["Automatic channel creation failed. Please create a channel."] = "";
+App::$strings["Account creation error"] = "";
+App::$strings["Verify failed"] = "";
+App::$strings["Token verification failed"] = "";
+App::$strings["Request not inside time frame"] = "";
+App::$strings["Identity unknown"] = "";
+App::$strings["dId2 mistaken"] = "";
+App::$strings["Your Registration ID"] = "";
+App::$strings["Registration verification"] = "";
+App::$strings["Hold on, you can start verification in"] = "";
+App::$strings["Please remember your verification token for ID"] = "";
+App::$strings["Token validity"] = "";
+App::$strings["Resend email"] = "";
+App::$strings["Registration status"] = "";
+App::$strings["Verification successful!"] = "";
+App::$strings["Your login ID is"] = "";
+App::$strings["After your account has been approved by our administrator you will be able to login with your login ID and your provided password."] = "";
+App::$strings["Registration request revoked"] = "";
+App::$strings["Sorry for any inconvience. Thank you for your response."] = "";
+App::$strings["Please enter your verification token for ID"] = "";
+App::$strings["Please check your email!"] = "";
+App::$strings["Verification token"] = "";
+App::$strings["ID expired"] = "";
+App::$strings["You will require the verification token for ID"] = "";
+App::$strings["Unknown or expired ID"] = "";
+App::$strings["dId2 malformed"] = "";
+App::$strings["Edit Layout"] = "Endre layout";
+App::$strings["\$Projectname Server - Setup"] = "\$Projectname-tjener - oppsett";
+App::$strings["Could not connect to database."] = "Fikk ikke kontakt med databasen.";
+App::$strings["Could not connect to specified site URL. Possible SSL certificate or DNS issue."] = "Fikk ikke kontakt med det angitte nettstedets URL. Problemet kan muligens skyldes SSL-sertifikatet eller DNS.";
+App::$strings["Could not create table."] = "Kunne ikke lage tabellen.";
+App::$strings["Your site database has been installed."] = "Databasen til ditt nettsted har blitt installert.";
+App::$strings["You may need to import the file \"install/schema_xxx.sql\" manually using a database client."] = "Du må kanskje importere filen \"install/schmea_xxx.sql\" manuelt ved å bruke en databaseklient.";
+App::$strings["Please see the file \"install/INSTALL.txt\"."] = "Vennligst les filen \"install/INSTALL.txt\".";
+App::$strings["System check"] = "Systemsjekk";
+App::$strings["Check again"] = "Sjekk igjen";
+App::$strings["Database connection"] = "Databaseforbindelse";
+App::$strings["In order to install \$Projectname we need to know how to connect to your database."] = "For å installere \$Projectname må du oppgi hvordan din database kan kontaktes.";
+App::$strings["Please contact your hosting provider or site administrator if you have questions about these settings."] = "Vennligst kontakt din nettstedstilbyder eller nettstedsadministrator hvis du har spørsmål om disse innstillingene.";
+App::$strings["The database you specify below should already exist. If it does not, please create it before continuing."] = "Databasen du oppgir nedenfor må finnes på forhånd. Hvis den ikke finnes, vennligst lag den før du fortsetter.";
+App::$strings["Database Server Name"] = "Navn på databasetjener";
+App::$strings["Default is 127.0.0.1"] = "Standard er 127.0.0.1";
+App::$strings["Database Port"] = "Databaseport";
+App::$strings["Communication port number - use 0 for default"] = "Kommunikasjonsportnummer - bruk 0 for standard";
+App::$strings["Database Login Name"] = "Database innloggingsnavn";
+App::$strings["Database Login Password"] = "Database innloggingspassord";
+App::$strings["Database Name"] = "Databasenavn";
+App::$strings["Database Type"] = "Databasetype";
+App::$strings["Site administrator email address"] = "E-postadressen til administrator ved nettstedet";
+App::$strings["Your account email address must match this in order to use the web admin panel."] = "Din konto sin e-postadresse må være lik denne for å kunne bruke web-administrasjonspanelet.";
+App::$strings["Website URL"] = "Nettstedets URL";
+App::$strings["Please use SSL (https) URL if available."] = "Vennligst bruk SSL (https) URL hvis tilgjengelig.";
+App::$strings["Please select a default timezone for your website"] = "Vennligst velg en standard tidssone for ditt nettsted";
+App::$strings["Site settings"] = "Nettstedets innstillinger";
+App::$strings["PHP version 8.0 or greater is required."] = "";
+App::$strings["PHP version"] = "";
+App::$strings["Could not find a command line version of PHP in the web server PATH."] = "Fant ikke en kommandolinjeversjon av PHP i webtjenerens sti (PATH).";
+App::$strings["If you don't have a command line version of PHP installed on server, you will not be able to run background polling via cron."] = "Hvis du ikke har en kommandolinjeversjon av PHP installert på tjeneren, så vil du ikke kunne kjøre bakgrunnshenting via cron.";
+App::$strings["PHP executable path"] = "PHP-kjørefilens sti";
+App::$strings["Enter full path to php executable. You can leave this blank to continue the installation."] = "Skriv full sti til kjørefilen for PHP. Du kan la denne stå blank for å fortsette installasjonen.";
+App::$strings["Command line PHP"] = "Kommandolinje PHP";
+App::$strings["Unable to check command line PHP, as shell_exec() is disabled. This is required."] = "";
+App::$strings["The command line version of PHP on your system does not have \"register_argc_argv\" enabled."] = "Kommandolinjeversjonen av PHP på ditt system har ikke \"register_argc_argv\" påskrudd.";
+App::$strings["This is required for message delivery to work."] = "Dette er påkrevd for at meldingslevering skal virke.";
+App::$strings["PHP register_argc_argv"] = "PHP register_argc_argv";
+App::$strings["This is not sufficient to upload larger images or files. You should be able to upload at least 4 MB at once."] = "";
+App::$strings["Your max allowed total upload size is set to %s. Maximum size of one file to upload is set to %s. You are allowed to upload up to %d files at once."] = "Den største totale opplastingsstørrelsen du er tillatt er satt til %s. Filstørrelsen på en enkelt fil er satt til å maksimalt være %s. Du har lov til å laste opp inntil %d filer samtidig.";
+App::$strings["You can adjust these settings in the server php.ini file."] = "";
+App::$strings["PHP upload limits"] = "PHP opplastingsgrenser";
+App::$strings["Error: the \"openssl_pkey_new\" function on this system is not able to generate encryption keys"] = "Feil: \"openssl_pkey_new\"-funksjonen på dette systemet er ikke i stand til å lage krypteringsnøkler";
+App::$strings["If running under Windows, please see \"http://www.php.net/manual/en/openssl.installation.php\"."] = "Ved kjøring på Windows, vennligst se \"http://www.php.net/manual/en/openssl.installation.php\".";
+App::$strings["Generate encryption keys"] = "Lag krypteringsnøkler";
+App::$strings["Error: the sodium encryption library is not installed."] = "";
+App::$strings["Generate ed25519 encryption keys"] = "Lag krypteringsnøkler";
+App::$strings["Error: one of \"bcmath\" or \"gmp\" (bigmath library) extensions are required."] = "";
+App::$strings["Bigmath library (either bcmath or gmp)"] = "";
+App::$strings["libCurl PHP module"] = "libCurl PHP-modul";
+App::$strings["GD graphics PHP module"] = "GD graphics PHP-modul";
+App::$strings["OpenSSL PHP module"] = "OpenSSL PHP-modul";
+App::$strings["PDO database PHP module"] = "";
+App::$strings["mb_string PHP module"] = "mb_string PHP-modul";
+App::$strings["xml PHP module"] = "xml PHP modul";
+App::$strings["zip PHP module"] = "";
+App::$strings["intl PHP module"] = "xml PHP modul";
+App::$strings["Apache mod_rewrite module"] = "Apache mod_rewrite-modul";
+App::$strings["Error: Apache webserver mod-rewrite module is required but not installed."] = "Feil: Apache web-tjenerens mod-rewrite-modul er påkrevd, men ikke installert.";
+App::$strings["exec"] = "";
+App::$strings["Error: exec is required but is either not installed or has been disabled in php.ini"] = "";
+App::$strings["shell_exec"] = "";
+App::$strings["Error: shell_exec is required but is either not installed or has been disabled in php.ini"] = "";
+App::$strings["Error: libCURL PHP module required but not installed."] = "Feil: libCURL PHP-modul er påkrevd, men er ikke installert.";
+App::$strings["Error: GD PHP module with JPEG support or ImageMagick graphics library required but not installed."] = "";
+App::$strings["Error: openssl PHP module required but not installed."] = "Feil: openssl PHP-modul er påkrevd, men er ikke installert.";
+App::$strings["Error: PDO database PHP module missing a driver for either mysql or pgsql."] = "";
+App::$strings["Error: PDO database PHP module required but not installed."] = "";
+App::$strings["Error: mb_string PHP module required but not installed."] = "Feil: mb_string PHP-modul er påkrevd, men er ikke installert.";
+App::$strings["Error: xml PHP module required for DAV but not installed."] = "Feil: XML PHP modul er påkrevet for DAV, men den er ikke installert.";
+App::$strings["Error: zip PHP module required but not installed."] = "";
+App::$strings["Error: intl PHP module required but not installed."] = "Feil: openssl PHP-modul er påkrevd, men er ikke installert.";
+App::$strings[".htconfig.php is writable"] = ".htconfig.php kan skrives til";
+App::$strings["The web installer needs to be able to create a file called \".htconfig.php\" in the top folder of your web server and it is unable to do so."] = "Web-installasjonen må kunne lage en fil kalt \".htconfig.php\" i toppkatalogen til web-tjeneren din, men dette får den ikke til.";
+App::$strings["This is most often a permission setting, as the web server may not be able to write files in your folder - even if you can."] = "Dette er oftest tillatelsesinnstilling, ettersom webtjeneren kanskje kan skrive til filer i din mappe - selv om du kan.";
+App::$strings["Please see install/INSTALL.txt for additional information."] = "";
+App::$strings["This software uses the Smarty3 template engine to render its web views. Smarty3 compiles templates to PHP to speed up rendering."] = "";
+App::$strings["In order to store these compiled templates, the web server needs to have write access to the directory %s under the top level web folder."] = "";
+App::$strings["Please ensure that the user that your web server runs as (e.g. www-data) has write access to this folder."] = "Vennligst sikre at brukeren som din web-tjeneste kjører som (for eksempel www-data) har skrivetilgang til denne katalogen.";
+App::$strings["Note: as a security measure, you should give the web server write access to %s only--not the template files (.tpl) that it contains."] = "Merknad: som et sikkerhetstiltak bør du bare gi webtjerenn skrivetilgang til %s - ikke til malfilene (.tpl) som den inneholder.";
+App::$strings["%s is writable"] = "%s kan skrives til";
+App::$strings["This software uses the store directory to save uploaded files. The web server needs to have write access to the store directory under the top level web folder"] = "";
+App::$strings["store is writable"] = "lageret kan skrives til";
+App::$strings["SSL certificate cannot be validated. Fix certificate or disable https access to this site."] = "SSL-sertifikatet kan ikke kontrolleres. Fiks sertifikatet eller skru av https tilgang til dette nettstedet.";
+App::$strings["If you have https access to your website or allow connections to TCP port 443 (the https: port), you MUST use a browser-valid certificate. You MUST NOT use self-signed certificates!"] = "Hvis du har HTTPS-tilgang til ditt nettsted eller tillater forbindelser til TCP port 443 (HTTPS-porten), så MÅ du bruke nettlesergodkjent sertifkater. Du MÅ IKKE bruke egensignert sertifikater!";
+App::$strings["This restriction is incorporated because public posts from you may for example contain references to images on your own hub."] = "Denne begrensningen er tatt inn fordi offentlige innlegg fra deg kan for eksempel inneholde referanser til bilder på din egen hub.";
+App::$strings["If your certificate is not recognized, members of other sites (who may themselves have valid certificates) will get a warning message on their own site complaining about security issues."] = "Hvis sertifikatet ditt ikke gjenkjennes, så vil medlemmer på andre nettsteder (som selv kan ha godkjente sertifikater) få en beskjed med en advarsel på deres eget nettsted som klager over sikkerhetsproblemer.";
+App::$strings["This can cause usability issues elsewhere (not just on your own site) so we must insist on this requirement."] = "Dette kan gi problemer med brukervennlighet (ikke bare på ditt eget nettsted), så vi må insistere på dette kravet.";
+App::$strings["Providers are available that issue free certificates which are browser-valid."] = "Det finnes tilbydere som utsteder gratis sertifikater som er gyldige i nettlesere.";
+App::$strings["If you are confident that the certificate is valid and signed by a trusted authority, check to see if you have failed to install an intermediate cert. These are not normally required by browsers, but are required for server-to-server communications."] = "";
+App::$strings["SSL certificate validation"] = "SSL sertifikat-kontroll";
+App::$strings["Url rewrite in .htaccess is not working. Check your server configuration.Test: "] = "URL omskriving (rewrite) i .htaccess virker ikke. Sjekk konfigurasjonen til tjeneren din. Test: ";
+App::$strings["Url rewrite is working"] = "URL rewrite virker";
+App::$strings["The database configuration file \".htconfig.php\" could not be written. Please use the enclosed text to create a configuration file in your web server root."] = "Databasekonfigurasjonsfilen \".htconfig.php\" kunne ikke skrives. Vennligst bruk den medfølgende teksten for å lage en konfigurasjonsfil i toppkatalogen av din web-tjener.";
+App::$strings["<h1>What next?</h1>"] = "";
+App::$strings["IMPORTANT: You will need to [manually] setup a scheduled task for the poller."] = "VIKTIG: Du må [manuelt] sette opp en automatisert tidfestet oppgave til bakgrunnshenteren.";
+App::$strings["toggle full screen mode"] = "";
+App::$strings["Invalid profile identifier."] = "Ugyldig profil-identifikator.";
+App::$strings["Profile Visibility Editor"] = "Endre profilsynlighet";
+App::$strings["Click on a contact to add or remove."] = "Klikk på en kontakt for å legge til eller fjerne.";
+App::$strings["Visible To"] = "Synlig for";
+App::$strings["All Connections"] = "Alle forbindelser";
+App::$strings["Language App"] = "";
+App::$strings["Select an alternate language"] = "Velg et annet språk";
+App::$strings["Edit post"] = "Endre innlegg";
+App::$strings["Item is not editable"] = "Elementet kan ikke endres";
+App::$strings["Active"] = "Aktiv";
+App::$strings["Blocked"] = "Blokkert";
+App::$strings["Ignored"] = "Ignorert";
+App::$strings["Hidden"] = "Skjult";
+App::$strings["Archived/Unreachable"] = "Arkivert/utilgjengelig";
+App::$strings["Active Connections"] = "Aktive forbindelser";
+App::$strings["Show active connections"] = "";
+App::$strings["Show pending (new) connections"] = "Vis ventende (nye) forbindelser";
+App::$strings["Only show blocked connections"] = "Vis bare forbindelser som er blokkert";
+App::$strings["Only show ignored connections"] = "Vis bare ignorerte forbindelser";
+App::$strings["Only show archived/unreachable connections"] = "";
+App::$strings["Only show hidden connections"] = "Vis bare skjulte forbindelser";
+App::$strings["Show all connections"] = "Vis alle forbindelser";
+App::$strings["Pending approval"] = "Venter på godkjenning";
+App::$strings["Archived"] = "Arkivert";
+App::$strings["Not connected at this location"] = "";
+App::$strings["%1\$s [%2\$s]"] = "%1\$s [%2\$s]";
+App::$strings["Edit connection"] = "Endre forbindelse";
+App::$strings["Delete connection"] = "Slett forbindelse";
+App::$strings["Channel address"] = "Kanaladresse";
+App::$strings["Call"] = "";
+App::$strings["Status"] = "Status";
+App::$strings["Connected"] = "Forbundet";
+App::$strings["Ignore connection"] = "Ignorer forbindelse";
+App::$strings["Recent activity"] = "Nylig aktivitet";
+App::$strings["Connect at this location"] = "";
+App::$strings["Search your connections"] = "Søk blant dine forbindelser";
+App::$strings["Contact search"] = "";
+App::$strings["Calendar entries imported."] = "Kalenderhendelsene er importert.";
+App::$strings["No calendar entries found."] = "Ingen kalenderhendelser funnet.";
+App::$strings["Event title"] = "Tittel på hendelse";
+App::$strings["Start date and time"] = "Startdato og tidspunkt";
+App::$strings["End date and time"] = "";
+App::$strings["Timezone:"] = "Tidssone";
+App::$strings["Month"] = "måned";
+App::$strings["Week"] = "uke";
+App::$strings["Day"] = "dag";
+App::$strings["List month"] = "";
+App::$strings["List week"] = "";
+App::$strings["List day"] = "";
+App::$strings["More"] = "";
+App::$strings["Less"] = "";
+App::$strings["Select calendar"] = "";
+App::$strings["Delete all"] = "";
+App::$strings["Sorry! Editing of recurrent events is not yet implemented."] = "";
+App::$strings["Could not fetch calendar resource. The selected calendar might be disabled."] = "";
+App::$strings["Default Calendar"] = "";
+App::$strings["Default Addressbook"] = "";
+App::$strings["This directory server requires an access token"] = "Denne katalogtjeneren krever en tilgangsnøkkel (access token)";
+App::$strings["Name and Secret are required"] = "";
+App::$strings["Add OAuth2 application"] = "";
+App::$strings["Grant Types"] = "";
+App::$strings["leave blank unless your application sepcifically requires this"] = "";
+App::$strings["Authorization scope"] = "";
+App::$strings["OAuth2 Application not found."] = "";
+App::$strings["leave blank unless your application specifically requires this"] = "";
+App::$strings["Connected OAuth2 Apps"] = "";
+App::$strings["%s - (Experimental)"] = "%s - (Eksperimentelt)";
+App::$strings["Display Settings"] = "Visningsinnstillinger";
+App::$strings["Theme Settings"] = "Temainnstillinger";
+App::$strings["Custom Theme Settings"] = "Tilpassede temainnstillinger";
+App::$strings["Content Settings"] = "Innholdsinnstillinger";
+App::$strings["Display Theme:"] = "Visningstema:";
+App::$strings["Select scheme"] = "Velg skjema";
+App::$strings["Threaded conversation view"] = "";
+App::$strings["Display replies below their parent message (default yes)"] = "";
+App::$strings["Enable user zoom on mobile devices"] = "Skru på brukerstyrt zoom på mobile enheter";
+App::$strings["Update browser every xx seconds"] = "Oppdater nettleser hvert xx sekunder";
+App::$strings["Minimum of 10 seconds, no maximum"] = "Minimum 10 sekunder, ikke noe maksimum";
+App::$strings["Maximum number of conversations to load at any time:"] = "Maksimalt antall samtaler å laste samtidig:";
+App::$strings["Maximum of 30 items"] = "";
+App::$strings["Show emoticons (smilies) as images"] = "Vis emoticons (smilefjes) som bilder";
+App::$strings["Link post titles to source"] = "Lenk innleggets tittel til kilden";
+App::$strings["Display new member quick links menu"] = "Vis hurtiglenker for nye medlemmer";
+App::$strings["Max height of content (in pixels)"] = "Maks høyde for innhold (i piksler)";
+App::$strings["Click to expand content exceeding this height"] = "Klikk for å vise hele innlegg som overskrider denne grensen";
+App::$strings["Stream Settings"] = "Instillinger for tidslinjen";
+App::$strings["Personal menu to display in your channel pages"] = "Personlig meny som kan vises på dine kanalsider";
+App::$strings["Channel Home Settings"] = "";
+App::$strings["Not valid email."] = "Ikke gyldig e-post.";
+App::$strings["Protected email address. Cannot change to that email."] = "Beskyttet e-postadresse. Kan ikke endre til den e-postadressen.";
+App::$strings["System failure storing new email. Please try again."] = "Systemfeil ved lagring av ny e-post. Vennligst prøv igjen.";
+App::$strings["Password verification failed."] = "Passordbekreftelsen mislyktes.";
+App::$strings["Passwords do not match. Password unchanged."] = "Passordene stemmer ikke overens. Passord uforandret.";
+App::$strings["Empty passwords are not allowed. Password unchanged."] = "Tomme passord er ikke tillatt. Passord uforandret.";
+App::$strings["Password changed."] = "Passord endret.";
+App::$strings["Password update failed. Please try again."] = "Passord oppdatering mislyktes. Vennligst prøv igjen.";
+App::$strings["Account Settings"] = "Kontoinnstillinger";
+App::$strings["Current Password"] = "Nåværende passord";
+App::$strings["Enter New Password"] = "Skriv nytt passord";
+App::$strings["Confirm New Password"] = "Bekreft nytt passord";
+App::$strings["Leave password fields blank unless changing"] = "La passordfeltene stå blanke om det ikke skal endres";
+App::$strings["Multi-Factor Authentication"] = "Flerfaktorautentisering";
+App::$strings["DId2 or Email Address:"] = "DId2 eller epostadresse:";
+App::$strings["Remove this account including all its channels"] = "Slett denne kontoen inkludert alle dens kanaler";
+App::$strings["Editor Settings"] = "Instillinger for redigering";
+App::$strings["Additional Features"] = "Ekstra funksjoner";
+App::$strings["Directory Settings"] = "";
+App::$strings["No feature settings configured"] = "Ingen funksjonsinnstillinger er konfigurert";
+App::$strings["Addon Settings"] = "Instillinger for tillegg";
+App::$strings["Please save/submit changes to any panel before opening another."] = "Husk å lagre endringene i ett panel før du åpner andre.";
+App::$strings["Channel Manager Settings"] = "";
+App::$strings["Privacy settings updated."] = "Personverninnstillingene ble oppdatert.";
+App::$strings["Only those you specifically allow"] = "Bare de du spesifikt tillater";
+App::$strings["Approved connections"] = "Godkjente forbindelser";
+App::$strings["Any connections"] = "Enhver forbindelse";
+App::$strings["Anybody on this website"] = "Enhver ved dette nettstedet";
+App::$strings["Anybody in this network"] = "Enhver i dette nettverket";
+App::$strings["Anybody authenticated"] = "Enhver som er autentisert";
+App::$strings["Anybody on the internet"] = "Enhver på Internett";
+App::$strings["Advise: set to \"Anybody on the internet\" and use privacy groups to restrict access"] = "";
+App::$strings["Privacy Settings"] = "Personverninnstillinger";
+App::$strings["Advanced configuration"] = "";
+App::$strings["Proceed with caution"] = "";
+App::$strings["Changing advanced configuration settings can impact your, and your contacts channels functionality and security."] = "";
+App::$strings["Accept the risk and continue"] = "";
+App::$strings["Automatically approve new contacts"] = "Automatisk godkjenn nye kontakter";
+App::$strings["Opt-out of search engine indexing"] = "Ikke la søkemotorer indeksere denne kanalen";
+App::$strings["Group actor"] = "";
+App::$strings["Allow this channel to act as a forum"] = "";
+App::$strings["Accept all messages which mention you"] = "Godta alle meldinger som nevner deg";
+App::$strings["This setting bypasses normal permissions"] = "Denne instillingen overstyrer vanlig tilgangskontroll";
+App::$strings["Accept unsolicited comments for moderation"] = "Godta kommentarer fra fremmede for moderering";
+App::$strings["Otherwise they will be silently dropped"] = "Ellers vil de avvises uten varsel";
+App::$strings["Enable OCAP access"] = "Slå på OCAP tilgangskontroll";
+App::$strings["Grant limited posts the right to access linked private media"] = "Gi begrensede innlegg tilgang til private media som er lenket fra dem";
+App::$strings["Events Settings"] = "";
+App::$strings["Photos Settings"] = "";
+App::$strings["Conversation Settings"] = "";
App::$strings["Please select a channel role"] = "Velg en kanalrolle";
App::$strings["Your channel address is"] = "Din kanaladresse er";
App::$strings["Your files/photos are accessible via WebDAV at"] = "Dine filer og foto er tilgjengelig via WebDAV på";
@@ -2468,7 +3343,6 @@ App::$strings["You are poked/prodded/etc. in a post"] = "Du ble prikket/oppildne
App::$strings["Someone likes your post/comment"] = "";
App::$strings["Show visual notifications including:"] = "Vis visuelle varslinger om:";
App::$strings["Unseen stream activity"] = "Ny aktivitet i nettverksstrømmen";
-App::$strings["Unseen channel activity"] = "Ny kanalaktivitet";
App::$strings["Unseen private messages"] = "Nye private meldinger";
App::$strings["Recommended"] = "Anbefalt";
App::$strings["Upcoming events"] = "Kommende hendelser";
@@ -2478,10 +3352,8 @@ App::$strings["Not available in all themes"] = "Ikke tilgjengelig i alle temaer"
App::$strings["System (personal) notifications"] = "System (personlige) varslinger";
App::$strings["System info messages"] = "System infomeldinger";
App::$strings["System critical alerts"] = "System kritiske varsel";
-App::$strings["New connections"] = "Nye forbindelser";
App::$strings["System Registrations"] = "Systemregistreringer";
App::$strings["Unseen shared files"] = "Nye delte filer";
-App::$strings["Unseen public stream activity"] = "Ny aktivitet i den offentlige strømmen";
App::$strings["Unseen likes and dislikes"] = "Nye liker/ikke liker";
App::$strings["Unseen forum posts"] = "Ny forumpost";
App::$strings["Email notification hub (hostname)"] = "Hub for epostvarsler (tjenernavn)";
@@ -2503,23 +3375,10 @@ App::$strings["This website expires after %d days."] = "";
App::$strings["This website does not expire imported content."] = "Dette nettstedet sletter ikke importert innhold.";
App::$strings["The website limit takes precedence if lower than your limit."] = "Nettstedets grense vil gjelde dersom den er lavere enn din grense.";
App::$strings["Words one per line or #tags, \$categories, /patterns/, lang=xx, lang!=xx - leave blank to import all posts"] = "Ett ord per linje eller #emneknagger, \$kategorier, /mønster/, lang=xx, lang!=xx - la være tomt for å importere alle innlegg";
-App::$strings["No feature settings configured"] = "Ingen funksjonsinnstillinger er konfigurert";
-App::$strings["Addon Settings"] = "Instillinger for tillegg";
-App::$strings["Please save/submit changes to any panel before opening another."] = "Husk å lagre endringene i ett panel før du åpner andre.";
-App::$strings["Editor Settings"] = "Instillinger for redigering";
-App::$strings["Photos Settings"] = "";
App::$strings["Default profile for new contacts"] = "";
App::$strings["Profiles Settings"] = "";
-App::$strings["Max height of content (in pixels)"] = "Maks høyde for innhold (i piksler)";
-App::$strings["Click to expand content exceeding this height"] = "Klikk for å vise hele innlegg som overskrider denne grensen";
-App::$strings["Stream Settings"] = "Instillinger for tidslinjen";
-App::$strings["Directory Settings"] = "";
-App::$strings["Personal menu to display in your channel pages"] = "Personlig meny som kan vises på dine kanalsider";
-App::$strings["Channel Home Settings"] = "";
-App::$strings["Calendar Settings"] = "";
-App::$strings["Conversation Settings"] = "";
-App::$strings["Additional Features"] = "Ekstra funksjoner";
App::$strings["Connections Settings"] = "";
+App::$strings["Calendar Settings"] = "";
App::$strings["Password is required"] = "Navn er påkrevd";
App::$strings["The provided password is not correct"] = "";
App::$strings["Account Multi-Factor Authentication"] = "Tofaktorautentisering";
@@ -2532,904 +3391,35 @@ App::$strings["Enable Multi-Factor Authentication"] = "Mislykket autentisering";
App::$strings["Logging in will require you to be in possession of your smartphone"] = "";
App::$strings["Your account password"] = "Ditt nye passord er";
App::$strings["Test"] = "Sjekk koden";
-App::$strings["Events Settings"] = "";
-App::$strings["Not valid email."] = "Ikke gyldig e-post.";
-App::$strings["Protected email address. Cannot change to that email."] = "Beskyttet e-postadresse. Kan ikke endre til den e-postadressen.";
-App::$strings["System failure storing new email. Please try again."] = "Systemfeil ved lagring av ny e-post. Vennligst prøv igjen.";
-App::$strings["Password verification failed."] = "Passordbekreftelsen mislyktes.";
-App::$strings["Passwords do not match. Password unchanged."] = "Passordene stemmer ikke overens. Passord uforandret.";
-App::$strings["Empty passwords are not allowed. Password unchanged."] = "Tomme passord er ikke tillatt. Passord uforandret.";
-App::$strings["Password changed."] = "Passord endret.";
-App::$strings["Password update failed. Please try again."] = "Passord oppdatering mislyktes. Vennligst prøv igjen.";
-App::$strings["Account Settings"] = "Kontoinnstillinger";
-App::$strings["Current Password"] = "Nåværende passord";
-App::$strings["Enter New Password"] = "Skriv nytt passord";
-App::$strings["Confirm New Password"] = "Bekreft nytt passord";
-App::$strings["Leave password fields blank unless changing"] = "La passordfeltene stå blanke om det ikke skal endres";
-App::$strings["Multi-Factor Authentication"] = "Flerfaktorautentisering";
-App::$strings["DId2 or Email Address:"] = "DId2 eller epostadresse:";
-App::$strings["Remove this account including all its channels"] = "Slett denne kontoen inkludert alle dens kanaler";
-App::$strings["Privacy settings updated."] = "Personverninnstillingene ble oppdatert.";
-App::$strings["Only those you specifically allow"] = "Bare de du spesifikt tillater";
-App::$strings["Approved connections"] = "Godkjente forbindelser";
-App::$strings["Any connections"] = "Enhver forbindelse";
-App::$strings["Anybody on this website"] = "Enhver ved dette nettstedet";
-App::$strings["Anybody in this network"] = "Enhver i dette nettverket";
-App::$strings["Anybody authenticated"] = "Enhver som er autentisert";
-App::$strings["Anybody on the internet"] = "Enhver på Internett";
-App::$strings["Advise: set to \"Anybody on the internet\" and use privacy groups to restrict access"] = "";
-App::$strings["Privacy Settings"] = "Personverninnstillinger";
-App::$strings["Advanced configuration"] = "";
-App::$strings["Proceed with caution"] = "";
-App::$strings["Changing advanced configuration settings can impact your, and your contacts channels functionality and security."] = "";
-App::$strings["Accept the risk and continue"] = "";
-App::$strings["Automatically approve new contacts"] = "Automatisk godkjenn nye kontakter";
-App::$strings["Opt-out of search engine indexing"] = "Ikke la søkemotorer indeksere denne kanalen";
-App::$strings["Group actor"] = "";
-App::$strings["Allow this channel to act as a forum"] = "";
-App::$strings["Accept all messages which mention you"] = "Godta alle meldinger som nevner deg";
-App::$strings["This setting bypasses normal permissions"] = "Denne instillingen overstyrer vanlig tilgangskontroll";
-App::$strings["Accept unsolicited comments for moderation"] = "Godta kommentarer fra fremmede for moderering";
-App::$strings["Otherwise they will be silently dropped"] = "Ellers vil de avvises uten varsel";
-App::$strings["Enable OCAP access"] = "Slå på OCAP tilgangskontroll";
-App::$strings["Grant limited posts the right to access linked private media"] = "Gi begrensede innlegg tilgang til private media som er lenket fra dem";
-App::$strings["%s - (Experimental)"] = "%s - (Eksperimentelt)";
-App::$strings["Display Settings"] = "Visningsinnstillinger";
-App::$strings["Theme Settings"] = "Temainnstillinger";
-App::$strings["Custom Theme Settings"] = "Tilpassede temainnstillinger";
-App::$strings["Content Settings"] = "Innholdsinnstillinger";
-App::$strings["Display Theme:"] = "Visningstema:";
-App::$strings["Select scheme"] = "Velg skjema";
-App::$strings["Preload images before rendering the page"] = "Last inn bildene før gjengivelsen av siden";
-App::$strings["The subjective page load time will be longer but the page will be ready when displayed"] = "Den personlige opplevelsen av lastetiden vil være lenger, men siden vil være klar når den vises";
-App::$strings["Enable user zoom on mobile devices"] = "Skru på brukerstyrt zoom på mobile enheter";
-App::$strings["Update browser every xx seconds"] = "Oppdater nettleser hvert xx sekunder";
-App::$strings["Minimum of 10 seconds, no maximum"] = "Minimum 10 sekunder, ikke noe maksimum";
-App::$strings["Maximum number of conversations to load at any time:"] = "Maksimalt antall samtaler å laste samtidig:";
-App::$strings["Maximum of 30 items"] = "";
-App::$strings["Show emoticons (smilies) as images"] = "Vis emoticons (smilefjes) som bilder";
-App::$strings["Link post titles to source"] = "Lenk innleggets tittel til kilden";
-App::$strings["New Member Links"] = "Lenker for nye medlemmer";
-App::$strings["Display new member quick links menu"] = "Vis hurtiglenker for nye medlemmer";
-App::$strings["Channel Manager Settings"] = "";
-App::$strings["Change Order of Pinned Navbar Apps"] = "";
-App::$strings["Change Order of App Tray Apps"] = "";
-App::$strings["Use arrows to move the corresponding app left (top) or right (bottom) in the navbar"] = "";
-App::$strings["Use arrows to move the corresponding app up or down in the app tray"] = "";
-App::$strings["Like/Dislike"] = "Liker/Liker ikke";
-App::$strings["This action is restricted to members."] = "Denne handlingen er begrenset til medlemmer.";
-App::$strings["Please <a href=\"rmagic\">login with your \$Projectname ID</a> or <a href=\"register\">register as a new \$Projectname member</a> to continue."] = "Vennligst <a href=\"rmagic\">logg inn med din \$Projectname ID</a> eller <a href=\"register\">registrer deg som et nytt \$Projectname-medlem</a> for å fortsette.";
-App::$strings["Invalid request."] = "Ugyldig forespørsel.";
-App::$strings["thing"] = "ting";
-App::$strings["Channel unavailable."] = "Kanalen er utilgjengelig.";
-App::$strings["Previous action reversed."] = "Forrige handling er omgjort.";
-App::$strings["profile"] = "Profil";
-App::$strings["%1\$s is attending %2\$s's %3\$s"] = "%1\$s deltar på %2\$ss %3\$s";
-App::$strings["%1\$s is not attending %2\$s's %3\$s"] = "%1\$s deltar ikke på %2\$ss %3\$s";
-App::$strings["%1\$s may attend %2\$s's %3\$s"] = "%1\$s deltar kanskje på %2\$ss %3\$s";
-App::$strings["Action completed."] = "Handling ferdig.";
-App::$strings["Thank you."] = "Tusen takk.";
-App::$strings["Connection added."] = "";
-App::$strings["Away"] = "Borte";
-App::$strings["Online"] = "Online";
-App::$strings["Blocked accounts"] = "";
-App::$strings["Expired accounts"] = "";
-App::$strings["Expiring accounts"] = "";
-App::$strings["Message queues"] = "Meldingskøer";
-App::$strings["Your software should be updated"] = "Programvaren bør oppdateres";
-App::$strings["Summary"] = "Sammendrag";
-App::$strings["Registered accounts"] = "Registrerte kontoer";
-App::$strings["Pending registrations"] = "Ventende registreringer";
-App::$strings["Registered channels"] = "Registrerte kanaler";
-App::$strings["Version"] = "Versjon";
-App::$strings["Repository version (master)"] = "";
-App::$strings["Repository version (dev)"] = "";
-App::$strings["Unknown App"] = "";
-App::$strings["Authorize"] = "";
-App::$strings["Do you authorize the app %s to access your channel data?"] = "";
-App::$strings["This page is available only to site members"] = "";
-App::$strings["Welcome"] = "Velkommen";
-App::$strings["What would you like to do?"] = "";
-App::$strings["Please bookmark this page if you would like to return to it in the future"] = "";
-App::$strings["Upload a profile photo"] = "";
-App::$strings["Upload a cover photo"] = "";
-App::$strings["Edit your default profile"] = "";
-App::$strings["View friend suggestions"] = "Vis venneforslag";
-App::$strings["View the channel directory"] = "";
-App::$strings["View/edit your channel settings"] = "";
-App::$strings["View the site or project documentation"] = "";
-App::$strings["Visit your channel homepage"] = "";
-App::$strings["View your connections and/or add somebody whose address you already know"] = "";
-App::$strings["View your personal stream (this may be empty until you add some connections)"] = "";
-App::$strings["View the public stream. Warning: this content is not moderated"] = "";
-App::$strings["Create a new channel"] = "Lag en ny kanal";
-App::$strings["Current Channel"] = "Gjeldende kanal";
-App::$strings["Switch to one of your channels by selecting it."] = "Bytt til en av dine kanaler ved å velge den.";
-App::$strings["Default Channel"] = "Standardkanal";
-App::$strings["Make Default"] = "Gjør til standard";
-App::$strings["%d new introductions"] = "%d nye introduksjoner";
-App::$strings["Delegated Channel"] = "Delegert kanal";
-App::$strings["Items tagged with: %s"] = "Elementer merket med: %s";
-App::$strings["Search results for: %s"] = "Søkeresultater for: %s";
-App::$strings["Channel name changes are not allowed within 48 hours of changing the account password."] = "";
-App::$strings["Change channel nickname/address"] = "";
-App::$strings["Any/all connections on other networks will be lost!"] = "";
-App::$strings["New channel address"] = "";
-App::$strings["Rename Channel"] = "";
-App::$strings["Calendar entries imported."] = "Kalenderhendelsene er importert.";
-App::$strings["No calendar entries found."] = "Ingen kalenderhendelser funnet.";
-App::$strings["Event title"] = "Tittel på hendelse";
-App::$strings["Start date and time"] = "Startdato og tidspunkt";
-App::$strings["End date and time"] = "";
-App::$strings["Timezone:"] = "Tidssone";
-App::$strings["Month"] = "måned";
-App::$strings["Week"] = "uke";
-App::$strings["Day"] = "dag";
-App::$strings["List month"] = "";
-App::$strings["List week"] = "";
-App::$strings["List day"] = "";
-App::$strings["More"] = "";
-App::$strings["Less"] = "";
-App::$strings["Select calendar"] = "";
-App::$strings["Channel Calendars"] = "";
-App::$strings["CalDAV Calendars"] = "";
-App::$strings["Delete all"] = "";
-App::$strings["Sorry! Editing of recurrent events is not yet implemented."] = "";
-App::$strings["Could not fetch calendar resource. The selected calendar might be disabled."] = "";
-App::$strings["Default Calendar"] = "";
-App::$strings["Default Addressbook"] = "";
-App::$strings["Invalid abook_id"] = "";
-App::$strings["View profile"] = "Vis profil";
-App::$strings["Select a role for this contact"] = "";
-App::$strings["Contact roles"] = "";
-App::$strings["Roles"] = "";
-App::$strings["Compare permissions"] = "";
-App::$strings["Permission"] = "";
-App::$strings["Privacy groups"] = "Personverngrupper";
-App::$strings["Content filter"] = "";
-App::$strings["Contact updated"] = "";
-App::$strings["Contact update failed"] = "";
-App::$strings["Refresh succeeded"] = "";
-App::$strings["Refresh failed"] = "Forny";
-App::$strings["Block status updated"] = "";
-App::$strings["Block failed"] = "";
-App::$strings["Ignore status updated"] = "";
-App::$strings["Ignore failed"] = "";
-App::$strings["Archive status updated"] = "";
-App::$strings["Archive failed"] = "";
-App::$strings["Hide status updated"] = "";
-App::$strings["Hide failed"] = "";
-App::$strings["Contact removed"] = "";
-App::$strings["Delete failed"] = "";
-App::$strings["Refresh"] = "Forny";
-App::$strings["Refetch contact info"] = "";
-App::$strings["vcard"] = "";
-App::$strings["Suggestions"] = "Forslag";
-App::$strings["See more..."] = "Se mer...";
-App::$strings["Share This"] = "Del dette";
-App::$strings["View %s's profile - %s"] = "Vis %s sin profil - %s";
-App::$strings["Don't show"] = "Ikke vis";
-App::$strings["Add new group"] = "";
-App::$strings["Remove term"] = "Fjern begrep";
-App::$strings["photo/image"] = "foto/bilde";
-App::$strings["Read mode"] = "";
-App::$strings["Edit mode"] = "";
-App::$strings["Editing"] = "";
-App::$strings["Saving"] = "";
-App::$strings["Saved"] = "";
-App::$strings["Suggested Chatrooms"] = "Foreslåtte chatrom";
-App::$strings["Bookmarked Chatrooms"] = "Bokmerkede chatrom";
-App::$strings["Tasks"] = "Oppgaver";
-App::$strings["Public and restricted messages"] = "";
-App::$strings["Direct messages"] = "Direktemeldinger";
-App::$strings["Starred messages"] = "";
-App::$strings["Notices"] = "Varsel";
-App::$strings["No messages"] = "Ingen meldinger";
-App::$strings["Unseen"] = "Ulest";
-App::$strings["Filter by name or address"] = "Filtrer etter navn eller adresse";
-App::$strings["Click to show more"] = "";
-App::$strings["__ctx:widget__ Activity"] = "aktivitet";
-App::$strings["App Collections"] = "Appsamlinger";
-App::$strings["Installed apps"] = "Installerte apper";
-App::$strings["Toggle post editor"] = "Vis redigering av innlegg";
-App::$strings["Toggle personal notes"] = "";
-App::$strings["Channel activities"] = "";
-App::$strings["Archives"] = "Arkiv";
-App::$strings["No recent activities"] = "Nylig aktivitet";
-App::$strings["__ctx:noun__ new connection"] = array(
- 0 => "ny forbindelse",
- 1 => "nye forbindelser",
-);
-App::$strings["__ctx:noun__ notice"] = array(
- 0 => "varsel",
- 1 => "varsel",
-);
-App::$strings["Account settings"] = "Kontoinnstillinger";
-App::$strings["Channel settings"] = "Kanalinnstillinger";
-App::$strings["Privacy settings"] = "Personverninnstillinger";
-App::$strings["Display settings"] = "Visningsinnstillinger";
-App::$strings["Manage locations"] = "";
-App::$strings["Rating Tools"] = "Vurderingsverktøy";
-App::$strings["Rate Me"] = "Vurder meg";
-App::$strings["View Ratings"] = "Vis vurderinger";
-App::$strings["App Categories"] = "Appkategorier";
-App::$strings["Direct Messages"] = "Direktemeldinger";
-App::$strings["Show direct (private) messages"] = "Vis direktemeldinger (private meldinger)";
-App::$strings["Events"] = "Hendelser";
-App::$strings["Show posts that include events"] = "";
-App::$strings["Polls"] = "Spørreskjema";
-App::$strings["Show posts that include polls"] = "Vis innlegg som inneholder spørreskjema";
-App::$strings["Show posts related to the %s privacy group"] = "";
-App::$strings["Show my privacy groups"] = "";
-App::$strings["Show posts to this forum"] = "";
-App::$strings["Show forums"] = "";
-App::$strings["Starred Posts"] = "";
-App::$strings["Show posts that I have starred"] = "";
-App::$strings["Personal Posts"] = "";
-App::$strings["Show posts that mention or involve me"] = "";
-App::$strings["Show posts that I have filed to %s"] = "";
-App::$strings["Show filed post categories"] = "";
-App::$strings["Panel search"] = "";
-App::$strings["Filter by name"] = "Filtrer etter navn";
-App::$strings["Remove active filter"] = "";
-App::$strings["Stream Filters"] = "Filtere for tidslinjen";
-App::$strings["New network activity notifications"] = "";
-App::$strings["Network stream"] = "";
-App::$strings["Mark all notifications read"] = "";
-App::$strings["Show new posts only"] = "";
-App::$strings["New home activity notifications"] = "";
-App::$strings["Home stream"] = "";
-App::$strings["Mark all notifications seen"] = "";
-App::$strings["New direct messages notifications"] = "Varsel om nye direktemeldinger";
-App::$strings["Direct messages stream"] = "Strøm for direktemeldinger";
-App::$strings["New events notifications"] = "";
-App::$strings["View events"] = "";
-App::$strings["Mark all events seen"] = "Merk alle hendelser som sett";
-App::$strings["New connections notifications"] = "";
-App::$strings["View all connections"] = "";
-App::$strings["New files notifications"] = "";
-App::$strings["View all notices"] = "";
-App::$strings["Mark all notices seen"] = "";
-App::$strings["Registrations"] = "";
-App::$strings["New registrations notifications"] = "";
-App::$strings["New public stream notifications"] = "";
-App::$strings["Public stream"] = "";
-App::$strings["Sorry, you have got no notifications at the moment"] = "";
-App::$strings["Add new guest"] = "";
-App::$strings["Add new role"] = "";
-App::$strings["Role members"] = "";
-App::$strings["Profile Creation"] = "Oppretting av profil";
-App::$strings["Upload profile photo"] = "Last opp profilbilde";
-App::$strings["Upload cover photo"] = "Last opp omslagsbilde";
-App::$strings["Find and Connect with others"] = "Finn andre";
-App::$strings["View the directory"] = "Se i katalogen";
-App::$strings["Manage your connections"] = "Behandle forbindelser";
-App::$strings["Communicate"] = "Kommuniser";
-App::$strings["View your channel homepage"] = "Vis kanalens hjemmeside";
-App::$strings["View your network stream"] = "Vis nettverksstrømmen";
-App::$strings["Documentation"] = "Dokumentasjon";
-App::$strings["Missing Features?"] = "Noe som mangler?";
-App::$strings["Pin apps to navigation bar"] = "Fest apper til navigasjonslinjen";
-App::$strings["Install more apps"] = "Legg til flere apper";
-App::$strings["View public stream"] = "";
-App::$strings["Add New Connection"] = "Legg til ny forbindelse";
-App::$strings["Enter channel address"] = "Skriv kanaladressen";
-App::$strings["Examples: bob@example.com, https://example.com/barbara"] = "Eksempel: ola@eksempel.no, https://eksempel.no/kari";
-App::$strings["Member registrations waiting for confirmation"] = "";
-App::$strings["Inspect queue"] = "Inspiser kø";
-App::$strings["Queueworker"] = "";
-App::$strings["DB updates"] = "Databaseoppdateringer";
-App::$strings["Addon Features"] = "";
-App::$strings["Overview"] = "";
-App::$strings["Select Channel"] = "";
-App::$strings["Read-write"] = "";
-App::$strings["Read-only"] = "";
-App::$strings["Channel Calendar"] = "";
-App::$strings["Shared CalDAV Calendars"] = "";
-App::$strings["Share this calendar"] = "";
-App::$strings["Calendar name and color"] = "";
-App::$strings["Create new CalDAV calendar"] = "";
-App::$strings["Calendar Name"] = "";
-App::$strings["Calendar Tools"] = "";
-App::$strings["Import calendar"] = "";
-App::$strings["Select a calendar to import to"] = "";
-App::$strings["Addressbooks"] = "";
-App::$strings["Addressbook name"] = "";
-App::$strings["Create new addressbook"] = "";
-App::$strings["Addressbook Name"] = "";
-App::$strings["Addressbook Tools"] = "";
-App::$strings["Import addressbook"] = "";
-App::$strings["Select an addressbook to import to"] = "";
-App::$strings["Commented Date"] = "Sist kommentert";
-App::$strings["Order by last commented date"] = "Sorter etter dato for siste kommentar";
-App::$strings["Posted Date"] = "Innleggsdato";
-App::$strings["Order by last posted date"] = "Sorter etter dato innlegg ble postet";
-App::$strings["Date Unthreaded"] = "Utrådet";
-App::$strings["Order unthreaded by date"] = "Sorter innlegg og kommentarer uavhengig av hverandre etter dato";
-App::$strings["Stream Order"] = "Sortering av innlegg";
-App::$strings["Chat Members"] = "";
-App::$strings["Source channel not found."] = "Fant ikke kildekanalen.";
-App::$strings["generic profile image"] = "";
-App::$strings["random geometric pattern"] = "";
-App::$strings["monster face"] = "";
-App::$strings["computer generated face"] = "";
-App::$strings["retro arcade style face"] = "";
-App::$strings["Hub default profile photo"] = "";
-App::$strings["Information"] = "";
-App::$strings["Libravatar addon is installed, too. Please disable Libravatar addon or this Gravatar addon.<br>The Libravatar addon will fall back to Gravatar if nothing was found at Libravatar."] = "";
-App::$strings["Save Settings"] = "";
-App::$strings["Default avatar image"] = "";
-App::$strings["Select default avatar image if none was found at Gravatar. See README"] = "";
-App::$strings["Rating of images"] = "";
-App::$strings["Select the appropriate avatar rating for your site. See README"] = "";
-App::$strings["Gravatar settings updated."] = "";
-App::$strings["Send your identity to all websites"] = "";
-App::$strings["Send ZID"] = "";
-App::$strings["An account has been created for you."] = "";
-App::$strings["Authentication successful but rejected: account creation is disabled."] = "";
-App::$strings["DB Cleanup Failure"] = "";
-App::$strings["[cart] Item Added"] = "";
-App::$strings["Order already checked out."] = "";
-App::$strings["Drop database tables when uninstalling."] = "";
-App::$strings["Cart Settings"] = "";
-App::$strings["Shop"] = "";
-App::$strings["Profile Unavailable."] = "Profilen er ikke tilgjengelig.";
-App::$strings["Order Not Found"] = "";
-App::$strings["You must be logged into the Grid to shop."] = "";
-App::$strings["Invalid channel"] = "Ugyldig kanal";
-App::$strings["Order not found."] = "";
-App::$strings["Access denied."] = "Ingen tilgang";
-App::$strings["No Order Found"] = "";
-App::$strings["An unknown error has occurred Please start again."] = "";
-App::$strings["Requirements not met."] = "";
-App::$strings["Review your order and complete any needed requirements."] = "";
-App::$strings["Invalid Payment Type. Please start again."] = "";
-App::$strings["Order not found"] = "";
-App::$strings["Enable Order/Item Options"] = "";
-App::$strings["Label"] = "";
-App::$strings["Instructions"] = "";
-App::$strings["Enable Subscription Management Module"] = "";
-App::$strings["Cannot include subscription items with different terms in the same order."] = "";
-App::$strings["Select Subscription to Edit"] = "";
-App::$strings["Edit Subscriptions"] = "";
-App::$strings["Subscription SKU"] = "";
-App::$strings["Catalog Description"] = "";
-App::$strings["Subscription available for purchase."] = "";
-App::$strings["Maximum active subscriptions to this item per account."] = "";
-App::$strings["Subscription price."] = "";
-App::$strings["Quantity"] = "";
-App::$strings["Term"] = "";
-App::$strings["Enable Manual Cart Module"] = "";
-App::$strings["New Sku"] = "";
-App::$strings["Cannot save edits to locked item."] = "";
-App::$strings["Changes Locked"] = "";
-App::$strings["Item available for purchase."] = "";
-App::$strings["Price"] = "";
-App::$strings["Photo URL"] = "";
-App::$strings["Enable Paypal Button Module (API-v2)"] = "";
-App::$strings["Use Production Key"] = "";
-App::$strings["Paypal Sandbox Client Key"] = "";
-App::$strings["Paypal Sandbox Secret Key"] = "";
-App::$strings["Paypal Production Client Key"] = "";
-App::$strings["Paypal Production Secret Key"] = "";
-App::$strings["Paypal button payments are not enabled."] = "";
-App::$strings["Paypal button payments are not properly configured. Please choose another payment option."] = "";
-App::$strings["Enable Paypal Button Module"] = "";
-App::$strings["Enable Hubzilla Services Module"] = "";
-App::$strings["SKU not found."] = "";
-App::$strings["Invalid Activation Directive."] = "";
-App::$strings["Invalid Deactivation Directive."] = "";
-App::$strings["Add to this privacy group"] = "";
-App::$strings["Set user service class"] = "";
-App::$strings["You must be using a local account to purchase this service."] = "";
-App::$strings["Add buyer to privacy group"] = "";
-App::$strings["Add buyer as connection"] = "";
-App::$strings["Set Service Class"] = "";
-App::$strings["Access Denied."] = "Ingen tilgang";
-App::$strings["Access Denied"] = "Ingen tilgang";
-App::$strings["Invalid Item"] = "Ugyldig element.";
-App::$strings["Error: order mismatch. Please try again."] = "";
-App::$strings["Manual payments are not enabled."] = "";
-App::$strings["Finished"] = "";
-App::$strings["Enable Test Catalog"] = "";
-App::$strings["Enable Manual Payments"] = "";
-App::$strings["Base Merchant Currency"] = "";
-App::$strings["Edit Article"] = "";
-App::$strings["View Articles"] = "";
-App::$strings["Add Article"] = "";
-App::$strings["Hubzilla File Storage Import"] = "";
-App::$strings["This will import all your cloud files from another server."] = "";
-App::$strings["Hubzilla Server base URL"] = "";
-App::$strings["Since modified date yyyy-mm-dd"] = "";
-App::$strings["Until modified date yyyy-mm-dd"] = "";
-App::$strings["View Larger"] = "";
-App::$strings["Tile Server URL"] = "";
-App::$strings["A list of <a href=\"http://wiki.openstreetmap.org/wiki/TMS\" target=\"_blank\">public tile servers</a>"] = "";
-App::$strings["Nominatim (reverse geocoding) Server URL"] = "";
-App::$strings["A list of <a href=\"http://wiki.openstreetmap.org/wiki/Nominatim\" target=\"_blank\">Nominatim servers</a>"] = "";
-App::$strings["Default zoom"] = "";
-App::$strings["The default zoom level. (1:world, 18:highest, also depends on tile server)"] = "";
-App::$strings["Include marker on map"] = "";
-App::$strings["Include a marker on the map."] = "";
-App::$strings["WYSIWYG status editor"] = "";
-App::$strings["WYSIWYG Status App"] = "";
-App::$strings["WYSIWYG Status"] = "";
-App::$strings["Network error"] = "";
-App::$strings["API error"] = "";
-App::$strings["Unknown issue"] = "";
-App::$strings["Unable to retrieve email address from remote identity provider"] = "";
-App::$strings["Unable to login using email address "] = "";
-App::$strings["Social Authentication using your social media account"] = "";
-App::$strings["This app enables one or more social provider sign-in buttons on the login page."] = "";
-App::$strings["Add an identity provider"] = "";
-App::$strings["Enable "] = "Skru på";
-App::$strings["Key"] = "";
-App::$strings["Word"] = "";
-App::$strings["Secret"] = "";
-App::$strings["Add a custom provider"] = "";
-App::$strings["Remove an identity provider"] = "";
-App::$strings["Social authentication"] = "";
-App::$strings["Error while saving provider settings"] = "";
-App::$strings["Custom provider already exists"] = "";
-App::$strings["Social authentication settings saved."] = "";
-App::$strings["You are now authenticated to pumpio."] = "";
-App::$strings["return to the featured settings page"] = "tilbake til funksjonsinnstillinger";
-App::$strings["Post to Pump.io"] = "";
-App::$strings["Pump.io Settings saved."] = "";
-App::$strings["Pump.io servername"] = "";
-App::$strings["Without \"http://\" or \"https://\""] = "";
-App::$strings["Pump.io username"] = "";
-App::$strings["Without the servername"] = "";
-App::$strings["You are not authenticated to pumpio"] = "";
-App::$strings["(Re-)Authenticate your pump.io connection"] = "";
-App::$strings["Post to pump.io by default"] = "";
-App::$strings["Should posts be public"] = "";
-App::$strings["Mirror all public posts"] = "";
-App::$strings["Pump.io Crosspost Connector"] = "";
-App::$strings["Block Completely"] = "Blokker helt";
-App::$strings["superblock settings updated"] = "innstillingene til superblokk ble oppdatert";
-App::$strings["Currently blocked"] = "Blokkert for øyeblikket";
-App::$strings["No channels currently blocked"] = "Ingen kanaler er blokkert i øyeblikket";
-App::$strings["Gallery"] = "";
-App::$strings["Photo Gallery"] = "";
-App::$strings["You're welcome."] = "";
-App::$strings["Ah shucks..."] = "";
-App::$strings["Don't mention it."] = "";
-App::$strings["&lt;blush&gt;"] = "";
-App::$strings["System defaults:"] = "";
-App::$strings["Preferred Clipart IDs"] = "";
-App::$strings["List of preferred clipart ids. These will be shown first."] = "";
-App::$strings["Default Search Term"] = "Standard søkeord";
-App::$strings["The default search term. These will be shown second."] = "Standard søkeord. Disse vil vises som nummer to.";
-App::$strings["Return After"] = "";
-App::$strings["Page to load after image selection."] = "";
-App::$strings["Profile List"] = "";
-App::$strings["Order of Preferred"] = "";
-App::$strings["Sort order of preferred clipart ids."] = "";
-App::$strings["Newest first"] = "";
-App::$strings["As entered"] = "";
-App::$strings["Order of other"] = "";
-App::$strings["Sort order of other clipart ids."] = "";
-App::$strings["Most downloaded first"] = "";
-App::$strings["Most liked first"] = "";
-App::$strings["Preferred IDs Message"] = "";
-App::$strings["Message to display above preferred results."] = "";
-App::$strings["Uploaded by: "] = "";
-App::$strings["Drawn by: "] = "";
-App::$strings["Use this image"] = "";
-App::$strings["Or select from a free OpenClipart.org image:"] = "";
-App::$strings["Search Term"] = "Søkeord";
-App::$strings["Unknown error. Please try again later."] = "";
-App::$strings["Profile photo updated successfully."] = "";
-App::$strings["Hubzilla Directory Stats"] = "";
-App::$strings["Total Hubs"] = "";
-App::$strings["Hubzilla Hubs"] = "";
-App::$strings["Friendica Hubs"] = "";
-App::$strings["Diaspora Pods"] = "";
-App::$strings["Hubzilla Channels"] = "";
-App::$strings["Friendica Channels"] = "";
-App::$strings["Diaspora Channels"] = "";
-App::$strings["Aged 35 and above"] = "";
-App::$strings["Aged 34 and under"] = "";
-App::$strings["Average Age"] = "";
-App::$strings["Known Chatrooms"] = "";
-App::$strings["Known Tags"] = "";
-App::$strings["Please note Diaspora and Friendica statistics are merely those **this directory** is aware of, and not all those known in the network. This also applies to chatrooms,"] = "";
-App::$strings["Please contact your site administrator.<br />The provided API URL is not valid."] = "Den oppgitte URLen for APIet er ikke gyldig. Kontakt serverens administrator.";
-App::$strings["We could not contact the GNU social API with the Path you entered."] = "";
-App::$strings["GNU social settings updated."] = "";
-App::$strings["Globally Available GNU social OAuthKeys"] = "";
-App::$strings["There are preconfigured OAuth key pairs for some GNU social servers available. If you are using one of them, please use these credentials.<br />If not feel free to connect to any other GNU social instance (see below)."] = "";
-App::$strings["Provide your own OAuth Credentials"] = "";
-App::$strings["No consumer key pair for GNU social found. Register your Hubzilla Account as an desktop client on your GNU social account, copy the consumer key pair here and enter the API base root.<br />Before you register your own OAuth key pair ask the administrator if there is already a key pair for this Hubzilla installation at your favourite GNU social installation."] = "";
-App::$strings["OAuth Consumer Key"] = "";
-App::$strings["OAuth Consumer Secret"] = "";
-App::$strings["Base API Path"] = "";
-App::$strings["Remember the trailing /"] = "";
-App::$strings["GNU social application name"] = "";
-App::$strings["To connect to your GNU social account click the button below to get a security code from GNU social which you have to copy into the input box below and submit the form. Only your <strong>public</strong> posts will be posted to GNU social."] = "";
-App::$strings["Log in with GNU social"] = "";
-App::$strings["Copy the security code from GNU social here"] = "";
-App::$strings["Cancel Connection Process"] = "";
-App::$strings["Current GNU social API is"] = "";
-App::$strings["Cancel GNU social Connection"] = "";
-App::$strings["Currently connected to: "] = "";
-App::$strings["<strong>Note</strong>: Due your privacy settings (<em>Hide your profile details from unknown viewers?</em>) the link potentially included in public postings relayed to GNU social will lead the visitor to a blank page informing the visitor that the access to your profile has been restricted."] = "";
-App::$strings["Post to GNU social by default"] = "";
-App::$strings["If enabled your public postings will be posted to the associated GNU-social account by default"] = "";
-App::$strings["Clear OAuth configuration"] = "";
-App::$strings["GNU-Social Crosspost Connector"] = "";
-App::$strings["Post to GNU social"] = "";
-App::$strings["API URL"] = "";
-App::$strings["Application name"] = "";
-App::$strings["Post to Livejournal"] = "";
-App::$strings["Posted by"] = "";
-App::$strings["Source"] = "";
-App::$strings["Livejournal username"] = "";
-App::$strings["Livejournal password"] = "";
-App::$strings["Post to Livejournal by default"] = "";
-App::$strings["Send wall-to-wall posts to Livejournal"] = "";
-App::$strings["Add link to original post"] = "";
-App::$strings["Livejournal Crosspost Connector"] = "";
-App::$strings["pageheader Settings saved."] = "";
-App::$strings["Message to display on every page on this server"] = "";
-App::$strings["Page Header"] = "";
-App::$strings["Rainbow Tag App"] = "";
-App::$strings["Add some colour to tag clouds"] = "";
-App::$strings["Rainbow Tag"] = "";
-App::$strings["Recent Channel/Profile Viewers"] = "";
-App::$strings["No entries."] = "";
-App::$strings["Your account on %s will expire in a few days."] = "";
-App::$strings["Your test account is about to expire."] = "";
-App::$strings["nofed Settings saved."] = "";
-App::$strings["Federate posts by default"] = "";
-App::$strings["No Federation"] = "";
-App::$strings["Federate"] = "";
-App::$strings["Your Webbie:"] = "";
-App::$strings["Fontsize (px):"] = "";
-App::$strings["Link:"] = "";
-App::$strings["Like us on Hubzilla"] = "";
-App::$strings["Embed:"] = "";
-App::$strings["This website is tracked using the <a href='http://www.piwik.org'>Piwik</a> analytics tool."] = "";
-App::$strings["If you do not want that your visits are logged this way you <a href='%s'>can set a cookie to prevent Piwik from tracking further visits of the site</a> (opt-out)."] = "";
-App::$strings["Piwik Base URL"] = "";
-App::$strings["Absolute path to your Piwik installation. (without protocol (http/s), with trailing slash)"] = "";
-App::$strings["Site ID"] = "";
-App::$strings["Show opt-out cookie link?"] = "";
-App::$strings["Asynchronous tracking"] = "";
-App::$strings["Enable frontend JavaScript error tracking"] = "";
-App::$strings["This feature requires Piwik >= 2.2.0"] = "";
-App::$strings["Page to load after login"] = "";
-App::$strings["Examples: &quot;apps&quot;, &quot;network?f=&gid=37&quot; (privacy collection), &quot;channel&quot; or &quot;notifications/system&quot; (leave blank for default network page (grid)."] = "";
-App::$strings["Startpage"] = "";
-App::$strings["Twitter settings updated."] = "";
-App::$strings["No consumer key pair for Twitter found. Please contact your site administrator."] = "";
-App::$strings["At this Hubzilla instance the Twitter plugin was enabled but you have not yet connected your account to your Twitter account. To do so click the button below to get a PIN from Twitter which you have to copy into the input box below and submit the form. Only your <strong>public</strong> posts will be posted to Twitter."] = "";
-App::$strings["Log in with Twitter"] = "";
-App::$strings["Copy the PIN from Twitter here"] = "";
-App::$strings["<strong>Note:</strong> Due your privacy settings (<em>Hide your profile details from unknown viewers?</em>) the link potentially included in public postings relayed to Twitter will lead the visitor to a blank page informing the visitor that the access to your profile has been restricted."] = "";
-App::$strings["Twitter post length"] = "";
-App::$strings["Maximum tweet length"] = "";
-App::$strings["Send public postings to Twitter by default"] = "";
-App::$strings["If enabled your public postings will be posted to the associated Twitter account by default"] = "";
-App::$strings["Twitter Crosspost Connector"] = "";
-App::$strings["Post to Twitter"] = "";
-App::$strings["Submit Settings"] = "";
-App::$strings["Fuzzloc Settings updated."] = "";
-App::$strings["Minimum offset in meters"] = "";
-App::$strings["Maximum offset in meters"] = "";
-App::$strings["Fuzzy Location"] = "";
-App::$strings["No username found in import file."] = "Ingen brukernavn ble funnet i importfilen.";
-App::$strings["Import completed."] = "Import ferdig.";
-App::$strings["%1\$s dislikes %2\$s's %3\$s"] = "";
-App::$strings["Diaspora Protocol Settings updated."] = "";
-App::$strings["The diaspora protocol does not support location independence. Connections you make within that network may be unreachable from alternate channel locations."] = "";
-App::$strings["Prevent your hashtags from being redirected to other sites"] = "";
-App::$strings["Sign and forward posts and comments with no existing Diaspora signature"] = "";
-App::$strings["Followed hashtags (comma separated, do not include the #)"] = "";
-App::$strings["Diaspora Protocol"] = "";
-App::$strings["Please install the statistics addon to be able to configure a diaspora relay"] = "";
-App::$strings["Diaspora Relay Handle"] = "";
-App::$strings["Address of a diaspora relay. Example: relay@diasporarelay.tld"] = "";
-App::$strings["Diaspora relay could not be imported"] = "";
-App::$strings["No subject"] = "";
-App::$strings["Photos imported"] = "";
-App::$strings["Redmatrix Photo Album Import"] = "";
-App::$strings["This will import all your Redmatrix photo albums to this channel."] = "";
-App::$strings["Redmatrix Server base URL"] = "";
-App::$strings["Redmatrix Login Username"] = "";
-App::$strings["Redmatrix Login Password"] = "";
-App::$strings["Import just this album"] = "";
-App::$strings["Leave blank to import all albums"] = "";
-App::$strings["Maximum count to import"] = "";
-App::$strings["0 or blank to import all available"] = "";
-App::$strings["Some setting"] = "";
-App::$strings["A setting"] = "";
-App::$strings["Skeleton Settings"] = "";
-App::$strings["QR code"] = "";
-App::$strings["QR Generator"] = "";
-App::$strings["Enter some text"] = "";
-App::$strings["Dreamwidth Crosspost Connector Settings saved."] = "";
-App::$strings["Dreamwidth username"] = "";
-App::$strings["Dreamwidth password"] = "";
-App::$strings["Post to Dreamwidth by default"] = "";
-App::$strings["Link description (default:"] = "";
-App::$strings["Dreamwidth Crosspost Connector"] = "";
-App::$strings["Post to Dreamwidth"] = "";
-App::$strings["Show Upload Limits"] = "";
-App::$strings["Hubzilla configured maximum size: "] = "";
-App::$strings["PHP upload_max_filesize: "] = "";
-App::$strings["PHP post_max_size (must be larger than upload_max_filesize): "] = "";
-App::$strings["NSA Bait App"] = "";
-App::$strings["Make yourself a political target."] = "";
-App::$strings["No server specified"] = "";
-App::$strings["Posts imported"] = "";
-App::$strings["Files imported"] = "";
-App::$strings["This will import all your conversations and cloud files from a cloned channel on another server. This may take a while if you have lots of posts and or files."] = "";
-App::$strings["Include posts"] = "";
-App::$strings["Conversations, Articles, Cards, and other posted content"] = "";
-App::$strings["Include files"] = "";
-App::$strings["Files, Photos and other cloud storage"] = "";
-App::$strings["Original Server base URL"] = "";
-App::$strings["Friendica Crosspost Connector Settings saved."] = "";
-App::$strings["Send public postings to Friendica by default"] = "";
-App::$strings["Friendica API Path"] = "";
-App::$strings["https://{sitename}/api"] = "";
-App::$strings["Friendica login name"] = "";
-App::$strings["Friendica password"] = "";
-App::$strings["Friendica Crosspost Connector"] = "";
-App::$strings["Post to Friendica"] = "";
-App::$strings["New registration"] = "";
-App::$strings["Message sent to %s. New account registration: %s"] = "";
-App::$strings["Redmatrix File Storage Import"] = "";
-App::$strings["This will import all your Redmatrix cloud files to this channel."] = "";
-App::$strings["Send test email"] = "";
-App::$strings["No recipients found."] = "";
-App::$strings["Mail sent."] = "";
-App::$strings["Sending of mail failed."] = "";
-App::$strings["Mail Test"] = "";
-App::$strings["Message subject"] = "";
-App::$strings["Errors encountered deleting database table "] = "";
-App::$strings["Drop tables when uninstalling?"] = "";
-App::$strings["If checked, the Rendezvous database tables will be deleted when the plugin is uninstalled."] = "";
-App::$strings["Mapbox Access Token"] = "";
-App::$strings["If you enter a Mapbox access token, it will be used to retrieve map tiles from Mapbox instead of the default OpenStreetMap tile server."] = "";
-App::$strings["Rendezvous"] = "";
-App::$strings["This identity has been deleted by another member due to inactivity. Please press the \"New identity\" button or refresh the page to register a new identity. You may use the same name."] = "";
-App::$strings["Welcome to Rendezvous!"] = "";
-App::$strings["Enter your name to join this rendezvous. To begin sharing your location with the other members, tap the GPS control. When your location is discovered, a red dot will appear and others will be able to see you on the map."] = "";
-App::$strings["Let's meet here"] = "";
-App::$strings["New marker"] = "";
-App::$strings["Edit marker"] = "";
-App::$strings["New identity"] = "";
-App::$strings["Delete marker"] = "";
-App::$strings["Delete member"] = "";
-App::$strings["Edit proximity alert"] = "";
-App::$strings["A proximity alert will be issued when this member is within a certain radius of you.<br><br>Enter a radius in meters (0 to disable):"] = "";
-App::$strings["distance"] = "";
-App::$strings["Proximity alert distance (meters)"] = "";
-App::$strings["A proximity alert will be issued when you are within a certain radius of the marker location.<br><br>Enter a radius in meters (0 to disable):"] = "";
-App::$strings["Marker proximity alert"] = "";
-App::$strings["Reminder note"] = "";
-App::$strings["Enter a note to be displayed when you are within the specified proximity..."] = "";
-App::$strings["Add new rendezvous"] = "";
-App::$strings["Create a new rendezvous and share the access link with those you wish to invite to the group. Those who open the link become members of the rendezvous. They can view other member locations, add markers to the map, or share their own locations with the group."] = "";
-App::$strings["You have no rendezvous. Press the button above to create a rendezvous!"] = "";
-App::$strings["Channel is required."] = "";
-App::$strings["Hubzilla Crosspost Connector Settings saved."] = "";
-App::$strings["Send public postings to Hubzilla channel by default"] = "";
-App::$strings["Hubzilla API Path"] = "";
-App::$strings["Hubzilla login name"] = "";
-App::$strings["Hubzilla channel name"] = "";
-App::$strings["Hubzilla password"] = "";
-App::$strings["Hubzilla Crosspost Connector"] = "";
-App::$strings["Post to Hubzilla"] = "";
-App::$strings["Workflow user."] = "";
-App::$strings["This channel"] = "";
-App::$strings["Create New Workflow Item"] = "";
-App::$strings["Workflow"] = "";
-App::$strings["No Workflows Available"] = "";
-App::$strings["Add item to which workflow"] = "";
-App::$strings["Create Workflow Item"] = "";
-App::$strings["Link"] = "";
-App::$strings["Web link."] = "";
-App::$strings["Brief description or title"] = "";
-App::$strings["Notes and Info"] = "";
-App::$strings["Used to order links"] = "";
-App::$strings["Body"] = "";
-App::$strings["Workflow Settings"] = "";
-App::$strings["Workflow settings"] = "Instillinger for redigering";
-App::$strings["Possible adult content"] = "";
-App::$strings["%s - view"] = "";
-App::$strings["NSFW Settings saved."] = "";
-App::$strings["This app looks in posts for the words/text you specify below, and collapses any content containing those keywords so it is not displayed at inappropriate times, such as sexual innuendo that may be improper in a work setting. It is polite and recommended to tag any content containing nudity with #NSFW. This filter can also match any other word/text you specify, and can thereby be used as a general purpose content filter."] = "";
-App::$strings["Comma separated list of keywords to hide"] = "";
-App::$strings["Word, /regular-expression/, lang=xx, lang!=xx"] = "";
-App::$strings["NSFW"] = "";
-App::$strings["Three Dimensional Tic-Tac-Toe"] = "";
-App::$strings["3D Tic-Tac-Toe"] = "";
-App::$strings["New game"] = "";
-App::$strings["New game with handicap"] = "";
-App::$strings["Three dimensional tic-tac-toe is just like the traditional game except that it is played on multiple levels simultaneously. "] = "";
-App::$strings["In this case there are three levels. You win by getting three in a row on any level, as well as up, down, and diagonally across the different levels."] = "";
-App::$strings["The handicap game disables the center position on the middle level because the player claiming this square often has an unfair advantage."] = "";
-App::$strings["You go first..."] = "";
-App::$strings["I'm going first this time..."] = "";
-App::$strings["You won!"] = "";
-App::$strings["\"Cat\" game!"] = "";
-App::$strings["I won!"] = "";
-App::$strings["Photo Cache settings saved."] = "";
-App::$strings["Saves a copy of images from external sites locally to increase your anonymity in the web."] = "";
-App::$strings["Minimal photo size for caching"] = "";
-App::$strings["In pixels. From 1 up to 1024, 0 will be replaced with system default."] = "";
-App::$strings["Photo Cache"] = "";
-App::$strings["Logfile archive directory"] = "";
-App::$strings["Directory to store rotated logs"] = "";
-App::$strings["Logfile size in bytes before rotating"] = "";
-App::$strings["Number of logfiles to retain"] = "";
-App::$strings["XMPP settings updated."] = "";
-App::$strings["XMPP App"] = "";
-App::$strings["Embedded XMPP (Jabber) client"] = "";
-App::$strings["Individual credentials"] = "";
-App::$strings["Jabber BOSH server"] = "";
-App::$strings["XMPP Settings"] = "";
-App::$strings["Jabber BOSH host"] = "";
-App::$strings["Use central userbase"] = "";
-App::$strings["If enabled, members will automatically login to an ejabberd server that has to be installed on this machine with synchronized credentials via the \"auth_ejabberd.php\" script."] = "";
-App::$strings["(No Title)"] = "(Ingen tittel)";
-App::$strings["Wiki page create failed."] = "Kunne ikke opprette wikiside.";
-App::$strings["Wiki not found."] = "Fant ikke wikien.";
-App::$strings["Destination name already exists"] = "Navnet finnes allerede";
-App::$strings["Page not found"] = "Fant ikke siden";
-App::$strings["Error reading page content"] = "Det oppstod en feil under lesing av innholder på siden";
-App::$strings["Error reading wiki"] = "Det oppstod en feil ved lesing av wikien";
-App::$strings["Page update failed."] = "Oppdatering av siden mislyktes.";
-App::$strings["Nothing deleted"] = "Ingenting ble slettet";
-App::$strings["Compare: object not found."] = "Sammanlign: fant ikke objektet.";
-App::$strings["Page updated"] = "Siden ble oppdatert";
-App::$strings["Wiki resource_id required for git commit"] = "Wiki ressurs_id er nødvendig for å lagre i git";
-App::$strings["__ctx:wiki_history__ Message"] = "Melding";
-App::$strings["Date"] = "Dato";
-App::$strings["Revert"] = "Tilbakestill";
-App::$strings["Compare"] = "Sammenlign";
-App::$strings["Wiki updated successfully"] = "Wikien ble oppdatert";
-App::$strings["Wiki files deleted successfully"] = "Wikifiler ble slettet";
-App::$strings["Error retrieving wiki"] = "Det oppstod en feil ved henting av wikien";
-App::$strings["Error creating zip file export folder"] = "Det oppstod en feil ved eksport av mappe til zip fil";
-App::$strings["Error downloading wiki: "] = "";
-App::$strings["Wikis"] = "Wikier";
-App::$strings["Wiki name"] = "";
-App::$strings["Content type"] = "";
-App::$strings["Any&nbsp;type"] = "";
-App::$strings["Lock content type"] = "";
-App::$strings["Create a status post for this wiki"] = "";
-App::$strings["Edit Wiki Name"] = "";
-App::$strings["Wiki not found"] = "Fant ikke wikien.";
-App::$strings["Rename page"] = "";
-App::$strings["Error retrieving page content"] = "";
-App::$strings["New page"] = "";
-App::$strings["Revision Comparison"] = "";
-App::$strings["Short description of your changes (optional)"] = "";
-App::$strings["New page name"] = "";
-App::$strings["Embed image from photo albums"] = "";
-App::$strings["History"] = "";
-App::$strings["Error creating wiki. Invalid name."] = "";
-App::$strings["A wiki with this name already exists."] = "";
-App::$strings["Wiki created, but error creating Home page."] = "";
-App::$strings["Error creating wiki"] = "";
-App::$strings["Error updating wiki. Invalid name."] = "";
-App::$strings["Error updating wiki"] = "";
-App::$strings["Wiki delete permission denied."] = "";
-App::$strings["Error deleting wiki"] = "";
-App::$strings["New page created"] = "";
-App::$strings["Cannot delete Home"] = "";
-App::$strings["Current Revision"] = "";
-App::$strings["Selected Revision"] = "";
-App::$strings["You must be authenticated."] = "";
-App::$strings["Add new page"] = "Legg til ny side";
-App::$strings["View Cards"] = "";
-App::$strings["Wiki Pages"] = "Wikisider";
-App::$strings["Page name"] = "Sidenavn";
-App::$strings["Who likes me?"] = "";
-App::$strings["Edit Card"] = "";
-App::$strings["Add Card"] = "";
-App::$strings["Libertree Crosspost Connector Settings saved."] = "";
-App::$strings["Libertree API token"] = "";
-App::$strings["Libertree site URL"] = "";
-App::$strings["Post to Libertree by default"] = "";
-App::$strings["Libertree Crosspost Connector"] = "";
-App::$strings["Post to Libertree"] = "";
-App::$strings["Send email to all members"] = "";
-App::$strings["%1\$d of %2\$d messages sent."] = "";
-App::$strings["Send email to all hub members."] = "";
-App::$strings["Sender Email address"] = "";
-App::$strings["Test mode (only send to hub administrator)"] = "";
-App::$strings["Use markdown for editing posts"] = "";
-App::$strings["We encountered a problem while logging in with the OpenID you provided. Please check the correct spelling of the ID."] = "Vi støtte på et problem under innloggingen med din OpenID. Vennligst sjekk at ID-en er stavet riktig.";
-App::$strings["The error message was:"] = "Feilmeldingen var:";
-App::$strings["OpenID protocol error. No ID returned."] = "OpenID protokollfeil. Ingen ID ble returnert.";
-App::$strings["Welcome %s. Remote authentication successful."] = "Velkommen %s. Ekstern autentisering er vellykket.";
-App::$strings["First Name"] = "Fornavn";
-App::$strings["Last Name"] = "Etternavn";
-App::$strings["Full Name"] = "Fullt navn";
-App::$strings["Profile Photo 16px"] = "Profilbilde 16px";
-App::$strings["Profile Photo 32px"] = "Profilbilde 32px";
-App::$strings["Profile Photo 48px"] = "Profilbilde 48px";
-App::$strings["Profile Photo 64px"] = "Profilbilde 64px";
-App::$strings["Profile Photo 80px"] = "Profilbilde 80px";
-App::$strings["Profile Photo 128px"] = "Profilbilde 128px";
-App::$strings["Birth Year"] = "Fødselsår";
-App::$strings["Birth Month"] = "Fødselsmåne";
-App::$strings["Birth Day"] = "Fødselsdag";
-App::$strings["Birthdate"] = "Fødselsdato";
-App::$strings["Popular Channels"] = "";
-App::$strings["Channels to auto connect"] = "";
-App::$strings["Comma separated list"] = "";
-App::$strings["IRC Settings"] = "";
-App::$strings["IRC settings saved."] = "";
-App::$strings["IRC Chatroom"] = "";
-App::$strings["__ctx:opensearch__ Search %1\$s (%2\$s)"] = "Søk %1\$s (%2\$s)";
-App::$strings["__ctx:opensearch__ \$Projectname"] = "\$Projectname";
-App::$strings["Search \$Projectname"] = "";
-App::$strings["ActivityPub Protocol Settings updated."] = "";
-App::$strings["The activitypub protocol does not support location independence. Connections you make within that network may be unreachable from alternate channel locations."] = "";
-App::$strings["Send activities of type note instead of article"] = "";
-App::$strings["Microblog services such as Mastodon do not properly support articles"] = "";
-App::$strings["Activitypub Protocol"] = "";
-App::$strings["Not allowed."] = "";
-App::$strings["Insane Journal Crosspost Connector Settings saved."] = "";
-App::$strings["Insane Journal Crosspost Connector App"] = "";
-App::$strings["Relay public postings to Insane Journal"] = "";
-App::$strings["InsaneJournal username"] = "";
-App::$strings["InsaneJournal password"] = "";
-App::$strings["Post to InsaneJournal by default"] = "";
-App::$strings["Insane Journal Crosspost Connector"] = "";
-App::$strings["Post to Insane Journal"] = "";
-App::$strings["Random Planet App"] = "";
-App::$strings["Set a random planet from the Star Wars Empire as your location when posting"] = "";
-App::$strings["Project Servers and Resources"] = "";
-App::$strings["Project Creator and Tech Lead"] = "";
-App::$strings["And the hundreds of other people and organisations who helped make the Hubzilla possible."] = "";
-App::$strings["The Redmatrix/Hubzilla projects are provided primarily by volunteers giving their time and expertise - and often paying out of pocket for services they share with others."] = "";
-App::$strings["There is no corporate funding and no ads, and we do not collect and sell your personal information. (We don't control your personal information - <strong>you do</strong>.)"] = "";
-App::$strings["Help support our ground-breaking work in decentralisation, web identity, and privacy."] = "";
-App::$strings["Your donations keep servers and services running and also helps us to provide innovative new features and continued development."] = "";
-App::$strings["Donate"] = "";
-App::$strings["Choose a project, developer, or public hub to support with a one-time donation"] = "";
-App::$strings["Donate Now"] = "";
-App::$strings["<strong><em>Or</em></strong> become a project sponsor (Hubzilla Project only)"] = "";
-App::$strings["Please indicate if you would like your first name or full name (or nothing) to appear in our sponsor listing"] = "";
-App::$strings["Sponsor"] = "";
-App::$strings["Special thanks to: "] = "";
-App::$strings["Fediquest App"] = "Endre app";
-App::$strings["A distributed quest for a given word (game)."] = "";
-App::$strings["To start a game, enter [fediquest]your_word[/fediquest] somewhere in a toplevel post."] = "";
-App::$strings["Your contacts can post their guess in the comments."] = "";
-App::$strings["Your channel will evaluate the guess and automatically post the response."] = "";
-App::$strings["Correct letters"] = "";
-App::$strings["Letters contained in the word but at the wrong spot"] = "";
-App::$strings["Letters not contained in the word"] = "";
-App::$strings["ERROR: word length is not correct!"] = "";
-App::$strings["Post to WordPress"] = "";
-App::$strings["Wordpress Settings saved."] = "";
-App::$strings["WordPress username"] = "";
-App::$strings["WordPress password"] = "";
-App::$strings["WordPress API URL"] = "";
-App::$strings["Typically https://your-blog.tld/xmlrpc.php"] = "";
-App::$strings["WordPress blogid"] = "";
-App::$strings["For multi-user sites such as wordpress.com, otherwise leave blank"] = "";
-App::$strings["Post to WordPress by default"] = "";
-App::$strings["Forward comments (requires hubzilla_wp plugin)"] = "";
-App::$strings["Wordpress Post"] = "";
-App::$strings["Allow magic authentication only to websites of your immediate connections"] = "";
-App::$strings["Authchoose"] = "";
-App::$strings["Your channel has been upgraded to \$Projectname version"] = "";
-App::$strings["Please have a look at the"] = "";
-App::$strings["git history"] = "";
-App::$strings["change log"] = "";
-App::$strings["for further info."] = "";
-App::$strings["\$Projectname Upgrade Info"] = "\$Projectname";
-App::$strings["Do not show this again"] = "";
-App::$strings["Hide Aside App"] = "";
-App::$strings["Fade out aside areas after a while when using endless scroll"] = "";
-App::$strings["text to include in all outgoing posts from this site"] = "";
+App::$strings["Authentication failed."] = "Autentisering mislyktes.";
+App::$strings["Edit Block"] = "Endre byggekloss";
+App::$strings["Email Verification Required"] = "";
+App::$strings["A verification token was sent to your email address [%s]. Enter that token here to complete the account verification step. Please allow a few minutes for delivery, and check your spam folder if you do not see the message."] = "En verifikasjonskode ble sendt til epostadressen din [%s]. Skriv inn koden du mottok her for å fullføre kontoverifikasjonen. Det kan ta noen minutter før du mottar koden. Sjekk også at eposten ikke har havnet i mappen for søppelpost om du ikke ser den etter en stund.";
+App::$strings["Resend Email"] = "";
+App::$strings["Validation token"] = "";
+App::$strings["Nothing to import."] = "Ingenting å importere.";
+App::$strings["Unable to download data from old server"] = "Ikke i stand til å laste ned data fra gammel tjener";
+App::$strings["Your service plan only allows %d channels."] = "Din tjenesteplan tillater bare %d kanaler.";
+App::$strings["No channel. Import failed."] = "Ingen kanal. Import mislyktes.";
+App::$strings["Channel exists but has been marked removed on this hub. Import failed."] = "";
+App::$strings["Automatic content and files import was not possible due to API version incompatiblity. Please import content and files manually!"] = "";
+App::$strings["You must be logged in to use this feature."] = "Du må være innlogget for å bruke denne funksjonen.";
+App::$strings["Channel Import"] = "";
+App::$strings["Use this form to import an existing channel from a different server/hub. You may retrieve the channel identity from the old server/hub via the network or provide an export file."] = "Bruk dette skjemaet for å importere en eksisterende kanal fra en annen tjener/hub. Du kan hente inn kanalidentiteten fra den gamle tjeneren/huben via nettverket eller ved å bruke en eksportfil.";
+App::$strings["Or provide the old server/hub details"] = "Eller oppgi detaljene fra den gamle tjeneren/hub-en";
+App::$strings["Your old identity address (xyz@example.com)"] = "Din gamle identitetsadresse (xyz@example.com)";
+App::$strings["Your old login email address"] = "Din gamle innloggings e-postadresse";
+App::$strings["Your old login password"] = "Ditt gamle innloggingspassord";
+App::$strings["Import your items and files (limited by available memory)"] = "";
+App::$strings["For either option, please choose whether to make this hub your new primary address, or whether your old location should continue this role. You will be able to post from either location, but only one can be marked as the primary location for files, photos, and media."] = "Enten du tar det ene eller det andre valget, vennligst angi om du vil at denne hubben skal være din nye primære adresse, eller om din gamle plassering skal fortsette å ha denne rollen. Du kan lage innlegg fra den ene eller den andre plasseringen, men bare en av dem kan markeres som den primære plasseringen for filer, bilder og media.";
+App::$strings["Make this hub my primary location"] = "Gjør dette nettstedet til min primære plassering";
+App::$strings["Move this channel (disable all previous locations)"] = "";
+App::$strings["Use this channel nickname instead of the one provided"] = "";
+App::$strings["Leave blank to keep your existing channel nickname. You will be randomly assigned a similar nickname if either name is already allocated on this site."] = "";
+App::$strings["This process may take several minutes to complete. Please submit the form only once and leave this page open until finished."] = "Denne prosessen kan ta flere minutter å fullføre. Vennligst send inn dette skjemaet bare en gang og la siden være åpen inntil den er ferdig.";
+App::$strings["network"] = "nettverk";
+App::$strings["Enter a folder name"] = "";
+App::$strings["or select an existing folder (doubleclick)"] = "";
+App::$strings["Poll not found."] = "Fant ikke spørreskjema.";
+App::$strings["Invalid response."] = "";
+App::$strings["Response submitted. Updates may not appear instantly."] = "";
diff --git a/view/pdl/mod_dreport.pdl b/view/pdl/mod_dreport.pdl
new file mode 100644
index 000000000..a75fa334a
--- /dev/null
+++ b/view/pdl/mod_dreport.pdl
@@ -0,0 +1,7 @@
+[template]doubleleft[/template]
+[region=aside]
+[widget=notifications][var=sys_only]1[/var][/widget]
+[/region]
+[region=content]
+$content
+[/region]
diff --git a/view/php/default.php b/view/php/default.php
index 8d2c092a3..324981dff 100644
--- a/view/php/default.php
+++ b/view/php/default.php
@@ -11,28 +11,28 @@
*/
?>
<!DOCTYPE html >
-<html prefix="og: http://ogp.me/ns#" <?php if(x($page,'color_mode')) echo $page['color_mode'] ?>>
+<html prefix="og: http://ogp.me/ns#" <?php if(!empty($page['color_mode'])) echo $page['color_mode'] ?>>
<head>
- <title><?php if(x($page,'title')) echo $page['title'] ?></title>
+ <title><?php if(!empty($page['title'])) echo $page['title'] ?></title>
<script>var baseurl="<?php echo z_root() ?>";</script>
- <?php if(x($page,'htmlhead')) echo $page['htmlhead'] ?>
+ <?php if(!empty($page['htmlhead'])) echo $page['htmlhead'] ?>
</head>
<body <?php if($page['direction']) echo 'dir="rtl"' ?> >
- <?php if(x($page,'banner')) echo $page['banner']; ?>
- <header><?php if(x($page,'header')) echo $page['header']; ?></header>
- <?php if(x($page,'nav')) echo $page['nav']; ?>
+ <?php if(!empty($page['banner'])) echo $page['banner']; ?>
+ <header><?php if(!empty($page['header'])) echo $page['header']; ?></header>
+ <?php if(!empty($page['nav'])) echo $page['nav']; ?>
<main>
<div class="content">
<div class="columns">
- <aside id="region_1" class="d-none d-lg-block"><div class="aside_spacer_top_left"></div><div class="aside_spacer_left"><div id="left_aside_wrapper" class="aside_wrapper"><?php if(x($page,'aside')) echo $page['aside']; ?></div></div></aside>
- <section id="region_2"><?php if(x($page,'content')) echo $page['content']; ?>
+ <aside id="region_1" class="d-none d-lg-block"><div class="aside_spacer_top_left"></div><div class="aside_spacer_left"><div id="left_aside_wrapper" class="aside_wrapper"><?php if(!empty($page['aside'])) echo $page['aside']; ?></div></div></aside>
+ <section id="region_2"><?php if(!empty($page['content'])) echo $page['content']; ?>
<div id="page-footer"></div>
<div id="pause"></div>
</section>
- <aside id="region_3" class="d-none d-xl-block"><div class="aside_spacer_top_right"></div><div class="aside_spacer_right"><div id="right_aside_wrapper" class="aside_wrapper"><?php if(x($page,'right_aside')) echo $page['right_aside']; ?></div></div></aside>
+ <aside id="region_3" class="d-none d-xl-block"><div class="aside_spacer_top_right"></div><div class="aside_spacer_right"><div id="right_aside_wrapper" class="aside_wrapper"><?php if(!empty($page['right_aside'])) echo $page['right_aside']; ?></div></div></aside>
</div>
</div>
</main>
- <footer><?php if(x($page,'footer')) echo $page['footer']; ?></footer>
+ <footer><?php if(!empty($page['footer'])) echo $page['footer']; ?></footer>
</body>
</html>
diff --git a/view/theme/redbasic/css/style.css b/view/theme/redbasic/css/style.css
index 7f56bce25..36023e511 100644
--- a/view/theme/redbasic/css/style.css
+++ b/view/theme/redbasic/css/style.css
@@ -953,6 +953,7 @@ a .drop-icons:hover {
}
.wall-item-pinned i {
+ display: inline-block;
transform: rotate(45deg);
}
diff --git a/view/theme/redbasic/js/redbasic.js b/view/theme/redbasic/js/redbasic.js
index 0859aae73..144714b50 100644
--- a/view/theme/redbasic/js/redbasic.js
+++ b/view/theme/redbasic/js/redbasic.js
@@ -53,7 +53,7 @@ document.addEventListener('DOMContentLoaded', function () {
testElem.style.display = 'none';
testElem.id = 'css3-calc';
document.body.appendChild(testElem);
-
+
if (testElem.offsetWidth === 10) {
window.addEventListener('resize', function () {
if (window.innerWidth < 992) {
@@ -134,17 +134,6 @@ document.addEventListener('DOMContentLoaded', function () {
}
});
- document.querySelectorAll('.notifications-btn').forEach(function (element) {
- element.addEventListener('click', function (e) {
- e.preventDefault();
- e.stopPropagation();
- let navCollapse = document.getElementById('navbar-collapse-2');
- if (navCollapse && navCollapse.classList.contains('show')) {
- navCollapse.classList.remove('show');
- }
- });
- });
-
$("input[data-role=cat-tagsinput]").tagsinput({
tagClass: 'badge rounded-pill bg-warning text-dark'
});
diff --git a/view/tpl/breadcrumb.tpl b/view/tpl/breadcrumb.tpl
index 205b712d9..f1332b4e2 100644
--- a/view/tpl/breadcrumb.tpl
+++ b/view/tpl/breadcrumb.tpl
@@ -1,5 +1,5 @@
<nav aria-label="breadcrumb">
- <ol class="breadcrumb bg-transparent">
+ <ol class="breadcrumb bg-transparent section-content-wrapper">
{{foreach $breadcrumbs as $breadcrumb}}
{{if $breadcrumb@last}}
<li class="breadcrumb-item active h3 pt-3 pb-3" aria-current="page">{{$breadcrumb.name}}</li>
diff --git a/view/tpl/comment_item.tpl b/view/tpl/comment_item.tpl
index 6b7e163eb..53036b5cd 100644
--- a/view/tpl/comment_item.tpl
+++ b/view/tpl/comment_item.tpl
@@ -1,16 +1,12 @@
- {{if $threaded}}
- <div class="comment-wwedit-wrapper threaded" id="comment-edit-wrapper-{{$id}}" style="display: block;">
- {{else}}
- <div class="comment-wwedit-wrapper" id="comment-edit-wrapper-{{$id}}" style="display: block;">
- {{/if}}
- <form class="comment-edit-form" style="display: block;" id="comment-edit-form-{{$id}}" action="item" method="post" onsubmit="post_comment({{$id}}); return false;">
+ <div class="comment-wwedit-wrapper{{if $threaded}} threaded{{/if}}" id="comment-edit-wrapper-{{$id}}">
+ <form class="comment-edit-form" id="comment-edit-form-{{$id}}" action="item" method="post" onsubmit="post_comment({{$id}}); return false;">
<input type="hidden" name="type" value="{{$type}}" />
<input type="hidden" name="profile_uid" value="{{$profile_uid}}" />
<input type="hidden" name="parent" value="{{$parent}}" />
<input type="hidden" name="return" value="{{$return_path}}" />
<input type="hidden" name="jsreload" value="{{$jsreload}}" />
<input type="hidden" name="preview" id="comment-preview-inp-{{$id}}" value="0" />
- {{if $anoncomments && ! $observer}}
+ {{if $anoncomments && !$observer}}
<div id="comment-edit-anon-{{$id}}" style="display: none;" >
{{include file="field_input.tpl" field=$anonname}}
{{include file="field_input.tpl" field=$anonmail}}
@@ -74,5 +70,5 @@
</div>
<div class="clear"></div>
</form>
+ <div id="comment-edit-preview-{{$id}}" class="comment-edit-preview mt-4"></div>
</div>
- <div id="comment-edit-preview-{{$id}}" class="comment-edit-preview mt-4"></div>
diff --git a/view/tpl/conv_frame.tpl b/view/tpl/conv_frame.tpl
index 4237c671b..0acf7b24c 100644
--- a/view/tpl/conv_frame.tpl
+++ b/view/tpl/conv_frame.tpl
@@ -18,4 +18,22 @@
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
+<div class="modal" id="reactions" tabindex="-1" role="dialog" aria-hidden="true">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h3 class="modal-title" id="reactions_title"></h3>
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-hidden="true"></button>
+ </div>
+ <div class="modal-header" id="reactions_action">
+ </div>
+ <div class="modal-body list-group" id="reactions_body">
+ {{$wait}}
+ </div>
+ <div class="ps-3 pe-3" id="reactions_extra_top"></div>
+ <div class="ps-3 pe-3" id="reactions_extra_middle"></div>
+ <div class="ps-3 pe-3" id="reactions_extra_bottom"></div>
+ </div><!-- /.modal-content -->
+ </div><!-- /.modal-dialog -->
+</div><!-- /.modal -->
{{include file="contact_edit_modal.tpl"}}
diff --git a/view/tpl/conv_item.tpl b/view/tpl/conv_item.tpl
index da1e5a472..4abb34a31 100644
--- a/view/tpl/conv_item.tpl
+++ b/view/tpl/conv_item.tpl
@@ -1,10 +1,10 @@
-{{if $item.comment_firstcollapsed}}
+{{if !$item.threaded && $item.comment_firstcollapsed}}
<div id="hide-comments-outer-{{$item.parent}}" class="hide-comments-outer fakelink small" onclick="showHideComments({{$item.id}});">
- <i id="hide-comments-icon-{{$item.id}}" class="bi bi-chevron-down align-middle hide-comments-icon"></i> <span id="hide-comments-label-{{$item.id}}" class="hide-comments-label align-middle">{{$item.hide_text}}</span>&nbsp;<span id="hide-comments-total-{{$item.id}}" class="hide-comments-label align-middle">{{$item.num_comments}}</span>
+ <i id="hide-comments-icon-{{$item.id}}" class="bi bi-chevron-down align-middle hide-comments-icon"></i> <span id="hide-comments-label-{{$item.id}}" class="hide-comments-label align-middle" data-expanded="{{$item.collapse_comments}}" data-collapsed="{{$item.expand_comments}}">{{$item.expand_comments}}</span>{{if !$item.threaded}}&nbsp;<span id="hide-comments-total-{{$item.id}}" class="hide-comments-label align-middle">{{$item.num_comments}}</span>{{/if}}
</div>
<div id="collapsed-comments-{{$item.id}}" class="collapsed-comments" style="display: none;">
{{/if}}
- <div id="thread-wrapper-{{$item.id}}" class="thread-wrapper{{if $item.toplevel}} {{$item.toplevel}} generic-content-wrapper h-entry {{else}} u-comment h-cite{{/if}} clearfix{{if $item.is_contained}} is-contained{{/if}}{{if $item.is_new && !$item.event && !$item.photo && !$item.title && !$item.is_comment}} is-new{{/if}}" data-b64mids='{{$item.mids}}'>
+ <div id="thread-wrapper-{{$item.id}}" class="thread-wrapper{{if $item.toplevel}} {{$item.toplevel}} generic-content-wrapper h-entry{{else}} u-comment h-cite{{/if}} clearfix{{if $item.is_contained}} is-contained{{/if}}{{if $item.is_new && !$item.event && !$item.photo && !$item.title && !$item.is_comment}} is-new{{/if}}" data-b64mids='{{$item.mids}}'>
<a name="item_{{$item.id}}" ></a>
<div class="wall-item-outside-wrapper{{if $item.is_comment}} comment{{/if}}{{if $item.previewing}} preview{{/if}}" id="wall-item-outside-wrapper-{{$item.id}}" >
<div class="rounded wall-item-content-wrapper{{if $item.is_comment}} comment{{/if}}" id="wall-item-content-wrapper-{{$item.id}}">
@@ -28,7 +28,7 @@
{{/if}}
<div class="p-2 wall-item-head{{if !$item.title && !$item.event && !$item.photo}} rounded-top{{/if}} clearfix">
<div class="lh-sm text-end float-end">
- <div class="wall-item-ago opacity-75" id="wall-item-ago-{{$item.id}}">
+ <div class="wall-item-ago text-body-secondary" id="wall-item-ago-{{$item.id}}">
{{if $item.location}}
{{$item.location}}
{{/if}}
@@ -51,7 +51,7 @@
{{/if}}
<small class="autotime" title="{{$item.isotime}}"><time class="dt-published" datetime="{{$item.isotime}}">{{$item.localtime}}</time>{{if $item.expiretime}}&nbsp;{{$item.expiretime}}{{/if}}</small>
</div>
- {{if $item.thr_parent_uuid}}
+ {{if !$item.threaded && $item.thr_parent_uuid}}
<a href="javascript:doscroll('{{$item.thr_parent_uuid}}',{{$item.parent}});" class="ms-3" title="{{$item.top_hint}}"><i class="bi bi-chevron-double-up"></i></a>
{{/if}}
{{if $item.pinned}}
@@ -85,9 +85,9 @@
</div>
{{/if}}
<div class="text-truncate">
- <a href="{{$item.profile_url}}" class="lh-sm wall-item-name-link u-url"{{if $item.app}} title="{{$item.str_app}}"{{/if}}><span class="wall-item-name{{$item.sparkle}}" id="wall-item-name-{{$item.id}}" ><bdi>{{$item.name}}</bdi></span></a>{{if $item.owner_url}}&nbsp;{{$item.via}}&nbsp;<a href="{{$item.owner_url}}" title="{{$item.olinktitle}}" class="wall-item-name-link"><span class="wall-item-name{{$item.osparkle}}" id="wall-item-ownername-{{$item.id}}"><bdi>{{$item.owner_name}}</bdi></span></a>{{/if}}
+ <a href="{{$item.profile_url}}" class="lh-sm wall-item-name-link u-url"{{if $item.app}} title="{{$item.str_app}}"{{/if}}><span class="wall-item-name{{$item.sparkle}}" id="wall-item-name-{{$item.id}}" ><bdi>{{$item.name}}</bdi></span></a>{{if $item.owner_url}}&nbsp;{{$item.via}}&nbsp;<a href="{{$item.owner_url}}" title="{{$item.owner_addr}}" class="wall-item-name-link"><span class="wall-item-name{{$item.osparkle}}" id="wall-item-ownername-{{$item.id}}"><bdi>{{$item.owner_name}}</bdi></span></a>{{/if}}
</div>
- <small class="lh-sm text-truncate d-block wall-item-addr opacity-75">{{$item.author_id}}</small>
+ <small class="lh-sm text-truncate d-block wall-item-addr text-body-secondary">{{$item.author_id}}</small>
</div>
</div>
{{if $item.divider}}
@@ -110,47 +110,16 @@
<div class="p-2 wall-item-tools d-flex justify-content-between">
<div class="wall-item-tools-left hstack gap-1" id="wall-item-tools-left-{{$item.id}}">
{{foreach $item.responses as $verb=>$response}}
- {{if $item.reactions_allowed || (!$item.reactions_allowed && $response.count)}}
- <div class="">
- <button type="button" title="{{$response.count}} {{$response.button.label}}" class="btn btn-sm btn-link{{if !$item.my_responses.$verb}} link-secondary{{/if}} wall-item-{{$response.button.class}}"{{if $response.modal}} data-bs-toggle="modal" data-bs-target="#{{$verb}}Modal-{{$item.id}}"{{else if $response.count}} data-bs-toggle="dropdown"{{elseif $item.reactions_allowed}} onclick="{{$response.button.onclick}}({{$item.id}},'{{$verb}}'); return false;"{{/if}} id="wall-item-{{$verb}}-{{$item.id}}">
- <i class="bi bi-{{$response.button.icon}} generic-icons"></i>{{if $response.count}}<span style="display: inline-block; margin-top: -.25rem;" class="align-top">{{$response.count}}</span>{{/if}}
- </button>
- {{if $response.modal}}
- <div class="modal" id="{{$verb}}Modal-{{$item.id}}">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">
- <h3 class="modal-title">{{$response.count}} {{$response.button.label}}</h3>
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-hidden="true"></button>
- </div>
- {{if $item.reactions_allowed && !($verb === 'announce' && $item.my_responses.$verb)}} {{** undo announce is not yet supported **}}
- <div class="modal-header">
- <a href="#" class="text-reset" onclick="{{$response.button.onclick}}({{$item.id}},'{{$verb}}'); return false;">{{if $item.my_responses.$verb}}- {{$item.reaction_str.1}}{{else}}+ {{$item.reaction_str.0}}{{/if}}</a>
- </div>
- {{/if}}
- <div class="modal-body response-list">
- <ul class="nav nav-pills flex-column">
- {{foreach $response.list as $liker}}
- {{$liker}}
- {{/foreach}}
- </ul>
- </div>
- <div class="modal-footer clear">
- <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">{{$item.modal_dismiss}}</button>
- </div>
- </div><!-- /.modal-content -->
- </div><!-- /.modal-dialog -->
- </div><!-- /.modal -->
- {{else}}
- <div class="dropdown-menu">
- {{if $item.reactions_allowed && !($verb === 'announce' && $item.my_responses.$verb)}} {{** undo announce is not yet supported **}}
- <a href="#" class="text-reset dropdown-item" onclick="{{$response.button.onclick}}({{$item.id}},'{{$verb}}'); return false;">{{if $item.my_responses.$verb}}- {{$item.reaction_str.1}}{{else}}+ {{$item.reaction_str.0}}{{/if}}</a>
- <div class="dropdown-divider"></div>
- {{/if}}
- {{foreach $response.list as $liker}}{{$liker}}{{/foreach}}
- </div>
- {{/if}}
- </div>
+ {{if !($verb == 'comment' && (($item.toplevel && !$item.blog_mode) || $response.count == 0))}}
+ {{if !$item.threaded && $item.blog_mode && $verb == 'comment'}}
+ <a href="{{$item.viewthread}}" target="_thread" title="{{$response.count}} {{$response.button.label}}" class="btn btn-sm btn-link{{if !$item.observer_activity.$verb}} link-secondary{{/if}} wall-item-{{$response.button.class}}" id="wall-item-{{$verb}}-{{$item.id}}">
+ <i class="bi bi-chat generic-icons"></i>{{if $response.count}}<span style="display: inline-block; margin-top: -.25rem;" class="align-top">{{$response.count}}</span>{{/if}}
+ </a>
+ {{else}}
+ <button type="button" title="{{$response.count}} {{$response.button.label}}" class="btn btn-sm btn-link{{if !$item.observer_activity.$verb}} link-secondary{{/if}} wall-item-reaction wall-item-{{$response.button.class}}" id="wall-item-{{$verb}}-{{$item.id}}" data-item-id="{{$item.id}}" data-item-mid="{{$item.rawmid}}" data-item-verb="{{$verb}}" data-item-parent="{{$item.parent}}" data-item-uuid="{{$item.mid}}" data-item-reaction-count="{{$response.count}}">
+ <i class="bi bi-{{$response.button.icon}} generic-icons"></i>{{if $response.count}}<span style="display: inline-block; margin-top: -.25rem;" class="align-top">{{$response.count}}</span>{{/if}}
+ </button>
+ {{/if}}
{{/if}}
{{/foreach}}
{{if $item.toplevel && $item.emojis && $item.reactions}}
@@ -192,7 +161,7 @@
</div>
{{/if}}
{{if $item.reply_to}}
- <button type="button" title="{{$item.reply_to.0}}" class="btn btn-sm btn-link link-secondary" onclick="doreply({{$item.parent}}, {{$item.id}}, '{{$item.author_id}}', '{{$item.reply_to.2}} {{$item.name|escape:javascript}}');">
+ <button type="button" title="{{$item.reply_to.0}}" class="btn btn-sm btn-link link-secondary" onclick="doreply({{$item.parent}}, {{$item.id}}, '{{$item.author_id}}', '{{$item.reply_to.2}}: {{$item.name|escape:javascript}}');">
<i class="bi bi-arrow-90deg-left generic-icons" ></i>
</button>
{{/if}}
@@ -228,6 +197,9 @@
{{if $item.star}}
<a class="dropdown-item" href="#" onclick="dostar({{$item.id}}); return false;"><i id="starred-{{$item.id}}" class="generic-icons-nav bi{{if $item.star.isstarred}} starred bi-star-fill{{else}} unstarred bi-star{{/if}}" title="{{$item.star.toggle}}"></i>{{$item.star.toggle}}</a>
{{/if}}
+ {{if $item.expand}}
+ <a class="dropdown-item dropdown-item-expand" href="#" data-item-id="{{$item.id}}" data-item-uuid="{{$item.mid}}"><i id="expand-{{$item.id}}" class="generic-icons-nav bi bi-arrows-angle-expand" title="{{$item.expand}}"></i>{{$item.expand}}</a>
+ {{/if}}
{{if $item.thread_action_menu}}
{{foreach $item.thread_action_menu as $mitem}}
<a class="dropdown-item" {{if $mitem.href}}href="{{$mitem.href}}"{{/if}} {{if $mitem.action}}onclick="{{$mitem.action}}"{{/if}} {{if $mitem.title}}title="{{$mitem.title}}"{{/if}} ><i class="generic-icons-nav bi bi-{{$mitem.icon}}"></i>{{$mitem.title}}</a>
@@ -246,7 +218,7 @@
{{/if}}
{{if $item.settings}}
<div class="dropdown-divider"></div>
- <a class="dropdown-item conversation-settings-link" href="" data-bs-toggle="modal" data-bs-target="#conversation_settings">{{$item.settings}}</a>
+ <a class="dropdown-item conversation-settings-link" href="#" data-bs-toggle="modal" data-bs-target="#conversation_settings">{{$item.settings}}</a>
{{/if}}
</div>
</div>
@@ -255,17 +227,34 @@
</div>
</div>
</div>
- {{if $item.toplevel}}
+ {{if $item.thread_level == 1}}
+ {{if $item.toplevel && $item.load_more && $item.threaded}}
+ <div id="load-more-progress-wrapper-{{$item.id}}" class="progress{{if $item.blog_mode}} d-none{{/if}}" role="progressbar" aria-valuenow="{{$item.comments_total_percent}}" aria-valuemin="0" aria-valuemax="100" style="height: 1px">
+ <div id="load-more-progress-{{$item.id}}" class="progress-bar bg-info" style="width: {{$item.comments_total_percent}}%; margin-left: auto; margin-right: auto;" data-comments-total="{{$item.comments_total}}"></div>
+ </div>
+ <div id="load-more-{{$item.id}}" class="load-more text-center text-secondary cursor-pointer{{if $item.blog_mode}} d-none{{/if}}" title="{{$item.load_more_title}}" onclick="request(0, '{{$item.rawmid}}', 'load', {{$item.parent}}, ''); return false;">
+ <span id="load-more-dots-{{$item.id}}" class="load-more-dots rounded"><span class="dot-1">-</span> <span class="dot-2">-</span> <span class="dot-3">-</span></span>
+ </div>
+ {{/if}}
+ <div id="wall-item-sub-thread-wrapper-{{$item.id}}" class="wall-item-sub-thread-wrapper">
{{foreach $item.children as $child}}
{{include file="{{$child.template}}" item=$child}}
{{/foreach}}
- {{/if}}
+ </div>
{{if $item.comment}}
- <div id="wall-item-comment-wrapper-{{$item.id}}" class="p-2 rounded wall-item-comment-wrapper{{if $item.children}} wall-item-comment-wrapper-wc{{/if}}">
+ <div id="wall-item-comment-wrapper-{{$item.id}}" class="p-2 rounded wall-item-comment-wrapper{{if $item.children}} wall-item-comment-wrapper-wc{{/if}}{{if $item.comment_hidden}} d-none{{/if}}">
{{$item.comment}}
</div>
{{/if}}
+
+ {{else}}
+ <div id="wall-item-sub-thread-wrapper-{{$item.id}}" class="wall-item-sub-thread-wrapper">
+ {{foreach $item.children as $child}}
+ {{include file="{{$child.template}}" item=$child}}
+ {{/foreach}}
+ </div>
+ {{/if}}
</div>
-{{if $item.comment_lastcollapsed}}
+{{if !$item.threaded && $item.comment_lastcollapsed}}
</div>
{{/if}}
diff --git a/view/tpl/dreport.tpl b/view/tpl/dreport.tpl
index 0b8cfdb11..848a07737 100644
--- a/view/tpl/dreport.tpl
+++ b/view/tpl/dreport.tpl
@@ -14,7 +14,7 @@
</div>
<div>
- <table>
+ <table class="table table-hover table-borderless">
{{if $entries}}
{{foreach $entries as $e}}
<tr>
diff --git a/view/tpl/head.tpl b/view/tpl/head.tpl
index 18941f454..f34d0564e 100644
--- a/view/tpl/head.tpl
+++ b/view/tpl/head.tpl
@@ -16,7 +16,6 @@
var justifiedGalleryActive = false;
{{if $channel_hash}}var channelHash = '{{$channel_hash}}';{{/if}}
var channelId = {{if $channel_id}}{{$channel_id}}{{else}}false{{/if}};{{* Used in e.g. autocomplete *}}
- var preloadImages = {{$preload_images}};
var auto_save_draft = {{$auto_save_draft}};
{{if $module}}var module = '{{$module}}';{{/if}}
</script>
diff --git a/view/tpl/jot-header.tpl b/view/tpl/jot-header.tpl
index 2c2b662be..b9b8b3012 100644
--- a/view/tpl/jot-header.tpl
+++ b/view/tpl/jot-header.tpl
@@ -11,6 +11,8 @@
var activeCommentID = 0;
var activeCommentText = '';
+ var isModalAction = false;
+
var postSaveTimer = null;
function initEditor(cb){
@@ -211,6 +213,7 @@
$.get('{{$baseurl}}/share/' + id, function(data) {
$('#like-rotator-' + id).hide();
updateInit();
+ close_modal();
});
}
@@ -413,19 +416,41 @@
// Fetch the photo album list
getPhotoAlbumList();
- // Remove any existing click event listeners on the modal body
- const modalBodyAlbumDialog = document.getElementById('embedPhotoModalBodyAlbumDialog');
- modalBodyAlbumDialog.replaceWith(modalBodyAlbumDialog.cloneNode(true)); // This effectively removes all event listeners
+ if (activeCommentID) {
+ const modalEl = document.getElementById('reactions');
+ isModalAction = modalEl.classList.contains('show');
+ }
- // Show the modal
- const modalEl = document.getElementById('embedPhotoModal');
- const modal = new bootstrap.Modal(modalEl);
- modal.show();
+ if (isModalAction) {
+ // Remove any existing click event listeners on the modal body
+ const modalBodyAlbumDialog = document.getElementById('reactions_extra_middle');
+ modalBodyAlbumDialog.replaceWith(modalBodyAlbumDialog.cloneNode(true)); // This effectively removes all event listeners
- // Reset activeCommentID when the modal is closed
- modalEl.addEventListener('hide.bs.modal', event => {
- activeCommentID = 0;
- });
+ const modalEl = document.getElementById('reactions');
+
+ // Reset activeCommentID when the modal is closed
+ modalEl.addEventListener('hide.bs.modal', event => {
+ activeCommentID = 0;
+ isModalAction = false;
+ document.getElementById('reactions_extra_middle').innerHTML = '';
+ document.getElementById('reactions_extra_top').innerHTML = '';
+ });
+ }
+ else {
+ // Remove any existing click event listeners on the modal body
+ const modalBodyAlbumDialog = document.getElementById('embedPhotoModalBodyAlbumDialog');
+ modalBodyAlbumDialog.replaceWith(modalBodyAlbumDialog.cloneNode(true)); // This effectively removes all event listeners
+
+ // Show the modal
+ const modalEl = document.getElementById('embedPhotoModal');
+ const modal = new bootstrap.Modal(modalEl);
+ modal.show();
+
+ // Reset activeCommentID when the modal is closed
+ modalEl.addEventListener('hide.bs.modal', event => {
+ activeCommentID = 0;
+ });
+ }
};
const choosePhotoFromAlbum = (album) => {
@@ -441,15 +466,16 @@
.then(data => {
if (data.status) {
- const modalLabel = document.getElementById('embedPhotoModalLabel');
- const modalBody = document.getElementById('embedPhotoModalBodyAlbumDialog');
+ const modalLabel = isModalAction ? document.getElementById('reactions_extra_top') : document.getElementById('embedPhotoModalLabel');
+ const modalBody = isModalAction ? document.getElementById('reactions_extra_middle') : document.getElementById('embedPhotoModalBodyAlbumDialog');
- modalLabel.innerHTML = '{{$modalchooseimages}}';
+ modalLabel.innerHTML = '<h3>{{$modalchooseimages}}</h3>';
modalBody.innerHTML = '<div><div class="nav nav-pills flex-column"><li class="nav-item"><a class="nav-link" href="#" onclick="initializeEmbedPhotoDialog(); return false;"><i class="bi bi-chevron-left"></i>&nbsp;{{$modaldiffalbum}}</a></li></div><br></div>';
modalBody.innerHTML += data.content;
// Make sure the loaded script is executed
const scripts = modalBody.querySelectorAll('script');
+
scripts.forEach(script => {
const scriptContent = script.textContent || script.innerText;
eval(scriptContent); // Execute the script
@@ -474,7 +500,6 @@
.then(ddata => {
if (ddata.status) {
addActiveEditorText(ddata.photolink);
- preview_post();
} else {
console.error("{{$modalerrorlink}}: " + ddata.errormsg);
}
@@ -501,10 +526,10 @@
.then(data => {
if (data.status) {
const albums = data.albumlist;
- const modalLabel = document.getElementById('embedPhotoModalLabel');
- const modalBodyList = document.getElementById('embedPhotoModalBodyAlbumList');
+ const modalLabel = isModalAction ? document.getElementById('reactions_extra_top') : document.getElementById('embedPhotoModalLabel');
+ const modalBodyList = isModalAction ? document.getElementById('reactions_extra_middle') : document.getElementById('embedPhotoModalBodyAlbumList');
- modalLabel.innerHTML = '{{$modalchoosealbum}}';
+ modalLabel.innerHTML = '<h3>{{$modalchoosealbum}}</h3>';
modalBodyList.innerHTML = '<ul class="nav nav-pills flex-column"></ul>';
albums.forEach(album => {
@@ -550,7 +575,9 @@
textarea.value = currentText + data;
textarea.focus();
textarea.click();
- preview_comment(activeCommentID);
+ if (!isModalAction) {
+ preview_comment(activeCommentID);
+ }
}
} else {
addeditortext(data);
diff --git a/view/tpl/js_strings.tpl b/view/tpl/js_strings.tpl
index 38168ccfe..0e438f450 100644
--- a/view/tpl/js_strings.tpl
+++ b/view/tpl/js_strings.tpl
@@ -5,8 +5,6 @@
'delitem' : "{{$delitem}}",
'itemdel' : "{{$itemdel}}",
'comment' : "{{$comment}}",
- 'showmore' : "{{$showmore}}",
- 'showfewer' : "{{$showfewer}}",
'divgrowmore' : "{{$divgrowmore}}",
'divgrowless' : "{{$divgrowless}}",
'pwshort' : "{{$pwshort}}",
@@ -38,6 +36,7 @@
'pinned' : "{{$pinned}}",
'pin_item' : "{{$pin_item}}",
'unpin_item' : "{{$unpin_item}}",
+ 'dblclick_to_exit_zoom' : "{{$dblclick_to_exit_zoom}}",
'monthNames' : [ "{{$January}}","{{$February}}","{{$March}}","{{$April}}","{{$May}}","{{$June}}","{{$July}}","{{$August}}","{{$September}}","{{$October}}","{{$November}}","{{$December}}" ],
'monthNamesShort' : [ "{{$Jan}}","{{$Feb}}","{{$Mar}}","{{$Apr}}","{{$MayShort}}","{{$Jun}}","{{$Jul}}","{{$Aug}}","{{$Sep}}","{{$Oct}}","{{$Nov}}","{{$Dec}}" ],
diff --git a/view/tpl/notifications_widget.tpl b/view/tpl/notifications_widget.tpl
index 2156c1f4c..be66c00d4 100644
--- a/view/tpl/notifications_widget.tpl
+++ b/view/tpl/notifications_widget.tpl
@@ -10,26 +10,31 @@
document.addEventListener("DOMContentLoaded", function() {
let notificationsWrapper = document.getElementById('notifications_wrapper');
let notificationsParent = notificationsWrapper ? notificationsWrapper.parentElement.id : null;
- let notificationsBtn = document.querySelector('.notifications-btn');
+ let notificationsBtn = document.querySelectorAll('.notifications-btn');
// Event listener for notifications button
if (notificationsBtn) {
- notificationsBtn.addEventListener('click', function() {
- // Remove the 'd-none' class to show the notifications wrapper
- notificationsWrapper.classList.remove('d-none');
-
- // Check if the notifications wrapper has the 'fs' class
- if (notificationsWrapper.classList.contains('fs')) {
- // Prepend the notifications wrapper back to its original parent and hide it
- document.getElementById(notificationsParent).appendChild(notificationsWrapper);
- notificationsWrapper.classList.add('d-none');
- } else {
- // Otherwise, prepend the notifications wrapper to 'main'
- document.querySelector('main').prepend(notificationsWrapper);
- }
+ notificationsBtn.forEach(function (element) {
+ element.addEventListener('click', function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+
+ // Remove the 'd-none' class to show the notifications wrapper
+ notificationsWrapper.classList.remove('d-none');
+
+ // Check if the notifications wrapper has the 'fs' class
+ if (notificationsWrapper.classList.contains('fs')) {
+ // Prepend the notifications wrapper back to its original parent and hide it
+ document.getElementById(notificationsParent).appendChild(notificationsWrapper);
+ notificationsWrapper.classList.add('d-none');
+ } else {
+ // Otherwise, prepend the notifications wrapper to 'main'
+ document.querySelector('main').prepend(notificationsWrapper);
+ }
- // Toggle the 'fs' class
- notificationsWrapper.classList.toggle('fs');
+ // Toggle the 'fs' class
+ notificationsWrapper.classList.toggle('fs');
+ });
});
}
@@ -83,6 +88,7 @@
}
else {
if (!document.hidden) {
+ sse_fallback();
sse_fallback_interval = setInterval(sse_fallback, updateInterval);
}
@@ -621,17 +627,23 @@
}
// Update visibility of notification button and sections
- let notificationsBtn = document.querySelector('.notifications-btn');
+ let notificationsBtn = document.querySelectorAll('.notifications-btn');
let noNotifications = document.querySelector('#no_notifications');
let notifications = document.querySelector('#notifications');
let navbarCollapse = document.querySelector('#navbar-collapse-1');
if (any_available) {
- notificationsBtn.style.opacity = 1;
+ notificationsBtn.forEach(btn => {
+ btn.style.opacity = 1;
+ });
noNotifications.style.display = 'none';
notifications.style.display = 'block';
} else {
- notificationsBtn.style.opacity = 0.5;
+ if (notificationsBtn) {
+ notificationsBtn.forEach(btn => {
+ btn.style.opacity = 0.5;
+ });
+ }
if (navbarCollapse) navbarCollapse.classList.remove('show');
noNotifications.style.display = 'block';
notifications.style.display = 'none';
@@ -713,7 +725,7 @@
<div class="text-truncate pe-1">
<strong title="{2} - {3}">{2}</strong>
</div>
- <small class="autotime-narrow opacity-75" title="{5}"></small>
+ <small class="autotime-narrow text-body-secondary" title="{5}"></small>
</div>
<div class="text-truncate">{4}</div>
</div>
diff --git a/view/tpl/pinned_item.tpl b/view/tpl/pinned_item.tpl
index aa5b94664..db3a175da 100644
--- a/view/tpl/pinned_item.tpl
+++ b/view/tpl/pinned_item.tpl
@@ -1,6 +1,6 @@
<div id="pinned-wrapper-{{$id}}" class="pinned-item toplevel_item generic-content-wrapper h-entry" data-b64mids='{{$mids}}'>
<div class="wall-item-outside-wrapper" id="pinned-item-outside-wrapper-{{$id}}">
- <div class="clearfix wall-item-content-wrapper" id="pinned-item-content-wrapper-{{$id}}">
+ <div class="wall-item-content-wrapper" id="pinned-item-content-wrapper-{{$id}}">
{{if $photo}}
<div class="wall-photo-item" id="pinned-photo-item-{{$id}}">
{{$photo}}
@@ -26,17 +26,51 @@
{{/if}}
</div>
{{if ! $is_new}}
- <hr class="m-0">
+ <hr class="m-0">
{{/if}}
{{/if}}
- <div class="p-2 lh-sm d-flex wall-item-head{{if !$title && !$event && !$photo}} rounded-top{{/if}}{{if $is_new && !$event}} wall-item-head-new{{/if}}" >
- <div class="wall-item-info pe-2" id="wall-item-info-{{$id}}" >
- <div class="wall-item-photo-wrapper{{if $owner_url}} wwfrom{{/if}} h-card p-author" id="wall-item-photo-wrapper-{{$id}}">
- <img src="{{$thumb}}" class="fakelink wall-item-photo{{$sparkle}} u-photo p-name" id="wall-item-photo-{{$id}}" alt="{{$name}}" loading="lazy" data-bs-toggle="dropdown" />
- {{if $thread_author_menu}}
- <i class="bi bi-caret-down wall-item-photo-caret cursor-pointer" data-bs-toggle="dropdown"></i>
+ <div class="p-2 wall-item-head{{if !$title && !$event && !$photo}} rounded-top{{/if}}{{if $is_new && !$event}} wall-item-head-new{{/if}}" >
+ <div class="lh-sm text-end float-end">
+ <div class="wall-item-ago text-body-secondary" id="pinned-item-ago-{{$id}}">
+ {{if $location}}
+ {{$location}}
+ {{/if}}
+ {{if $editedtime}}
+ <i class="bi bi-pencil" title="{{$editedtime}}"></i>
+ {{/if}}
+ {{if $verified}}
+ <i class="bi bi-shield-check" title="{{$verified}}"></i>
+ {{elseif $forged}}
+ <i class="bi bi-shield-exclamation text-danger" title="{{$forged}}"></i>
+ {{/if}}
+ {{if $no_comment}}
+ <i class="bi bi-ban" title="{{$no_comment}}"></i>
+ {{/if}}
+ {{if $delayed}}
+ <i class="bi bi-clock" title="{{$delayed}}"></i>
+ {{/if}}
+ {{if $expiretime}}
+ <i class="bi bi-clock-history" title="{{$expiretime}}"></i>
+ {{/if}}
+ <small class="autotime" title="{{$isotime}}"><time class="dt-published" datetime="{{$isotime}}">{{$localtime}}</time>{{if $expiretime}}&nbsp;{{$expiretime}}{{/if}}</small>
+ </div>
+ {{if $pinned}}
+ <div class="wall-item-pinned" title="{{$pinned}}" id="pinned-item-pinned-{{$id}}"><i class="bi bi-pin-fill"></i></div>
+ {{/if}}
+ </div>
+ <div class="float-start wall-item-info pe-2" id="pinned-item-info-{{$id}}" >
+ <div class="wall-item-photo-wrapper{{if $owner_url}} wwfrom{{/if}} h-card p-author" id="pinned-item-photo-wrapper-{{$id}}">
+ {{if $item.contact_id}}
+ <div class="spinner-wrapper contact-edit-rotator contact-edit-rotator-{{$contact_id}}"><div class="spinner s"></div></div>
+ {{/if}}
+ <img src="{{$thumb}}" class="fakelink wall-item-photo{{$sparkle}} u-photo p-name" id="pinned-item-photo-{{$id}}" alt="{{$name}}" loading="lazy" data-bs-toggle="dropdown" />
+ {{if $item.author_is_group_actor}}
+ <i class="bi bi-chat-quote-fill wall-item-photo-group-actor" title="{{$author_is_group_actor}}"></i>
+ {{/if}}
+ {{if $item.thread_author_menu}}
+ <i class="bi bi-caret-down-fill wall-item-photo-caret cursor-pointer" data-bs-toggle="dropdown"></i>
<div class="dropdown-menu">
- {{foreach $thread_author_menu as $mitem}}
+ {{foreach $item.thread_author_menu as $mitem}}
<a class="dropdown-item{{if $mitem.class}} {{$mitem.class}}{{/if}}" {{if $mitem.href}}href="{{$mitem.href}}"{{/if}} {{if $mitem.action}}onclick="{{$mitem.action}}"{{/if}} {{if $mitem.title}}title="{{$mitem.title}}"{{/if}}{{if $mitem.data}} {{$mitem.data}}{{/if}}>{{$mitem.title}}</a>
{{/foreach}}
</div>
@@ -45,159 +79,57 @@
</div>
<div class="wall-item-author text-truncate">
<a href="{{$profile_url}}" title="{{$linktitle}}" class="wall-item-name-link u-url"><span class="wall-item-name" id="pinned-item-name-{{$id}}" >{{$name}}</span></a>{{if $owner_url}}&nbsp;{{$via}}&nbsp;<a href="{{$owner_url}}" title="{{$olinktitle}}" class="wall-item-name-link"><span class="wall-item-name" id="pinned-item-ownername-{{$id}}">{{$owner_name}}</span></a>{{/if}}<br>
- <small class="wall-item-addr opacity-75">{{$author_id}}</small>
- </div>
- <div class="text-end ms-auto">
- <div class="wall-item-ago text-nowrap opacity-75" id="wall-item-ago-{{$id}}">
- {{if $editedtime}}
- <i class="bi bi-pencil"></i>
- {{/if}}
- {{if $delayed}}
- <i class="bi fa-clock-o"></i>
- {{/if}}
- {{if $location}}
- <small class="wall-item-location p-location" id="wall-item-location-{{$id}}">{{$location}}</small>
- {{/if}}
- {{if $verified}}
- <i class="bi bi-check-lg text-success" title="{{$verified}}"></i>
- {{elseif $forged}}
- <i class="bi fa-exclamation text-danger" title="{{$forged}}"></i>
- {{/if}}
- <small class="autotime" title="{{$isotime}}"><time class="dt-published" datetime="{{$isotime}}">{{$localtime}}</time>{{if $editedtime}}&nbsp;{{$editedtime}}{{/if}}{{if $expiretime}}&nbsp;{{$expiretime}}{{/if}}</small>
- </div>
- <div class="wall-item-pinned" title="{{$pinned}}" id="wall-item-pinned-{{$id}}"><i class="bi fa-thumb-tack"></i></div>
+ <small class="wall-item-addr text-body-secondary">{{$author_id}}</small>
</div>
</div>
{{if $divider}}
- <hr class="wall-item-divider">
+ <hr class="wall-item-divider">
{{/if}}
{{if $body}}
- <div class="p-2 wall-item-content clearfix" id="pinned-item-content-{{$id}}">
- <div class="wall-item-body e-content" id="pinned-item-body-{{$id}}" >
- {{$body}}
- </div>
+ <div class="p-2 wall-item-content clearfix" id="pinned-item-content-{{$id}}">
+ <div class="wall-item-body e-content" id="pinned-item-body-{{$id}}" >
+ {{$body}}
</div>
+ </div>
{{/if}}
{{if $has_tags}}
- <div class="p-2 wall-item-tools clearfix">
- <div class="body-tags">
- <span class="tag">{{$mentions}} {{$tags}} {{$categories}} {{$folders}}</span>
- </div>
+ <div class="p-2 wall-item-tools clearfix">
+ <div class="body-tags">
+ <span class="tag">{{$mentions}} {{$tags}} {{$categories}} {{$folders}}</span>
</div>
+ </div>
{{/if}}
- <div class="p-2 clearfix wall-item-tools">
- <div class="float-end wall-item-tools-right">
- <div class="btn-group">
- <div id="pinned-rotator-{{$id}}" class="spinner-wrapper">
- <div class="spinner s"></div>
- </div>
- </div>
- <div class="btn-group">
- {{if $isevent}}
- <div class="btn-group">
- <button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle" data-bs-toggle="dropdown" id="pinned-item-attend-menu-{{$id}}" title="{{$attend_title}}">
- <i class="bi fa-calendar-check-o"></i>
- </button>
- <div class="dropdown-menu dropdown-menu-end">
- <a class="dropdown-item" href="#" title="{{$attend.0}}" onclick="itemAddToCal({{$id}}); dolike({{$id}},'attendyes'); return false;">
- <i class="item-act-list bi bi-check-lg{{if $my_responses.attend}} ivoted{{/if}}" ></i> {{$attend.0}}
- </a>
- <a class="dropdown-item" href="#" title="{{$attend.1}}" onclick="itemAddToCal({{$id}}), dolike({{$id}},'attendno'); return false;">
- <i class="item-act-list bi bi-x-lg{{if $my_responses.attendno}} ivoted{{/if}}" ></i> {{$attend.1}}
- </a>
- <a class="dropdown-item" href="#" title="{{$attend.2}}" onclick="itemAddToCal({{$id}}); dolike({{$id}},'attendmaybe'); return false;">
- <i class="item-act-list bi bi-question-lg{{if $my_responses.attendmaybe}} ivoted{{/if}}" ></i> {{$attend.2}}
- </a>
- </div>
- </div>
- {{/if}}
- {{if $canvote}}
- <div class="btn-group">
- <button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle" data-bs-toggle="dropdown" id="pinned-item-consensus-menu-{{$id}}" title="{{$vote_title}}">
- <i class="bi bi-check-square"></i>
- </button>
- <div class="dropdown-menu dropdown-menu-end" role="menu" aria-labelledby="wall-item-consensus-menu-{{$id}}">
- <a class="dropdown-item" href="#" title="{{$conlabels.0}}" onclick="dolike({{$id}},'agree'); return false;">
- <i class="item-act-list bi bi-check-lg{{if $my_responses.agree}} ivoted{{/if}}" ></i> {{$conlabels.0}}
- </a>
- <a class="dropdown-item" href="#" title="{{$conlabels.1}}" onclick="dolike({{$id}},'disagree'); return false;">
- <i class="item-act-list bi bi-x-lg{{if $my_responses.disagree}} ivoted{{/if}}" ></i> {{$conlabels.1}}
- </a>
- <a class="dropdown-item" href="#" title="{{$conlabels.2}}" onclick="dolike({{$id}},'abstain'); return false;">
- <i class="item-act-list bi bi-question-lg{{if $my_responses.abstain}} ivoted{{/if}}" ></i> {{$conlabels.2}}
- </a>
- </div>
- </div>
- {{/if}}
- <div class="btn-group">
- <button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle" data-bs-toggle="dropdown" id="pinned-item-menu-{{$id}}">
- <i class="bi bi-gear"></i>
- </button>
- <div class="dropdown-menu dropdown-menu-end" role="menu" aria-labelledby="wall-item-menu-{{$id}}">
- {{if $share}}
- <a class="dropdown-item" href="#" onclick="jotShare({{$id}},{{$item_type}}); return false;"><i class="generic-icons-nav bi fa-retweet" title="{{$share.0}}"></i>{{$share.0}}</a>
- {{/if}}
- {{if $embed}}
- <a class="dropdown-item" href="#" onclick="jotEmbed({{$id}},{{$item_type}}); return false;"><i class="generic-icons-nav bi fa-share" title="{{$embed.0}}"></i>{{$embed.0}}</a>
- {{/if}}
- {{if $plink}}
- <a class="dropdown-item" href="{{$plink.href}}" title="{{$plink.title}}" class="u-url"><i class="generic-icons-nav bi bi-box-arrow-up-right"></i>{{$plink.title}}</a>
- {{/if}}
- {{if $pinme}}
- <a class="dropdown-item dropdown-item-pinnable" href="#" onclick="dopin({{$id}}); return false;"><i class="generic-icons-nav bi fa-thumb-tack"></i>{{$pinme}}</a>
- {{/if}}
- {{if $hide}}
- <a class="dropdown-item" href="#" onclick="dopinhide({{$id}}); return false;" class="u-url"><i class="generic-icons-nav bi fa-remove"></i>{{$hide}}</a>
- {{/if}}
- </div>
+ <div class="p-2 wall-item-tools d-flex justify-content-between">
+ <div class="wall-item-tools-left hstack gap-1" id="pinned-item-tools-left-{{$id}}">
+ {{foreach $responses as $verb=>$response}}
+ <button type="button" title="{{$response.count}} {{$response.button.label}}" class="disabled btn btn-sm btn-link{{if !$observer_activity.$verb}} link-secondary{{/if}} wall-item-{{$response.button.class}}" id="pinned-item-{{$verb}}-{{$id}}">
+ <i class="bi bi-{{$response.button.icon}} generic-icons"></i>{{if $response.count}}<span style="display: inline-block; margin-top: -.25rem;" class="align-top">{{$response.count}}</span>{{/if}}
+ </button>
+ {{/foreach}}
+ <div class="">
+ <div id="like-rotator-{{$id}}" class="spinner-wrapper">
+ <div class="spinner s"></div>
</div>
</div>
</div>
- {{if $responses || $attachments}}
- <div class="wall-item-tools-left btn-group" id="pinned-item-tools-left-{{$id}}">
- {{if $attachments}}
- <div class="wall-item-tools-left btn-group" id="pinned-item-tools-left-{{$id}}">
- <div class="btn-group">
- <button type="button" class="btn btn-outline-secondary btn-sm wall-item-like dropdown-toggle" data-bs-toggle="dropdown" id="pinned-attachment-menu-{{$id}}">
- <i class="bi bi-paperclip"></i>
- </button>
- <div class="dropdown-menu">{{$attachments}}</div>
- </div>
- </div>
- {{/if}}
- {{foreach $responses as $verb=>$response}}
- {{if $response.count}}
- <div class="btn-group">
- <button type="button" class="btn btn-outline-secondary btn-sm wall-item-like dropdown-toggle"{{if $response.modal}} data-bs-toggle="modal" data-bs-target="#{{$verb}}Modal-{{$id}}"{{else}} data-bs-toggle="dropdown"{{/if}} id="pinned-item-{{$verb}}-{{$id}}">{{$response.count}} {{$response.button}}</button>
- {{if $response.modal}}
- <div class="modal" id="pinned-{{$verb}}Modal-{{$id}}">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-header">
- <h3 class="modal-title">{{$response.count}} {{$response.button}}</h3>
- <button type="button" class="btn-close" data-bs-dismiss="modal" aria-hidden="true"></button>
- </div>
- <div class="modal-body response-list">
- <ul class="nav nav-pills flex-column">
- {{foreach $response.list as $liker}}<li class="nav-item">{{$liker}}</li>{{/foreach}}
- </ul>
- </div>
- <div class="modal-footer clear">
- <button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">{{$modal_dismiss}}</button>
- </div>
- </div>
- </div>
- </div>
- {{else}}
- <div class="dropdown-menu">
- {{foreach $response.list as $liker}}{{$liker}}{{/foreach}}
- </div>
- {{/if}}
- </div>
+ <div class="wall-item-tools-right hstack gap-1" id="pinned-item-tools-right-{{$id}}">
+ {{if $attachments}}
+ <div class="">
+ <button type="button" class="btn btn-sm btn-link link-secondary wall-item-attach" data-bs-toggle="dropdown" id="pinned-attachment-menu-{{$id}}"><i class="bi bi-paperclip generic-icons"></i></button>
+ <div class="dropdown-menu dropdown-menu-end">{{$attachments}}</div>
+ </div>
+ {{/if}}
+ <div class="">
+ <button type="button" class="btn btn-sm btn-link link-secondary" data-bs-toggle="dropdown" id="wall-item-menu-{{$item.id}}">
+ <i class="bi bi-three-dots-vertical generic-icons"></i>
+ </button>
+ <div class="dropdown-menu dropdown-menu-end" role="menu" aria-labelledby="wall-item-menu-{{$item.id}}">
+ {{if $plink}}
+ <a class="dropdown-item" href="{{$plink.href}}" title="{{$plink.title}}" class="u-url"><i class="generic-icons-nav bi bi-box-arrow-up-right"></i>{{$plink.title}}</a>
{{/if}}
- {{/foreach}}
+ </div>
</div>
- {{/if}}
+ </div>
</div>
</div>
</div>
diff --git a/view/tpl/search_item.tpl b/view/tpl/search_item.tpl
index d693c4d37..a40b25554 100644
--- a/view/tpl/search_item.tpl
+++ b/view/tpl/search_item.tpl
@@ -22,7 +22,7 @@
{{/if}}
<div class="p-2 wall-item-head{{if !$item.title && !$item.event && !$item.photo}} rounded-top{{/if}}{{if $item.is_new && !$item.event && !$item.is_comment}} wall-item-head-new{{/if}}" >
<div class="lh-sm text-end float-end">
- <div class="wall-item-ago opacity-75" id="wall-item-ago-{{$item.id}}">
+ <div class="wall-item-ago text-body-secondary" id="wall-item-ago-{{$item.id}}">
{{if $item.location}}
{{$item.location}}
{{/if}}
@@ -83,7 +83,7 @@
<div class="text-truncate">
<a href="{{$item.profile_url}}" class="lh-sm wall-item-name-link u-url"{{if $item.app}} title="{{$item.str_app}}"{{/if}}><span class="wall-item-name{{$item.sparkle}}" id="wall-item-name-{{$item.id}}" ><bdi>{{$item.name}}</bdi></span></a>{{if $item.owner_url}}&nbsp;{{$item.via}}&nbsp;<a href="{{$item.owner_url}}" title="{{$item.olinktitle}}" class="wall-item-name-link"><span class="wall-item-name{{$item.osparkle}}" id="wall-item-ownername-{{$item.id}}"><bdi>{{$item.owner_name}}</bdi></span></a>{{/if}}
</div>
- <small class="lh-sm text-truncate d-block wall-item-addr opacity-75">{{$item.author_id}}</small>
+ <small class="lh-sm text-truncate d-block wall-item-addr text-body-secondary">{{$item.author_id}}</small>
</div>
</div>
{{if $item.divider}}
diff --git a/view/tpl/settings_display.tpl b/view/tpl/settings_display.tpl
index d745d14b5..f77dfd409 100644
--- a/view/tpl/settings_display.tpl
+++ b/view/tpl/settings_display.tpl
@@ -61,8 +61,8 @@
{{include file="field_checkbox.tpl" field=$nosmile}}
{{include file="field_checkbox.tpl" field=$title_tosource}}
{{include file="field_checkbox.tpl" field=$user_scalable}}
- {{include file="field_checkbox.tpl" field=$preload_images}}
{{include file="field_checkbox.tpl" field=$start_menu}}
+ {{include file="field_checkbox.tpl" field=$thread_allow}}
<div class="settings-submit-wrapper" >
<button type="submit" name="submit" class="btn btn-primary">{{$submit}}</button>
</div>